Библиотека TensorFlow

Простая рекуррентная нейронная сеть в TensorFlow

Здравствуйте и вновь добро пожаловать на занятия по теме «Рекуррентные нейронные сети на языке Python: глубокое обучение, часть 5».

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

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

Ранее я предлагал слушателям, которые интересовались реализацией в TensorFlow, попытаться разобраться в этом самим. По сути, мы сейчас как раз делаем именно то, что я говорил ранее: берём объекты, которые уже определены в TensorFlow, и связываем их, как делали это в Theano. Однако есть ряд предостережений, о которых следует помнить.

Главное, что следует помнить, заключается в следующем. Если помните мой курс по свёрточным нейронным сетям, называвшийся «Глубокое обучение на языке Python, часть 3», главное отличие между Theano и TensorFlow в том, что у них разный порядок следования размерностей. Если Theano ожидает описания изображения в виде количество_образцов x количество_цветов x ширина x высота, то в TensorFlow всё совсем по-другому: количество_образцов x ширина x высота x количество цветов. В этой лекции мы столкнёмся с той же проблемой: TensorFlow упорядочивает размерность не так, как Theano. Лично мне вариант в Theano кажется более естественным, но ваше мнение может оказаться другим.

Итак, прежде всего важно указать, что в TensorFlow встроена работа с пакетами. Ранее мы обсуждали, как реализовать пакеты в Theano, так что некоторое представление о принципах работы у вас уже есть. Обратите внимание, что X теперь является трёхмерным тензором в форме количество_образцов x длина_последовательности x количество признаков. Мы будем соблюдать это условие вне РНС-ячеек TensorFlow, но внутри у них всё работает очень необычно.

В TensorFlow последовательности на самом деле являются списками тензоров. Так, предположим, что все наши последовательности имеют длину T (до конца этой лекции будем считать, что все последовательности имеют одну и ту же длину; последовательности с переменной длиной сложнее для работы, так что мы разберёмся с ними в последующих лекциях). Так вот, пусть у нас есть N образцов в пакете, все с длиной T, и у них D признаков. Естественно представить это как массив размерностью NxTxD. В TensorFlow всё это на самом деле преобразуется в T разных списков, каждый с размерностью NxD – довольно странная вещь, как по мне.

Происходит это в три этапа. Вначале транспонируются первая и вторая размерности X, так что столбец 0 меняется местами со столбцом 1. T оказывается вынесенным:

x = tf.transpose(x, (1, 0, 2))

 На втором этапе преобразовывается форма X так, чтобы получился двухмерный массив размерности N*TxD:

x = tf.reshape(x, (T*batch_sz, D))

И на третьем этапе этот двухмерный массив делится на T разных тензоров. В TensorFlow есть для этого соответствующая функция split:

x = tf.split(0, T, x)

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

Тут появляются два модуля, которые требуют нашего внимания. Первый – tensorflow.python.ops.rnn, он помогает построить граф, который вставляется в рекуррентный нейрон и его вход и волшебным образом возвращает узел исходящего графа. Второй модуль – tensorflow.python.ops.rnn_cell. Этот модуль, как можно понять из строки с кодом, включает в себя различные типы рекуррентных нейронов:

from tensorflow.python.ops import rnn

from tensorflow.python.ops.rnn_cell import BasicRNNCell, GRUCell, LSTMCell

Интерфейс у них одинаковый, по принципу «plug and play».

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

rnn_unit = BasicRNNCell(num_units=self.M, activation=self.f)

Как же ею теперь воспользоваться? Модуль rnn имеет функцию, которая странным образом также называется rnn. Она берёт РНС-ячейку и ранее сгенерированную последовательность и возвращает выход и скрытое состояние РНС-ячейки.

outputs, states = rnn.rnn(rnn_unit, sequenceX, dtype=tf.float32)

Мне всё же кажется, что функция с названием get_rnn_output (или похожим) будет, вероятно, уместнее, так что можете попробовать и так:

from tensorflow.python.ops.rnn import rnn as get_rnn_output

outputs, states = get_rnn_output(rnn_unit, sequenceX, dtype=tf.float32)

Что дальше? Не забывайте, что нужно проявлять осторожность с размерностями. Поскольку TensorFlow любит размерность времени ставить первой, то результат будет в виде TxNxM, где T – длина последовательности, N – величина пакета, а M – количество скрытых узлов. Нам же нужно, чтобы N шло первым, а T – вторым, поэтому вновь транспонируем столбец 0 и столбец 1:

outputs = tf.transpose(outputs, (1, 0, 2))

outputs = tf.reshape(outputs, (T*batch_sz, M))

Следующее – умножение на выходные весовые коэффициенты, чтобы получить логиты. Не забывайте, что в TensorFlow мы не будем реализовывать конечное софтмакс, поскольку встроенная в TensorFlow кросс-энтропийная функция сделает это за нас. Имейте также в виду, что в проблеме чётности мы рассматриваем всю последовательность как последовательность прогнозов, чтобы соответствовать последовательности целевых переменных. Поэтому нам не нужен, к примеру, последний логит – нам они нужны все.

logits = tf.matmul(outputs, self.Wo) + self.bo

Итак, у нас теперь есть логиты в форме T*NxK, но не забывайте, что кросс-энтропийная функция TensorFlow не содержит размерности времени, а нам нужно оставить логиты в том виде, как они есть, то есть как если бы у нас было T*N различных не связанных между собой образцов. Поэтому нам необходимо перед вызовом кросс-энтропийной функции преобразовать целевые переменные. Вместо размерности NxT они должны стать одномерным массивом размерностью T*N:

    targets = tf.reshape(tfY, (T*batch_sz,))

    cost_op = tf.reduce_mean(

      tf.nn.sparse_softmax_cross_entropy_with_logits(logits, targets)

    )

На этот момент мы охватили весь новый необходимый материал для создания простой рекуррентной нейронной сети в TensorFlow. Предполагается, что вы уже знаете, как создать пакетную простую рекуррентную нейронную сеть в Theano и как создать обычную нейронную сеть прямого распространения в TensorFlow. Последнее, что мы сделаем, – это запустим программу. Поэтому если вы не хотите писать код самостоятельно, хотя я настоятельно рекомендую именно это, отыщите в репозитарии файл tf_parity.py.

Итак, мы видим одно преимущество использования созданного нами кода: он работает намного быстрее. Он намного быстрее стохастического градиентного спуска рекуррентной нейронной сети Theano, а также быстрее пакетной рекуррентной нейронной сети Theano.

Вот и подошел наш курс к завершению, спасибо за внимание!

Понравилась статья? Поделить с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: