вторник, 4 сентября 2018 г.

TensorFlow: классификация текста с отзывами к фильмам, часть 1

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

Мы будем использовать IMDB набор данных, который содержит тексты 50 000 отзывов к фильмам из Internet Movie Data Вase (IMDB). Они разделены на 25 000 отзывов для тренировки и 25 000 отзывов для тестирования. Тренировочный и тестовый наборы сбалансированы - это означает, что они содержат одинаковое количество позитивных и негативных отзывов.

Это руководство использует tf.keras - высокоуровневое API для построения и тренировки моделей в TensorFlow.

# TensorFlow и tf.keras
import tensorflow as tf
from tensorflow import keras

# NumPy библиотека
import numpy as np
print(tf.version)

1.9.0

Загрузка IMDB набора данных

IMDB набор данных поставляется в комплекте с TensorFlow. Он уже предобработан так, что отзывы (последовательности слов) конвертированы в последовательности чисел, где каждое число представляет специфичное слово в словаре.

Следующий код загружает IMDB набор данных на вашу машину (или использует кэшированную копию если вы уже загружали его):

imdb = keras.datasets.imdb

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

Downloading data from https://s3.amazonaws.com/text-datasets/imdb.npz
17465344/17464789 [==============================] - 3s 0us/step

Аргумент num_words=10000 сохраняет 10 000 наиболее часто встречающихся слов в тренировочных данных. Редкие слова отсеиваются, чтобы сохранить размер данных управляемым.

Исследование данных

Рассмотрим формат данных. Набор приходит предобработанным: каждый пример - это массив чисел, представляющих слова в отзыве фильма. Каждая метка - число со значением 0 или 1, где 0 означает негативный отзыв, а 1 - позитивный отзыв.

print("Training entries: {}, labels: {}".format(len(train_data), len(train_labels)))

Training entries: 25000, labels: 25000

Текст отзывов конвертирован в числа, где каждое число представляет специфичное слово в словаре. Вот как выглядит первый отзыв:

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]

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

len(train_data[0]), len(train_data[1])

(218, 189)

Преобразование чисел обратно в слова

Это может быть полезным - знать как преобразовывать числа обратно в текст. Здесь мы создадим вспомогательную функцию для выполнения запросов к объекту словаря, который содержит картирование чисел в строки:

# Индексы словаря связи слов и соотвествующих им чисел
word_index = imdb.get_word_index()

# Первые индексы зарезервированы
word_index = {k:(v+3) for k,v in word_index.items()}
word_index[""] = 0
word_index[""] = 1
word_index[""] = 2 # unknown
word_index[""] = 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])

Downloading data from https://s3.amazonaws.com/text-datasets/imdb_word_index.json
1646592/1641221 [==============================] - 1s 1us/step

Теперь мы можем использовать decode_review функцию, чтобы отобразить текст для первого отзыва:

decode_review(train_data[0])

" 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 is an amazing actor and now the same being director 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 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 to the two little boy's that played the of norman and paul they were just brilliant children are often left out of the 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 what they have done don't you think the whole story was so lovely because it was true and was someone's life after all that was shared with us all"

Подготовка данных

Отзывы - массивы чисел - должны быть преобразованы в тензоры прежде чем передавать их в нейронную сеть. Это преобразование может быть выполнено двумя путями:

  • Одноразовое кодирование (one-hot-encoding) массивов для преобразования их в вектора нулей и единиц. Например, последовательность [3, 5] станет вектором с 10 000 измерений, которые все равны 0, исключая индексы 3 и 5, которые будут равны 1. Затем сделаем первый слой нашей сети - Dense слой - который может обрабатывать нецельночисловые данные векторов. Этот подход затрачивает много памяти, поскольку требует матрицу размера num_words * num_reviews (количество слов * количество отзывов).
  • С другой стороны, мы можем заполнить массивы, чтобы все они имели одинаковую длину, затем создадим цельночисловой тензор формы max_length * num_reviews (максимальная длина * количество отзывов). Мы можем использовать встроенный слой (embedding layer) способный обрабатывать такую форму как первый слой сети.

В этом руководстве мы будем использовать второй подход.

Ввиду того что отзывы должны быть одинаковой длины мы используем pad_sequences функцию, чтобы стандартизировать длину:

train_data = keras.preprocessing.sequence.pad_sequences(train_data, value=word_index[""], padding='post', maxlen=256)

test_data = keras.preprocessing.sequence.pad_sequences(test_data, value=word_index[""], 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]

Продолжение этого руководства по классификации текста на TensorFlow читайте в следующем посте.