Загрузка текста

Смотрите на TensorFlow.org Запустите в Google Colab Изучайте код на GitHub Скачайте ноутбук

Это руководство предлагает пример того как использовать tf.data.TextLineDataset для загрузки примеров из текстовых файлов. TextLineDataset предназначен для создания набора данных из текстового файла, в которых каждый пример это строка текста в исходном файле. Это потенциально полезно для любой текстовых данных которые изначально строковые (например, поэзия или логи ошибок).

В этом руководстве мы будем использовать три разных английских перевода одного и того же текста - "Илиады" Гомера, и обучим модель определять переводчика на основе одной строки из текста.

Установка

import tensorflow as tf

import tensorflow_datasets as tfds
import os

Тексты трех переводов выполнили:

Текстовые файлы использованные в этом руководстве были подвергнуты некоторым типичным задачам предварительной обработки, в основном удаление материала - шапка и футер документа, нумерация строк, заголовки глав. Скачайте эти файлы локально.

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
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/cowper.txt
819200/815980 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/derby.txt
811008/809730 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/butler.txt
811008/807992 [==============================] - 0s 0us/step

'/home/kbuilder/.keras/datasets'

Загрузите текст в датасеты

Переберите файлы, загружая каждый в свой датасет.

Каждый пример нужно пометить индивидуально, так что используйте 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.take и print, чтобы посмотреть как выглядят пары (example, label). Свойство numpy показывает каждое значение тензора.

for ex in all_labeled_data.take(5):
  print(ex)
(<tf.Tensor: shape=(), dtype=string, numpy=b'redoubtable daughter, the Trito-born, went about among the host of the'>, <tf.Tensor: shape=(), dtype=int64, numpy=2>)
(<tf.Tensor: shape=(), dtype=string, numpy=b'Thou hast defrauded me of great renown,'>, <tf.Tensor: shape=(), dtype=int64, numpy=0>)
(<tf.Tensor: shape=(), dtype=string, numpy=b'while dark death and the strong hand of fate gripped him and closed his'>, <tf.Tensor: shape=(), dtype=int64, numpy=2>)
(<tf.Tensor: shape=(), dtype=string, numpy=b'Who with heart-freshening joy the prize received.'>, <tf.Tensor: shape=(), dtype=int64, numpy=0>)
(<tf.Tensor: shape=(), dtype=string, numpy=b'upon the Achaeans.'>, <tf.Tensor: shape=(), dtype=int64, numpy=2>)

Закодируйте текстовые строки числами

Модели машинного обучения работают с числами, не словами, так что строковые значения необходимо конвертировать в списки с числами. Чтобы сделать это поставьте в соответствие каждому слову свое число.

Создайте словарь

Сперва создайте словарь токенизировав текст в коллекцию отдельных отличающихся слов. Есть несколько способов сделать это и в 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
17178

Закодируйте примеры

Создайте кодировщик передав vocabulary_set в tfds.features.text.TokenTextEncoder. Метод encode кодировщика берет строку текста и возвращает список чисел.

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

Вы можете посмотреть одну строку чтобы увидеть как выглядит результат работы кодировщика.

example_text = next(iter(all_labeled_data))[0].numpy()
print(example_text)
b'redoubtable daughter, the Trito-born, went about among the host of the'

encoded_example = encoder.encode(example_text)
print(encoded_example)
[12425, 364, 12763, 15941, 6350, 12860, 5128, 11837, 12763, 16967, 7576, 12763]

Теперь запустите кодировщик на датасете обернув его в tf.py_function и передав в метод map датасета.

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

def encode_map_fn(text, label):
  # py_func doesn't set the shape of the returned tensors.
  encoded_text, label = tf.py_function(encode, 
                                       inp=[text, label], 
                                       Tout=(tf.int64, tf.int64))

  # `tf.data.Datasets` work best if all components have a shape set
  #  so set the shapes manually: 
  encoded_text.set_shape([None])
  label.set_shape([])

  return encoded_text, label


all_encoded_data = all_labeled_data.map(encode_map_fn)

Разбейте датасет на тестовую и обучающую выборки

Используйте tf.data.Dataset.take и tf.data.Dataset.skip чтобы создать небольшой тестовый и большой обучающий датасеты.

Перед передачей в модель датасет должны быть разбиты на пакеты. Обычно количество записей в пакете и их размерность должно быть одинаковые.

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

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

Сейчас, test_data и train_data являются не коллекциями пар (example, label), а коллекциями пакетов. Каждый пакет это пара вида (много примеров, много меток) представленная в виде массивов.

Чтобы проиллюстрировать:

sample_text, sample_labels = next(iter(test_data))

sample_text[0], sample_labels[0]
(<tf.Tensor: shape=(16,), dtype=int64, numpy=
 array([12425,   364, 12763, 15941,  6350, 12860,  5128, 11837, 12763,
        16967,  7576, 12763,     0,     0,     0,     0])>,
 <tf.Tensor: shape=(), dtype=int64, numpy=2>)

Так как мы ввели новую кодировку токенов (нуль использовался для заполнения), размер словаря увеличился на единицу.

vocab_size += 1

Постройте модель

model = tf.keras.Sequential()

Первый слой конвертирует целочисленные представления в плотные векторные вложения. См. руководство Вложения слов для подробностей.

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

Следующий слой является Long Short-Term Memory слоем, который позволяет моедли понять слова в контексте других слов. Двунаправленная обертка LSTM позволяет ей выучить взаимодействие элементов как с предыдущими так и с последующими элементами.

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

И наконец у нас есть серии из одной и более плотно связанных слоев, последний из которых - выходной слой. Выходной слой генерирует вероятность для всех меток. Та у которой большая вероятность и является предсказанием модели для этого примера.

# Один или более плотных слоев.
# Отредактируйте список в строке `for` чтобы поэкспериментировать с размером слоев.
for units in [64, 64]:
  model.add(tf.keras.layers.Dense(units, activation='relu'))

# Выходной слой. Первый аргумент - число меток.
model.add(tf.keras.layers.Dense(3, activation='softmax'))

Наконец скомпилируйте модель. Для softmax категоризационной модели используйте sparse_categorical_crossentropy в виде функции потерь. Вы можете попробовать другие оптимизаторы, но adam очень часто используемая.

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

Обучите модель

Наша модель, работающая на этих данных, дает достойные результаты (около 83%).

model.fit(train_data, epochs=3, validation_data=test_data)
Epoch 1/3
697/697 [==============================] - 9s 12ms/step - loss: 0.5092 - accuracy: 0.7536 - val_loss: 0.3785 - val_accuracy: 0.8274
Epoch 2/3
697/697 [==============================] - 8s 12ms/step - loss: 0.2961 - accuracy: 0.8687 - val_loss: 0.3617 - val_accuracy: 0.8352
Epoch 3/3
697/697 [==============================] - 8s 12ms/step - loss: 0.2301 - accuracy: 0.8975 - val_loss: 0.3876 - val_accuracy: 0.8312

<tensorflow.python.keras.callbacks.History at 0x7fd2146ffcf8>
eval_loss, eval_acc = model.evaluate(test_data)

print('\nEval loss: {:.3f}, Eval accuracy: {:.3f}'.format(eval_loss, eval_acc))
79/79 [==============================] - 1s 16ms/step - loss: 0.3876 - accuracy: 0.8312

Eval loss: 0.388, Eval accuracy: 0.831