밑바닥부터 시작하는 딥러닝 - seq2seq(2)

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 클래스이다. Encoder 클래스와 Decoder 클래스는 이미 구현되어 있기 때문에 그 기능들을 연결하기만 하면 된다.

 

seq2seq 평가

Trainer 클래스를 사용해 이 규칙대로 작업을 수행할 것이다. 또한, 매 에폭마다 seq2seq가 테스트 데이터를 풀게 하여 학습 중간중간 정답률을 측정하고자 할 것이다.

그럼 학습 코드를 살펴보자.

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

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

# 모델 / 옵티마이저 / 트레이너 생성
model = seq2seq(vocab_size, wordvec_size, hidden_size)
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)
    acc = float(correct_num) / len(x_test)
    acc_list.append(acc)
    print('검증 정확도 %.3f%%' % (acc * 100))
# 그래프 그리기
x = np.arange(len(acc_list))
plt.plot(x, acc_list, marker='o')
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.ylim(0, 1.0)
plt.show()

학습이 진행됨에 따라 에폭마다의 정답률을 그린 그래프이다. 이 결과를 보면 seq2seq는 초기에는 정답을 잘 맞히지 못했다. 그러나 학습을 거듭할수록 조금씩 정답에 가까워지면서, 몇 개씩은 맞히기 시작한다. 

25에폭에서 중단했지만, 정답률은 약 10% 정도였다. 그래프의 성장 추이를 보면, 학습을 더 거듭하면 더 정확해질 여지가 있어 보인다.

 


seq2seq 개선

이번에는 학습 속도를 개선해볼 것이다. 총 2가지를 알아볼 예정인데 첫 번째 방법은 입력 데이터 반전(Reverse)이다.

이 트릭을 사용하면 많은 경우 학습 진행이 빨라져, 결과적으로 최종 정확도도 좋아진다고 한다.

그러면 코드를 살펴보자.

x_train, x_test = x_train[:, ::-1], x_test[:, ::-1]

데이터셋을 반전시키기만 하고 학습을 시키면 된다.

입력 데이터를 반전시킨 것만으로 정답률이 약 50%까지 올랐다. 물론, 데이터를 반전시키는 효과는 어떤 문제를 다루느냐에 따라 다르지만, 대부분의 경우 더 좋은 결과로 이어진다.

그렇다면 왜 입력 데이터를 반전시키는 것만으로 학습의 진행이 빨라지고 정확도가 향상되는 걸까?

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

 

TAGS.

Comments