[혼자 공부하는 머신러닝+딥러닝] 24강. 순환 신경망으로 IMDB 리뷰 분류하기 - 영화 리뷰 감상평을 분류해보자
https://www.youtube.com/watch?v=YIrpKw04ic8&list=PLJN246lAkhQjoU0C4v8FgtbjOIXxSs_4Q&index=30
- IMDB 리뷰 데이터셋
- 유명한 인터넷 영화 데이터베이스에서 수집한 리뷰를 감상평에 따라 긍정 혹은 부정으로 분류를 해 놓은 데이터셋이다.
- 총 50,000개의 샘플로 이루어져 있으며 훈련 데이터와 테스트 데이터에 각각 25,000개씩 나눠져 있다.
- 말뭉치 : 자연어 처리에서 사용하는 훈련 데이터셋
- 토큰 : 텍스트에서 공백으로 구분되는 문자열
- 소문자로 변환해서 사용하기도 하고 구둣점은 삭제한다.
- 이러한 긍정, 부정적인 표현을 분석하는 것에 대해 감성/감정 분석이라 한다.
- 데이터 준비하기
- 케라스에서 제공하는 샘플 데이터는 훈련에 쉽게 사용할 수 있도록 전처리가 되어 있다.
- imdb에서 제공하는 어휘사전의 크기를 제한할 수 있다.
- load_data 함수의 num_words 파라미터에 사용할 어휘사전 크기를 정의할 수 있다.
- 어휘사전이 크면 클수록 요구되는 컴퓨팅 자원이 증가된다.
from tensorflow.keras.datasets import imdb
(train_input, train_target), (test_input, test_target) = imdb.load_data(
num_words=300)
print(train_input.shape, test_input.shape)
(25000,) (25000,)
# 첫 번째 리뷰의 길이 218 토큰
print(len(train_input[0]))
218
# 두 번째 리뷰의 길이 189 토큰
print(len(train_input[1]))
189
# 샘플 시작부분의 토큰은 1이다.
# 어휘 사전에 없는 단어는 모두 2로 표시된다.
print(train_input[0])
[1, 14, 22, 16, 43, 2, 2, 2, 2, 65, 2, 2, 66, 2, 4, 173, 36, 256, 5, 25, 100, 43, 2, 112, 50, 2, 2, 9, 35, 2, 284, 5, 150, 4, 172, 112, 167, 2, 2, 2, 39, 4, 172, 2, 2, 17, 2, 38, 13, 2, 4, 192, 50, 16, 6, 147, 2, 19, 14, 22, 4, 2, 2, 2, 4, 22, 71, 87, 12, 16, 43, 2, 38, 76, 15, 13, 2, 4, 22, 17, 2, 17, 12, 16, 2, 18, 2, 5, 62, 2, 12, 8, 2, 8, 106, 5, 4, 2, 2, 16, 2, 66, 2, 33, 4, 130, 12, 16, 38, 2, 5, 25, 124, 51, 36, 135, 48, 25, 2, 33, 6, 22, 12, 215, 28, 77, 52, 5, 14, 2, 16, 82, 2, 8, 4, 107, 117, 2, 15, 256, 4, 2, 7, 2, 5, 2, 36, 71, 43, 2, 2, 26, 2, 2, 46, 7, 4, 2, 2, 13, 104, 88, 4, 2, 15, 297, 98, 32, 2, 56, 26, 141, 6, 194, 2, 18, 4, 226, 22, 21, 134, 2, 26, 2, 5, 144, 30, 2, 18, 51, 36, 28, 224, 92, 25, 104, 4, 226, 65, 16, 38, 2, 88, 12, 16, 283, 5, 16, 2, 113, 103, 32, 15, 16, 2, 19, 178, 32]
# 타깃 데이터는 0(부정)과 1(긍정)으로 나뉘어 진다.
print(train_target[:20])
[1 0 0 1 0 0 1 0 1 0 1 0 0 0 0 0 1 1 0 1]
- 훈련 세트 준비
- 20% 정도를 검증 세트로 분리한다.
from sklearn.model_selection import train_test_split
train_input, val_input, train_target, val_target = train_test_split(
train_input, train_target, test_size=0.2, random_state=42)
- 훈련 세트 내 샘플들의 길이를 평균과 중간값을 알아보자.
- 평균 : 239
- 중간 값 : 178
import numpy as np
lengths = np.array([len(x) for x in train_input])
print(np.mean(lengths), np.median(lengths))
239.00925 178.0
- 히스토그램으로 길이에 대해 시각화를 해보자.
- 대부분 300개 미만의 길이인 것을 확인할 수 있다.
import matplotlib.pyplot as plt
plt.hist(lengths)
plt.xlabel('length')
plt.ylabel('frequency')
plt.show()
- 시퀀스 패딩
- 어떤 문장이 있을 때, 문장에 사용되는 토큰을 7개로 정의 하고 이 문장을 표현하는데 3개의 토큰만 사용 했다면 나머지 4개의 토큰은 비워져 있다. 이 때 비워진 토큰 자리를 0으로 채우는 것을 패딩이라 한다. 만약 정의된 토큰의 최대 길이보다 긴 문장의 경우, 정의된 길이 이후의 문장은 잘라내 버린다.
- 이번 강의에서는 padding의 길이를 100으로 정의한다.
- pad_sequences() 함수를 사용하여 패딩 설정을 하면 지정된 maxlen보다 긴 문장의 앞부분을 잘라버린다.
- 이유로 문장의 뒷부분이 더 의미가 있다고 가정을 하기 때문이다.
- 문장이 짧은 경우 문장의 앞부분에 0을 채워 넣는다.
- pad_sequences() 함수의 매개변수 padding을 기본값인 pre에서 post로 바꾸면 샘플의 뒷부분을 잘라내도록 패딩 설정을 할 수 있다.
from tensorflow.keras.preprocessing.sequence import pad_sequences
train_seq = pad_sequences(train_input, maxlen=100)
# 훈련 데이터의 샘플당 길이가 100으로 변경되었다.
print(train_seq.shape)
(20000, 100)
# 첫번째 샘플 확인
print(train_seq[0])
[ 10 4 20 9 2 2 2 5 45 6 2 2 33 269 8 2 142 2
5 2 17 73 17 204 5 2 19 55 2 2 92 66 104 14 20 93
76 2 151 33 4 58 12 188 2 151 12 215 69 224 142 73 237 6
2 7 2 2 188 2 103 14 31 10 10 2 7 2 5 2 80 91
2 30 2 34 14 20 151 50 26 131 49 2 84 46 50 37 80 79
6 2 46 7 14 20 10 10 2 158]
# 6번째 샘플 확인
print(train_seq[5])
[ 0 0 0 0 1 2 195 19 49 2 2 190 4 2 2 2 183 10
10 13 82 79 4 2 36 71 269 8 2 25 19 49 7 4 2 2
2 2 2 10 10 48 25 40 2 11 2 2 40 2 2 5 4 2
2 95 14 238 56 129 2 10 10 21 2 94 2 2 2 2 11 190
24 2 2 7 94 205 2 10 10 87 2 34 49 2 7 2 2 2
2 2 290 2 46 48 64 18 4 2]
- 순환 신경망 모델 만들기
- 기본 신경망인 simpleRNN을 사용하기로 한다.
- 첫번째 파라미터는 뉴런의 개수이다.
- input_shape는 타임스텝의 길이가 100개인 토큰과 이전에 정의한 어휘사전 크기 300을 같이 정의한다.
- 이진분류를 위해 danse 층에 뉴런 1개만 두고 활성화 함수로 sigmoid를 정의한다.
- 이렇게 만들면 순환층 1개가 있는 간단한 모델이 만들어진다.
from tensorflow import keras
model = keras.Sequential()
# 타임스텝의 길이 100, 어휘사전 크기 300
model.add(keras.layers.SimpleRNN(8, input_shape=(100, 300)))
model.add(keras.layers.Dense(1, activation='sigmoid'))
- 원-핫 인코딩 (one-hot encoding)
- 토큰을 정수로 변환한 데이터를 신경망에 주입하게 되면, 의미없이 할당된 정수 중 큰 값의 경우 큰 활성화 출력을 만들게 된다. 사실 이 정수들 사이에는 어떠한 관련이 없기 때문에 정수값에 있는 크기 속성을 없애고 각 정수들을 상호 관련이 없는 무의미한 방식으로 인코딩 하는 것이 원-핫 인코딩이다.
- 300개의 어휘사전을 사용했기 때문에 300개의 벡터로 이루어진 원-핫 인코딩을 만들어야 한다.
- 하나의 특정한 단어의 위치만 1이고 나머지는 0인 인코딩을 만든다.
- to_categorical() 함수를 사용하여 훈련세트와 검증 세트를 원-핫 인코딩으로 변환한다.
- 어휘사전 크기만큼 300차원의 배열이 생성되었다.
# 훈련세트
train_oh = keras.utils.to_categorical(train_seq)
# 300개의 어휘사전 크기만큼 차원이 생성되었다.
print(train_oh.shape)
(20000, 100, 300)
# 첫 리뷰의 첫 단어를 원-핫 인코딩한 결과이다.
print(train_oh[0][0][:12])
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0.]
# 모든 원소를 더하면 1이다.
print(np.sum(train_oh[0][0]))
1.0
# 검증세트
val_oh = keras.utils.to_categorical(val_seq)
- 모델 구조 확인
- RNN 클래스 1개 추가, Dense 층 1개 추가
- RNN 클래스 가중치 : 300개의 입력, 8개의 뉴런, 순환되는 은닉상태는 출력과 동일하다 8개가 있다. 8개의 절편이 있다.
- 300(입력) x 8(뉴런) [완전연결] + 8(뉴런) x 8(은닉) [완전연결 순환] + 8(절편)
- Dense 층의 가중치 : 8개의 출력에 1개의 가중치가 곱해지고 1개의 절편이 있기 때문에 9개가 된다.
- 8(입력) x 1(뉴런) + 1(절편)
model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
simple_rnn (SimpleRNN) (None, 8) 2472
dense (Dense) (None, 1) 9
=================================================================
Total params: 2481 (9.69 KB)
Trainable params: 2481 (9.69 KB)
Non-trainable params: 0 (0.00 Byte)
_________________________________________________________________
- 모델 훈련
- RMSprop 옵티마이저 사용
- learning_rate : 10의 -4승
- 이진분류이기 때문에 binary_crossentropy 손실함수 사용
- 정확도를 출력하기 위해 metrics=['accuracy'] 정의
- 조기종료를 위해 ModelCheckpoint와 EarlyStopping 선언
- 정확도를 확인하면 75%이다.
rmsprop = keras.optimizers.RMSprop(learning_rate=1e-4)
# 이진 분류, 정확도 출력
model.compile(optimizer=rmsprop, loss='binary_crossentropy',
metrics=['accuracy'])
# 조기종료 설정
checkpoint_cb = keras.callbacks.ModelCheckpoint('best-simplernn-model.h5',
save_best_only=True)
# 조기종료 설정
early_stopping_cb = keras.callbacks.EarlyStopping(patience=3,
restore_best_weights=True)
history = model.fit(train_oh, train_target, epochs=100, batch_size=64,
validation_data=(val_oh, val_target),
callbacks=[checkpoint_cb, early_stopping_cb])
- 그래프 그리기
- 최상의 epoch는 35이다.
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train', 'val'])
plt.show()
- 임베딩
- 원-핫 인코딩의 단점은 어휘사전과 패딩의 크기를 늘리면 차원의 크기가 급격하게 늘어난다는 것이다.
- 또한 각각의 단어간 관계, 의미를 무시하고 인코딩을 하기때문에 성능적으로 아쉬운 부분이 있다.
- 이 문제를 해결하기 위해 순환 신경망에서 단어 임베딩을 사용할 수 있다.
- 임베딩 벡터는 단어를 벡터값들로 채우기 때문에 두 단어 사이의 거리를 통해 가까운 정도를 찾아낼 수 있다.
- 차원의 개수를 많이 줄이지만 성능은 비슷한 결과를 만들어 낸다.
- 이전에 생성된 300개의 차원 출력과 비교해 16개의 벡터 출력을 만들어 크기가 대폭 감소된다.
- embedding의 가중치 파라미터는 300 x 16이다.
- simpleRNN의 가중치 파라미터는 16 x 8 + 8 x 8 + 8이다.
model2 = keras.Sequential()
# 어휘사전 크기 300, 출력 차원 16개 벡터
model2.add(keras.layers.Embedding(300, 16, input_length=100))
model2.add(keras.layers.SimpleRNN(8))
model2.add(keras.layers.Dense(1, activation='sigmoid'))
model2.summary()
Model: "sequential_1"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
embedding (Embedding) (None, 100, 16) 4800
simple_rnn_1 (SimpleRNN) (None, 8) 200
dense_1 (Dense) (None, 1) 9
=================================================================
Total params: 5009 (19.57 KB)
Trainable params: 5009 (19.57 KB)
Non-trainable params: 0 (0.00 Byte)
_________________________________________________________________
- 모델 훈련
- 정확도는 75%이다
rmsprop = keras.optimizers.RMSprop(learning_rate=1e-4)
model2.compile(optimizer=rmsprop, loss='binary_crossentropy',
metrics=['accuracy'])
checkpoint_cb = keras.callbacks.ModelCheckpoint('best-embedding-model.h5',
save_best_only=True)
early_stopping_cb = keras.callbacks.EarlyStopping(patience=3,
restore_best_weights=True)
history = model2.fit(train_seq, train_target, epochs=100, batch_size=64,
validation_data=(val_seq, val_target),
callbacks=[checkpoint_cb, early_stopping_cb])
- 그래프 그리기
- 최상의 epoch는 20이다.
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train', 'val'])
plt.show()
- reference :
'데이터분석-머신러닝-AI > 강의 정리' 카테고리의 다른 글
[혼자 공부하는 머신러닝+딥러닝] 25강. LSTM과 GRU 셀 - 고급 순환층 (1) | 2023.11.29 |
---|---|
[혼자 공부하는 머신러닝+딥러닝] 23강. 순차 데이터와 순환 신경망 - 자연어 처리를 위한 신경망 (1) | 2023.11.28 |
[혼자 공부하는 머신러닝+딥러닝] 22강. 합성곱 신경망의 시각화 - 가중치, 특성치 시각화 (1) | 2023.11.27 |
[혼자 공부하는 머신러닝+딥러닝] 21강. 합성곱 신경망을 사용한 이미지 분류 - 패션 mnist를 이용한 분류 코딩 학습 (0) | 2023.11.26 |
[혼자 공부하는 머신러닝+딥러닝] 20강. 합성곱 신경망의 개념과 동작 원리 배우기 - 2차원 이미지를 그대로 학습 해보자! (2) | 2023.11.26 |
댓글