Tensorflow

케라스 예제: IMDB 텍스트 감정 분류

카카오그래놀라 2020. 11. 11. 00:45

케라스: 가공하지 않은 텍스트로부터 감정분류

Text classification from scratch

 

Text sentiment classification

 

Authors: Mark Omernick, Francois Chollet
Date created: 2019/11/06
Last modified: 2020/05/17
Description: Text sentiment classification starting from raw text files.

- Keras

- Github

- Colab

 

소개

Introduction

이 예제는 가공하지 않은 텍스트로부터 텍스트 분류를 수행하는 방법을 보여줍니다. 가공되지 않은 IMDB 감정 분류 데이터 셋을 어떻게 처리하는지 보여줍니다. 단어 분할 및 색인 생성을 위해 TextVectorization 레이어를 사용하겠습니다.

This example shows how to do text classification starting from raw text (as a set of text files on disk). We demonstrate the workflow on the IMDB sentiment classification dataset (unprocessed version). We use the TextVectorization layer for word splitting & indexing.

 

라이브러리 설치

Setup
import tensorflow as tf
import numpy as np

 

Load the data: IMDB movie review sentiment classification

데이터를 다운로드 받고, 경로를 확인합시다.

Let's download the data and inspect its structure.

# curl: 리눅스에서 다운로드시 사용하는 명령어
# tar: 리눅스에서 압축파일과 관련된 명령어
# 윈도우 유저는 아래의 URL을 브라우저에 입력시 다운로드가 가능합니다.
# 각자의 경로에 데이터를 다운, 압축해제해주세요.

!curl -O https://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz
!tar -xf aclImdb_v1.tar.gz

 

train 폴더와 test폴더를 확인할 수 있습니다.

The aclImdb folder contains a train and test subfolder:

!ls aclImdb
!ls aclImdb/test
!ls aclImdb/train

 

Train 폴더 밑에 pos폴더(긍정적인 말이 담긴 폴더)와 neg폴더(부정적인 말이 담긴 폴더)가 있습니다.

The aclImdb/train/pos and aclImdb/train/neg folders contain text files, each of which represents on review (either positive or negative):

!cat aclImdb/train/pos/6248_7.txt

 

우리는 이번 예제에서 긍정, 부정 분류를 할 것이기에 나머지 폴더는 지웁시다.

We are only interested in the pos and neg subfolders, so let's delete the rest:

!rm -r aclImdb/train/unsup

 

 tf.keras.preprocessing.text_dataset_from_directory를 활용하여 폴더별로 정리된 텍스트 파일들을 label처리가 된 tf.data.Dataset로 불러올 수 있습니다.

 위 utility를 활용하여, training 셋, validation 셋, test 셋을 만들어봅시다. train 셋과 valid 셋은 train폴더 안에 담긴 파일들을 각각 80%, 20%로 분리하여 만들겠습니다.

 Test 셋을 사용해서는 안되는 것을 Validation 데이터셋을 통해서 할 수 있습니다. 예를 들어 각종 하이퍼 파라미터 튜닝을 할 수 있습니다.

 그러나 모델을 실제로 사용하기 전에, 반드시 사용가능한 모든 training data를 사용하여(validation data를 포함하여) 재학습시켜야 합니다. 모델의 성능을 극대화할 수 있습니다.

Validation split이나 subset을 만드는 경우, 반드시 랜덤 시드 값을 고정하거나, shuffle을 False로 설정해야 합니다. 이래야 Train set과 Validation Set간의 교차가 발생하지 않습니다.

You can use the utility tf.keras.preprocessing.text_dataset_from_directory to generate a labeled tf.data.Dataset object from a set of text files on disk filed into class-specific folders.

Let's use it to generate the training, validation, and test datasets. The validation and training datasets are generated from two subsets of the train directory, with 20% of samples going to the validation dataset and 80% going to the training dataset.

Having a validation dataset in addition to the test dataset is useful for tuning hyperparameters, such as the model architecture, for which the test dataset should not be used.

Before putting the model out into the real world however, it should be retrained using all available training data (without creating a validation dataset), so its performance is maximized.

When using the validation_split & subset arguments, make sure to either specify a random seed, or to pass shuffle=False, so that the validation & training splits you get have no overlap.

batch_size = 32

raw_train_ds = tf.keras.preprocessing.text_dataset_from_directory(
    "aclImdb/train",
    batch_size=batch_size, # 배치 사이즈 지정
    validation_split=0.2, # validation set의 크기 지정
    subset="training", # training 데이터셋으로 지정
    seed=1337, # 시드값을 고정합니다.
)
raw_val_ds = tf.keras.preprocessing.text_dataset_from_directory(
    "aclImdb/train",
    batch_size=batch_size,
    validation_split=0.2, # validation set의 크기 지정
    subset="validation", # validation 데이터셋으로 지정
    seed=1337, # 시드값을 위와 같이 설정합니다.
)
raw_test_ds = tf.keras.preprocessing.text_dataset_from_directory(
    "aclImdb/test", batch_size=batch_size
)

print(
    "Number of batches in raw_train_ds: %d"
    % tf.data.experimental.cardinality(raw_train_ds)
)
print(
    "Number of batches in raw_val_ds: %d" % tf.data.experimental.cardinality(raw_val_ds)
)
print(
    "Number of batches in raw_test_ds: %d"
    % tf.data.experimental.cardinality(raw_test_ds)
)

# Found 25000 files belonging to 2 classes.
# Using 20000 files for training.
# Found 25000 files belonging to 2 classes.
# Using 5000 files for validation.
# Found 25000 files belonging to 2 classes.
# Number of batches in raw_train_ds: 625
# Number of batches in raw_val_ds: 157
# Number of batches in raw_test_ds: 782

 

Let's preview a few samples:

# 정규화 및 토큰화가 예상대로 작동하는지 확인하려면 raw 데이터를 살펴 보는 것이 중요합니다.
# train셋에서 몇 가지 예를 들어보고 이를 살펴볼 수 있습니다.
# 이것은 eager execution이 빛을 발하는 곳 중 하나입니다.
# Session/Graph context에서 평가할 필요없이 .numpy ()를 사용하여 이러한 텐서를 평가할 수 있습니다.

# 만들어진 tf.data.Dataset에서 take 메서드를 이용해 배치 하나를 가져와봅시다.
for text_batch, label_batch in raw_train_ds.take(1):
	# 위에서 1 배치 안에 32개를 담아놨는데, 우리는 5개만 확인해봅시다.
    for i in range(5):
        print(text_batch.numpy()[i])
        print(label_batch.numpy()[i])


# b'I\'ve seen tons of science fiction from the 70s; some horrendously bad, ....
# 1
# b'First than anything, I\'m not going to praise I\xc3\xb1arritu\'s short film, ...
# 1
# b'Blood Castle (aka Scream of the Demon Lover, Altar of Blood, Ivanna--the best, ...
# 1
# b"I was talked into watching this movie by a friend who blubbered on about what a ...
# 0
# b"Michelle Rodriguez is the defining actress who could be the charging force ....
# 1

# 1 => 0은 부정, 1은 긍정

 

Prepare the data

`<br />` 태그를 지웁시다.

from tensorflow.keras.layers.experimental.preprocessing import TextVectorization
import string
import re

# 위의 데이터를 살펴보면 원시 텍스트에 '<br />'형식의 HTML 중단 태그가 포함되어 있음을 알 수 있습니다.
# 이러한 태그는 default standardizer(HTML 태그를 제거하지 않음)에 의해 제거되지 않습니다.
# 이 때문에 사용자 스스로 standardization 기능을 만들어야합니다.
def custom_standardization(input_data):
    lowercase = tf.strings.lower(input_data)
    stripped_html = tf.strings.regex_replace(lowercase, "<br />", " ")
    return tf.strings.regex_replace(
        stripped_html, "[%s]" % re.escape(string.punctuation), ""
    )


# 모델 상수들(Constants).
max_features = 20000
embedding_dim = 128
sequence_length = 500

# 이제 사용자 지정 표준화가 완료되었으므로 텍스트 벡터화 레이어를 인스턴스화 할 수 있습니다.
# 이 레이어를 사용하여 문자열을 정규화, 분할 및
# 정수로 매핑하므로 'output_mode'를 'int'로 설정합니다.
# default split function와 위에서 정의한 사용자 지정 표준화를 사용하고 있습니다.
# 모델의 후반부에서 CNN이 비정형 시퀀스를 지원하지 않기 때문에 
# 최대 시퀀스 길이도 명시적으로 설정했습니다.
vectorize_layer = TextVectorization(
    standardize=custom_standardization,
    max_tokens=max_features,
    output_mode="int",
    output_sequence_length=sequence_length,
)

# Now that the vocab layer has been created, call `adapt` on a text-only
# dataset to create the vocabulary. You don't have to batch, but for very large
# datasets this means you're not keeping spare copies of the dataset in memory.

# 이제 어휘 레이어가 생성되었으므로 텍스트 전용 데이터 세트에서 'adapt'를 호출하여 어휘를 생성합니다.
# 일괄 처리 할 필요는 없지만 매우 큰 데이터 세트의 경우 
# 이는 데이터 세트의 예비 사본을 메모리에 보관하지 않음을 의미합니다.

# Let's make a text-only dataset (no labels):
text_ds = raw_train_ds.map(lambda x, y: x)
# Let's call `adapt`:
vectorize_layer.adapt(text_ds)

 

데이터를 벡터화하는 2가지 방법

텍스트 벡터화 레이어를 사용하는 2가지 방법이 있습니다.

옵션 1: 모델의 일부분으로 만들기. 아래와 같이 raw 문자열을 처리하는 모델을 만드는 방법이 있습니다.

Option 1: Make it part of the model, so as to obtain a model that processes raw strings, like this:

text_input = tf.keras.Input(shape=(1,), dtype=tf.string, name='text')
x = vectorize_layer(text_input)
x = layers.Embedding(max_features + 1, embedding_dim)(x)
...

 

옵션 2: 텍스트 데이터 셋에 적용하여 단어 인덱스 데이터셋을 얻는 방법이 있습니다. 이후, 모델에 정수(int) 시퀀스를 모델에 입력합니다.

Option 2: Apply it to the text dataset to obtain a dataset of word indices, then feed it into a model that expects integer sequences as inputs.

 

두 옵션간의 결과에서의 차이는 없습니다.

 하지만 두 옵션 사이의 중요한 차이점은 옵션 2를 사용하면 GPU에서 훈련 할 때 비동기 CPU 처리 및 데이터 버퍼링을 수행 할 수 있다는 점입니다. 따라서 GPU에서 모델을 학습하는 경우, 2번째 옵션을 사용하여 최상의 성능을 얻을 수 있습니다. 아래에서도 옵션 2로 진행하겠습니다.
 모델을 제품으로 내보내려면 위의 옵션 1에 대한 코드와 같이 raw 문자열을 입력으로 받아들이는 모델을 만들어야합니다.(end-to-end 모델) 이는 모델 훈련 후에 할 수 있습니다. 마지막 섹션에서 해당 작업을 수행합니다.

An important difference between the two is that option 2 enables you to do asynchronous CPU processing and buffering of your data when training on GPU. So if you're training the model on GPU, you probably want to go with this option to get the best performance. This is what we will do below.

If we were to export our model to production, we'd ship a model that accepts raw strings as input, like in the code snippet for option 1 above. This can be done after training. We do this in the last section.

def vectorize_text(text, label):
    text = tf.expand_dims(text, -1)
    return vectorize_layer(text), label


# Vectorize the data.
train_ds = raw_train_ds.map(vectorize_text)
val_ds = raw_val_ds.map(vectorize_text)
test_ds = raw_test_ds.map(vectorize_text)

# Do async prefetching / buffering of the data for best performance on GPU.
train_ds = train_ds.cache().prefetch(buffer_size=10)
val_ds = val_ds.cache().prefetch(buffer_size=10)
test_ds = test_ds.cache().prefetch(buffer_size=10)

 

Build a model

임베딩 레이어로부터 시작하는 simple Conv1D 모델을 만들어 봅시다.

from tensorflow.keras import layers

# voca(어휘)인덱스에 대한 integer input
inputs = tf.keras.Input(shape=(None,), dtype="int64")

# 다음으로, 우리는 해당 vocab 인덱스를 차원 공간에 매핑하는 레이어를 추가합니다: Embedding, 'embedding_dim'
x = layers.Embedding(max_features, embedding_dim)(inputs)
x = layers.Dropout(0.5)(x)

# Conv1D + global max pooling
x = layers.Conv1D(128, 7, padding="valid", activation="relu", strides=3)(x)
x = layers.Conv1D(128, 7, padding="valid", activation="relu", strides=3)(x)
x = layers.GlobalMaxPooling1D()(x)

# We add a vanilla hidden layer
# (vanilla hidden layer: 별 의미 없습니다. ConvD, LSTM 이 아닌 그냥 hidden layer라는 의미입니다.)
x = layers.Dense(128, activation="relu")(x)
x = layers.Dropout(0.5)(x)

# 시그모이드를 사용하여 단일 unit output layer로 사영합니다.
predictions = layers.Dense(1, activation="sigmoid", name="predictions")(x)

model = tf.keras.Model(inputs, predictions)

# binary crossentropy loss와 adam 옵티마이저를 사용하여 model을 compile합니다.
model.compile(loss="binary_crossentropy", optimizer="adam", metrics=["accuracy"])

 

Train the model

epochs = 3

# 모델을 훈련시키고, validation 데이터셋을 이용해 모델을 훈련진행상황을 확인해봅시다.
model.fit(train_ds, validation_data=val_ds, epochs=epochs)

 

Test 셋에 대해서 모델 평가하기

Evaluate the model on the test set
model.evaluate(test_ds)

# loss, accuracy
# [0.39986345171928406, 0.8649600148200989]

 

end-to-end model 모델 만들기

Make an end-to-end model

raw 문자열을 처리할 수있는 모델을 만드는 방법은 매우 간단합니다. 우리가 훈련한 weights를 사용하여 쉽게 새로운 모델을 만들 수 있습니다.

If you want to obtain a model capable of processing raw strings, you can simply create a new model (using the weights we just trained):

# 문자열을 입력으로 받습니다.
inputs = tf.keras.Input(shape=(1,), dtype="string")
# 문자열을 벡터화합니다.
indices = vectorize_layer(inputs)
# 벡터화된 문자열을 통해 결과를 예측합니다.
outputs = model(indices)

# end to end model
end_to_end_model = tf.keras.Model(inputs, outputs)
end_to_end_model.compile(
    loss="binary_crossentropy", optimizer="adam", metrics=["accuracy"]
)

# raw_test_ds를 
# Test it with `raw_test_ds`, which yields raw strings
end_to_end_model.evaluate(raw_test_ds)

# 예측결과: loss, accuracy
# [0.3998638987541199, 0.8649600148200989]