모두야

CH6 ) 게이트가 추가된 RNN - (3) 본문

밑.시.딥/2권

CH6 ) 게이트가 추가된 RNN - (3)

미미밍2 2021. 9. 27. 18:18
반응형

언어 모델 구현하기

# coding: utf-8
import sys
sys.path.append('..')
from common.time_layers import *
from common.base_model import BaseModel


class Rnnlm(BaseModel):
    def __init__(self, vocab_size=10000, wordvec_size=100, hidden_size=100):
        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.layers = [
            TimeEmbedding(embed_W),
            TimeLSTM(lstm_Wx, lstm_Wh, lstm_b, stateful=True),
            TimeAffine(affine_W, affine_b)
        ]
        self.loss_layer = TimeSoftmaxWithLoss()
        self.lstm_layer = self.layers[1]

        # 모든 가중치와 기울기를 리스트에 모은다.
        self.params, self.grads = [], []
        for layer in self.layers:
            self.params += layer.params
            self.grads += layer.grads

    def predict(self, xs):
        for layer in self.layers:
            xs = layer.forward(xs)
        return xs

    def forward(self, xs, ts):
        score = self.predict(xs)
        loss = self.loss_layer.forward(score, ts)
        return loss

    def backward(self, dout=1):
        dout = self.loss_layer.backward(dout)
        for layer in reversed(self.layers):
            dout = layer.backward(dout)
        return dout

    def reset_state(self):
        self.lstm_layer.reset_state()
    
    # 매개변수 읽기/쓰기
    def save_params(self,file_name='Rnnlm.pkl'):
        with open(file_name,'wb') as f:
            pickle.dump(self.params,f)
            
    def load_params(self,file_name='Rnnlm.pkl'):
        with open(file_name,'rb') as f:
            self.params = pickle.load(f)

신경망 학습 - PTB 데이터셋

# coding: utf-8
import sys
sys.path.append('..')
from common.optimizer import SGD
from common.trainer import RnnlmTrainer
from common.util import eval_perplexity
from dataset import ptb
#from rnnlm import Rnnlm


# 하이퍼파라미터 설정
batch_size = 20
wordvec_size = 100
hidden_size = 100  # RNN의 은닉 상태 벡터의 원소 수
time_size = 35     # RNN을 펼치는 크기
lr = 20.0
max_epoch = 4
max_grad = 0.25

# 학습 데이터 읽기
corpus, word_to_id, id_to_word = ptb.load_data('train')
corpus_test, _, _ = ptb.load_data('test')
vocab_size = len(word_to_id)
xs = corpus[:-1]
ts = corpus[1:]

# 모델 생성
model = Rnnlm(vocab_size, wordvec_size, hidden_size) # Rnnlm 사용
optimizer = SGD(lr) 
trainer = RnnlmTrainer(model, optimizer) # RnnlmTrainer사용

# 기울기 클리핑을 적용하여 학습
trainer.fit(xs, ts, max_epoch, batch_size, time_size, max_grad, # 모델의 기울기를 구해 매개변수 갱신한다.
            eval_interval=20) #20번째 반복마다 퍼플렉서티를 평가하라     # max_grad : 기울기 클리핑 적용
trainer.plot(ylim=(0, 500))

# 테스트 데이터로 평가
model.reset_state()
ppl_test = eval_perplexity(model, corpus_test)
print('테스트 퍼플렉서티: ', ppl_test)

# 매개변수 저장
model.save_params()

 

퍼플렉서티 평가 중 ...

234 / 235

테스트 퍼플렉서티: 133.41907499405525

 

- 데이터셋 어휘 수 10,000개 중 후보 단어를 133개로 줄였다.

- 더 줄여야 성능이 좋다고 할 수 있다.


RNNLM 추가 개선

1. LSTM 계층 다층화

LSTM을 여러겹 쌓아 모델 정확도를 향상시키자.

 

첫번째 LSTM 계층의 은닉상태가 두번째 LSTM 계층에 입력된다.

 

몇 층 정도로 쌓을까? - 하이퍼파라미터

2~4층 정도가 적당하긴함

 

 

 

 

 

 

 

 

 

 

 


2. 드롭아웃에 의한 과적합 억제

층을 깊게 쌒을 수록 표현력이 풍부하지만, 과적합을 일으킬 수 있다.

과적합 억제 방법

  1. 훈련 데이터양 늘리기
  2. 모델 복잡도 줄이기
  3. 모델 복잡도에 페널티 주는 정규화(normalization)
  4. 드롭아웃 : 훈련 시 계층 내의 뉴런 몇개를 무시하며 학습한다.

RNN에 드롭아웃을 계층의 깊이 방향(상하 방향)으로 삽입하는 것이 좋다.

=> RNN의 시간 방향 정규화(변형 드롭아웃)

 

 


3. 가중치 공유(weight tying)

 

Embedding 계층의 가중치와  Affine 계층의 가중치를 공유한다.

  1. 학습하는 매개변수 수가 크게 줄어든다.
    (과적합억제)
  2. 정확도도 향상된다.

 

 

 

 

 

 

 


개선점을 추가한 RNNLM 구현

 

  1. LSTM 계층 다층화
  2. 드롭아웃 사용 (깊이 방향)
  3. 가중치 공유 (Embedding 계층과 Affine 계층에서 가중치 공유)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

# coding: utf-8
import sys
sys.path.append('..')
from common import config
# GPU에서 실행하려면 아래 주석을 해제하세요(CuPy 필요).
# ==============================================
# config.GPU = True
# ==============================================
from common.optimizer import SGD
from common.trainer import RnnlmTrainer
from common.util import eval_perplexity, to_gpu
from dataset import ptb
#from better_rnnlm import BetterRnnlm


# 하이퍼파라미터 설정
batch_size = 20
wordvec_size = 650
hidden_size = 650
time_size = 35
lr = 20.0
max_epoch = 40
max_grad = 0.25
dropout = 0.5

# 학습 데이터 읽기
corpus, word_to_id, id_to_word = ptb.load_data('train')
corpus_val, _, _ = ptb.load_data('val')
corpus_test, _, _ = ptb.load_data('test')

if config.GPU:
    corpus = to_gpu(corpus)
    corpus_val = to_gpu(corpus_val)
    corpus_test = to_gpu(corpus_test)

vocab_size = len(word_to_id)
xs = corpus[:-1]
ts = corpus[1:]

model = BetterRnnlm(vocab_size, wordvec_size, hidden_size, dropout)
optimizer = SGD(lr)
trainer = RnnlmTrainer(model, optimizer)

best_ppl = float('inf')
for epoch in range(max_epoch):
    trainer.fit(xs, ts, max_epoch=1, batch_size=batch_size,
                time_size=time_size, max_grad=max_grad)

    model.reset_state()
    ppl = eval_perplexity(model, corpus_val)
    print('검증 퍼플렉서티: ', ppl) # 매 에폭마다 검증데이터로 퍼플렉서티 평가

    if best_ppl > ppl:
        best_ppl = ppl
        model.save_params()
    else:
        lr /= 4.0       # 기존 퍼플렉서티보다 낮으면 학습률을 1/4로 줄인다.
        optimizer.lr = lr

    model.reset_state()
    print('-' * 50)


# 테스트 데이터로 평가
model.reset_state()
ppl_test = eval_perplexity(model, corpus_test)
print('테스트 퍼플렉서티: ', ppl_test)

정리

  • 단순한 RNN에서는 기울기 폭발/소실 문제가 있었다.
  • 이를 해결하기 위해 게이트가 추가된 RNN(LSTM,GRU)를 사용하였다.
  • LSTM 계층을 사용한 언어 모델을 만들고, PTB 데이터셋을 학습하여 퍼플렉서티를 구하였다.
  • LSTM 다층화, 드롭아웃, 가중치 공유 기법을 적용하여 정확도를 향상시켰다.
  • 다음 장에서는 언어 모델을 사용해 문장을 생성해보자.
반응형