수학과의 좌충우돌 프로그래밍

[딥러닝]05.imdb 영화 리뷰 이진 분류 본문

인공지능/케라스창시자에게 배우는 딥러닝

[딥러닝]05.imdb 영화 리뷰 이진 분류

ssung.k 2019. 3. 25. 21:35

영화 리뷰 이진 분류

IMDB 데이터 셋

영화 리뷰에 대한 데이터 50000개로 이루어져 있습니다. 이 중 25,000개의 훈련데이터와 25,000개의 테스트데이터로 나눠지며 각각 50%씩 긍정 리뷰와 부정 리뷰가 있습니다. 이 데이터는 이미 전처리가 되어 있어서 각 리뷰가 숫자로 변환되어있습니다. 리뷰를 숫자로 바꾸는 원리는 사전에 있는 단어와 숫자를 매핑시켜서 단어대신 숫자로 표현한 것입니다. 그럼 데이터를 불러와보겠습니다.

from keras.datasets import imdb

(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)

num_words=10000 는 훈련데이터에서 가장 자주 사용하는 단어 1만개만 사용하겠다는 의미입니다. 사용 빈도수가 10000등 안에 들지 못하는 데이터는 training을 시키지 않습니다. 이런 과정을 통해 적절한 크기의 벡터 데이터를 얻을 수 있습니다.

이제 데이터가 잘 저장 되었는지 확인해보도록 하겠습니다.

train_data[0] # [1,14,22, ... , 19,178,32]
train_labels[0] # 1

위에서 말했듯이 train_data 는 각 단어에 매핑되는 숫자로 전처리 되어있습니다. train_labels 는 긍정과 부정을 나타내는 labels 로서 부정은 0, 긍정은 1을 의미합니다.

 

리뷰 데이터 복구

현재 숫자로 전처리 되어있는 데이터를 다시 영어로 복구하는 과정을 살펴보겠습니다.

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

한 줄씩 살펴보도록 하겠습니다.

word_index = imdb.get_word_index()
# {'bettina': 25184, "'cannes'": 70227, 'karel': 87565, 'heorot': 30490, 'karen': 4112, "1992's": 34683, 'snorer': 34586, [생략] } 

imdb.get_word_index() 는 단어와 정수를 매핑한 딕셔너리를 리턴합니다.

reverse_word_index = dict(
    [(value,key) for (key, value) in word_index.items()]
)
# {25184: 'bettina', 70227: "'cannes'", 87565: 'karel', 30490: 'heorot', 4112: 'karen', 34683: "1992's", 34586: 'snorer', [생략] } 

items() 는 dictionary의 key 와 value를 list 로 반환을 하고, value와 key 값을 뒤집어줘서 dict 형식으로 반환합니다.

decode_review = ' '.join(
    [reverse_word_index.get(i-3, '?') for i in train_data[0]]
)
# ? 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 [생략]

이제 리뷰를 디코딩 하는 작업입니다. train_data 을 디코딩하는데 get() 은 key값을 통해 value 를 찾아주고 못찾은 경우에는 '?' 로 대체합니다. 이 작업이 필요한 이유는 위에서 데이터를 받아올 때 num_words=10000 을 통해 만 개의 자주 쓰이는 단어만 가져왔으므로 나머지 단어에 대해서는 값이 존재하지 않으므로 다음 작업이 필요합니다. 그리고 각 단어를 join() 을 통해서 공백으로 이어주었습니다. 이 때 0,1,2 에 해당하는 값은 '패딩', '문서 시작', '사전에 없음' 을 위함이므로 3을 빼고 계산합니다.

 

데이터 준비

신경망에 현재의 데이터 형태인 숫자 리스트를 대입할 수는 없습니다. 따라서 리스트를 텐서로 바꿔줘야 합니다. 그 과정에서 원 핫 인코딩 방식을 사용했습니다.

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

먼저 len(sequences) * 10000 크기의 모든 성분이 0인 2차원 넘파이 배열을 만들어줍니다.

results[i, sequence] = 1.

넘파이 배열에만 있는 방법인데 2차원 배열 results 에 대해서 i번째 행에 sequence 리스트의 원소를 index로 하여 접근을 합니다. 이리고 그 자리에만 1을 대입하여 원 핫 인코딩을 실행하는 것이죠.

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

이제 이 함수를 이용해서 각 데이터를 텐서로 변경해주었습니다. 다음으로는 레이블도 바꿔줘야하는데 레이블은 쉽게 벡터로 변경할 수 있습니다.

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

 

신경망 모델

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'))
  • 16개의 은닉 유닛을 가진 2개의 은닉 층
  • 중간에 있는 은닉층은 활상화 함수로 relu 사용
  • 마지막 층은 확률을 출력하기 위해서 시그모이드 사용

 

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

모델의 옵티마이저 방식, 손실함수, 어떤 값을 모니터링 할지 결정할 수 있습니다.

 

x_val = x_train[:10000]
partial_x_train = x_train[10000:]
y_val = y_train[:10000]
partial_y_train[10000:]

훈련데이터와 검증데이터를 분리하는 작업입니다. 앞에서부터 만 개의 데이터를 슬라이싱 해서 검증데이터로 사용합니다.

 

history = model.fit(partial_x_train,
                    partial_y_train,
                    epochs = 20,
                    batch_size = 512,
                    validation_data=(x_val, y_val))

모델을 훈련하는 과정입니다. 1,2 번째 파라미터로 훈련할 데이터를 받습니다. 그 외에도 epoch, batch_size 를 지정해주고 검증데이터를 지정해줍니다. epoch은 전체 데이터를 한 번 트레이닝 한 것을 1epoch 이라고 하며 20이라는 것은 전체 샘플에 대해서 20번 반복하는 것을 의미합니다. batch_size 는 한 번에 몇 개씩 처리할지를 의미합니다. batch_size를 지정하는 이유는 한 번에 모든 데이터를 훈련시키려고 하면 컴퓨터의 메모리를 초과하게 될 수 있기 때문입니다.

 

훈련과 검증 손실,정확도

matplotlib 를 이용해서 손실과 정확도를 시각적으로 한 눈에 알아보도록 하겠습니다. 그러기 위해서는 훈련하는 동안 각각의 데이터가 어떻게 변하는지 알아야하는데 위에서 model.fit() 가 이 역할을 해줍니다.

history_dict = history.history
""" {'val_loss': [
		0.3798849132537842,
 		[생략]
 		0.6957954946517945],
 	 'val_acc': [
 	 	0.8679999997138977,
		[생략]
 		0.8677999996185303],
 	 'loss': [
 	 	0.5084402885437012,
 	 	[생략]
  	    0.004765280481427908],
	 'acc': [
	 	0.7812000001907349,
		[생략]
	    0.9996666666666667]}
  """

이제 그래프를 그려보도록 하겠습니다.

import matplotlib.pyplot as plt

history_dict = history.history
loss = history_dict['loss']
val_loss = history_dict['val_loss']

epochs = range(1,len(loss)+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= 'valadation acc')
plt.title('Training and validation accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()

plt.show()

 

훈련데이터에 대해서는 epoch이 증가하면서 정확도는 증가하고, 손실은 감소합니다. 하지만 검증데이터에 대해서는 원치 않은 결과가 나옵니다. 더군다나 정확도같은 경우에는 4번째 epoch부터 오히려 떨어지는 결과를 초래합니다. 이런 현상을 과대 적합(overfitting) 이라고 합니다. 이러한 문제를 막기 위해서 epoch의 수를 4로 줄이고 평가해보도록 하겠습니다.

history = model.fit(partial_x_train,
                    partial_y_train,
                    epochs = 4,
                    batch_size = 512,
                    validation_data=(x_val, y_val))

results = model.evaluate(x_test, y_test)
# [0.8765790511918068, 0.86476]

86% 정도의 정확도에 달성한 걸 확인할 수 있습니다.

Comments