본 포스팅에서는 케라스(tf.keras)를 사용하여 텍스트로 작성된 리뷰를 긍정적 또는 부정적 영화 리뷰로 분류합니다.
클래스가 두 개인 바이너리 분류(binary classification) 입니다.
본 포스팅은 다음 텐서플로우 문서를 참고하여 작성하였습니다.
Text classification with movie reviews
https://www.tensorflow.org/tutorials/keras/basic_text_classification
텐서플로우 1.12를 기준으로 설명합니다.
import tensorflow as tf |
1.12.0 |
최초작성 2019. 1. 17
IMDB 데이터셋 다운로드
Internet Movie Database(https://www.imdb.com/)에서 가져온 50,000개의 영화 리뷰를 포함하고 있는 IMDB 데이터 셋을 사용합니다.
텐서플로우를 사용하여 다음처럼 IMDB 데이터셋을 가져올 수 있습니다.
아규먼트 num_words=10000은 데이터셋에 있는 리뷰에서 많이 나타나는 상위 10,000개의 단어만 사용하겠다는 의미입니다.
imdb = keras.datasets.imdb |
데이터셋을 둘로 나누어 25,000개의 리뷰는 학습을 위해 사용하고 25,000개는 학습된 결과를 검증하기 위해 사용합니다.
각각 훈련 데이터셋(train_data, train_labels), 테스트 데이터셋(test_data, test_labels)이라고 부릅니다.
훈련 데이터셋과 테스트 데이터셋의 크기가 같은 것은 동일한 개수의 긍정적인 리뷰와 부정적 리뷰를 포함하고 있기 때문입니다.
print("Train datas: {}, labels: {}".format(len(train_data), len(train_labels))) |
Train datas: 25000, labels: 25000 Test datas: 25000, labels: 25000 |
입력으로 사용되는 train_data와 test_data는 영화 리뷰 텍스트에 있는 단어를 정수로 변환한 데이터입니다.
각 정수는 사전에서 등록되어 있는 특정 단어를 나타냅니다.
train_data의 첫번째 데이터를 출력해보면 다음처럼 정수를 포함한 배열이 출력됩니다.
print(train_data[0]) |
[1, 14, 22, 16, 43, 530, 973, 1622, 1385, 65, 458, 4468, 66, 3941, 4, 173, 36, 256, 5, 25, 100, 43, 838, 112, 50, 670, 2, 9, 35, 480, 284, 5, 150, 4, 172, 112, 167, 2, 336, 385, 39, 4, 172, 4536, 1111, 17, 546, 38, 13, 447, 4, 192, 50, 16, 6, 147, 2025, 19, 14, 22, 4, 1920, 4613, 469, 4, 22, 71, 87, 12, 16, 43, 530, 38, 76, 15, 13, 1247, 4, 22, 17, 515, 17, 12, 16, 626, 18, 2, 5, 62, 386, 12, 8, 316, 8, 106, 5, 4, 2223, 5244, 16, 480, 66, 3785, 33, 4, 130, 12, 16, 38, 619, 5, 25, 124, 51, 36, 135, 48, 25, 1415, 33, 6, 22, 12, 215, 28, 77, 52, 5, 14, 407, 16, 82, 2, 8, 4, 107, 117, 5952, 15, 256, 4, 2, 7, 3766, 5, 723, 36, 71, 43, 530, 476, 26, 400, 317, 46, 7, 4, 2, 1029, 13, 104, 88, 4, 381, 15, 297, 98, 32, 2071, 56, 26, 141, 6, 194, 7486, 18, 4, 226, 22, 21, 134, 476, 26, 480, 5, 144, 30, 5535, 18, 51, 36, 28, 224, 92, 25, 104, 4, 226, 65, 16, 38, 1334, 88, 12, 16, 283, 5, 16, 4472, 113, 103, 32, 15, 16, 5345, 19, 178, 32] |
다음 코드를 사용하여 train_data의 첫번째 리뷰에 있는 정수들을 단어로 변환해 볼 수 있습니다.
from tensorflow import keras |
<START> this film was just brilliant casting location scenery story direction everyone's really suited the part they played and you could just imagine being there robert <UNK> is an amazing actor and now the same being director <UNK> father came from the same scottish island as myself so i loved the fact there was a real connection with this film the witty remarks throughout the film were great it was just brilliant so much that i bought the film as soon as it was released for <UNK> and would recommend it to everyone to watch and the fly fishing was amazing really cried at the end it was so sad and you know what they say if you cry at a film it must have been good and this definitely was also <UNK> to the two little boy's that played the <UNK> of norman and paul they were just brilliant children are often left out of the <UNK> list i think because the stars that play them all grown up are such a big profile for the whole film but these children are amazing and should be praised for what they have done don't you think the whole story was so lovely because it was true and was someone's life after all that was shared with us all |
라벨로 사용되는 train_labels와 test_labels은 입력을 부정적인 리뷰와 부정적인 리뷰로 분류해 놓은 것입니다.
0 또는 1의 값을 갖습니다. 0은 부정적인 리뷰, 1은 긍정적인 리뷰를 의미합니다.
데이터셋에 있는 리뷰 텍스트의 길이(단어 개수)는 모두 다릅니다. 다음은 첫번째 리뷰와 두번째 리뷰의 길이를 출력해본 결과입니다.
뉴럴 네트워크의 입력으로 사용하려면 모두 같은 길이가 되도록 조정해줘야 합니다.
print(len(train_data[0]), len(train_data[1])) |
(218, 189) |
데이터 준비하기
pad_sequences 함수를 사용하여 모든 배열의 크기가 256으로 같아지도록 값이 0인 원소를 적절히 추가해줍니다.
train_data = keras.preprocessing.sequence.pad_sequences(train_data, |
이제 모든 리뷰 텍스트 길이가 256이 됩니다.
print(len(train_data[0]), len(train_data[1])) |
256 256 |
출력해보면 배열 크기를 맞추기위해 0을 채운 것을 볼 수 있습니다.
print(train_data[0]) |
[ 1 14 22 16 43 530 973 1622 1385 65 458 4468 66 3941 4 173 36 256 5 25 100 43 838 112 50 670 2 9 35 480 284 5 150 4 172 112 167 2 336 385 39 4 172 4536 1111 17 546 38 13 447 4 192 50 16 6 147 2025 19 14 22 4 1920 4613 469 4 22 71 87 12 16 43 530 38 76 15 13 1247 4 22 17 515 17 12 16 626 18 2 5 62 386 12 8 316 8 106 5 4 2223 5244 16 480 66 3785 33 4 130 12 16 38 619 5 25 124 51 36 135 48 25 1415 33 6 22 12 215 28 77 52 5 14 407 16 82 2 8 4 107 117 5952 15 256 4 2 7 3766 5 723 36 71 43 530 476 26 400 317 46 7 4 2 1029 13 104 88 4 381 15 297 98 32 2071 56 26 141 6 194 7486 18 4 226 22 21 134 476 26 480 5 144 30 5535 18 51 36 28 224 92 25 104 4 226 65 16 38 1334 88 12 16 283 5 16 4472 113 103 32 15 16 5345 19 178 32 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] |
..
..
모델 생성하기
뉴럴 네트워크를 생성하기 위해 레이어를 쌓으려면 몇개의 레이어를 사용할지, 각 레이어를 위해 몇개의 뉴런을 사용할지를 결정해야 합니다.
다음처럼 4개의 레이어로 구성되는 모델을 생성합니다.
# input shape is the vocabulary count used for the movie reviews (10,000 words) |
model.add(keras.layers.Embedding(vocab_size, 16))
첫번째 레이어는 임베딩(Embedding) 레이어입니다.
첫번째 아규먼트인 vocab_size는 사전의 크기 10,000이며(앞에서 상위 단어 10,000개만 사용하도록 했습니다.) 두번째 아규먼트인 16은 생성되는 임베딩 벡터의 크기입니다.
10,000개 의 단어에 대한 크기 16의 임베딩 벡터를 10,000개 만듭니다.
차원이 추가되어 넘파이 배열의 shape는 (25000, 256, 16) 이 됩니다.
25,000개의 리뷰 텍스트가 있으며, 각 리뷰는 256개의 단어로 구성되고, 각 단어는 16개의 원소로 구성된 벡터로 표현됩니다.
layer 0 output (25000, 256, 16) # 첫번째 리뷰의 임베딩 결과를 출력해보면 크기 16인 256개의 임베딩 벡터가 출력됩니다. 똑같은 단어는 같은 임베딩 벡터입니다. [[-4.51784134e-02 8.43267515e-03 2.41203420e-02 4.95403446e-02 2.92846598e-02 -1.98755395e-02 5.22672012e-03 3.98325063e-02 -4.06740978e-03 1.85608603e-02 2.21599676e-02 4.39548381e-02 3.50121409e-03 3.62660550e-02 -2.22286936e-02 4.82255332e-02]
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.30433361e-02 -3.33103910e-02 1.49245970e-02 3.34262848e-03] [-4.08503413e-02 -1.63457543e-03 -2.30084546e-02 -1.88462492e-02 1.23437643e-02 1.17091164e-02 2.51974910e-03 -2.21333392e-02 -3.31593677e-03 3.02489139e-02 2.21424177e-03 -4.39639576e-02 3.30433361e-02 -3.33103910e-02 1.49245970e-02 3.34262848e-03]] |
model.add(keras.layers.GlobalAveragePooling1D())
GlobalAveragePooling1D 레이어는 고정된 크기(여기에선 16)의 출력 벡터를 리턴합니다.
입력으로 shape가 (25000, 256, 16)인 배열이 사용됩니다. 두번째 차원(리뷰당 단어개수 256개) 방향으로 평균을 구하여 shape가 (25000, 16)인 배열을 생성합니다.
입력으로 사용되는 리뷰에 포함된 단어 개수가 변경되더라도 같은 크기의 벡터로 처리할 수 있게 됩니다.
layer 1 output (25000, 16) [-0.00634271 0.00400057 -0.00282727 0.00280815 0.00067278 0.00357414 -0.00044041 -0.005514 0.00050653 0.00501671 -0.00345321 -0.01061774 0.00476874 0.00029571 0.00374537 0.00056968] |
model.add(keras.layers.Dense(16, activation=tf.nn.relu))
뉴런이 16개인 fully-connected layer(Dense)에 입력 벡터를 통과시킵니다.
크기 16인 벡터가 출력됩니다.
layer 2 output (25000, 16) [0.00242995 0. 0.0011177 0.00598094 0.00098548 0.00424779 0. 0. 0. 0.00465069 0.00174703 0. 0. 0. 0. 0. ] |
model.add(keras.layers.Dense(1, activation=tf.nn.sigmoid))
마지막 층은 하나의 노드로 구성된 출력 레이어에 fully-connected하게 연결됩니다.
본 포스팅의 예제는 바이너리 분류이기 때문에 모델의 출력은 확률입니다. 그래서 마지막 레이어는 하나의 노드를 가지며 시그모이드 함수를 활성화 함수로 사용합니다.
시그모이드 함수는 출력이 0 ~ 1사이의 실수값을 갖도록 합니다.
layer 3 output (25000, 1) [0.5010359] |
손실 함수와 옵티마이저 설정
모델을 학습시키려면 손실 함수(Loss function)와 옵티마이저(optimizer)가 필요합니다.
옵티마이저로는 Adam 알고리즘을 구현한 AdamOptimizer를 사용합니다.
경사 하강법을 사용하는 GradientDescentOptimizer보다 성능이 좋다고 합니다.
손실함수로 binary_crossentropy를 사용합니다. binary_crossentropy는 확률 분포간의 거리를 측정합니다.
여기에선 예측된 분포와 정답(=라벨) 분포간의 거리를 측정합니다.
모델의 성능을 평가하기 위해 전체 데이터셋에서 올바르게 분류된 이미지 비율을 표시하는 정확도(accuracy)를 사용합니다.
model.compile(optimizer=tf.train.AdamOptimizer(), |
검증 데이터셋 생성
모델이 학습되는 동안 모델의 정확성을 체크하기 위해 테스트 데이터셋 대신에 검증 데이터셋을 사용합니다.
(학습되는 동안에는 훈련 데이터셋만 사용하고 나중에 모델의 정확도를 평가할때에만 테스트 셋을 사용합니다. )
다음 코드처럼 훈련 데이터셋을 둘로 나누어 일부를 검증 데이터셋(x_val, y_val )으로 사용하도록 합니다.
훈련 데이터 셋으로는 15,000개, 검증 데이터 셋으로는 10,000개를 사용하게 됩니다.
x_val = train_data[:10000] |
모델 학습 시키기
훈련 데이터셋(partial_x_train, partial_y_train)을 512개의 작은 미니배치(mini-batches)로 분할하여 입력으로 사용합니다.
512개씩 나누어 전체 훈련 데이터셋을 모델 학습에 사용하는 것을 40번 반복하도록 합니다.
모델이 학습되는 동안 모델의 손실(loss)과 정확도(acc)를 계산하기 위해 검증 데이터셋(x_val, y_val)을 사용합니다
history = model.fit(partial_x_train, |
학습이 되는 동안 손실(loss)과 정확도(acc)가 출력됩니다.
Epoch 1/40 512/15000 [>.............................] - ETA: 15s - loss: 0.6932 - acc: 0.4922 1536/15000 [==>...........................] - ETA: 5s - loss: 0.6931 - acc: 0.5065 2560/15000 [====>.........................] - ETA: 3s - loss: 0.6930 - acc: 0.5145 3584/15000 [======>.......................] - ETA: 2s - loss: 0.6929 - acc: 0.5215 4608/15000 [========>.....................] - ETA: 1s - loss: 0.6928 - acc: 0.5193 5632/15000 [==========>...................] - ETA: 1s - loss: 0.6927 - acc: 0.5126 6656/15000 [============>.................] - ETA: 1s - loss: 0.6926 - acc: 0.5110 7680/15000 [==============>...............] - ETA: 0s - loss: 0.6924 - acc: 0.5117 8704/15000 [================>.............] - ETA: 0s - loss: 0.6923 - acc: 0.5124 9728/15000 [==================>...........] - ETA: 0s - loss: 0.6921 - acc: 0.5126 10752/15000 [====================>.........] - ETA: 0s - loss: 0.6920 - acc: 0.5149 11776/15000 [======================>.......] - ETA: 0s - loss: 0.6919 - acc: 0.5145 12800/15000 [========================>.....] - ETA: 0s - loss: 0.6916 - acc: 0.5192 13824/15000 [==========================>...] - ETA: 0s - loss: 0.6915 - acc: 0.5197 14848/15000 [============================>.] - ETA: 0s - loss: 0.6913 - acc: 0.5203 15000/15000 [==============================] - 2s 108us/step - loss: 0.6913 - acc: 0.5213 - val_loss: 0.6888 - val_acc: 0.5731 Epoch 2/40 15000/15000 [==============================] - 1s 66us/step - loss: 0.6835 - acc: 0.6941 - val_loss: 0.6780 - val_acc: 0.7441 Epoch 3/40 15000/15000 [==============================] - 1s 67us/step - loss: 0.6672 - acc: 0.7605 - val_loss: 0.6585 - val_acc: 0.7512 Epoch 4/40 ….. ….. ….. ….. ….. ….. ….. ….. ….. ….. ….. ….. ….. ….. ….. ….. ….. ….. ….. ….. ….. ….. ….. ….. ….. ….. ….. ….. ….. Epoch 39/40 15000/15000 [==============================] - 1s 63us/step - loss: 0.0938 - acc: 0.9757 - val_loss: 0.3087 - val_acc: 0.8834 Epoch 40/40 15000/15000 [==============================] - 1s 66us/step - loss: 0.0900 - acc: 0.9771 - val_loss: 0.3117 - val_acc: 0.8826 |
모델 성능 평가하기
테스트 셋으로 평가한 결과 정확도는 0.87입니다.
results = model.evaluate(test_data, test_labels) |
32/25000 [..............................] - ETA: 1s 1216/25000 [>.............................] - ETA: 1s 2112/25000 [=>............................] - ETA: 1s ………………………………………………………………………... 23104/25000 [==========================>...] - ETA: 0s 23840/25000 [===========================>..] - ETA: 0s 24864/25000 [============================>.] - ETA: 0s 25000/25000 [==============================] - 1s 51us/step [0.3225130436515808, 0.87356] |
정확도와 손실 그래프 그리기
model.fit() 메소드는 학습되는 동안 측정된 정확도, 손실등을 리턴합니다.
history = model.fit(partial_x_train, |
4개의 엔트리로 구성됩니다. 각각 학습 정확도, 검증 정확도, 학습 손실, 검증 손실을 의미합니다.
history_dict = history.history |
dict_keys(['acc', 'val_acc', 'loss', 'val_loss']) |
다음 코드를 추가하여 정확도와 손실을 그래프로 그려볼 수 있습니다.
import matplotlib.pyplot as plt history_dict.keys()
|
그래프에서 점선은 학습 손실과 학습 정확도, 실선은 검증 손실과 검증 정확도를 나타냅니다.
반복 횟수가 증가함에 따라 학습 손실은 감소하고 정확도는 상승합니다.
검증 손실과 정확도의 경우에는 상황이 다릅니다. 20번 반복한 후 부터는 변화가 없습니다.
학습시 사용하는 데이터셋을 입력으로 사용했을때보다 검증에 사용하는 데이터셋을 입력으로 사용했을때 더 결과가 안좋은 것을 오버피팅(overfitting)이라고 합니다.
학습용 데이터셋에 최적화되어 있어서 새로운 데이터셋을 입력으로 사용시 모델의 성능이 떨어지는 겁니다.
예측하기
학습된 모델에 테스트 데이터셋을 입력으로 사용하여 예측을 해봅니다.
predictions = model.predict(test_data) |
예측된 값 중 10개만 출력해보았습니다. 첫번째 줄은 뉴럴 네트워크의 출력이며 두번째 줄은 어느 클래스에 속하는지 판정한 결과입니다.
[[0.09011856 0.99828243 0.7261485 0.7149344 0.99290705 0.92161864 0.9481936 0.02148814 0.96879655 0.9984517 ]] [[0 1 1 1 1 1 1 0 1 1]] |
마지막 업데이트 2019. 1. 17
'Deep Learning & Machine Learning > 강좌&예제 코드' 카테고리의 다른 글
텐서플로우 기초 강좌 - 1. 간단한 수식 계산 (0) | 2019.02.04 |
---|---|
Tensorflow 강좌 - Tensorboard 간단한 사용방법 (5) | 2019.01.22 |
Tensorflow 강좌 - Neural Network를 이용하여 실제 손글씨 숫자 인식해보기(MNIST) (7) | 2018.09.15 |
Tensorflow 강좌 - Logistic Regression를 이용하여 실제 손글씨 숫자 인식해보기(MNIST) (6) | 2018.09.15 |
예제로 배우는 텐서플로우 강좌 - 7. 훈련된 모델 파라미터 저장하기 (0) | 2018.08.31 |
시간날때마다 틈틈이 이것저것 해보며 블로그에 글을 남깁니다.
블로그의 문서는 종종 최신 버전으로 업데이트됩니다.
여유 시간이 날때 진행하는 거라 언제 진행될지는 알 수 없습니다.
영화,책, 생각등을 올리는 블로그도 운영하고 있습니다.
https://freewriting2024.tistory.com
제가 쓴 책도 한번 검토해보세요 ^^
그렇게 천천히 걸으면서도 그렇게 빨리 앞으로 나갈 수 있다는 건.
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!