본문 바로가기
Deep Learning/Hands On Machine Learning

16.3 신경망 기계 번역을 위한 인코더-디코더 네트워크

by 대소기 2021. 11. 24.

인코더 디코더 네트워크

인코더 디코더 네트워크 구조

* 위 그림은 영어 단어를 입력하면 프랑스어로 번역해주는 인코더-디코더 네트워크를 뜻한다.

* 인코더 부분의 input은 I drink milk를 뒤집어 놓은 문장인 것을 확인할 수 있다. 이는 문장의 시작 단어가 인코더의 마지막에 가도록 하기 위해서이며, 디코더가 번역할 첫 단어가 문장의 시작 단어( ' I ' ) 이기 때문이다.

* 각 단어는 초기에 1차원 값(ex - milk : 288) 으로 표현되어 있지만, Embedding layer를 지나면서 단어 임베딩으로 변환되어 인코더, 디코더의 input으로 들어가게 된다.

* 각 time step마다 디코더는 vocabulary dictionary에 있는 단어에 대한 점수를 출력하고 이 output은 다시 softmax 층으로 입력되어 확률을 출력하게 된다. 예를 들어 디코더의 첫 번째 time step의 Je는 0.2의 확률 값을 가지고, Tu는 0.01의 확률 값을 가지게 된다면 가장 높은 확률 값을 띠는 것은 Je이기 때문에 Je가 출력된다.

* 이러한 작업은 소프트맥스를 사용한 일반적인 분류 작업과 매우 유사하다. 손실함수 또한 'sparse_categorical_crossentropy'를 사용한다.

*추론 과정에서는 이전 time step의 output을 다음 step의 입력으로 집어넣는 방식을 사용하게 된다.

인코더 디코더 네트워크 구현

* 인코더 디코더 네트워크를 구현하기 위해서는 몇 가지를 더 살펴봐야 한다.

* 먼저 앞서 구조에서 살펴본 바에 의하면 인코더와 디코더의 입력 sequence의 길이가 동일하다고 가정하였다. 하지만, 실제로 문장의 길이는 다르다. 이를 해결하기 위해 문장을 비슷한 길이의 bucket으로 그루핑 해야 한다(1

6길이 버킷, 7

12길이 버킷 등으로 길이마다 그루핑). 또한 버킷에 담긴 문장이 모두 동일한 길이가 되도록 padding을 추가해야 한다('I drink milk'가 1~6 길이 버킷에 들어가게 되면 ', , , milk, drink, I' 가 된다.).

* EOS 토큰 이후의 출력은 모두 무시해야 한다. 예를 들어 'Je bois du lait oui'를 출력하면 마지막 단어에 대한 손실은 무시한다(마스킹 처리를 통해).

* 출력 어휘 사전의 크기가 50000개와 같이 매우 크다면 출력 벡터가 50000차원이 될 것이고 방대한 양의 연산이 필요해진다. 이를 방지하기 위하여 tf.nn.sampled_softmax_loss()를 사용해 sampling softmax를 적용한다. sampling softmax는 타깃 단어에 대한 로짓이 아닌 무작위로 sampling한 단어의 로짓만을 고려하기 때문에 연산량을 줄일 수 있다. 하지만, sampling softmax는 타깃 값을 알고 있어야 하기 때문에 추론 시에는 사용 불가능하다.

import tensorflow_addons as tfa

encoder_inputs = keras.layers.Input(shape=[None], dtype=np.int32)
decoder_inputs = keras.layers.Input(shape=[None], dtype=np.int32)
sequence_lengths = keras.layers.Input(shape=[], dtype=np.int32)

embeddings = keras.layers.Embedding(vocab_size, embed_size)
encoder_embeddings = embeddings(encoder_inputs)
decoder_embeddings = embeddings(decoder_inputs)

encoder = keras.layers.LSTM(512, return_state=True) #return hidden state
encoder_outputs, state_h, state_c = encoder(encoder_embeddings)
encoder_state = [state_h, state_c]

sampler = tfa.seq2seq.sampler.TrainingSampler()

decoder_cell = keras.layers.LSTMCell(512)
output_layer = keras.layers.Dense(vocab_size)
decoder = tfa.seq2seq.basic_decoder.BasicDecoder(decoder_cell, sampler,
                                                 output_layer=output_layer)
final_outputs, final_state, final_sequence_lengths = decoder(
    decoder_embeddings, initial_state=encoder_state,
    sequence_length=sequence_lengths)
Y_proba = tf.nn.softmax(final_outputs.rnn_output)

model = keras.models.Model(
    inputs=[encoder_inputs, decoder_inputs, sequence_lengths],
    outputs=[Y_proba])

* TrainingSampler는 tensorflow addons에서 지원하는 샘플러 중 하나이다. 각 step에서 디코더에게 이전 step의 출력이 무엇인지를 알려주는 역할을 한다. 추론 시에는 실제로 출력되는 토큰의 임베딩이 된다. 훈련 시에는 이전 타깃 토큰의 임베딩이 된다.

양방향 RNN

* 일반적인 순환층은 이전 time step의 정보와 현재의 입력만 출력에 사용하기 때문에 과거 및 현재의 정보만을 활용한다고 할 수 있다.

* 하지만 번역과 같은 NLP 문제에서 이렇게 과거의 정보만을 입력으로 받는 것은 충분하지 않다. 번역의 경우 과거의 문장 흐름 만으로 다음 단어가 어떤 것이 올지 예측하는 것은 매우 어렵다. 그렇기 때문에 두 개의 순환층을 사용해 하나는 앞에서부터, 하나는 뒤에서부터 단어를 읽어 각 time step마다 두 출력을 연결해 prediction을 출력하는 방법을 사용한다.

* 이러한 양방향 순환층을 keras로 구현하기 위해서는 아래와 같은 코드를 사용한다.

keras.layers.Bidirectional(keras.layers.GRU(10, return_sequence=True)

빔 검색(beam search)

* 인코더 - 디코더 모델을 활용하여 문장을 번역할 때 step마다 가장 확률이 높은 단어만을 선택하는 것은 잘못된 번역으로 이어지는 경우가 많다. 예를 들어 훈련 세트에 'Comment vas-tu jouer?' 라는 'How will you play?'를 뜻하는 프랑스어 문장이 다수 들어있다고 해보자. 하지만, 인코더에 입력으로 'Comment vas-tu?'라는 'How are you?'를 뜻하는 문장이 들어왔다고 가정해보자. 이 경우 How are you?가 target임에도 불구하고 How will you?로 번역할 가능성이 높아진다. Comment vas-tu가 입력되었을 때 How 다음에 가장 확률이 높은 단어는 will이기 때문이다.

* 이러한 문제를 방지하기 위하여 가장 확률이 높은 단어가 아닌 k개의 가능성 있는 문장 리스트를 생성하여 k개의 가능성 있는 문장을 만드는 방법을 사용한다. 이 방법이 beam search이다. 여기서 k는 beam width라고 부른다.

* 빔 검색으로 'Comment vas-tu jouer?'를 번역한다고 해보자. beam width는 3이다. 디코더의 첫 번째 time step에서는 3개의 가장 가능성 높은 단어 리스트와 추정확률을 생성한다. How(75%), What(3%), You(1%)가 가장 가능성이 높다. 이제 3개의 단어를 각 모델이라고 생각하고 각 모델별로 다음 단어로 가능성이 높은 3개의 확률을 출력할 것이다. How로 시작한 모델의 경우를 보면 다음 단어로 가능성이 높은 3개는 will(36%), are(32%), do(16%)이다. 이 확률은 How가 주어졌다는 조건이 붙은 조건부 확률이다. 이 조건부 확률을 계산하는 과정에서 각 모델은 어휘사전에 있는 단어 개수, 예를 들어 10000개의 단어가 있다고 했을 때 10000개에 대한 조건부 확률을 일일히 계산하게 된다. 그리고 이 최초 단어의 추정 확률과 조건부 확률을 곱한다. 예를 들어 How will은 75% x 36% = 27%이다. 그리고 모든 모델 중 가장 확률이 높은 3개의 문장을 추린다. 예를 들자면 전체 문장 중 How will(27%), How are(24%), How do(12%)와 같이 3개의 문장을 추리는 것과 같다. 그리고 동일한 과정으로 3개의 문장을 계속해서 추려 나간다. 그러면 최종적으로 How do you do(7%), How are you (6%), How are you doing(3%) 와 같은 문장을 얻을 수 있다. 이 중 가장 확률이 높은 문장을 선택한다.

빔 검색 구현

* 이를 tensorflow addons를 활용하면 쉽게 구현할 수 있다.

* 이 빔 검색은 짧은 문장에서는 잘 작동하지만 긴 문장에서는 기억 문제 때문에 잘 작동하지 않는다. 이를 해결하기 위해서는 어텐션 메커니즘을 사용할 수 있다.

model = keras.models.Sequential([
    keras.layers.GRU(10, return_sequences=True, input_shape=[None, 10]),
    keras.layers.Bidirectional(keras.layers.GRU(10, return_sequences=True))
])

model.summary()