데이터분석-머신러닝-AI/강의 정리

[혼자 공부하는 머신러닝+딥러닝] 24강. 순환 신경망으로 IMDB 리뷰 분류하기 - 영화 리뷰 감상평을 분류해보자

bluebamus 2023. 11. 29.

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 :

https://velog.io/@katinon/%ED%98%BC%EA%B3%B5%EB%A8%B8%EC%8B%A0-%EC%88%9C%ED%99%98-%EC%8B%A0%EA%B2%BD%EB%A7%9D%EC%9C%BC%EB%A1%9C-IMDB-%EB%A6%AC%EB%B7%B0-%EB%B6%84%EB%A5%98%ED%95%98%EA%B8%B0

 

[혼공머신] 순환 신경망으로 IMDB 리뷰 분류하기

혼자 공부하는 머신러닝+딥러닝 책에 기반한 정리글입니다.전체 소스코드는 아래 Github 링크에서 확인할 수 있습니다.Github 링크이번 편에서는 딥러닝으로 댓글을 분석해서 평가가 긍정적인지,

velog.io

https://velog.io/@clayryu328/%ED%98%BC%EC%9E%90-%EA%B3%B5%EB%B6%80%ED%95%98%EB%8A%94-%EB%A8%B8%EC%8B%A0%EB%9F%AC%EB%8B%9D%EB%94%A5%EB%9F%AC%EB%8B%9D-10-%EC%88%9C%ED%99%98-%EC%8B%A0%EA%B2%BD%EB%A7%9D%EC%9C%BC%EB%A1%9C-IMDB-%EB%A6%AC%EB%B7%B0-%EB%B6%84%EB%A5%98%ED%95%98%EA%B8%B0

 

[혼자 공부하는 머신러닝+딥러닝] #10 순환 신경망으로 IMDB 리뷰 분류하기

언어가 분해가 된다고??

velog.io

 

댓글