본문 바로가기

데이터 과학/딥러닝(Deep Learning)

Attention 모델 - 구현편

목차

1. Seq2Seq의 문제점

2. Encoder의 개선

3-1. Decoder의 개선 (1)

  3-1-1. 대응 관계를 찾는 방법

3-2. Decoder의 개선 (2)

3-3. Decoder의 개선 (3)

3-4. Time Attention 계층의 구현

4. Attention을 갖춘 Seq2Seq의 구현

5. 평가


1. Seq2Seq의 문제점

 

짧은 문장이든, 긴 문장이든 Encoder에서 같은 길이의 벡터만 출력한다.

 

2. Encoder의 개선

Encoder의 출력 길이는 입력 문장의 길이에 따라 바꿔준다.

LSTM 계층의 은닉 상태 벡터를 모두 이용한다.

지금까지는 LSTM 계층의 마지막 은닉 상태만을 Decoder에 전달했었다.

 

5개 단어 입력 되면, 5개 벡터 출력한다

3-1. Decoder의 개선 (1)

지금까지와 똑같이 Encoder의 마지막 은닉 상태 벡터는 Decoder의 첫번째 LSTM 계층에 전달하되, '어떤 계산'을 추가 한다.

'어떤 계산'은 hs(각 LSTM 계층의 은닉벡터를 원소로 하는 행렬)시각별 LSTM 계의 은닉 상태를 입력으로 받는다. 

 

시각별 LSTM 계층의 은닉 상태라 하면, Decoder의 입력된 단어와 대응 관계인 hs 행렬의 원소를 뜻한다.

 

하고자 하는건,

'입력과 출력의 단어 중 대응 관계(Alignment)'를 학습 시키는 것.

즉, '도착어 단어'와 대응관계 있는 '출발어 단어' 정보를 골라 내는 것. 그리고 해당 정보를 이용해 번역하는것.

 

 

 

개선된 Decoder 구조

 

기존의 Decoder 구조

 

3-1-1. 대응 관계를 찾는 방법

- 앞에서 표현한 '골라낸다'는 행위를 어떻게 수학적으로 나타낼 수 있을까?

- 방법 : hs에 가중치 벡터(a)를 둬서, 문맥 벡터(c)를 생성한다.

 

가중치 벡터(a)hs의 각 벡터와 LSTM 블록으로 부터 나온 결과 h얼마나 비슷한지를 수치로 나타낸 것.

학습할 대상이다. 

 

 

문맥 벡터를 나타낸 신경망

class WeightSum:
    def __init__(self):
        self.params, self.grads = [], []
        self.cache = None

    def forward(self, hs, a):
        N, T, H = hs.shape

        ar = a.reshape(N, T, 1).repeat(H, axis=2)
        t = hs * ar
        c = np.sum(t, axis=1) # T(5)축을 없엔다.

        self.cache = (hs, ar)
        return c

    def backward(self, dc):
        hs, ar = self.cache
        N, T, H = hs.shape
        
        # sum의 역전파 - repeat
        dt = dc.reshape(N, 1, H).repeat(T, axis=1) 
        dar = dt * hs
        dhs = dt * ar
        
        #repeat의 역전파 - sum
        da = np.sum(dar, axis=2) 

        return dhs, da

 

3-2. Decoder의 개선 (2)

- 앞에서는 가중치(a)를 둬서 맥락벡터를 구할 수 있었다.

- 이제는 가중치(a)를 구하는 방법을 알아본다.

 

 가중치 벡터(a) hs의 각 벡터와 LSTM 블록으로 부터 나온 결과 h 얼마나 비슷한지를 수치로 나타낸 것.

학습할 대상이다. 비슷함의 척도는 두 벡터의 내적 값으로 판단한다.

 

※ 앞에서 a는 '가중치' 였고, 여기서 h는 LSTM의 은닉벡터이다. hs와 h의 유사도를 구하기 위해 내적하는 것이다. 

class AttentionWeight:
    def __init__(self):
        self.params, s.grads = []. []
        self.softmax = Softmax()
        self.cache = None

    def forward(self, hs, h):
        N, T, H = 10, 5, 4
        hr = h.reshape(N, 1, H).repeat(T, axis=1) # hs와 h의 유사도를 구하기 위해 내적하는 것
        t = hs * hr
        s = np.sum(t, axis=2) # H(4)축을 없엔다.
        a = self.softmax.forward(s)

        self.cache = (hs, hr)
        return a

    def backward(self, da):
        hs, hr = self.cache
        N, T, H = hs.shape

        ds = self.softmax.backward(da)
        dt = ds.reshape(N, T, 1).repeat(H, axis=2)
        dhs = dt * hr
        dhr = dt * hs
        dh = np.sum(dhr, axis=1)

        return dhs, dh

 

3-3. Decoder의 개선 (3)

이제 맥락벡터(c)를 구할 차례이다.

앞서구한 Attention Weight(3-2) 계층은 hs와 h의 유사도를 나타내는 수치값 이었다.

Weight Sum계층(3-1)은 hs와 a를 곱하여(Pointwise Muliplication) 맥락 벡터 c를 계산한다.

 

Weight Sum + Attention Weight = Attention

class Attention:
    def __init__(self):
        self.params, self.grads = [], []
        self.attention_weight_layer = AttentionWeight()
        self.weight_sum_layer = WeightSum()
        self.attention_weight = None

    def forward(self, hs, h):
        a = self.attention_weight_layer.forward(hs, h)
        out = self.weight_sum_layer.forward(hs, a)
        self.attention_weight = a
        return out

    def backward(self, dout):
        dhs0, da = self.weight_sum_layer.backward(dout)
        dhs1, dh = self.attention_weight_layer.backward(da)
        dhs = dhs0 + dhs1
        return dhs, dh

※ Weight Sum + Attention Weight = Attention

 

 

Attention 계층을 갖춘 Decoder 계층

기존의 Decoder 계층에서 Affine 계층은 LSTM 계층의 은닉벡터 만을 입력으로 받았으나, Attention 계층의 맥락벡터(c) 까지 같이 입력으로 받게 되었다. 

 

3-4. Time Attention 계층의 구현

시계열의 Attention 계층을 한대 모아, Time Attention 계층으로 구현할 수 있다.

 

class TimeAttention:
    def __init__(self):
        self.params, self.grads = [], []
        self.layers = None
        self.attention_weight = None

    def forward(self, hs_enc, hs_dec):
        N, T, H = hs_dec.shape
        out = np.empty_like(hs_enc)
        self.layers = []
        self.attention_weight = []
        for t in range(T):
            layer = Attention()
            out[:, t, :] = layer.forward(hs_enc, hs_dec[:, t, :])
            self.layers.append(layer)
            self.attention_weight.append(layer.attention_weight)
        return out

    def backward(self, dout):
        N, T, H = dout.shape
        dhs_enc = 0
        dhs_dec = np.empty_like(dout)
        for t in range(T):
            layer = self.layers[t]
            dhs, dh = layer.backward(dout[:, t, :])
            dhs_enc += dhs
            dhs_dec[:, t, :] = dh
        return dhs_enc, dhs_dec

 

4. Attention을 갖춘 Seq2Seq의 구현

이제 Seq2Seq를 Attention 계층을 넣어서 수정해 본다.

 

4-1. Encoder의 구현

class AttentionEncoder(Encoder):
    def forward(self, xs):
        xs = self.embed.forward(xs)
        hs = self.lstm.forward(xs)
        return hs

    def backward(self, dhs):
        dout = self.lstm.backward(dhs)
        dout = self.embed.backward(dout)
        return dout

 

4-2. Decoder의 구현

class AttentionDecoder:
    def __init__(self, vocab_size, wordvec_size, hidden_size):
        V, D, H = vocab_size, wordvec_size, hidden_size
        rn = np.random.randn
        embed_W = (rn(V, D) / 100).astype('f')
        lstm_Wx = (rn(D, 4 * H) / np.sqrt(D)).astype('f')
        lstm_Wh = (rn(D, 4 * H) / np.sqrt(H)).astype('f')
        lstm_b = np.zeros(4 * H).astype('f')
        affine_W = (rn(2 * H, V) / np.sqrt(2 * H)).astype('f')
        affine_b = np.zeros(V).astype('f')

        self.embed = TimeEmbedding(embed_W)
        self.lstm = TimeLSTM(lstm_Wx, lstm_Wh, lstm_b, stateful=True)
        self.attention = TimeAttention() # 추가됨
        self.affine = TimeAffine(affine_W, affine_b)
        layers = [self.embed, self.lstm, self.attention, self.affine]

        self.params, self.grads = [], []
        for layer in layers:
            self.params += layer.params
            self.grads += layer.grads

    def forward(self, xs, enc_hs):
        h = enc_hs[:, -1]
        self.lstm.set_state(h)

        out = self.embed.forward(xs)
        dec_hs = self.lstm.forward(out)
        c = self.attention.forward(enc_hs, dec_hs)
        out = np.concatenate((c, dec_hs), axis=2)
        score = self.affine.forward(out)

        return score

    def backward(self, dscore):
        dout = self.affine.backward(dscore)
        N, T, H2 = dout.shape
        H = H2 // 2

        dc, ddec_hs0 = dout[:,:,:H], dout[:,:,H:]
        denc_hs, ddec_hs1 = self.attention.backward(dc)
        ddec_hs = ddec_hs0 + ddec_hs1
        dout = self.lstm.backward(ddec_hs)
        dh = self.lstm.dh
        denc_hs[:, -1] += dh
        self.embed.backward(dout)

        return denc_hs

    def generate(self, enc_hs, start_id, sample_size):
        sampled = []
        sample_id = start_id
        h = enc_hs[:, -1]
        self.lstm.set_state(h)

        for _ in range(sample_size):
            x = np.array([sample_id]).reshape((1, 1))

            out = self.embed.forward(x)
            dec_hs = self.lstm.forward(out)
            c = self.attention.forward(enc_hs, dec_hs)
            out = np.concatenate((c, dec_hs), axis=2)
            score = self.affine.forward(out)

            sample_id = np.argmax(score.flatten())
            sampled.append(sample_id)

        return sampled

 

4-3. Seq2Seq의 구현

class AttentionSeq2Seq(Seq2seq):
    def __init__(self, vocab_size, wordvec_size, hidden_size):
        args = vocab_size, wordvec_size, hidden_size
        self.encoder - AttentionEncoder(*args) #추가됨
        self.decoder = AttentionDecoder(*args) #추가됨
        self.softmax - TimeSoftmaxWithLoss()
        
        self.params = self.encoder.params + self.decoder.params
        self.grads = self.encoder.grads + self.decoder.grads
반응형

'데이터 과학 > 딥러닝(Deep Learning)' 카테고리의 다른 글

PCA(주성분 분석) 정리  (0) 2021.02.10
Autoencoder 설명  (0) 2021.02.06
Sigmoid 함수 vs Softmax 함수  (0) 2021.02.01
Transformer 설명  (0) 2021.01.14
RNN & LSTM 설명 및 구현  (2) 2020.11.10