본문 바로가기

인공지능/Toy Projects

DeepMC_3 / 2 Levels Attention Mechanism

목차

    Introduction

    저번 D)Multi-Scale Deep Learning에 이어서 다음 부분인 E)Attention Mechanism부분이다.

    DeepMC 모델에서 사용된 attention은 2가지 종류로 해당 논문에서는 '2 levels of attention models' 라고 표현하고 있다.

    2 levels은 각각 Position Based Content Attention Layer, Scale Guided Attention Layer이다.

    Method & Metarial

    D) Multi-scale Deep Learning에서 다중 스케일로 encoding 된 데이터를 attention mechanism을 이용해 Decoder에 입력할 context vector(c)를 계산하는 과정이다.

    2 level attention은 각각 Position Based Content Attention Layer, Scale Guided Attention Layer이고, 구조는 다음 그림과 같다.

    그림이 잘못되었는지 수식이 잘못되었는지는 모르겠는데, Position Based Content Attention Layer에서 Scale Guided Attention Layer로 가는 Context Vector(c)가 그림상에는 존재하는데, 수식상에는 존재하지 않는다.

    단순히 Position Based Content Attention Layer의 context vector가 Scale Guided Attention Layer에 영향을 주고 있다고 표현하고 싶었다기엔 뭔가 이상한 느낌이긴 하다. (사실 논문에 오타도 몇개 있고, 생략된 부분도 많아서 reference를 찾아가면서 구현했기 때문에 그림이 잘못 되었을 가능성이 있다고 생각한다.)

    Position Based Content Attention Layer

    첫번째 Attention인 Position Based Content Attention Layer는 CNNLSTM stack에서 나온 Long-scale encoding에 대한 attention으로 수식과 각 변수에 대한 설명은 다음과 같다.

    where𝑊_𝑎,𝑈_𝑎, 𝜋 ∈ R^{2𝐻×(𝑇 +𝑇 ′)} and 𝑣_𝑎 are trained in conjunction with the entire DeepMC deep learning architecture, Δ^{(𝑖,𝑗)} ∈ R^{𝑇} is a binary vector that is 1 on dimension (𝑖 +𝑇 − 𝑗) and 0 elsewhere, ⊙ denotes the element wise multiplication (Handmard product) and Δ ∈ R^{𝑇 +𝑇 ′} has 1 on its first T coordinates and 0 on the last T’.

    위 식에 나온 기호 중 추가 설명이 필요한 부분은 i, j, T`, T, h_j, s_(i-1)인데, T는 # of Encoder timesteps, T`는 # of Decoder timesteps, i는 Decoder time step index로 Decoder LSTM cell의 인덱스(1 <= i <= T`)이고, j는 Encoder의 time step index(1 <= j <= T)이고, h_j는 CNNLSTM의 output인 j번째 hidden state이고, s_(i-1)은 Decoder LSTM cell의 i-1번째 hidden state이다.

    Decoder의 i-1번째 LSTM Cell의 hidden state(s_(i-1))가 i번째 LSTM Cell의 input(s_(i-1))으로 들어가고 i번째 context vector(c_i)를 만드는데 사용된다.

     

    Scale Guided Attention Layer

    두번째 Attention인 Scale Guided Attention Layer는 CNN stack의 output에 대한 attention으로 수식은 다음과 같다.

    N은 WPD에서 설정한 # of Level, o^(j)는 j번째 CNNstack의 output으로 s_(i-1)과 concat되어 energy가 계산된다.

    본 실험에서는 생성가능한 25개의 조합(5 levels)중에 7개의 CNNstack만 사용했다.

    또 특이한 점은 Attention weight를 만들 때, softmax가 아닌 그냥 sigma 값을 분모로 해서 나눴다. 

    그점에 대한 설명은 따로 존재하지 않았다.

    We do not generate all combinations of various signals for each individual predictor sensor signal to keep the size of the deep neural network manageable.

    Result

    CNNstack, CNNLSTM을 구현할때보다 훨씬 어려웠다.

    input, output shape를 맞추는 작업이 가장 힘들었던 것 같다.

    주석으로 각각에 대해 설명을 해두긴 했지만 이해하기 어려운 부분이 있을 수 있다.

    class Position_based_content_attention(nn.Module):
        # input / CNNLSTM encoder output & s_i-1
        # output / Position_based_content_attention context vector c_i
        def __init__(self,num_encoder_hidden : int , num_encoder_times : int, num_decoder_hidden : int,num_decoder_times : int, batch_size : int):
            
            super().__init__()
    
            # Hyper parameter
            self.num_encoder_hidden = num_encoder_hidden
            self.num_encoder_times = num_encoder_times
            self.num_decoder_hidden = num_decoder_hidden
            self.num_decoder_times = num_decoder_times
            self.batch_size = batch_size
    
            # Weights
            self.W_a = nn.Linear(self.num_decoder_hidden, self.num_decoder_times)
            self.U_a = nn.Linear(self.num_encoder_hidden*2, self.num_encoder_times)
            self.v_a = nn.Linear(self.num_encoder_times+self.num_decoder_times,1)
            self.phi_weight = nn.Linear(self.num_encoder_times + self.num_decoder_times, self.num_encoder_hidden * 2)
        
        def forward(self, LSTM, s_i, i):
            # 1 <= j <= T  , num_encoder_times T is lstmstack seq length, in this case T = 18
            # 1 <= i <= T' , num_decoder_times T' is lstm decoder seq length, in this case T' = 12
    
            # Position based content attention layer
            v_a_output = []
            for j in range(self.num_encoder_times):
                    
                # delta_i_j / (encoder time step + decoder time step)
                delta_i_j = torch.zeros((self.num_encoder_times + self.num_decoder_times))
                delta_i_j[i+self.num_encoder_times-j] = 1
    
                # phi_delta / (encoder hidden size * 2)
                phi_delta = self.phi_weight(delta_i_j)
    
                # LSTM / (batch size, encoder time step, encoder hidden size * 2)
                # phi_delta_hadamard / (batch size, encoder hidden size * 2)
                phi_delta_hadamard = phi_delta*LSTM[:,j]
    
                # U_a_output / (batch size, encoder time step)
                U_a_output = self.U_a(phi_delta_hadamard)
                
                # W_a_output / (batch size, decoder time step)
                W_a_output = self.W_a(s_i)
    
                # concat / (batch,size, decoder time step + encoder time step)
                concat = torch.cat((W_a_output,U_a_output), dim=1)
    
                # delta_i_t_j / (decoder time step + encoder time step)
                delta_i_T_j = torch.zeros((self.num_encoder_times+self.num_decoder_times))
                delta_i_T_j[:self.num_encoder_times] = 1
    
                # concat_tanh / (batch size, decoder time step + encoder time step)
                concat_tanh = torch.tanh(concat)
    
                # concat_tanh_hadamard_delta / (batch size, decoder time step + encoder time step)
                concat_tanh_hadamard_delta = concat_tanh*delta_i_T_j
    
                # v_a_output / (batch size, 1)
                v_a_output.append(self.v_a(concat_tanh_hadamard_delta))
            
            # e_ij / (batch size, encoder time step)
            e_ij = torch.cat(v_a_output,dim=1)
    
            # a_ij / (batch size, encoder time step)
            a_ij = torch.softmax(e_ij,dim=1)
    
            # a_ij / (batch size, 1, encoder time step)
            a_ij = a_ij.unsqueeze(1)
    
            # LSTM / (batch size, encoder time step, encoder hidden size * 2)
            # c_i / (batch size, 1, encoder hidden size)
            c_i = torch.bmm(a_ij,LSTM)
    
            return c_i
    
    class Scaled_Guided_Attention(nn.Module):
        # intput / CNN encoder output & s_i-1
        # output / Scaled_Guided_Attention context vector c_prime_i
        def __init__(self,num_encoder_hidden : int , num_encoder_times : int, num_decoder_hidden : int,num_decoder_times : int, batch_size : int, num_of_CNN_stacks : int, cnn_output_size : int):
            
            super().__init__()
            
            # Hyper parameter
            self.num_encoder_hidden = num_encoder_hidden
            self.num_encoder_times = num_encoder_times
            self.num_decoder_hidden = num_decoder_hidden
            self.num_decoder_times = num_decoder_times
            self.batch_size = batch_size
            self.num_of_CNN_stacks = num_of_CNN_stacks
            self.cnn_output_size = cnn_output_size
            
            # energy weight
            linear = nn.Linear(self.num_decoder_hidden+self.cnn_output_size,1)
            self.w_i_j_T = [copy.deepcopy(linear) for i in range(self.num_of_CNN_stacks)]
        
        def forward(self, CNNs, s_i):
            # 1 <= j <= T  , num_encoder_times T is lstmstack seq length, in this case T = 18
            # 1 <= i <= T' , num_decoder_times T' is lstm decoder seq length, in this case T' = 12
    
            # energy vector
            e_prime_ij = []
    
            for i in range(self.num_of_CNN_stacks):
                # CNNs / (batch size, # of CNN stack, output size of CNN stack)
                # s_i / (batch size, decoder hidden size)
                e_prime_ij.append(self.w_i_j_T[i](torch.cat((CNNs[:,i,:],s_i),dim=1)))
            
            # e_prime_ij / (batch size, # of CNN stack)
            e_prime_ij = torch.tanh(torch.cat(e_prime_ij,dim=1))
    
            # a_prime_ij / (batch size, # of CNN stack)
            # attention is not softmax in Scaled Guided Attention Layer
            a_prime_ij = torch.exp(e_prime_ij)/torch.sum(e_prime_ij)
    
            # a_prime_ij / (batch size, 1, # of CNN stack)
            a_prime_ij = a_prime_ij.unsqueeze(1)
    
            # CNNs / (batch size, # of CNN stack, output size of CNN stack)
            # c_prime_i / (batch size, 1, output size of CNN stack)
            c_prime_i = torch.bmm(a_prime_ij,CNNs)
            
            return c_prime_i

     

    Conclusion & Discussion

    논문에 설명이 부실하여 reference를 찾아가면서 수식에 대한 이해와 기호에 대한 설명을 보충했다.

    앞서 작성한 CNNstack, CNNLSTM을 구현할때와는 차원이 다르게 힘들었다.

    다음 글은 모델의 마지막 부분인 Decoder부분으로 LSTM cell을 이용해 i-1번째 hidden state를 이용해 c_i, c`_i를 계산하고 이를 Decoder에 넣어 최종 output을 뽑는 부분까지 기술한다.

     

    DeepMC 모델에 대한 전체 코드는 다음 github에 정리중이다.

    https://github.com/wlsdml1114/DeepMC

     

    GitHub - wlsdml1114/DeepMC

    Contribute to wlsdml1114/DeepMC development by creating an account on GitHub.

    github.com