본문 바로가기

Minding's Programming/Tensorflow tutorial

[Tensorflow Tutorial] 기본 텍스트 분류 - 영화 리뷰를 사용한 텍스트 분류

728x90
반응형

https://www.tensorflow.org/tutorials/keras/text_classification?hl=ko 

 

영화 리뷰를 사용한 텍스트 분류  |  TensorFlow Core

Note: 이 문서는 텐서플로 커뮤니티에서 번역했습니다. 커뮤니티 번역 활동의 특성상 정확한 번역과 최신 내용을 반영하기 위해 노력함에도 불구하고 공식 영문 문서의 내용과 일치하지 않을 수

www.tensorflow.org

 


  • 영화 리뷰 텍스트를 긍정 또는 부정으로 분류
  • 이진(binary) 분류 문제
  • 인터넷 영화 데이터베이스의 IMDB 데이터셋 사용 : 5만개의 영화리뷰 텍스트
  • 25000개의 훈련데이터셋, 25000개의 테스트 데이터셋
  • 긍정적 리뷰와 부정적 리뷰 개수 동일

IMDB 데이터셋 다운로드

  • 리뷰들은 미리 전처리 된 상태 : 정수 시퀸스로 변환
  • 각 정수는 어휘사전에 있는 특정 단어 의미
  • 파라미터 num_words는 훈련 데이터에서 가장 많이 등장하는 상위 단어 개수
imdb = keras.datasets.imdb

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

 

데이터 탐색

  • 데이터셋은 정수배열
  • 레이블은 0 또는 1 (0: 부정적, 1: 긍정적)
print("훈련 샘플: {}, 레이블: {}".format(len(train_data), len(train_labels)))
print(train_data[0])

>>>
훈련 샘플: 25000, 레이블: 25000
[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]
  • 영화 리뷰들은 길이가 다름
  • 신경망의 입력은 길이가 같아야 하기 때문에 추후에 문제 해결
len(train_data[0]), len(train_data[1])

>>>
(218, 189)

정수를 단어로 다시 변환하기

# 단어와 정수 인덱스를 매핑한 딕셔너리
word_index = imdb.get_word_index()

# 처음 몇 개 인덱스는 사전에 정의됨 (공백, 시작표시, Unknown, Unused)
word_index = {k:(v+3) for k,v in word_index.items()}
word_index["<PAD>"] = 0
word_index["<START>"] = 1
word_index["<UNK>"] = 2  # unknown
word_index["<UNUSED>"] = 3

reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])

def decode_review(text):
    return ' '.join([reverse_word_index.get(i, '?') for i in text])
# decode_review 함수 이용해 첫 번째 리뷰 텍스트 출력
decode_review(train_data[0])

>>>
<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 wha

데이터 준비

  • 리뷰(정수 배열)는 신경망에 주입하기 전 텐서로 변환되어야 함
  • 변환 방법
    1. One-Hot Encoding : 정수배열을 0과 1로 이루어진 벡터로 변환
      • 실수 벡터 데이터를 다룰 수 있는 Dense층을 신경망의 첫 번째 층으로 사용
      • num_words * num_reviews 크기의 행렬이 필요하기 때문에 메모리 많이 사용
    2. 정수 배열의 길이가 모두 같도록 패딩 추가
      • max_length * num_reviews 크기의 정수 텐서 만듬
      • 이런 형태의 텐서 다룰 수 있는 임베딩(embedding)층을 첫 번째 층으로 사용
  • 두 번째 방식 사용 : pad_sequences 함수 사용해 길이 맞춰줌
# 두 번째 방식 사용
train_data = keras.preprocessing.sequence.pad_sequences(train_data,
                                                        value=word_index["<PAD>"],
                                                        padding='post',
                                                        maxlen=256)

test_data = keras.preprocessing.sequence.pad_sequences(test_data,
                                                       value=word_index["<PAD>"],
                                                       padding='post',
                                                       maxlen=256)
# 샘플 길이 확인
len(train_data[0]), len(train_data[1])

>>>
(256, 256)
# 패딩된 첫 번째 리뷰 내용 확인
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]

모델 구성

  • 모델 구조 결정
    • 모델에서 얼마나 많은 층을 사용할 것인가?
    • 각 층에서 얼마나 많은 은닉 유닛(층)을 사용할 것인가?
  • 입력크기는 영화 리뷰 데이터셋에 적용된 어휘 사전의 크기
# 입력 크기는 영화 리뷰 데이터셋에 적용된 어휘 사전의 크기입니다(10,000개의 단어)
vocab_size = 10000

model = keras.Sequential()
model.add(keras.layers.Embedding(vocab_size, 16, input_shape=(None,)))
model.add(keras.layers.GlobalAveragePooling1D())
model.add(keras.layers.Dense(16, activation='relu'))
model.add(keras.layers.Dense(1, activation='sigmoid'))

model.summary() # 모델 구조 확인

>>>
Model: "sequential_4"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding_2 (Embedding)      (None, None, 16)          160000    
_________________________________________________________________
global_average_pooling1d_2 ( (None, 16)                0         
_________________________________________________________________
dense_8 (Dense)              (None, 16)                272       
_________________________________________________________________
dense_9 (Dense)              (None, 1)                 17        
=================================================================
Total params: 160,289
Trainable params: 160,289
Non-trainable params: 0
_________________________________________________________________
  1. Embedding 층
    • 정수로 인코딩된 단어를 입력받고 각 단어 인덱스에 해당하는 임베딩 벡터를 찾음
    • 벡터는 모델이 훈련되면서 학습됨
    • 벡터는 출력 배열에 새로운 차원으로 추가 (최종차원 (batch, sequence, embedding))
  2. GlobalAveragePooling1D 층
    • sequence 차원에 대해 평균 계산하여 각 샘플에 대해 고정된 길이 출력벡터 반환
    • 길이가 다른 입력을 다루는 가장 간단한 방법
  3. 16개의 은닉층을 가진 완전연결층(Dense) 통과
  4. 출력노드를 가진 완전연결층
    • sigmoid 활성화 함수 사용하여 0과 1 사이의 실수 출력 (확률)

손실함수와 옵티마이저

model.compile(optimizer='adam',
              loss='binary_crossentropy', # 이진분류에 적합한 손실 함수 (정답타깃분포 - 예측 분포 사이의 거리 측정)
              metrics=['accuracy'])

검증세트 만들기

# 검증세트로 앞 부분 10000개 분리 (보통은 랜덤으로 분리함)

x_val = train_data[:10000]
partial_x_train = train_data[10000:]

y_val = train_labels[:10000]
partial_y_train = train_labels[10000:]

모델 훈련

  • 512개의 샘플로 이루어진 미니배치에서 40번의 epoch동안 훈련
  • x_train, y_train 텐서에 있는 모든 샘플에 대해 40번 반복
  • 검증세트에서 모델의 손실과 정확도 모니터링
history = model.fit(partial_x_train,
                    partial_y_train,
                    epochs=40,
                    batch_size=512,
                    validation_data=(x_val, y_val),
                    verbose=1)
                    
>>>
Epoch 39/40
30/30 [==============================] - 0s 13ms/step - loss: 0.0941 - accuracy: 0.9752 - val_loss: 0.3085 - val_accuracy: 0.8831
Epoch 40/40
30/30 [==============================] - 0s 15ms/step - loss: 0.0902 - accuracy: 0.9775 - val_loss: 0.3116 - val_accuracy: 0.8828

모델 평가

  • 손실과 정확도
results = model.evaluate(test_data,  test_labels, verbose=2)

print(results)

>>>
782/782 - 1s - loss: 0.3321 - accuracy: 0.8728
[0.3320859670639038, 0.8727999925613403]

정확도와 손실 그래프 그리기

  • History 객체에 훈련동안 일어난 정보가 담긴 dictionary 들어 있음
history_dict = history.history
history_dict.keys()

>>>
dict_keys(['loss', 'accuracy', 'val_loss', 'val_accuracy'])
acc = history_dict['accuracy']
val_acc = history_dict['val_accuracy']
loss = history_dict['loss']
val_loss = history_dict['val_loss']

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

# "bo"는 "파란색 점"입니다
plt.plot(epochs, loss, 'bo', label='Training loss')
# b는 "파란 실선"입니다
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()   # 그림을 초기화합니다

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

  • 검증 손실과 정확도에서 약 15~20번째 epoch부터 과대적합 현상 보임
  • 과대적합을 막기 위해 20번째 epoch 근처에서 훈련 멈출 수 있음
  • callback을 사용하여 자동으로 훈련 멈출 수 있음

케라스와 텐서플로 허브를 사용한 영화 리뷰 텍스트 분류하기

  • TF Hub와 Keras 사용한 기초적인 전이학습(transfer learning)

 

728x90