Уважаемый читатель!
Эта статья была переиздана на Эдукаора и также была с открытым исходным кодом. К сожалению, TensorFlow 2.0 изменил API, поэтому он не работает для более поздних версий. Мы приветствуем любую помощь в обновлении учебников. Я также рекомендую вам изучить PyTorch.
В этом руководстве я объясню, как создать простую работающую рекуррентную нейронную сеть в TensorFlow. Это первая из семи частей, в которых рассматриваются различные аспекты и методы построения рекуррентных нейронных сетей в TensorFlow. Краткое введение в TensorFlow доступно здесь. А пока давайте начнем с RNN!
Что такое RNN?
Это сокращение от «Рекуррентная нейронная сеть», и в основном это нейронная сеть, которую можно использовать, когда ваши данные обрабатываются как последовательность, где имеет значение конкретный порядок точек данных. Что еще более важно, эта последовательность может иметь произвольную длину.
Самый простой пример - это, пожалуй, временной ряд чисел, где задача состоит в том, чтобы предсказать следующее значение с учетом предыдущих значений. Входными данными в RNN на каждом временном шаге является текущее значение, а также вектор состояния, которые представляют то, что сеть «видела» на временных шагах раньше. Этот вектор состояния представляет собой закодированную память RNN, изначально установленную в ноль.
Лучшая и наиболее полная статья, объясняющая RNN: s, которую я нашел до сих пор, - это эта статья исследователей из UCSD, которую я настоятельно рекомендую. На данный момент вам нужно только понять основы, прочитать до раздела Современные архитектуры RNN. Это будет рассмотрено позже.
Хотя эта статья содержит некоторые объяснения, в основном она сосредоточена на практической части, как ее построить. Предлагаем вам поискать больше теории в Интернете, там есть много хороших объяснений.
Настраивать
Мы создадим простую Echo-RNN, которая запоминает входные данные, а затем повторяет их через несколько временных шагов. Сначала давайте установим некоторые константы, которые нам понадобятся, их значение станет ясно через мгновение.
Генерировать данные
Теперь сгенерируйте обучающие данные, вход в основном представляет собой случайный двоичный вектор. Выходной сигнал будет «эхом» входа, сдвинутым на echo_step
шаг вправо.
Обратите внимание на преобразование данных в матрицу с batch_size
строками. Нейронные сети обучаются путем аппроксимации градиента функции потерь по отношению к весам нейронов, рассматривая только небольшое подмножество данных, также известное как мини-пакет. Теоретическая причина этого более подробно рассматривается в этом вопросе. При изменении формы весь набор данных помещается в матрицу, которая позже будет разделена на эти мини-пакеты.
Построение вычислительного графа
TensorFlow сначала создает вычислительный граф, который указывает, какие операции будут выполняться. Входные и выходные данные этого графа обычно представляют собой многомерные массивы, также известные как тензоры. Затем график или его части могут выполняться итеративно в сеансе, это может быть выполнено на ЦП, графическом процессоре или даже на ресурсе на удаленном сервере.
Переменные и заполнители
Две основные структуры данных TensorFlow, которые будут использоваться в этом примере, - это заполнители и переменные. При каждом запуске пакетные данные передаются в заполнители, которые являются «начальными узлами» вычислительного графа. Также RNN-состояние предоставляется в качестве заполнителя, который сохраняется из вывода предыдущего запуска.
Веса и смещения сети объявляются как переменные TensorFlow, что делает их постоянными во время выполнения и позволяет обновлять их постепенно для каждого пакета.
На рисунке ниже показана матрица входных данных, а текущий пакет batchX_placeholder
выделен пунктирным прямоугольником. Как мы увидим позже, это «окно пакета» сдвигается на truncated_backprop_length
шага вправо при каждом запуске, отсюда и стрелка. В нашем примере ниже batch_size = 3
, truncated_backprop_length = 3
и total_series_length = 36
. Обратите внимание, что эти числа предназначены только для наглядности, значения в коде отличаются. Порядковый индекс ряда показан в виде чисел в некоторых точках данных.
Распаковка
Теперь пришло время построить часть графика, которая напоминает фактическое вычисление RNN, сначала мы хотим разделить пакетные данные на смежные временные шаги.
Как вы можете видеть на рисунке ниже, это делается путем распаковки столбцов (axis = 1
) пакета в список Python. RNN будет одновременно тренироваться на разных участках временного ряда; шаги с 4 по 6, с 16 по 18 и с 28 по 30 в текущем примере партии. Причина использования имен переменных “plural”_”series”
состоит в том, чтобы подчеркнуть, что переменная представляет собой список, который представляет собой временной ряд с несколькими записями на каждом этапе.
Тот факт, что обучение выполняется одновременно в трех местах в нашем временном ряду, требует от нас сохранения трех экземпляров состояний при продвижении вперед. Это уже учтено, поскольку вы видите, что заполнитель init_state
имеет batch_size
строк.
Вперед пас
Теперь давайте построим ту часть графика, которая выполняет фактическое вычисление RNN.
Обратите внимание на конкатенацию в строке 6, на самом деле мы хотим вычислить сумму двух аффинных преобразований current_input * Wa + current_state * Wb
на рисунке ниже. Объединив эти два тензора, вы будете использовать только одно матричное умножение. Добавление смещения b
транслируется на все образцы в партии.
Вы можете задаться вопросом, что должно означать имя переменной truncated_backprop_length
. Когда RNN обучается, она фактически рассматривается как глубокая нейронная сеть с повторяющимися весами на каждом уровне. Эти слои не будут развернуты до начала времени, что было бы слишком затратно с точки зрения вычислений, и поэтому они усекаются с ограниченным числом временных шагов. В приведенных выше примерах схем ошибка передается на три шага в нашем пакете.
Расчет убытка
Это последняя часть графика, полностью связанный слой softmax от состояния до вывода, который сделает классы горячим кодированием, , а затем вычислит потерю партия.
Последняя строка добавляет функцию обучения, TensorFlow автоматически выполнит обратное распространение для нас - граф вычислений выполняется один раз для каждого мини-пакета, а веса сети обновляются постепенно.
Обратите внимание на вызов API для sparse_softmax_cross_entropy_with_logits
, он автоматически вычисляет softmax внутренне, а затем вычисляет перекрестную энтропию. В нашем примере классы являются взаимоисключающими (они равны нулю или единице), что является причиной использования Sparse-softmax, подробнее о нем вы можете прочитать в API. Использование должно иметьlogits
формы [batch_size, num_classes]
и labels
формы [batch_size]
.
Визуализация тренировки
Есть функция визуализации, чтобы мы могли видеть, что происходит в сети во время обучения. Он будет отображать потери с течением времени, показывать обучающие входные данные, обучающие выходные данные и текущие прогнозы, сделанные сетью для различных серий выборок в обучающем пакете.
Проведение тренировки
Пришло время подвести итоги и обучить сеть, в TensorFlow график выполняется за сеанс. Новые данные генерируются для каждой эпохи (это не обычный способ, но в данном случае он работает, поскольку все предсказуемо).
Вы можете видеть, что мы продвигаемся на truncated_backprop_length
шагов вперед на каждой итерации (строки 15–19), но возможны разные шаги. Эта тема более подробно раскрыта в этой статье. Обратной стороной этого является то, что truncated_backprop_length
должны быть значительно больше, чем зависимости от времени (в нашем случае три шага), чтобы инкапсулировать соответствующие данные обучения. В противном случае может быть много промахов, как вы можете видеть на рисунке ниже.
Также поймите, что это всего лишь простой пример, объясняющий, как работает RNN, эту функциональность можно легко запрограммировать всего несколькими строками кода. Сеть сможет точно изучить поведение эха, поэтому нет необходимости в тестовых данных.
Программа будет обновлять график по мере тренировки, как показано на рисунке ниже. Синие полосы обозначают обучающий входной сигнал (двоичный), красные полосы показывают эхо в обучающем выходе, а зеленые полосы - эхо, генерируемое сетью. Различные гистограммы показывают разные серии образцов в текущей партии.
Наш алгоритм достаточно быстро изучит задачу. График в верхнем левом углу показывает результат функции потерь, но почему на кривой есть всплески? Подумайте об этом на мгновение, ответ ниже.
Причина всплесков в том, что мы начинаем новую эпоху и генерируем новые данные. Поскольку форма матрицы изменяется, первый элемент в каждой строке соседствует с последним элементом в предыдущей строке. Первые несколько элементов во всех строках (кроме первого) имеют зависимости, которые не будут включены в состояние, поэтому сеть всегда будет плохо работать с первым пакетом.
Вся программа
Это вся программа, которую можно запустить, просто скопируйте и запустите. После каждой части цикла статей будет представлена вся исполняемая программа. Если на строку имеется ссылка по номеру, мы имеем в виду именно эти номера строк.
Следующий шаг
В следующем посте этой серии мы упростим создание вычислительного графа с помощью собственного TensorFlow RNN API.