갈루아의 반서재

케라스 버전 확인

import keras
keras.__version__

Using TensorFlow backend.

Out[1]:
'2.2.4'


IMDB 데이터셋


영화 리뷰 텍스트를 기반으로 해당 리뷰를 긍정과 부정으로 분류하는 방법, 즉 이진 분류 방법에 대해 알아본다. 

  • IMDB 데이터셋은 훈련데이터, 테스트데이터 각각 25,000개로 구성 (긍정리뷰 50%, 부정리뷰 50%)
  • 같은 데이터를 가지고 모델을 훈련하고 테스트해서는 안되기 때문에 훈련 데이터와 테스트 데이터를 나누게 됨
  • 훈련 데이터에서의 작동이 새로운 데이터에서의 작동을 보장해주지는 않음 (훈련 데이터의 레이블은 이미 알고 있기 때문에 이를 예측하는 모델은 필요없음)

데이터셋을 로드한다.

from keras.datasets import imdb
(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words = 10000)

Downloading data from https://s3.amazonaws.com/text-datasets/imdb.npz 17465344/17464789 [==============================] - 18s 1us/step

  • num_words = 10000 자주 나타나는 단어 10,000개를 사용하겠다는 의미 
  • train_data, test_data 리뷰 목록 (각 리뷰는 단어 인덱스의 리스트, 단어 시퀀스 인코딩)
  • train_labels, test_labels 부정 0, 긍정 1 을 나타내는 리스트


리뷰 하나를 열어보자. 

train_data[0]
Out[3]:
[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_labels[0]
Out[4]:
1

num_words = 10000 으로 제한했기 때문에 단어 인덱스는 10,000개를 넘지 않는다.

max([max(sequence) for sequence in train_data])
Out[5]:
9999

해당 리뷰를 원래의 영어 단어로 바꿔보자

word_index = imdb.get_word_index()
reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])
decoded_review = ' '.join([reverse_word_index.get(i - 3, '?') for i in train_data[0]])

decoded_review
Out[10]:
"? 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 ? is an amazing actor and now the same being director ? 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 ? 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 ? to the two little boy's that played the ? of norman and paul they were just brilliant children are often left out of the ? 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"


데이터 준비


신경망에 숫자 리스트를 넣을 수는 없다. 따라서 아래와 같이 해당 리스트를 텐서로 바꾸어야 한다. 

리스트를 텐서로 바꾸는 방법 

1) 같은 길이가 되도록 리스트에 패딩 추가 → (samples, sequence_length) 크기의 정수 텐서로 변환 → 정수 텐서를 다룰 수 있는 층을 신경망의 첫 번째 층으로 사용 (Embedding 층)

가장 긴 리뷰는 2,494개의 단어로 이루어져 있다. 이 경우 훈련 데이터를 변환한 텐서의 크기는 (25000, 2494)이다.

max([len(sequence) for sequence in train_data])
Out[29]:
2494

2) 리스트를 one-hot encoding 하여 0과 1의 벡터로 변환 

예를 들어, 시퀀스 [3, 5]를 인덱스 3과 5의 위치는 1이고 그 외는 모두 0인 10,000 차원의 벡터로 각각 변환

그 다음으로 부동 소수 벡터 데이터를 다룰 수 있는 Dense 층을 신경망의 첫 번째 층으로 사용한다. 

리스트가 하나의 벡터로 변환되므로 훈련 데이터를 변환한 텐서의 크기는 (25000, 10000)이 된다.


여기서는 2)의 방법을 이용하기로 한다. 정수 시퀀스를 이진 행렬로 디코딩해보자.

크기가 (len(sequences), dimension))이고 모든 원소가 0인 행렬을 만들고, results[i, sequence] = 1.  # results[i]에서 특정 인덱스의 위치를 1로 만든 다음, 훈련 데이터와 테스트 데이터를 벡터로 변환하면 된다.

import numpy as np

def vectorize_sequences(sequences, dimension=10000):
    results = np.zeros((len(sequences), dimension))
    for i, sequence in enumerate(sequences):
        results[i, sequence] = 1
    return results

x_train = vectorize_sequences(train_data)
x_test = vectorize_sequences(test_data)


샘플은 다음과 같이 나타나게 된다.

x_train[0]
Out[36]:
array([0., 1., 1., ..., 0., 0., 0.])


레이블을 다음과 같이 벡터로 바꾼다.

y_train = np.asarray(train_labels).astype('float32')
y_test = np.asarray(train_labels).astype('float32')


신경망 모델 만들기 Building our network


입력데이터는 벡터, 레이블은 스칼라(1 또는 0)

  • 16개의 은닉 유닛을 가진 두 개의 은닉층
  • 현재 리뷰의 감정을 스칼라 값의 예측으로 출력하는 세 번째 층

중간에 있는 은닉층은 활성화 함수로 relu를 사용하고, 마지막 층은 확률을 출력하기 위해 시그모이드 활성화 함수 사용한다. relu는 음수를 0으로 만드는 함수이고, 시그모이드는 임의의 값을 [0, 1] 사이로 압축하므로 출력 값을 확률처럼 해석할 수 있다. 즉, 이 신경망의 다음과 같은 구조를 가진다.


source : https://s3.amazonaws.com/book.keras.io/img/ch3/3_layer_network.png


위의 신경망을 케라스로 구현하면 다음과 같다. 

from keras import models
from keras import layers

model = models.Sequential()
model.add(layers.Dense(16, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(16, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))


이제 손실함수와 옵티마이저를 선택한다. rmsprop 옵티마이저와 binary_crossentropy 손실 함수 모델로 설정하고, 훈련하는 동안 정확도를 사용하여 모니터링한다.

model.compile(optimizer='rmsprop',
              loss='binary_crossentropy',
              metrics=['accuracy'])

훈련검증 Validating our approach


원본 훈련데이터에서 10,000개의 샘플을 가져와 검증 셋트를 만든다.

x_val = x_train[:10000]
partial_x_train = x_train[10000:]

y_val = y_train[:10000]
partial_y_train = y_train[10000:]

512개씩 미니샘플을 만들어 20번의 에포크 동안 훈련시킨다. x_train 과 y_train 텐서에 있는 모든 샘플에 대해 20번 반복한다. 동시에 다로 떼어놓은 10,000개의 샘플에서 손실과 정확도를 측정한다. validation_data 매개변수에 검증 데이터를 전달한다. 

history = model.fit(partial_x_train,
                    partial_y_train,
                    epochs=20,
                    batch_size=512,
                    validation_data=(x_val, y_val))
Train on 15000 samples, validate on 10000 samples
Epoch 1/20
15000/15000 [==============================] - 3s 210us/step - loss: 0.5048 - acc: 0.7872 - val_loss: 0.3774 - val_acc: 0.8706
Epoch 2/20
15000/15000 [==============================] - 3s 175us/step - loss: 0.2991 - acc: 0.9044 - val_loss: 0.3000 - val_acc: 0.8898
Epoch 3/20
15000/15000 [==============================] - 3s 174us/step - loss: 0.2173 - acc: 0.9285 - val_loss: 0.3082 - val_acc: 0.8716
Epoch 4/20
15000/15000 [==============================] - 3s 176us/step - loss: 0.1747 - acc: 0.9437 - val_loss: 0.2825 - val_acc: 0.8843
Epoch 5/20
15000/15000 [==============================] - 3s 175us/step - loss: 0.1422 - acc: 0.9539 - val_loss: 0.2855 - val_acc: 0.8855
Epoch 6/20
15000/15000 [==============================] - 3s 176us/step - loss: 0.1148 - acc: 0.9650 - val_loss: 0.3149 - val_acc: 0.8771
Epoch 7/20
15000/15000 [==============================] - 3s 175us/step - loss: 0.0977 - acc: 0.9710 - val_loss: 0.3131 - val_acc: 0.8843
Epoch 8/20
15000/15000 [==============================] - 3s 176us/step - loss: 0.0806 - acc: 0.9763 - val_loss: 0.3866 - val_acc: 0.8651
Epoch 9/20
15000/15000 [==============================] - 3s 175us/step - loss: 0.0661 - acc: 0.9821 - val_loss: 0.3640 - val_acc: 0.8780
Epoch 10/20
15000/15000 [==============================] - 3s 176us/step - loss: 0.0558 - acc: 0.9853 - val_loss: 0.3857 - val_acc: 0.8781
Epoch 11/20
15000/15000 [==============================] - 3s 175us/step - loss: 0.0438 - acc: 0.9897 - val_loss: 0.4189 - val_acc: 0.8765
Epoch 12/20
15000/15000 [==============================] - 3s 178us/step - loss: 0.0384 - acc: 0.9913 - val_loss: 0.4541 - val_acc: 0.8700
Epoch 13/20
15000/15000 [==============================] - 3s 188us/step - loss: 0.0294 - acc: 0.9934 - val_loss: 0.4704 - val_acc: 0.8737
Epoch 14/20
15000/15000 [==============================] - 3s 185us/step - loss: 0.0254 - acc: 0.9945 - val_loss: 0.5044 - val_acc: 0.8727
Epoch 15/20
15000/15000 [==============================] - 3s 175us/step - loss: 0.0169 - acc: 0.9986 - val_loss: 0.5418 - val_acc: 0.8693
Epoch 16/20
15000/15000 [==============================] - 3s 176us/step - loss: 0.0158 - acc: 0.9977 - val_loss: 0.5759 - val_acc: 0.8692
Epoch 17/20
15000/15000 [==============================] - 3s 177us/step - loss: 0.0130 - acc: 0.9983 - val_loss: 0.6071 - val_acc: 0.8695
Epoch 18/20
15000/15000 [==============================] - 3s 186us/step - loss: 0.0097 - acc: 0.9984 - val_loss: 0.6375 - val_acc: 0.8670
Epoch 19/20
15000/15000 [==============================] - 3s 176us/step - loss: 0.0071 - acc: 0.9993 - val_loss: 0.6920 - val_acc: 0.8645
Epoch 20/20
15000/15000 [==============================] - 3s 175us/step - loss: 0.0088 - acc: 0.9981 - val_loss: 0.7058 - val_acc: 0.8665


훈련하는 동안 발생한 정보를 담고 있는 딕셔너리인 history 의 속성을 살펴보자. 4개의 항목을 담고 있다. 맷플롯립을 사용하여 훈련과 검증 데이터에 대한 손실과 정확도를 그려보자.  

history_dict = history.history
history_dict.keys()
Out[25]:
dict_keys(['val_loss', 'val_acc', 'loss', 'acc'])


먼저 훈련과 검증손실 그래프이다. 

import matplotlib.pyplot as plt

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(1, len(acc) + 1)

plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()

plt.show()


다음으로 그래프를 초기화하고, 훈련과 검증 정확도를 그려보자.

plt.clf() 
acc = history_dict['acc']
val_acc = history_dict['val_acc']

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()

plt.show()


그래프에서 보듯이 손실은 에포크마다 감소하고 정확도는 에포크마다 증가하고 있다. 


다시 새로운 신경망을 4번의 에포크 동안만 훈련하고 테스트 테이터에서 평가해보자. 

model = models.Sequential()
model.add(layers.Dense(16, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(16, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

model.compile(optimizer='rmsprop',
              loss='binary_crossentropy',
              metrics=['accuracy'])

model.fit(x_train, y_train, epochs=4, batch_size=512)
results = model.evaluate(x_test, y_test)
Epoch 1/4
25000/25000 [==============================] - 3s 112us/step - loss: 0.4492 - acc: 0.8206
Epoch 2/4
25000/25000 [==============================] - 2s 77us/step - loss: 0.2584 - acc: 0.9073
Epoch 3/4
25000/25000 [==============================] - 2s 78us/step - loss: 0.1985 - acc: 0.9297
Epoch 4/4
25000/25000 [==============================] - 2s 77us/step - loss: 0.1675 - acc: 0.9411
25000/25000 [==============================] - 2s 82us/step

최종결과는 다음과 같다. 

results
Out[38]:
[1.8806207118988036, 0.49956]


위와 같이 훈련을 시킨 후 predict 메소드를 사용하여 어떤 리뷰가 긍정일 확률을 예측할 수 있다.

model.predict(x_test)
Out[39]:
array([[0.12138499],
       [0.9999572 ],
       [0.8089951 ],
       ...,
       [0.07709821],
       [0.06394621],
       [0.43717876]], dtype=float32)

예측 결과 긍정적인 리뷰로 분류되는 x_test[1]의 내용을 보자. 

word_index = imdb.get_word_index()
reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])
decoded_review = ' '.join([reverse_word_index.get(i - 3, '?') for i in test_data[1]])
In [49]:
decoded_review
Out[49]:
"? this film requires a lot of patience because it focuses on mood and character development the plot is very simple and many of the scenes take place on the same set in frances ? the sandy dennis character apartment but the film builds to a disturbing climax br br the characters create an atmosphere ? with sexual tension and psychological ? it's very interesting that robert altman directed this considering the style and structure of his other films still the trademark altman audio style is evident here and there i think what really makes this film work is the brilliant performance by sandy dennis it's definitely one of her darker characters but she plays it so perfectly and convincingly that it's scary michael burns does a good job as the mute young man regular altman player michael murphy has a small part the ? moody set fits the content of the story very well in short this movie is a powerful study of loneliness sexual ? and desperation be patient ? up the atmosphere and pay attention to the wonderfully written script br br i praise robert altman this is one of his many films that deals with unconventional fascinating subject matter this film is disturbing but it's sincere and it's sure to ? a strong emotional response from the viewer if you want to see an unusual film some might even say bizarre this is worth the time br br unfortunately it's very difficult to find in video stores you may have to buy it off the internet"

deep-learning-with-python-notebooks 3.5 imdb

IMDB Movie Review

https://www.kaggle.com/juanjotwo/deep-learning-with-python-notebooks-3-5-imdb