Общий взгляд на машинное обучение: классификация текста с помощью нейронных сетей и TensorFlow

Рассказывает Дебра Мескита


Разработчики часто говорят: «Хотите изучать машинное обучение, для начала узнайте, как работают алгоритмы». Но мой опыт показывает обратное.

Я считаю, сначала важно увидеть общую картину: как работают приложения. Как только вы поймете это, станет гораздо проще изучать функционирование алгоритмов.

Как же развить интуицию и понять все нюансы машинного обучения? Хороший вариант — создать модель такого обучения. Если опыта написания алгоритмов с нуля пока нет, можно использовать библиотеку, в которой они уже реализованы. Например, TensorFlow.

В этой статье мы создадим модель машинного обучения для классификации текста по категориям и обсудим следующие темы:

  1. Как работает TensorFlow?
  2. Что такое машинно-обучаемые модели?
  3. Что такое нейронная сеть?
  4. Как нейронная сеть обучается?
  5. Как управлять данными и передавать их на ввод нейронной сети?
  6. Как запускать модель и получать результаты прогнозирования?

Прим. пер. Вы можете найти полезные материалы по теме нейронных сетей в нашей подборке. И взглянуть на шпаргалки по их типам: первую и вторую части.

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

TensorFlow

TensorFlow — это библиотека с открытым кодом для машинного обучения, созданная Google. Название помогает понять, как с ней работать: тензоры являются многомерными массивами, которые текут (flow) через узлы графа.

tf.Graph

Каждое вычисление в TensorFlow представляется как граф потока данных. У него есть два элемента:

  1. Набор tf.Operation, который представляет единицы вычислений.
  2. Набор tf.Tensor, который представляет единицы данных.

Чтобы увидеть, как это все работает, создадим следующий граф потока данных:

Граф, выполняющий x+y

Определим x = [1, 3, 6] и y = [1, 1, 1]. Так как для представления единиц данных граф работает с tf.Tensor, создадим тензоры-константы:

Теперь определим единицу операции:

У нас есть все элементы графа. Пора его построить:

Так рабочий процесс TensorFlow и устроен: сначала вы создаете граф, а потом выполняете вычисления, действительно «запуская» узлы графа с операциями. Для этого необходимо создать tf.Session.

tf.Session

Объект tf.Session инкапсулирует среду, в которой выполняются объекты Operation и оцениваются объекты Tensor (по крайней мере, так сказано в документации). Чтобы сделать это, необходимо определить, какой граф мы будем использовать в сессии:

Для выполнения операций используется метод tf.Session.run(). Он совершает один «шаг» вычислений TensorFlow, запуская необходимый фрагмент графа для выполнения каждого объекта Operation и оценки каждого Tensor, переданного в аргументе fetches. В нашем случае запускается шаг операции сложения:

Прогнозирующая модель

Теперь, когда вы знаете, как TensorFlow работает, надо создать прогнозирующую модель. Вкратце:

Алгоритм машинного обучения + Данные = Прогнозирующая модель

Процесс построения таков:

Процесс создания прогнозирующей модели

Как можно заметить, она состоит из алгоритма машинного обучения, «натренированного» на данных. Из них формируется модель прогнозирования, далее выдается соответствующий результат:

Рабочий процесс прогнозирования

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

  • ввод: текст;
  • результат: категория.

У нас есть тренировочный набор данных, в котором все тексты помечены (каждая метка указывает, к какой категории он принадлежит). В машинном обучении такой подход называется обучением с учителем.

Мы классифицируем данные по категориям, следовательно, это задача классификации.

Для создания модели используем нейронные сети.

Нейронные сети

Нейронная сеть представляет собой вычислительную модель (способ описания системы с использованием математического языка и его принципов). Эта система скорее самообучающаяся и натренированная, нежели явно запрограммированная.

Нейронные сети имитируют центральную нервную систему человека. У них есть соединенные узлы, которые похожи на наши нейроны:

Нейронная сеть

Первым нейронным сетевым алгоритмом был перцептрон. Его внутреннюю работу хорошо раскрывает данная статья (обратите внимание на анимации).

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

Архитектура нейронной сети

У нашей нейронной сети будет 2 скрытых слоя (надо выбрать, сколько их будет в вашей модели — это часть проектирования архитектуры). Задача каждого скрытого уровня заключается в том, чтобы превратить входные данные во что-то, что мог бы использовать слой вывода.

Первый скрытый слой

Слой ввода и первый скрытый слой

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

В слое ввода один узел соответствует слову из набора данных. Рассмотрим это чуть позже.

Как объяснено в этой статье, каждый узел (нейрон) умножается на вес, т.е. имеет значение веса. В ходе обучения нейронная сеть регулирует эти показатели, чтобы произвести правильные выходные данные. Сеть также добавляет смещение.

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

Существует много видов функции активации. Используем усеченное линейное преобразование (ReLu). Эта функция определяется следующим образом:

f(x) = max(0,x) [вывод равен x или 0 (ноль), в зависимости от того, что больше]

Примеры: если x = −1, то f(x) = 0 (ноль); если x = 0,7, то f(x) = 0,7.

Второй скрытый слой

Второй скрытый слой делает то же самое, что и первый, но теперь входными данными являются выходные данные первого слоя:

Первый и второй скрытые слои

Слой вывода

И, наконец, мы добираемся до последнего пункта — слоя вывода. Чтобы получить его результаты, будем использовать унитарное кодирование. Здесь только один бит равен единице, а все остальные — нулевые. Например, мы хотим закодировать три категории: «спорт», «космос» и «компьютерная графика»:

Получим, что число узлов вывода равно числу классов входного набора данных.

Значения слоя вывода умножаются на веса, к ним добавляется смещение, но функция активации уже другая.

Мы хотим пометить каждый текст категорией, между собой они являются взаимоисключающими, т.к. текст не может принадлежать двум категориям одновременно. Чтобы достичь цели, вместо ReLu возьмем функцию Softmax. Она преобразует вывод для каждой категории в значение между 0 и 1, а также проверяет, что сумма всех значений равна 1. Так вывод покажет нам вероятность принадлежности текста к каждой категории:

Теперь у нас есть граф потока данных нейронной сети. Если перевести все в код, то получится примерно следующее:

Обучение нейронной сети

Как показал предыдущий опыт, значения весов обновляются, пока сеть обучается. Теперь разберём процесс в среде TensorFlow.

tf.Variable

Веса и смещения хранятся в переменных tf.Variable, которые содержат состояние в графе между вызовами run(). В машинном обучении принято работать с весами и смещениями, полученными через нормальное распределение:

Запустим сеть в первый раз с весами, полученными по нормальному распределению:

Чтобы узнать, учится ли сеть, необходимо сравнить выходные значения z с ожидаемыми значениями expected. Существует много методов, как посчитать потерю loss. Так как мы работаем с задачей на классификацию, лучшим способом вычисления ошибки будет перекрестной энтропии.

Сделаем это при помощи TensorFlow, используя метод tf.nn.softmax_cross_entropy_with_logits() (функцию активации softmax), и вычислим среднюю ошибку tf.reduced_mean():

Мы хотим найти лучшие значения весов и смещений для того, чтобы минимизировать ошибки при выводе — разницу между полученным и правильным значениями. Для этого воспользуемся методом градиентного спуска. А если быть точнее, то стохастическим градиентным спуском:

TensorFlow

Градиентный спуск

Существует множество алгоритмов для вычисления градиентного спуска, мы будем использовать адаптивную оценку моментов. Передадим значение learning_rate, которое определяет шаг значений для нахождения лучшего веса.

Метод tf.train.AdamOptimizer(learning_rate).minimize(loss) — это синтаксический сахар, который делает две вещи:

  1. compute_gradients(loss, ).
  2. apply_gradients().

Обновляя значения всех tf.Variables, нам не приходится передавать список переменных. И теперь есть код для тренировки сети:

Управление данными

Набором данных, который мы будем использовать (кстати, в нем содержится множество текстов на английском) надо управлять, чтобы передать нейронной сети. Для реализации необходимо сделать две вещи:

  1. Создать индекс для каждого слова.
  2. Создать матрицу для каждого текста, где значение равно единице, если слово есть в тексте, и нулю в противном случае.

Давайте взглянем на код, чтобы понять процесс:

В примере выше из текста «Hi from Brazil» получилась матрица [ 1. 1. 1.]. Что, если бы текст был просто «Hi»:

То же самое сделаем с метками (категориями текстов), но теперь будем использовать унитарное кодирование:

Запуск графа и получение результата

Настало время самой ожидаемой части — получения результатов от модели. Для начала давайте взглянем поближе на входной набор данных.

Набор данных

Будем использовать 20 Newsgroups — набор данных, содержащий 18 тысяч постов на 20 тем. Чтобы загрузить такой объем информации, воспользуемся библиотекой scikit-learn. Возьмем только 3 категории: comp.graphics, sci.space и rec.sport.baseball. Scikit-learn работает с двумя подмножествами: одним для обучения, вторым для проверки.

Совет: никогда не смотрите на тестовые данные, это может повлиять на ваши решения при создании модели. Не нужно создавать образец для прогнозирования каких-то конкретных данных, важно сделать модель с хорошим обобщением.

Вот так вы загрузите свои наборы данных:

Тренировка модели

В терминологии нейронных сетей одна эпоха = один передний проход (получение значений вывода) и один обратный проход (обновление весов) всех тренировочных примеров.

Помните метод tf.Session.run()? Рассмотрим его подробнее:

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

Параметр feed_dict указывает, куда мы передаем данные по каждому этапу работы. Также необходимо определить tf.placeholders, чтобы наполнить feed_dict.

Как гласит документация TensorFlow:

Плейсхолдер нужен исключительно в качестве цели наполнения. Он не инициализирован и не содержит данных.

Поэтому мы определим заполнители так:

Разобьем тренировочные данные на блоки согласно официальному сайту:

Если вы используете заполнитель для ввода, можно определять переменное измерение блока с помощью tf.placeholder(..., shape=[None,...]). Элемент None указывает на измерение с переменным размером.

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

Функция get_batches() показывает количество текстов вместе с размером блока. Теперь можно запустить модель:

У нас есть натренированная модель. Чтобы протестировать ее, необходимо создать элементы графа. Будем измерять точность, надо получить индексы спрогнозированного значения и индекс правильного значения, потому что мы используем унитарное кодирование. Затем проверить, равны ли они, и вычислить среднее для всего тестового набора данных:

Вот и все! Мы создали модель с использованием нейронной сети для классификации текстов по категориям. Поздравляю!

Конечный код вы можете посмотреть на странице GitHub.

Примечание: Измените значения, которые мы определили, чтобы посмотреть, как они влияют на тренировочное время и точность модели.

Перевод статьи «Big Picture Machine Learning: Classifying Text with Neural Networks and TensorFlow»