4 minute read

IMDB 리뷰 데이터셋

Internet Movie Database

imdb.com에서 수집한 리뷰를 감상평에 따라 긍정과 부정으로 분류해 놓은 데이터셋

50 ,000개의 샘플로이루어져 있고 훈련 데이터와 테스트 데이터에 각각 25,000개씩 나누어져 있다.

리뷰가 영화를 좋게 / 나쁘게 평가하는지 분류하는 모델을 만들어보자(감성, 감정 분석)

자연어 처리

NLP Natural language processing

컴퓨터를 사용해 인간의 언어를 처리하는 분야이다. 음성 인식 기계 번역 감성 분석 등이 있다.

자연어 처리 분야에서 훈련 데이터를 종종 말뭉치(corpus)라고 부른다. ex) IMDB 데이터셋 : 말뭉치

토큰

텍스트를 숫자 데이터로 바꿀 때는 문장에서 각각의 단어에 정수를 매핑하는데

일반적으로 영어 문장에서는 모두 소문자로 바꾸고 구둣점을 삭제한 다음 공백을 기준으로 분리한다.

이렇게 분리된 단어를 토큰이라고 부른다.

하나의 샘플은 여러 개의 토큰으로 이루어져 있고 1개의 토큰이 하나의 타임스탬프에 해당한다.

어휘 사전

훈련 세트에서 고유한 단어를 뽑아 만든 목록

케라스로 IMDB 불러오기

from tensorflow.keras.datasets import imdb

(train_input, train_target), (test_input, test_target) = imdb.load_data(num_words=500)
# 어휘사전의 단어를 가장 많이 사용하는 단어 500개만 사용하기

print(train_input.shape, test_input.shape)
#(25000,) (25000,)

print(len(train_input[0]))
#218
print(train_input[0])
#[1, 14, 22, 16, 43, 2, 2, 2, 2, 65, ..., 16, 2, 19, 178, 32] #train data를 확인해보면 토큰이 정수값으로 바뀌어있는 것을 확인할 수 있다.
#input값 맨 처음의 예약된 정수 1은 샘플의 시작 부분의 토큰이다.
#두 번째로 예약된 정수인 2는 500개의 어휘사전에 포함되지 않은 단어들이다. 

#input data는 파이썬 리스트를 묶은 넘파이 배열이다. 각각의 리뷰(샘플)마다 길이가 다르기 때문에(numpy배열은 길이가 다른 데이터를 표현하지 못함)
print(train_target[:20])
#[1 0 0 1 0 0 1 0 1 0 1 0 0 0 0 0 1 1 0 1]
#이진 분류 긍정:1 부정:0

훈련 세트 준비

train_input, val_input, train_target, val_target = train_test_split(
    train_input, train_target, test_size=0.2, random_state=42)
# 20% 검증 세트 

lengths = np.array([len(x) for x in train_input])
print(np.mean(lengths), np.median(lengths))
#239.00925 178.0 
#train_input의 단어 개수 평균 : 239 / 단어 개수 중간값 : 178
#긴 문장을 가진 리뷰가 존재하는 것을 알 수 있다.

plt.hist(lengths)
plt.xlabel('length')
plt.ylabel('frequency')
plt.show()

image-20220619190041068

시퀀스 패딩

from tensorflow.keras.preprocessing.sequence import pad_sequences

train_seq = pad_sequences(train_input, maxlen=100)
# 토큰의 길이를 최대 100개로 지정해주어서 패딩이 너무 많이 되지 않도록 해 준다.
print(train_seq.shape)
#(20000, 100) 
#5000개는 검증 세트로 활용하기 떄문에 20000개의 샘플과 100개의 토큰으로 구성되었다.

print(train_seq[0])
# [ 10   4  20   9   2 364 352   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 451   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 470 158]
# 첫번째 샘플을 보면 0으로 패딩된 것이 보이지 않으므로 100보다 긴 문장이다

print(train_input[0][-10:])
#[6, 2, 46, 7, 14, 20, 10, 10, 470, 158]
#원본의 뒷부분을 살펴보면 똑같으므로 앞부분이 짤린 것을 알 수 있다.
#문장의 뒷부분이 더 중요하다고 가정하고 있기 때문

print(train_seq[5])
# [  0   0   0   0   1   2 195  19  49   2   2 190   4   2 352   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 364 352   2   2  11 190
#   24 484   2   7  94 205 405  10  10  87   2  34  49   2   7   2   2   2
#    2   2 290   2  46  48  64  18   4   2]
# 앞부분에 0으로 패딩된 것을 볼 수 있다.

val_seq = pad_sequences(val_input, maxlen=100)

토큰의 개수보다 짧은 문장 샘플의 경우에 남는 자리가 생기는데 이 부분을 0으로 패딩해준다.

순환 신경망 모델 만들기

훈련 세트와 검증 세트(train_seq, val_seq) 의 각 토큰의 값은 단어를 숫자로 패딩한 임의의 값이므로 신경망에 들어가게 된다면 큰 정수를 부여받은 토큰은 큰 활성화 출력을 갖게 된다.

하지만 토큰값들은 임의로 부여된 값이므로 영향을 주면 안되기 때문에 서로 상호 관계없는 무의미한 값으로 인코딩하기 위해 원핫 인코딩을 사용한다.

500개의 어휘 사전을 사용했기 때문에 그 개수만큼의 배열의 원핫 인코딩을 해야 한다

from tensorflow import keras

model = keras.Sequential()

model.add(keras.layers.SimpleRNN(8, input_shape=(100, 500)))
#(뉴런의 개수, input_shape의 크기(100개토큰 , 어휘 사전의 크기500))
model.add(keras.layers.Dense(1, activation='sigmoid'))
#flatten층이 필요 없음
#이진분류이므로 뉴런 하나와 sigmoid
train_oh = keras.utils.to_categorical(train_seq) #원핫 인코딩을 위한 유틸리티
#20000x100
print(train_oh.shape)
#(20000, 100, 500)

print(train_oh[0][0][:12])
#[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0....] 500개
#어휘 사전을 원핫인코딩 되어서 해당 단어에 1 표시

print(np.sum(train_oh[0][0]))
# 1.0
#전부더하면 1이 나오는 것으로 원핫인코딩되어있다는 것을 검증

val_oh = keras.utils.to_categorical(val_seq)

model.summery()

image-20220628195014324

rnn class의 출력은 뉴런 개수인 8개로 나오는 것을 볼 수 있다. \(가중치 \\ 4072=500 \ast8 + 8\ast8+8 \\ 9=8 \ast1+1\) 4072=500(원핫 인코딩된 사전 배열)*8(출력층 뉴런개수)+8 * 8(완전연결)+8 절편

9=8(입력층)*(1출력층)+1 절편

모델 훈련

rmsprop = keras.optimizers.RMSprop(learning_rate=1e-4) #RMSprop 사용
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])

Rmsprop 이전데이터와 현재 데이터를 같이 사용해서 로컬미니멈에서 멈추는 경우 보완한 것

image-20220628201834161

어휘사전을 500개로 한정했음에도 불구하고 80%의 정확도를 보여준다.

image-20220628201626772

임베딩

원핫 인코딩을 사용하는 경우에는 사전의 개수를 늘리면 차원의 개수가 너무 크게 증가하고 토큰의 개수를 늘리는 것 또한 입력 데이터가 증하하는 단점이 있다.

model2 = keras.Sequential()

model2.add(keras.layers.Embedding(500, 16, input_length=100))
model2.add(keras.layers.SimpleRNN(8))
model2.add(keras.layers.Dense(1, activation='sigmoid'))

model2.summary()

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])

image-20220628202016082

참고

박해선,혼자 공부하는 머신러닝, 한빛미디어, 2021,500~525p