NeuroWhAI의 잡블로그

[Keras] 커스텀 RNN, GRU 셀 만들고 IMDB 학습 테스트 본문

개발 및 공부/라이브러리&프레임워크

[Keras] 커스텀 RNN, GRU 셀 만들고 IMDB 학습 테스트

NeuroWhAI 2018. 12. 1. 15:59


어텐션 매커니즘을 적용하기 전에 시험삼아 커스텀 RNN 셀로 RNN 레이어를 만들고
IMDB(영화 리뷰 및 평점) 데이터 세트를 학습시켜 보았습니다.

코드는 아래와 같습니다. (GRU는 글 맨 밑에 있습니다)
import numpy as np
import matplotlib.pyplot as plt
import keras
from keras import backend as K
from keras import layers, models, datasets
from keras.preprocessing import sequence

class MyRNNCell(layers.Layer):
  def __init__(self, units, **kwargs):
    self.units = units
    self.state_size = units
    super(MyRNNCell, self).__init__(**kwargs)

    
  def build(self, input_shape):
    self.input_kernel = self.add_weight(
        shape=(input_shape[-1], self.units),
        initializer='uniform',
        name='input_kernel')
    self.hidden_kernel = self.add_weight(
        shape=(self.units, self.units),
        initializer='uniform',
        name='hidden_kernel')
    self.output_kernel = self.add_weight(
        shape=(self.units, self.units),
        initializer='uniform',
        name='output_kernel')
    
    self.hidden_bias = self.add_weight(
        shape=(self.units,),
        initializer='zeros',
        name='hidden_bias')
    self.output_bias = self.add_weight(
        shape=(self.units,),
        initializer='zeros',
        name='output_bias')
    
    self.built = True

    
  def call(self, inputs, states):
    prev_hidden = states[0]
    
    h = K.dot(inputs, self.input_kernel) + K.dot(prev_hidden, self.hidden_kernel)
    h = K.tanh(h + self.hidden_bias)
    
    output = K.dot(h, self.output_kernel) + self.output_bias
    
    return output, [h]
  
  
  def compute_output_shape(self, input_shape):
    return (input_shape[0], self.units)

  
max_features = 20000
maxlen = 80
batch_size = 100
epochs = 4


(x_train, y_train), (x_test, y_test) = datasets.imdb.load_data(num_words=max_features)
x_train = sequence.pad_sequences(x_train, maxlen=maxlen)
x_test = sequence.pad_sequences(x_test, maxlen=maxlen)


x = layers.Input((maxlen,))
h = layers.Embedding(max_features, 128)(x)
h = layers.RNN(MyRNNCell(128))(h)
h = layers.Activation('relu')(h)
h = layers.Dropout(0.25)(h)
y = layers.Dense(1, activation='sigmoid')(h)
model = models.Model(x, y)

model.compile(loss='binary_crossentropy',
             optimizer='adam', metrics=['accuracy'])
model.summary()


print('Training stage')

history = model.fit(x_train, y_train,
                    batch_size=batch_size, epochs=epochs,
                    validation_data=(x_test, y_test),
                    verbose=2)


plt.plot(history.history['loss'], 'y', label='train loss')
plt.plot(history.history['val_loss'], 'r', label='val loss')
plt.legend(loc='upper right')
plt.show()

plt.plot(history.history['acc'], 'y', label='train acc')
plt.plot(history.history['val_acc'], 'r', label='val acc')
plt.legend(loc='upper right')
plt.show()


loss, acc = model.evaluate(x_test, y_test,
                           batch_size=batch_size, verbose=2)

print('Test performance: accuracy={0}, loss={1}'.format(acc, loss))

일반 커스텀 레이어랑 특별하게 다른 것은 없지만 RNN은 call 메소드가 반환하는 것이 더 있다는 게 다르죠.
RNN 셀의 구조는 3종류의 가중치(입력, 이전 히든 스테이트, 출력에 대한 가중치)를 가지는 가장 기본적인 구조입니다.

학습을 시켜보면 학습이 되긴 하지만 제대로 안되는 모습이 보입니다.


전형적인 과적합의 모습이죠.
드롭아웃을 넣어주니 좀 더 괜찮아지긴 했는데 갈수록 검증 정확도가 떨어지는건 여전하더군요.
일단 동작은 하니 테스트는 이쯤 해야겠습니다.

이제 다음 목표는 어텐션 매커니즘의 적용인데 될지 모르겠네요 ㅠㅠ


아래는 GRU 셀도 구현해본 코드 조각입니다.

공식은 여기를 참고하였고 위 코드보다 정확도가 약 2% 더 높게 나오더군요.

class MyGRUCell(layers.Layer):
  def __init__(self, units, **kwargs):
    self.units = units
    self.state_size = units
    super(MyGRUCell, self).__init__(**kwargs)

    
  def build(self, input_shape):
    self.input_update_kernel = self.add_weight(
        shape=(input_shape[-1], self.units),
        initializer='uniform',
        name='input_update_kernel')
    self.hidden_update_kernel = self.add_weight(
        shape=(self.units, self.units),
        initializer='uniform',
        name='hidden_update_kernel')
    self.input_reset_kernel = self.add_weight(
        shape=(input_shape[-1], self.units),
        initializer='uniform',
        name='input_reset_kernel')
    self.hidden_reset_kernel = self.add_weight(
        shape=(self.units, self.units),
        initializer='uniform',
        name='hidden_reset_kernel')
    self.input_kernel = self.add_weight(
        shape=(input_shape[-1], self.units),
        initializer='uniform',
        name='input_kernel')
    self.hidden_kernel = self.add_weight(
        shape=(self.units, self.units),
        initializer='uniform',
        name='hidden_kernel')
    
    self.built = True

    
  def call(self, inputs, states):
    prev_hidden = states[0]
    
    z = K.sigmoid(K.dot(inputs, self.input_update_kernel)
                  + K.dot(prev_hidden, self.hidden_update_kernel))
    r = K.sigmoid(K.dot(inputs, self.input_reset_kernel)
                  + K.dot(prev_hidden, self.hidden_reset_kernel))
    h_t = K.tanh(K.dot(inputs, self.input_kernel)
                 + r * K.dot(prev_hidden, self.hidden_kernel))
    h = z * prev_hidden + (1 - z) * h_t
    
    return h, [h]
  
  
  def compute_output_shape(self, input_shape):
    return (input_shape[0], self.units)




Comments