In [ ]:
#@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.

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

Note: Вся информация в этом разделе переведена с помощью русскоговорящего Tensorflow сообщества на общественных началах. Поскольку этот перевод не является официальным, мы не гарантируем что он на 100% аккуратен и соответствует официальной документации на английском языке. Если у вас есть предложение как исправить этот перевод, мы будем очень рады увидеть pull request в tensorflow/docs репозиторий GitHub. Если вы хотите помочь сделать документацию по Tensorflow лучше (сделать сам перевод или проверить перевод подготовленный кем-то другим), напишите нам на docs-ru@tensorflow.org list.

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

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

Установка


In [ ]:
import tensorflow as tf

import tensorflow_datasets as tfds
import os

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

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


In [ ]:
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

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

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

Каждый пример нужно пометить индивидуально, так что используйте tf.data.Dataset.map чтобы применить функцию расставляющую метки каждому элементу. Она переберет каждую запись в датасете возвращая пару (example, label).


In [ ]:
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)

Объедините эти размеченные наборы данных в один и перемешайте его.


In [ ]:
BUFFER_SIZE = 50000
BATCH_SIZE = 64
TAKE_SIZE = 5000

In [ ]:
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 показывает каждое значение тензора.


In [ ]:
for ex in all_labeled_data.take(5):
  print(ex)

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

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

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

Сперва создайте словарь токенизировав текст в коллекцию отдельных отличающихся слов. Есть несколько способов сделать это и в TensorFlow и в Python. В этом учебнике:

  1. Переберите numpy значения всех примеров.
  2. Используйте tfds.features.text.Tokenizer чтобы разбить их на токены.
  3. Соберите эти токены в множество Python чтобы избавиться от дубликатов
  4. Получите размер словаря для последующего использования.

In [ ]:
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

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

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


In [ ]:
encoder = tfds.features.text.TokenTextEncoder(vocabulary_set)

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


In [ ]:
example_text = next(iter(all_labeled_data))[0].numpy()
print(example_text)

In [ ]:
encoded_example = encoder.encode(example_text)
print(encoded_example)

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


In [ ]:
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 чтобы создать небольшой тестовый и большой обучающий датасеты.

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


In [ ]:
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), а коллекциями пакетов. Каждый пакет это пара вида (много примеров, много меток) представленная в виде массивов.

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


In [ ]:
sample_text, sample_labels = next(iter(test_data))

sample_text[0], sample_labels[0]

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


In [ ]:
vocab_size += 1

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


In [ ]:
model = tf.keras.Sequential()

Первый слой конвертирует целочисленные представления в плотные векторные вложения. См. руководство [Вложения слов] (../../tutorials/sequences/word_embeddings) для подробностей.


In [ ]:
model.add(tf.keras.layers.Embedding(vocab_size, 64))

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


In [ ]:
model.add(tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(64)))

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


In [ ]:
# Один или более плотных слоев.
# Отредактируйте список в строке `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 очень часто используемая.


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

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

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


In [ ]:
model.fit(train_data, epochs=3, validation_data=test_data)

In [ ]:
eval_loss, eval_acc = model.evaluate(test_data)

print('\nEval loss: {:.3f}, Eval accuracy: {:.3f}'.format(eval_loss, eval_acc))