소소하지만 소소하지 않은 개발 공부/머신 러닝 교과서

[머신러닝교과서] RNN을 사용한 영화 리뷰 감성 분석(2)

still..epochs 2023. 2. 1. 20:58

* 본 포스팅은 머신러닝교과서를 참조하여 작성되었습니다.

* https://github.com/rickiepark/python-machine-learning-book-3rd-edition

 

GitHub - rickiepark/python-machine-learning-book-3rd-edition: <머신 러닝 교과서 3판>의 코드 저장소

<머신 러닝 교과서 3판>의 코드 저장소. Contribute to rickiepark/python-machine-learning-book-3rd-edition development by creating an account on GitHub.

github.com

 

 

문장 인코딩을 위한 임베딩 층

이전 데이터 준비 단계에서 동일한 길이의 시퀀스를 생성했다. 이 시퀀스의 원소는 교유한 단어의 인덱스에 해당하는 정수이다. 이런 단어 인덱스를 입력 특성으로 변환하는 몇 가지 방법이 있다. 간단하게 원-핫 인코딩을 적용하여 인덱스를 0또는 1로 이루어진 벡터로 변환할 수 있다. 하지만 이렇게 많은 특성에서 훈련된 모델은 차원의 저주(curse of dimensionality)로 인한 영향을 받는다. 또 하나를 제외하고 모든 원소가 0이므로 특성 벡터가 매우 희소해진다.

 

좀 더 좋은 방법은 각 단어를 (정수가 아닌)실수 값을 가진 고정된 길이의 벡터로 변환하는 것이다. 원-핫 인코딩과 달리 고정된 길이의 벡터를 사용하여 무한히 많은 실수를 표현할 수 있다.

 

임베딩(embedding)이라고 하는 특성 학습 기법을 사용하여 데이터셋에 있는 단어를 표현하는 데 중요한 특성을 자동으로 학습할 수 있다. 원-핫 인코딩에 비해 임베딩의 장점은 다음과 같다.

  • 특성 공간의 차원이 축소되므로 차원의 저주로 인한 영향을 감소시킨다.
  • 신경망에서 임베딩 층이 최적화(학습)되기 때문에 중요한 특성이 추출된다.

위 그림은 임베딩이 토큰 인덱스를 어떻게 훈련 가능한 임베딩 행렬로 매핑하는지 보여 준다.

 

tf.keras.layers.Embedding을 사용하여 임베딩 층을 간단히 만들 수 있다.

from tensorflow.keras.layers import Embedding
model = tf.keras.Sequential()
model.add(Embedding(input_dim=100, output_dim=6, input_length=20, name='embed-layer'))
model.summary()

 

RNN 모델 만들기

케라스 Sequential 클래스를 사용하여 임베딩 층, RNN 층, 완전 연결 층을 연결한다. 순환 층에는 다음과 같은 클래스를 사용할 수 있다.

  • SimpleRNN: 완전 연결 순환 층인 기본 RNN
  • LSTM: 긴 의존성을 감지할 수 있는 LSTM RNN
  • GRU: LSTM의 대안인 GRU 유닛을 사용한 순환층

input_dim=1000과 output_dim=32 로 지정한 임베딩 층으로 RNN 모델을 시작한다. 그 다음 두 개의 SimpleRNN 순환 층을 추가한다. 마지막으로 완전 연결 층을 출력층으로 추가한다.

from tensorflow.keras import Sequential
from tensorflow.keras.layers import Embedding
from tensorflow.keras.layers import SimpleRNN
from tensorflow.keras.layers import Dense

model = Sequential()
model.add(Embedding(input_dim=1000, output_dim=32))
model.add(SimpleRNN(32, return_sequences=True))
model.add(SimpleRNN(32))
model.add(Dense(1))
model.summary()

 

 

감성 분석 작업을 위한 RNN 모델 만들기

시퀀스 길이가 길기 때문에 길게 미치는 영향을 감지하기 위해 LSTM 층을 사용한다. 또한, Bidirectional 래퍼 클래스로 LSTM 층을 감싼다. 이 층은 입력 시퀀스를 처음부터 끝가지 그리고 끝에서 처음까지 양방향으로 순환 층을 통과하도록 한다.

embedding_dim = 20
vocab_size = len(token_counts) + 2
tf.random.set_seed(1)

## 모델 만들기
bi_lstm_model = tf.keras.Sequential([
    tf.keras.layers.Embedding(
        input_dim=vocab_size,
        output_dim=embedding_dim,
        name='embed-layer'),
    
    tf.keras.layers.Bidirectional(
        tf.keras.layers.LSTM(64, name='lstm-layer'),
        name='bidir-lstm'),
    
    tf.keras.layers.Dense(64, activation='relu'),
    
    tf.keras.layers.Dense(1, activation='sigmoid')
])

bi_lstm_model.summary()

## 컴파일과 훈련
bi_lstm_model.compile(
    optimizer=tf.keras.optimizers.Adam(1e-3),
    loss=tf.keras.losses.BinaryCrossentropy(from_logits=False),
    metrics=['accuracy'])
history = bi_lstm_model.fit(
    train_data,
    validation_data=valid_data,
    epochs=10)

## 테스트 데이터에서 평가
test_results = bi_lstm_model.evaluate(test_data)
print('테스트 정확도: {:.2f}%'.format(test_results[1]*100))

열 번의 에포크 동안 모델을 훈련한 후 테스트 데이터에서 평가하여 84% 정확도를 얻었다. 물론 이 결과가 최신 모델과 비교했을 때 IMDb 데이터셋에서 얻을 수 있는 최댓값은 아니다.

 

SimpleRNN과 같은 다른 종류의 순환 층을 사용할 수도 있다. 일반적인 순환 층으로 만든 모델은 (훈련 데이터에서도) 좋은 예측 성능을 달성하지 못한다. 예를 들어 이진 코드에서 양방향 LSTM 층을 단방향 SimpleRNN 층으로 바꾸고 전체 길이를 사용한 시퀸스로 모델을 훈련하면, 훈련하는 동안 손실이 전혀 감소하지 않았다. 이 데이터셋에 있는 시퀀스가 너무 길기 때문이다. SimpleRNN 층을 사용한 모델은 장기간 의존성을 학습할 수 없고 그레이디언트 감소나 폭주로 인한 영향을 받는다.