[튜토리얼4] 텍스트 파일 불러오기

이번 튜토리얼에서는 텍스트 파일에서 데이터를 불러오기 위한 함수인 tf.data.TextLineDataset를 사용해 텍스트 데이터를 다중 분류하는 방법을 알아볼 것입니다.

TextLineDataset은 텍스트 파일에서 데이터셋을 생성해 각 예제가 원본 파일에서 텍스트 줄처럼 배열되도록 설계되었습니다. 이는 주로 시나 오류 로그와 같은 줄 기반의 텍스트 데이터에 유용하게 사용됩니다.

이번 튜토리얼에서는 호머의 일리아드의 같은 부분에 대한 세 가지의 다른 영어 번역문을 학습해서, 한 줄의 텍스트로 번역가를 구분하는 모델을 만들 것입니다.

import warnings
warnings.simplefilter('ignore')

import tensorflow as tf

import tensorflow_datasets as tfds
import os

목차

  1. 설정
  2. 데이터셋으로 텍스트 불러오기
  3. 텍스트 라인을 숫자로 인코딩하기
    • 3.1. 어휘 사전 만들기
    • 3.2. 인코딩
  4. 데이터셋을 테스트 및 훈련 배치(batch)로 분할하기
  5. 모델 생성하기
  6. 모델 학습시키기

1. 설정

번역문은 아래와 같은 세 가지 문헌을 사용합니다:

이번 튜토리얼에서 사용된 텍스트 파일은 문서 머리글과 바닥글, 줄 번호, 챕터 제목을 제거하는 것과 같은 몇 가지 일반적인 전처리 작업이 이미 적용되어있습니다.

3개의 텍스트 파일을 다운로드 받겠습니다.

DIRECTORY_URL = 'https://storage.googleapis.com/download.tensorflow.org/data/illiad/'
FILE_NAMES = ['cowper.txt', 'derby.txt', 'butler.txt']

for name in FILE_NAMES:
    text_dir = tf.keras.utils.get_file(name, origin=DIRECTORY_URL+name)
  
    parent_dir = os.path.dirname(text_dir)

parent_dir

2. 데이터셋으로 텍스트 불러오기

각 텍스트 데이터를 데이터셋으로 불러옵니다.

샘플마다 레이블(label)이 필요하므로 각 샘플과 레이블의 매칭을 위하여, tf.data.Dataset.map를 사용합니다.

그러면 데이터셋 내의 모든 샘플에 대한 (example, label)쌍이 반환될 것입니다.

def labeler(example, index):
    return example, tf.cast(index, tf.int64)  

labeled_data_sets = []

for i, file_name in enumerate(FILE_NAMES):
    lines_dataset = tf.data.TextLineDataset(os.path.join(parent_dir, file_name))
    labeled_dataset = lines_dataset.map(lambda ex: labeler(ex, i))
    labeled_data_sets.append(labeled_dataset)

이렇게 레이블이 표시된 데이터셋을 단일 데이터셋에 결합하고 섞어보겠습니다.

BUFFER_SIZE = 50000
BATCH_SIZE = 64
TAKE_SIZE = 5000
all_labeled_data = labeled_data_sets[0]
for labeled_dataset in labeled_data_sets[1:]:
    all_labeled_data = all_labeled_data.concatenate(labeled_dataset)
all_labeled_data = all_labeled_data.shuffle(BUFFER_SIZE, reshuffle_each_iteration=False)

tf.data.Dataset.takeprint를 이용해 (example, label)쌍의 내용을 확인할 수 있습니다. numpy는 각 텐서의 값을 보여줍니다.

for ex in all_labeled_data.take(5):
    print(ex)

3. 텍스트 라인을 숫자로 인코딩하기

머신러닝 모델은 단어가 아닌 숫자로 학습하기 때문에 문자열 값을 숫자의 목록으로 변환해야 합니다. 따라서 각 고유 단어를 고유한 정수로 매핑하는 과정이 필요합니다.

3.1 어휘 사전 만들기

먼저 텍스트를 개별 고유 단어의 집합으로 토큰화하여 어휘를 만듭니다. TensorFlow와 Python 모두 토큰화를 할 수 있는 몇 가지 방법이 있습니다. 이 튜토리얼의 경우에는 아래와 같은 방법을 사용합니다.

  1. 각 샘플들의 numpy 값을 얻습니다.
  2. 이를 토큰으로 구분하기 위해 tfds.features.text.Tokenizer를 사용합니다.
  3. 토큰들을 Python으로 모아 중복된 토큰을 제거합니다.
  4. 나중에 사용할 수 있도록 어휘 사전의 크기를 따로 저장해놓습니다.
tokenizer = tfds.features.text.Tokenizer()

vocabulary_set = set()
for text_tensor, _ in all_labeled_data:
    some_tokens = tokenizer.tokenize(text_tensor.numpy())
    vocabulary_set.update(some_tokens)

vocab_size = len(vocabulary_set)
vocab_size

3.2 인코딩

vocabulary_settfds.features.text.TokenTextEncoder에 전달하여 인코더를 만듭니다. 인코더의 encode 메서드는 문자열을 받아 정수 리스트를 반환합니다.

encoder = tfds.features.text.TokenTextEncoder(vocabulary_set)

하나의 샘플을 이용해 어떻게 출력이 되는지 확인해보겠습니다.

example_text = next(iter(all_labeled_data))[0].numpy()
print(example_text)
encoded_example = encoder.encode(example_text)
print(encoded_example)

이제 tf.py_function으로 변환하고 데이터셋의 map 메서드에 전달하는 방식으로 인코더를 실행해봅시다.

def encode(text_tensor, label):
    encoded_text = encoder.encode(text_tensor.numpy())
    return encoded_text, label

이를 데이터셋의 각 요소에 적용하기 위해서 Dataset.map을 사용합니다. Dataset.map은 그래프 모드로 실행됩니다.

그래서 위의 함수를 직접 ‘.map’할 수 없으며 tf.py_function으로 변환해야 합니다. tf.py_function은 python 함수에 일반 텐서(값과 그 값을 확인하기 위한 .numpy() 메서드를 포함)를 전달합니다.

def encode_map_fn(text, label):
    encoded_text, label = tf.py_function(encode, 
                                       inp=[text, label], 
                                       Tout=(tf.int64, tf.int64))
    encoded_text.set_shape([None])
    label.set_shape([])

    return encoded_text, label


all_encoded_data = all_labeled_data.map(encode_map_fn)

4. 데이터셋을 테스트 및 훈련 배치(batch)로 분할하기

작은 테스트 데이터셋과 그보다 큰 훈련 데이터셋을 만들기 위해 tf.data.Dataset.taketf.data.Dataset.skip를 사용할 것입니다.

모델에 학습시키기 전에 먼저 데이터셋을 배치(batch)로 만들어야 합니다. 일반적으로 배치 내부의 샘플은 동일한 크기와 모양이 되어야 하지만 현재 데이터셋은 텍스트마다 단어의 수가 다르게 구성되는 등 샘플들의 크기가 모두 같지는 않습니다. 따라서 이를 동일한 크기의 샘플들로 패딩하기 위해 배치(batch)가 아닌 tf.data.Dataset.padded_batch를 사용합니다.

train_data = all_encoded_data.skip(TAKE_SIZE).shuffle(BUFFER_SIZE)
train_data = train_data.padded_batch(BATCH_SIZE, ((None,), ()))

test_data = all_encoded_data.take(TAKE_SIZE)
test_data = test_data.padded_batch(BATCH_SIZE, ((None,), ()))

이제 test_datatrain_data는 (example, label)쌍이 아닌 배치(batch) 형태로 모인 것입니다. 또한 각 배치는 (여러 샘플, 여러 레이블) 쌍이 배열로 구성되어 있습니다.

그 예시는 다음과 같습니다:

sample_text, sample_labels = next(iter(test_data))

sample_text[0], sample_labels[0]

새로운 토큰 인코딩(패딩에 쓰이는 0)을 사용했기 때문에 어휘 사전의 크기가 1개 더 늘어났을 것입니다.

vocab_size += 1

5. 모델 생성하기

model = tf.keras.Sequential()

첫 번째 레이어는 정수형으로 인코딩된 데이터를 밀도 있는 벡터 임베딩(dense vector embeddings)으로 변환합니다.

model.add(tf.keras.layers.Embedding(vocab_size, 64))

다음 레이어는 LSTM(Long Short-Term Memory, LSTM) 계층으로, 해당 계층은 모델들이 다른 단어들과의 맥락에서 단어들을 이해할 수 있도록 해줍니다. LSTM의 양방향 래퍼(wrapper)는 LSTM이 데이터 포인트 이전 및 이후와 관련된 데이터 포인트를 학습하는 데 도움을 줍니다.

model.add(tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(64)))

마지막으로 하나 이상의 연결된 계층(densly connected layers)과 마지막 계층인 출력 계층(output layer)입니다. 출력 레이어는 모든 레이블에 대한 확률을 생성합니다. 가장 높은 확률을 가진 것이 모델이 샘플 레이블에 대해서 예측한 예측 값입니다.

#`for`문 안의 list를 내용을 수정하면 레이어의 크기에 따른 변화를 볼 수 있습니다.
for units in [64, 64]:
    model.add(tf.keras.layers.Dense(units, activation='relu'))

# 출력 레이어입니다. 첫 번째 인자는 최종적으로 출력해야 하는 레이블의 개수입니다.
model.add(tf.keras.layers.Dense(3, activation='softmax'))

마지막으로 모델을 컴파일합니다. 소프트맥스(softmax) 분류 모델의 경우 손실 함수(loss function)로 sparse_categorical_crossentropy를 사용합니다. 다른 최적화 도구도 사용해 볼 수 있지만 그 중 가장 많이 사용되는 것은 adam 옵티마이저 입니다.

model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

6. 모델 학습시키기

이 데이터로 실행되는 이 모델은 약 83%로 나쁘지 않은 결과를 도출합니다

model.fit(train_data, epochs=3, validation_data=test_data)
eval_loss, eval_acc = model.evaluate(test_data)

print('\n검증 손실: {:.3f}, 검증 정확도: {:.3f}'.format(eval_loss, eval_acc))

Copyright 2018 The TensorFlow Authors.

#@title Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.