RNN을 사용한 문장 생성(2)

Road.1·2021년 8월 11일
1

NLP

목록 보기
10/13
post-thumbnail

위 주제는 밑바닥부터 시작하는 딥러닝2 7강, CS224d를 바탕으로 작성한 글 입니다.

이전글에서 언어 모델을 사용하여 문장을 생성해보았다. 문장 생성을 위한 'seq2seq'를 알아보았다. 이전글 보기 RNN을 사용한 문장 생성(1)

오늘은 덧셈 데이터를 이용해서 seq2seq의 성능을 확인해 보자!

seq2seq 구현

Encoder

Encoder 클래스는 Embedding 계층과 LSTM 계층으로 구성된다.
Embedding 계층에서는 문자 ID("[1, 2, 3]")를 문자 벡터로 변환한다.
그리고 이 문자 벡터가 LSTM 계층으로 입력된다.
이 구성에서 더 위에는 다른 계층이 없으니 LSTM 계층의 위쪽 출력은 폐기된다.

Encoder 에서는 마지막 문자를 처리한 후 LSTM 계층이 은닉 상태 h를 출력한다.
그리고 이 은닉 상태 h가 Decoder 로 전달된다.
Encoder 에서는 LSTM 의 은닉 상태만을 Decoder 에 전달한다.

시간 방향을 한꺼번에 처리하는 Time 계층을 사용하여 Encoder를 구현한다.

class Encoder:
    def __init__(self, vocab_size, wordvec_size, hidden_size):
        # vocab_size 문자 종류 0~9, '+', '공백 문자', '_'
        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(H, 4 * H) / np.sqrt(H)).astype('f')
        lstm_b = np.zeros(4 * H).astype('f')
        
        self.embed = TimeEmbedding(embed_W)
        self.lstm = TimeLSTM(lstm_Wx, lstm_Wh, lstm_b, stateful=False)
        
        self.params = self.embed.params + self.lstm.params
        self.grads = self.embed.grads + self.lstm.grads
        self.hs = None
        
    def forward(self, xs):
        xs = self.embed.forward(xs)
        hs = self.lstm.forward(xs)
        self.hs = hs
        return hs[:, -1, :]
    
    def backward(self, dh):
        dhs = np.zeros_like(self.hs)
        dhs[:, -1, :] = dh
        
        dout = self.lstm.backward(dhs)
        dout = self.embed.backward(dout)
        return dout

__init__

  • vocab_size는 어휘 수이며, 0~9의 숫자와 '+', '공백문자', '_' 총 13가지의 문자를 사용한다.
  • wordvec_size는 문자 벡터의 차원 수
  • hidden_size는 LSTM 계층의 은닉 상태 벡터의 차원 수를 의미한다.
  • LSTM 계층이 상태를 유지하지 않기 때문에 stateful=False로 설정한다.

    이전 언어 모델은 '긴 시계열 데이터'가 하나뿐인 문제를 다뤘다. stateful을 True로 설정하여 은닉 상태를 유지한 채로 '긴 시계열 데이터'를 처리한 것이다. 하지만 '짧은 시계열 데이터'가 여러개인 문제는 문제마다 초기화를 해줘야하므로 False로 한다.

forward

  • xs를 받아서 Embed, lstm을 거친다. 나온 hs를 forward() 메서드의 출력으로 반환한다.

backward

  • LSTM 계층의 마지막 은닉 상태에 대한 기울기가 dh인수로 전해진다. 이 dh는 Decoder가 전해주는 기울기이다.
  • 모든 원소가 0인 dhs를 생성하고 dh를 해당 위치에 할당한다. 다음은 계층의 reverse로 backward() 메서드를 호출한다.

Decoder

Decoder는 Encoder 클래스가 출력한 h를 받아 목적으로 하는 다른 문자열을 출력한다.

Decoder의 계층 구성이다.

입력 데이터를 ['_', '6', '2', '']로 주고, 이에 대응하는 출력은 ['6', '2', '', '']이 되도록 한다. (Softmax with Loss시에 score와 decoder_ts 정답값을 넣어줘야 하기 때문이다)

RNN으로 문장을 생성할 때 학습 시와 생성 시의 데이터 부여 방법이 다르다.
학습 시는 정답을 알고 있기 때문에 시계열 방향의 데이터를 한꺼번에 보여줄 수 있다.
한편, 추론 시(새로운 문자열을 생성할 때)에는 최초 시작을 알리는 구분 문자(여기서는 '_') 하나만 준다.
그리고 그 출력으로부터 문자를 하나 샘플링하여, 그 샘플링 문자를 다음 입력으로 사용하는 과정을 반복한다.

이번에는 이전 처럼 '확률 분포'를 바탕으로 샘플링을 하지 않고, '결정적'인 답을 위해 가장 높은 확률의 답을 고른다.

argmax는 최댓값을 가진 원소의 인덱스(여기서는 문자ID)를 선택하는 메서드이다.
Softmax를 사용하지 않고 score가 가장 큰 문자를 선택한다.

이유는 Softmax는 입력된 Score의 벡터를 정규화하는데, 원소 값이 달라지긴 하지만, 대소 관계는 바뀌지 않는다. 어차피 최대 확률 정답을 뽑는 것이므로 Softmax를 생략할 수 있다.
Softmax with Loss 계층은 이후 구현할 Seq2seq 클래스에서 처리하기로 한다.

class Decoder:
  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(H, 4 * H) / np.sqrt(H)).astype('f')
      lstm_b = np.zeros(4 * H).astype('f')
      affine_W = (rn(H, V) / np.sqrt(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.affine = TimeAffine(affine_W, affine_b)
      
      self.params, self.grads = [], []
      for layer in (self.embed, self.lstm, self.affine):
          self.params += layer.params
          self.grads += layer.grads
      
  def forward(self, xs, h):
      self.lstm.set_state(h)
      
      out = self.embed.forward(xs)
      out = self.lstm.forward(out)
      score = self.affine.forward(out)
      return score
  
  def backward(self, dscore):
      dout = self.affine.backward(dscore)
      dout = self.lstm.backward(dout)
      dout = self.embed.backward(dout)
      dh = self.lstm.dh
      return dh
  
  def generate(self, h, start_id, sample_size):
      sampled = []
      sample_id = start_id
      self.lstm.set_state(h)
      
      for _ in range(sample_size):
          x = np.array(sample_id).reshape((1, 1))
          out = self.embed.forward(x)
          out = self.lstm.forward(out)
          score = self.affine.forward(out)
          
          sample_id = np.argmax(score.flatten())
          sampled.append(int(sample_id))
      
      return sampled

forward

  • Encoder에서 set_state(h)를 받아서 계층 순서로 역전파 한다.

backward

  • Softmax with Loss 계층으로부터 기울기 dscore를 받아 계층으로 각각 전파시킨다.
  • TimeLSTM 클래스에 시간 방향의 기울기가 dh에 저장되어 있다. dh를 꺼내서 backward의 출력으로 반환한다.

generate

  • Decoder 클래스는 학습 시와 문장 생성 시의 동작이 다르다.
    그래서 문장 생성을 담당하는 generate()메서드를 구현한다.
  • Encoder에서 h를 받는다.
  • 최초로 주어지는 문자 ID인 start_id
  • 생성하는 문자 수인 sample_size

Encoder의 출력 h를 Decoder의 Time LSTM 계층의 상태로 설정했다. 영벡터가 되면 안되므로 stateful = True로 해주었다. 한 번 설정된 이 은닉 상태는 재설정되지 않고, 즉 Encoder의 h를 유지하면서 순전파가 이루어진다.

Seq2seq 클래스

seq2seq클래스는 Encoder와 Decoder를 연결하고 Time Softmax with Loss 계층을 이용해 손실을 계산하면 된다.

class Seq2seq(BaseModel):
    def __init__(self, vocab_size, wordvec_size, hidden_size):
        V, D, H = vocab_size, wordvec_size, hidden_size
        self.encoder = Encoder(V, D, H)
        self.decoder = Decoder(V, D, H)
        self.softmax = TimeSoftmaxWithLoss()
        
        self.params = self.encoder.params + self.decoder.params
        self.grads = self.encoder.grads + self.decoder.grads
        
    def forward(self, xs, ts):
        # 입력은 처음부터 마지막 전까지, 출력은 처음 이후부터 마지막까지
        decoder_xs, decoder_ts = ts[:, :-1], ts[:, 1:]
        h = self.encoder.forward(xs)
        score = self.decoder.forward(decoder_xs, h)
        loss = self.softmax.forward(score, decoder_ts)
        return loss

    def backward(self, dout=1):
        dout = self.softmax.backward(dout)
        dh = self.decoder.backward(dout)
        dout = self.encoder.backward(dh)
        return dout

    def generate(self, xs, start_id, sample_size):
        h = self.encoder.forward(xs)
        sampled = self.decoder.generate(h, start_id, sample_size)
        return sampled

평가

seq2seq의 학습의 흐름은 다음과 같다.

  1. 학습 데이터에서 미니배치를 선택하고
  2. 미니배치로부터 기울기를 계산하고
  3. 기울기를 사용하여 매개변수를 갱신한다.

Tainer 클래스를 사용해 이 규칙대로 작업을 수행한다.
매 애폭마다 seq2seq 가 테스트 데이터를 풀게 하여(문자열을 생성하여) 학습 중간중간 정답률을 측정한다.

import sys
sys.path.append('..')
import numpy as np
import matplotlib.pyplot as plt
from dataset import sequence
from common.optimizer import Adam
from common.trainer import Trainer
from common.util import eval_seq2seq
from seq2seq import Seq2seq
from peeky_seq2seq import PeekySeq2seq

# 데이터셋 읽기
(x_train, t_train), (x_test, t_test) = sequence.load_data('addition.txt')
char_to_id, id_to_char = sequence.get_vocab()

# 개선 1 설정
# =============================================
# =============================================

# 하이퍼파라미터 설정
vocab_size = len(char_to_id)
wordvec_size = 16
hidden_size = 128
batch_size = 128
max_epoch = 25
max_grad = 5.0


model = Seq2seq(vocab_size, wordvec_size, hidden_size)
# 개선 2 설정
# =============================================
# =============================================

optimizer = Adam()
trainer = Trainer(model, optimizer)

acc_list = []
for epoch in range(max_epoch):
    trainer.fit(x_train, t_train, max_epoch=1,
                batch_size=batch_size, max_grad=max_grad)

    correct_num = 0
    for i in range(len(x_test)):
        question, correct = x_test[[i]], t_test[[i]]
        verbose = i < 10
        correct_num += eval_seq2seq(model, question, correct,
                                    id_to_char, verbose, is_reverse)

    acc = float(correct_num) / len(x_test)
    acc_list.append(acc)
    print('검증 정확도 %.3f%%' % (acc * 100))

평가 척도로 정답률을 사용하였는데, 에폭마다 테스트 데이터의 문제 중 몇 개를 풀게 하여 올바르게 답했는지를 채점한다.

참고로 정답률 측정에는 eval_seq2seq메서드를 사용하여 문제(question)를 모델(model)에 주고, 문자열을 생성해서 그것이 답(correct)과 같은지를 판정한다. 모델이 내놓은 답이 맞으면 1을 주고 틀리면 0을 돌려준다.

결과

Q 는 출제 문장
T는 정답
X/O는 모델이 놓은 답 틀리면 X, 맞추면 O로 표시가 된다.

  • 1 에포크
  • 25 에포크

학습을 진행 할수록 조금씩 정답에 가까워진다.

개선의 여지가 있어보인다. 지금부터는 seq2seq를 개선해보자.

seq2seq 개선

입력 데이터 반전(Reverse)

반전을 시키면 학습 진행이 빨라져서, 결과적으로 최종 정확도도 좋아진다.

위의 평가 코드에 개선 1에 넣어주면 된다.

is_reverse = False  # True
if is_reverse:
    x_train, x_test = x_train[:, ::-1], x_test[:, ::-1]
  • 25 에포크

    오! 어느정도 맞추기 시작했다.

놀랍게도 정답률이 50퍼센트로 올랐다.
단지 반전을 했을 뿐인데 성능이 이렇게 올라가는 이유는 무엇일까?

성능이 높아지는 이유

직관적으로는 기울기 전파가 원활해지기 때문이다.

예로 "나는 고양이로소이다"를 "I am a cat"으로 번역하는 문제에서,
'나'라는 단어가 'I'로 변환되는 과정을 생각해보자.
이때 '나'로부터 'I'까지 가려면 '는','고양이','로소','이다'까지 총 4단어 분량의 LSTM 계층을 거쳐야 한다.
따라서 역전파 시, 'I'로부터 전해지는 기울기가 '나'에 도달하기까지, 그 먼 거리만큼 영향을 더 받게 된다.

여기서 입력을 반전시키면, 즉 "이다 로소 고양이 는" 순으로 바꾸면 어떻게 될까?
이제 '나'와 'I'는 바로 옆이 되었으니 기울기가 직접 전해진다.
이처럼 입력 문장의 첫 부분에서는 반전 덕분에 대응하는 변환 후 단어와 가까우므로 (그런 경우가 많아지므로),
기울기가 더 잘 전해져서 학습 효율이 좋아진다고 생각할 수 있다.
다만, 입력 데이터를 반전해도 단어 사이의 평균적인 거리는 그대로이다.

엿보기(Peeky)

eq2seq의 Encoder 동작을 한번 더 살펴보자.

Encoder는 입력 문장(문제 문장)을 고정 길이 벡터 h로 변환한다.
이때 h 안에는 Decoder 에게 필요한 정보가 모두 담겨 있다.
즉, hDecoder 에 있어서는 유일한 정보인 셈이다.
이말은 최초 시각의 LSTM 계층만이 벡터 h를 이용하고 있다는 것이다.

개선 방법은 Encoder의 출력 h를 Decoder의 다른 계층에게도 전해주는 것이다

모든 시각의 Affine 계층LSTM 계층에 Encoder의 출력 h를 전해준다.

기존에는 하나의 LSTM이 소유하던 정보 h를 여러 계층이 공유하는 것을 알 수 있다.

이는 집단지성에 비유하여, 중요한 정보를 혼자가 아니라 많은 사람과 공유한다면 더 올바른 결정을 내릴 가능성이 커진다는 것과 같다.

'엿보기'라는 것이 다른 계층도 인코딩된 정보를 '엿본다'라고 해석하여 'Peeky Decoder'와 'Peeky seq2seq'라고 한다.

위 그림에서는 LSTM 계층과 Affine 계층에 입력되는 벡터가 2개씩이 되었다.
이는 실제로는 두 벡터가 연결된 것을 의미한다.
따라서 두 벡터를 연결시키는 concat 노드를 이용해 다음 그림처럼 그려야 정확한 계산 그래프가 된다.

class PeekyDecoder:
    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(H + D, 4 * H) / np.sqrt(H + D)).astype('f') # 변화
        lstm_Wh = (rn(H, 4 * H) / np.sqrt(H)).astype('f')
        lstm_b = np.zeros(4 * H).astype('f')
        affine_W = (rn(H + H, V) / np.sqrt(H + 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.affine = TimeAffine(affine_W, affine_b)

        self.params, self.grads = [], []
        for layer in (self.embed, self.lstm, self.affine):
            self.params += layer.params
            self.grads += layer.grads
        self.cache = None

    def forward(self, xs, h):
        N, T = xs.shape
        N, H = h.shape

        self.lstm.set_state(h)

        out = self.embed.forward(xs)
        hs = np.repeat(h, T, axis=0).reshape(N, T, H) # 변화
        out = np.concatenate((hs, out), axis=2) # 변화

        out = self.lstm.forward(out) 
        out = np.concatenate((hs, out), axis=2) # 변화

        score = self.affine.forward(out)
        self.cache = H
        return score

    def backward(self, dscore):
        H = self.cache

        dout = self.affine.backward(dscore)
        dout, dhs0 = dout[:, :, H:], dout[:, :, :H] # 변화 나누기
        dout = self.lstm.backward(dout)
        dembed, dhs1 = dout[:, :, H:], dout[:, :, :H] # 변화 나누기
        self.embed.backward(dembed)

        dhs = dhs0 + dhs1 # 변화
        dh = self.lstm.dh + np.sum(dhs, axis=1) # 변화
        return dh

    def generate(self, h, start_id, sample_size):
        sampled = []
        char_id = start_id
        self.lstm.set_state(h)

        H = h.shape[1]
        peeky_h = h.reshape(1, 1, H)
        for _ in range(sample_size):
            x = np.array([char_id]).reshape((1, 1))
            out = self.embed.forward(x)

            out = np.concatenate((peeky_h, out), axis=2)
            out = self.lstm.forward(out)
            out = np.concatenate((peeky_h, out), axis=2)
            score = self.affine.forward(out)

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

        return sampled

__init__

  • 기존 LSTM과 affine에 H만큼 가중치의 형상을 더해준다.

forward

  • hnp.repeat(h, T, axis=0).reshape(N, T, H)로 시계열만큼 복제해 hs에 저장한다.
  • np.concatenate((hs, out), axis=2)를 이용해 hs와 Embedding 계층의 출력을 연결하고, 이를 LSTM 계층에 입력한다.
    Affine도 마찬가지이다.

backward

  • affine와 LSTM 역전파시킨 부분을 [H: ], [ :H] 로 나누어 복제된 dhs을 하나로 더하여줘서 LSTM의 시간 방향 기울기가 전달되는 dh에 같이 더해주어 반환한다.

generate

  • peeky_h를 reshape(1, 1, H)하고 LSTM전 affine전에 np.concatenate()를 사용하여 합쳐준다.

class PeekySeq2seq(Seq2seq):
    def __init__(self, vocab_size, wordvec_size, hidden_size):
        V, D, H = vocab_size, wordvec_size, hidden_size
        self.encoder = Encoder(V, D, H)
        self.decoder = PeekyDecoder(V, D, H)
        self.softmax = TimeSoftmaxWithLoss()

        self.params = self.encoder.params + self.decoder.params
        self.grads = self.encoder.grads + self.decoder.grads

model = PeekySeq2seq(vocab_size, wordvec_size, hideen_size)

평가 코드에 개선2에 넣어주면 된다.

결과는 다음과 같다.

  • 25 에포크

    다 맞추었다!! 성능도 99퍼로 아주 뛰어난 것을 알 수 있다.

Peeky를 추가로 적용하자 seq2seq 의 결과가 월등히 좋아졌다.
이상의 실험 결과에서 ReversePeeky 가 함께 효과적으로 작동하고 있음을 알 수 있다.

입력 문장을 반전시키는 Reverse, 그리고 Encoder 의 정보를 널리 퍼지게 하는 Peeky 덕분에 만족할 만한 결과를 얻었다.

다음에 배울 "어텐션"이라는 기술로 seq2seq를 극적으로 진화시킬 수 있다.

주의할점은 Peeky를 사용하면 신경망의 가중치 매개변수가 커져서 계산량도 늘어난다. 더욱이 seq2seq의 정확도는 하이퍼파라미터에 영향을 크게 받는다. 예제에서의 결과는 믿음직스럽지만 실제 문제에서는 아닐 수 있다.

seq2seq를 이용하는 애플리케이션

seq2seq 는 한 시계열 데이터를 다른 시계열 데이터로 변환한다.
이 시계열 데이터를 변환하는 프레임워크는 다양한 문제에 적용할 수 있다.

  • 기계 번역: 한 언어의 문장을 다른 언어의 문장으로 변환
  • 자동 요약: 긴 문장을 짧게 요약된 문장으로 변환
  • 질의응답: 질문을 응답으로 변환
  • 메일 자동 응답: 받은 메일의 문장을 답변 글로 변환

seq2seq 는 2개가 짝을 이루는 시계열 데이터를 다루는 문제에 이용할 수 있다.
자연어 외에도 음성이나 영상 등에도 이용할 수 있다.
얼핏 보기에는 seq2seq 가 적용될 수 없을 것 같은 문제라도 입력/출력 데이터를 전처리하면 seq2seq 를 적용할 수 있는 경우도 있다.

챗봇

챗봇은 사람과 컴퓨터가 텍스트로 대화를 나누는 프로그램이다.
챗봇에도 seq2seq를 사용할 수 있다.
대화라는 것은 상대의 말과 자신의 말로 구성되기 때문에 상대의 말을 자신의 말로 변환하는 문제로 볼 수 있다.
즉, 대화의 텍스트 데이터가 준비되면 그것으로 seq2seq 를 학습시킬 수 있다.

대화를 보면, 챗봇은 훌륭하게 문제를 해결하고 있다.
VPN 연결이 되지 않은 사람을, 그 문제를 해결할 수 있는 URL 링크로 안내한 것이다.
물론 이 챗봇은 IT 헬프데스크에 한정된 문제를 대상으로 하기 때문에 범용적으로 사용할 수는 없다.
하지만 대화 기반으로 정답이나 힌트를 얻는 방식은 실용성이 높고 다양하게 응용하여 효과를 볼 수 있다.

알고리즘 학습

'덧셈'보다 더 고차원 적인 문제도 처리할 수 있다.
파이썬 코드의 소스 코드를 seq2seq에 입력하고 이를 학습 시킬 수 있다.

다음에는 RNN을 확장한 NTM(Neural Turing Machine) 모델을 이야기할 것이다. NTM 모델로는 컴퓨터(튜링 머신)가 메모리를 읽고 쓰는 순서를 학습하여 알고리즘을 재현할 것이다.

이미지 캡셔닝

지금까지는 seq2seq 가 텍스트를 다루는 예만을 보았다.

하지만 seq2seq 는 텍스트 외에도, 이미지나 음성 등 다양한 데이터를 처리할 수 있다.

이번절에서는 이미지를 문장으로 변환하는 이미지 캡셔닝을 살펴본다.

이미지 캡셔닝은 이미지를 문장으로 변환한다.
이 문제도 다음 그림과 같이 seq2seq 의 틀에서 해결할 수 있다.

그림을 보면 친숙한 신경망 구성으로 보인다. 다른점은 Encoder가 LSTM에서 합성곱 신경망(CNN)으로 바뀌었다.
Decoder는 같은 신경망이다.

겨우 LSTM을 CNN으로 대체한 것 만으로 seq2seq 는 이미지도 처리할 수 있다.

이 예에서는 이미지의 인코딩을 CNN이 수행한다. 이때 CNN의 최종 출력은 특징 맵이다.

특징 맵은 3차원(높이, 폭, 채널)이므로, 이를 Decoder 의 LSTM이 처리할 수 있도록 손질해야 한다.

그래서 CNN의 특징 맵을 1차원으로 평탄화(Falttening)한 후 완전연결인 Affine 계층에서 변환한다. 그런 다음 변환된 데이터를 Decoder에 전달하면 지금까지와 같은 문장 생성을 수행할 수 있다.

위 그림의 CNN에 VGG나 ResNet 등의 입증된 신경망을 사용하고, 가중치로는 다른 이미지 데이터셋(ImageNet 등)으로 학습을 끝낸 것을 이용한다.
이렇게 하면 좋은 인코딩을 얻을 수 있고, 좋은 문장을 생성할 수 있다.

몇가지 예시를 보여준다. im2txt라는, 텐서플로우로 생성된 예를 가져왔다.

훌륭한 결과가 나온 것을 알 수 있다.
이는 이미지와 설명을 듬뿍 담은 학습 데이터와
학습 데이터를 효율적으로 배울 수 있는 seq2seq 에 있다.

이미지 캡셔닝에 대해서는 후에 논문리뷰를 통한 글로 다시 작성 할 예정이다!

정리

이번장은 언어 모델을 사용하여 문장을 생성하는 기능을 추가해 보았다.
그리고 seq2seq를 Encoder-Decoder를 구현하여 간단한 덧셈 문제를 학습시키는데 성공하였다.
seq2seq는 좋은 성능과 큰 가능성을 가지고 있어 다양한 애플리케이션에 적용 가능하다.

그리고 seq2seq를 개선하는 Reverse와 Peeky를 구현해 그 효과를 확인하였다. 다음 장에서는 seq2seq를 더욱 개선할 "어텐션"기법을 공부할 것이다.

profile
AI 엔지니어가 되고싶은 대학생 입니다.

0개의 댓글