본문 바로가기

책/밑바닥부터 시작하는 딥러닝 2

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

seq2seq 개선

입력 데이터 반전

이 트릭을 사용하면 많은 경우에 학습이 빨리져서 결과적으로 최종 정확도도 좋아진다고 한다. 참고로 입력 데이터 반전은 다음과 같이 할 수 있다.

array = array[:, ::-1]

위와 같이 사용하면 배열의 행만 반전시킬 수 있다.

 

이 트릭을 사용해서 정확도가 향상됨을 보여준다. 왜 결과가 좋아지는지를 생각해보면 "나는 고양이이다"를 "I am a cat"으로 번역하는 문제에서 트릭을 사용하지 않으면 "나"로부터 "I"까지 가려면 여러 타임 스텝("는", "고양이" 등)을 거쳐야한다. 시간적으로 거리가 멀다. 입력을 반전시키면 이 거리가 짧아지게 되어 역전파시 기울기가 직접 전해진다. 입력 데이터 반전 덕분에 시간적으로 거리가 짧아지는 경우가 이전보다 더 많아지게 되는 것이다. 다만 단어 사이의 평균적인 거리는 그대로이다.

 

엿보기(Peeky)

디코더에게 있어 인코더의 마지막 은닉 상태 $h$만이 유일한 정보이다. 그런데 기존에는 이를 최초 시각의 LSTM 계층만 이용하고 있다.

이를 다른 시각과 다른 계층에도 전해주면 어떨까?

 

 

그림과 같이 모든 시각의 Affine 계층과 LSTM 계층에 $h$를 전달해준다. 이렇게되면 Affine과 LSTM은 2개의 입력을 받게 되는데 실제로는 두 벡터를 연결하여 입력으로 사용한다.

 

 

Peeky는 디코더 클래스에만 적용하면 되기 때문에 이를 PeekyDecoder로 구현해보자.

 

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

초기화 부분에서 이전과 달라진 점은 lstm_Wx, affine_W을 초기화 할때 hidden_state의 사이즈만큼 1차원 부분을 확장시켜 준다. 앞서 말했듯이 두 벡터(xs, h)를 concat하여 입력으로 사용하기 때문이다. 

    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

embedding을 거친 xs를 out에 저장한다. 그리고 h를 out과 concat하기 위해서 np.repeat 메서드를 사용해 시계열의 크기 T만큼 복제한다. 그런 다음 이를 LSTM에 입력하고 얻은 출력과 hs를 다시 concat하여 Affine의 입력으로 사용한다.

 

이를 이용해 PeekySeq2seq 클래스를 구현해보자. 앞서 구현했던 Seq2seq 클래스를 상속받고 초기화 부분만 바꿔주면 된다.

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

개선된 모델을 사용해 학습한 결과는 다음과 같다.

 

reverse와 peeky를 사용한 경우 최종적으로 100% 정확도에 도달한다.

' > 밑바닥부터 시작하는 딥러닝 2' 카테고리의 다른 글

8장 어텐션 (2)  (0) 2023.11.03
8장 어텐션 (1)  (0) 2023.11.03
7장 RNN을 사용한 문장 생성 (1)  (0) 2023.11.01
6장 게이트가 추가된 RNN (2)  (0) 2023.10.31
6장 게이트가 추가된 RNN (1)  (0) 2023.10.27