Написать пост

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

Аватар Богдан Федоренко

В статье подробно описывается пример создания нейронной сети для классификации текста при помощи TensorFlow. Материал понятен даже без спецподготовки.

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

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

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

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

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

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

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

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

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

TensorFlow

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

tf.Graph

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

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

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

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

			import tensorflow as tf

x = tf.constant([1,3,6]) 
y = tf.constant([1,1,1])
		

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

			import tensorflow as tf

x = tf.constant([1,3,6]) 
y = tf.constant([1,1,1])

op = tf.add(x,y)
		

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

			import tensorflow as tf

my_graph = tf.Graph()

with my_graph.as_default():
    x = tf.constant([1,3,6]) 
    y = tf.constant([1,1,1])

    op = tf.add(x,y)
		

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

tf.Session

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

			import tensorflow as tf

my_graph = tf.Graph()

with tf.Session(graph=my_graph) as sess:
    x = tf.constant([1,3,6]) 
    y = tf.constant([1,1,1])

    op = tf.add(x,y)
		

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

			import tensorflow as tf

my_graph = tf.Graph()

with tf.Session(graph=my_graph) as sess:
    x = tf.constant([1,3,6]) 
    y = tf.constant([1,1,1])

    op = tf.add(x,y)
    result = sess.run(fetches=op)
    print(result)

>>> [2 4 7]
		

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Слой вывода

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

			+-------------------+-----------+
|     категория     |  значение |
+-------------------|-----------+
|      спорт        |    001    |
|      космос       |    010    |
|   комп. графика   |    100    |
|-------------------|-----------|
		

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

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

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

			| 1.2                     0.46|
| 0.9   -> [softmax] ->   0.34|
| 0.4                     0.20|
		

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

			# Параметры сети
n_hidden_1 = 10        # количество признаков первого слоя
n_hidden_2 = 5         # количество признаков второго слоя
n_input = total_words  # Слова в словаре
n_classes = 3          # Категории

def multilayer_perceptron(input_tensor, weights, biases):
    layer_1_multiplication = tf.matmul(input_tensor, weights['h1'])
    layer_1_addition = tf.add(layer_1_multiplication, biases['b1'])
    layer_1_activation = tf.nn.relu(layer_1_addition)

# Скрытый слой с RELU активацией
    layer_2_multiplication = tf.matmul(layer_1_activation, weights['h2'])
    layer_2_addition = tf.add(layer_2_multiplication, biases['b2'])
    layer_2_activation = tf.nn.relu(layer_2_addition)

# Слой вывода с линейной активацией
    out_layer_multiplication = tf.matmul(layer_2_activation, weights['out'])
    out_layer_addition = out_layer_multiplication + biases['out']

return out_layer_addition
		

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

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

tf.Variable

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

			weights = {
    'h1': tf.Variable(tf.random_normal([n_input, n_hidden_1])),
    'h2': tf.Variable(tf.random_normal([n_hidden_1, n_hidden_2])),
    'out': tf.Variable(tf.random_normal([n_hidden_2, n_classes]))
}
biases = {
    'b1': tf.Variable(tf.random_normal([n_hidden_1])),
    'b2': tf.Variable(tf.random_normal([n_hidden_2])),
    'out': tf.Variable(tf.random_normal([n_classes]))
}
		

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

			input values: x
weights: w
bias: b
output values: z

expected values: expected
		

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

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

			# Конструирование модели
prediction = multilayer_perceptron(input_tensor, weights, biases)

# Определение потерь
entropy_loss = tf.nn.softmax_cross_entropy_with_logits(logits=prediction, labels=output_tensor)
loss = tf.reduce_mean(entropy_loss)
		

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

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

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

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

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

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

			learning_rate = 0.001

# Конструирование модели
prediction = multilayer_perceptron(input_tensor, weights, biases)

# Определение потери
entropy_loss = tf.nn.softmax_cross_entropy_with_logits(logits=prediction, labels=output_tensor)
loss = tf.reduce_mean(entropy_loss)

optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(loss)
		

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

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

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

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

			import numpy as np    #numpy — это пакет для научных вычислений
from collections import Counter

vocab = Counter()

text = "Hi from Brazil"

#Получение всех слов
for word in text.split(' '):
    vocab[word]+=1
        
#Конвертация слов в индексы
def get_word_2_index(vocab):
    word2index = {}
    for i,word in enumerate(vocab):
        word2index[word] = i
        
    return word2index

#Теперь у нас есть индекс
word2index = get_word_2_index(vocab)

total_words = len(vocab)

#Создание массива NumPy (наша матрица)
matrix = np.zeros((total_words),dtype=float)

#Заполнение значений
for word in text.split():
    matrix[word2index[word]] += 1

print(matrix)

>>> [ 1.  1.  1.]
		

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

			matrix = np.zeros((total_words),dtype=float)

text = "Hi"

for word in text.split():
    matrix[word2index[word.lower()]] += 1

print(matrix)

>>> [ 1.  0.  0.]
		

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

			y = np.zeros((3),dtype=float)

if category == 0:
    y[0] = 1.        # [ 1.  0.  0.]
elif category == 1:
    y[1] = 1.        # [ 0.  1.  0.]
else:
     y[2] = 1.       # [ 0.  0.  1.]
		

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

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

Набор данных

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

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

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

			from sklearn.datasets import fetch_20newsgroups

categories = ["comp.graphics","sci.space","rec.sport.baseball"]

newsgroups_train = fetch_20newsgroups(subset='train', categories=categories)
newsgroups_test = fetch_20newsgroups(subset='test', categories=categories)
		

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

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

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

			tf.Session.run(fetches, feed_dict=None, options=None, run_metadata=None)
		

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

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

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

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

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

			n_input = total_words # Слова в словаре
n_classes = 3         # Категории

input_tensor = tf.placeholder(tf.float32,[None, n_input],name="input")
output_tensor = tf.placeholder(tf.float32,[None, n_classes],name="output")
		

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

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

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

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

			training_epochs = 10
# Запуск графа
with tf.Session() as sess:
    sess.run(init) #инициализация нормальным распределением

    # Тренировочный цикл
    for epoch in range(training_epochs):
        avg_cost = 0.
        total_batch = int(len(newsgroups_train.data)/batch_size)
        # Цикл по всем блокам
        for i in range(total_batch):
            batch_x,batch_y = get_batch(newsgroups_train,i,batch_size)
            # Запустим оптимизацию
            c,_ = sess.run([loss,optimizer], feed_dict={input_tensor: batch_x, output_tensor:batch_y})
		

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

			# Тестирование модели
    index_prediction = tf.argmax(prediction, 1)
    index_correct = tf.argmax(output_tensor, 1)
    correct_prediction = tf.equal(index_prediction, index_correct)
   
 # Вычисление точности
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
    total_test_data = len(newsgroups_test.target)
    batch_x_test,batch_y_test = get_batch(newsgroups_test,0,total_test_data)
    print("Accuracy:", accuracy.eval({input_tensor: batch_x_test, output_tensor: batch_y_test}))

>>> Epoch: 0001 loss= 1133.908114347
    Epoch: 0002 loss= 329.093700409
    Epoch: 0003 loss= 111.876660109
    Epoch: 0004 loss= 72.552971845
    Epoch: 0005 loss= 16.673050320
    Epoch: 0006 loss= 16.481995190
    Epoch: 0007 loss= 4.848220565
    Epoch: 0008 loss= 0.759822878
    Epoch: 0009 loss= 0.000000000
    Epoch: 0010 loss= 0.079848485
    Optimization Finished!
    Accuracy: 0.75
		

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

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

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

Следите за новыми постами
Следите за новыми постами по любимым темам
56К открытий56К показов