본문 바로가기
Book Review/[케라스 창시자에게 배우는 딥러닝] 리뷰

신경망을 이용한 이진 분류: 영화 리뷰 분류

by 3n952 2022. 12. 28.

지금까지 다룬 내용을 바탕으로 신경망에서 가장 많이 다루는 이진 분류, 다중 분류, 회귀의 실제 문제를 풀어볼 예정입니다.

이번 포스트에서는 그 중에서 이진 분류의 문제를 다뤄보도록 하겠습니다.


영화 리뷰 분류: 이진 분류

이진 분류는 머신 러닝 문제에서 가장 많이 다루는 문제 중 하나입니다.

이 예제를 통해 리뷰 텍스트를 기반으로 영화 리뷰를 긍정/부정으로 분류하는 모델을 만들어보겠습니다.

 

- IMDB 데이터셋

영화 리뷰 분류를 위해 사용할 데이터는 IMDB데이터셋입니다.

인터넷 영화 데이터베이스로부터 가져온 5만 개로 이뤄진 데이터셋 입니다.

해당 데이터 셋은 훈련데이터 2만 5000개 테스트 데이터 2만 5000개, 긍정/부정이 각각 50%로 구성되어 있습니다.

케라스에 IMDB데이터 셋이 있기 때문에 import만 해주면 다운로드가 됩니다.

코드는 다음과 같습니다.

from tensorflow.keras.datasets import imdb
(train_data, train_label),(test_data, test_label) = imdb.load_data(num_words = 10000)

여기서 사용된 num_words 매개변수는 훈련 데이터에서 가장 자주 나타나는 단어 10000개를 정해주는 역할을 합니다.

이를 통해 적절한 크기의 벡터 데이터를 얻을 수 있고 분류 작업에 의미있게 사용됩니다.

 

train_data, test_data에는 리뷰를 담은 배열로, 단어 인덱스의 리스트입니다.

영어로 이뤄진 단어들을 인코딩하여 숫자 벡터로 표현한 것입니다.

train_label, test_label에는 긍정과 부정을 나타내는 리스트로, 각 리뷰가 긍정적인지, 부정적인지 나타내줍니다.

부정은 0, 긍정은 1로 나타냅니다.

 

 

-데이터 준비

train_data, test_data의 숫자 리스트는 모두 길이가 다르기 때문에

신경망에 주입하기 위해서는 동일한 크기의 배치로 만들어주어야 합니다.

 

멀티-핫 인코딩을 사용하여 숫자 리스트를 0과1의 벡터로 변환해보겠습니다.

코드는 다음과 같습니다.

import numpy as np

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

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

y_train = np.asarray(train_label).astype('float32')
y_test = np.asarray(test_label).astype('float32')

 

멀티 핫 인코딩으로 단어 시퀀스를 (25000, 10000)크기의 벡터로 변환했습니다.

쉽게 예를 들어보겠습니다.

가령 한국말로 "이건 재밌다." 라는 리뷰가 있고, 

이를 인코딩했을 때 시퀀스는 [3, 19]라고 매핑된다고 가정을 해보겠습니다.

시퀀스 [3, 19]를 멀티 핫 인코딩하면 3번째, 19번째 인덱스만 1이 되고 나머지는 0이 되는 것입니다. 

이를 통해 똑같은 배치 크기로 입력 데이터를 변환시켜줄 수 있습니다.

 

정답 레이블은 단순하게 넘파이의 asarray함수를 사용하여 변환시켜주면 됩니다.

각 레이블의 값이 하나의 벡터로 표현됩니다. 이 때 크기는 (25000, )입니다.

 

 

-신경망 모델 만들기

 

데이터가 준비 되었으면 이제 신경망 모델을 만들 차례입니다.

활성화함수로 relu함수를 사용하는 밀집(Dense) 연결 층을 쌓아 보겠습니다.

 

얼마나 많은 층을 사용하는지, 각 층에 몇개의 유닛을 둘 것인지에 따라 성능이 좌우됩니다.

따라서 해당 구조를 정하는 것은 매우 중요합니다. 하지만 이번 챕터에서는 그것을 정하는 방법을 배우는 것이 아니기 때문에

적당한 값을 미리 제시하겠습니다.

 

1. 16개의 유닛을 가진 2개의 은닉층

2. 현재 리뷰의 감정을 스칼라 값(0 또는 1)예측으로 출력하는 세 번째 층

으로 구성되도록 하겠습니다.

 

앞서 배운 케라스의 신경망 모델 구현과 비슷합니다. 

코드는 다음과 같습니다.

from tensorflow import keras
from keras import layers

model = keras.Sequential([
    layers.Dense(16, activation = 'relu'),
    layers.Dense(16, activation = 'relu'),
    layers.Dense(1, activation = 'sigmoid')
])

 

모델을 구성했으면 손실함수와 옵티마이저를 정해야합니다.

이진 분류 문제에는 binary_crossentropy손실이 적합합니다.

옵티마이저로는 rmsprop을 사용하겠습니다.

 

모델의 훈련하는 동안 모니터링하기 위해 정확도를 사용하겠습니다.

코드는 다음과 같습니다.

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

 

-훈련검증

훈련 검증을 위해서는 훈련 데이터셋이 아닌 검증 데이터셋으로 해야합니다.

여기에서는 훈련 데이터에서 10000개의 데이터를 따로 떼어내 검증 데이터 셋으로 만들어 모델을 평가하겠습니다.

코드는 다음과 같습니다.

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

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

 

fit()메서드는 history객체를 반환합니다. history객체엔 딕셔너리 속성을 가지고 있습니다.

이를 통해 훈련하는 동안 발생한 모든 정보를 확인 할 수 있습니다.

 

해당 정보(손실 값, 정확도)를 시각화하여 보다 직관적으로 확인해보겠습니다.

손실 값에 대한 정보를 먼저 시각화 해보겠습니다.

코드는 다음과 같습니다.

import matplotlib.pyplot as plt

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


plt.plot(loss_value, 'bo', label = 'training loss')
plt.plot(val_loss_value, 'b', label = 'validation loss')
plt.xlabel('epochs')
plt.ylabel('loss')
plt.legend()
plt.show

loss graph

 

이번에는 정확도에 대해 시각화를 해보겠습니다.

코드는 다음과 같습니다.

acc = history_dict['accuracy']
val_acc = history_dict['val_accuracy']

plt.clf()
plt.plot(acc, 'bo', label = 'training accuracy')
plt.plot(val_acc, 'b', label = 'validation accuracy')
plt.xlabel('epochs')
plt.ylabel('accuracy')
plt.legend()
plt.show

plt.clf()를 사용하여 그래프를 초기화했다는 점 말고는 위의 과정과 동일합니다.

 

accuracy graph

 

모델이 무작위로 초기화 할 것이므로 시각화 결과는 조금씩 다를 수 있습니다.

 

두 개의 시각화 그래프를 살펴보면 에포크5 이후 부터 과대적합이 이뤄지고 있음을 알 수 있습니다.

(검증 데이터에 대한 손실이 증가하고, 정확도는 감소한다 = 과대적합이 이뤄진다, 해당 구간 =  : 에포크 5이상인 구간)

 

적합한 에포크 값을 찾았으므로 모델을 처음부터 다시 학습하겠습니다.

과대적합을 막는 여러가지 기술이 있지만 여기서는 다루지 않겠습니다.

새로이 모델을 학습하는 데에 달라진 것은 epoch를 4로 설정하는 것 뿐입니다.

또한 바로 테스트 데이터로 모델의 성능을 측정해보도록 하겠습니다.

 

코드는 다음과 같습니다.

model2 = keras.Sequential([
    layers.Dense(16, activation = 'relu'),
    layers.Dense(16, activation = 'relu'),
    layers.Dense(1, activation = 'sigmoid')
])

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

model2.fit(partial_x_train, partial_y_train, epochs = 4, batch_size = 512, validation_data = (x_val, y_val))
results = model2.evaluate(x_test, y_test)

results를 출력하면 첫번째 인덱스는 손실, 두번째 인덱스는 정확도 값을 출력합니다.

 

 

-훈련된 모델로 새로운 값 예측하기

predict()메소드를 활용하여 어떤 리뷰가 긍정일지 그 확률을 예측할 수 있습니다.

우리가 관심이 있는, 즉 예측의 대상이 되는 값이 긍정인 리뷰이므로 레이블을 1로 뒀습니다.

따라서 결과적으로 나오는 값의 확률은 그 리뷰가 긍정일 확률임을 알 수 있습니다.

코드는 다음과 같습니다.

model2.predict(x_test)

 

값을 확인해보면 긍정일 확률이 0.99 또는 0.01로 어느정도 확신을 가지는 리뷰도 있지만 

반대로 0.6 또는 0.4d와 같은 확률을 가지는 것으로 보아 확신을 가지지 못하는 리뷰도 있음을 알 수 있습니다.

 

이번 포스트에서는 이진 분류 문제를 다뤘습니다.

단어 시퀀스를 입력 데이터로 신경망에 주입하기 위해서는 텐서로 주입해야합니다.

이를 위해 이진 벡터로 인코딩하여 데이터를 전처리해주었습니다.

또한 이진 분류에서 가장 많이 쓰이는 binary_crossentropy를 사용했습니다.