Долгая краткосрочная память
Долгая краткосрочная память (англ. Long short-term memory, LSTM) — особая разновидность архитектуры рекуррентных нейронных сетей, способная к обучению долговременным зависимостям, предложенная в 1997 году Сеппом Хохрайтером и Юргеном Шмидхубером[1].
Описание
Рекуррентные нейронные сети добавляют память к искуственным нейронным сетям, но реализуемая память получается короткой — на каждом шаге обучения информация в памяти смешивается с новой и через несколько итераций полностью перезаписывается.
LSTM-модули разработаны специально, чтобы избежать проблемы долговременной зависимости, запоминая значения как на короткие, так и на длинные промежутки времени. Это объясняется тем, что LSTM-модуль не использует функцию активации внутри своих рекуррентных компонентов. Таким образом, хранимое значение не размывается во времени и градиент не исчезает при использовании метода обратного распространения ошибки во времени (англ. Backpropagation Through Time, BPTT)[2][3] при тренировке сети.
Ключевые компоненты LSTM-модуля: состояние ячейки и различные фильтры. О состоянии ячейки можно говорить, как о памяти сети, которая передает соответствующую информацию по всей цепочке модулей. Таким образом, даже информация из ранних временных шагов может быть получена на более поздних, нивелируя эффект кратковременной памяти.
По мере того, как происходит обучение, состояние ячейки изменяется, информация добавляется или удаляется из состояния ячейки структурами, называемыми фильтрами. Фильтры контролируют поток информации на входах и на выходах модуля на основании некоторых условий. Они состоят из слоя сигмоидальной[4] нейронной сети и операции поточечного умножения.
Сигмоидальный слой возвращает числа в диапазоне [0; 1], которые обозначают, какую долю каждого блока информации следует пропустить дальше по сети. Умножение на это значение используется для пропуска или запрета потока информации внутрь и наружу памяти. Например, входной фильтр контролирует меру вхождения нового значения в память, а фильтр забывания контролирует меру сохранения значения в памяти. Выходной фильтр контролирует меру того, в какой степени значение, находящееся в памяти, используется при расчёте выходной функции активации.
Основные компоненты
- Состояние ячейки
- Фильтры, контролирующие состояние ячейки
- Забывания
- Входной
- Выходной
Принцип работы
Сперва “слой фильтра забывания” (англ. forget gate layer) определяет, какую информацию можно забыть или оставить. Значения предыдущего выхода
и текущего входа пропускаются через сигмоидальный слой. Полученные значения находятся в диапозоне [0; 1]. Значения, которые ближе к 0 будут забыты, а к 1 оставлены.Далее решается, какая новая информация будет храниться в состоянии ячейки. Этот этап состоит из двух частей. Сначала сигмоидальный слой под названием “слой входного фильтра” (англ. input layer gate) определяет, какие значения следует обновить. Затем tanh-слой[5] строит вектор новых значений-кандидатов , которые можно добавить в состояние ячейки.
Для замены старого состояния ячейки
на новое состояние . Необходимо умножить старое состояние на , забывая то, что решили забыть ранее. Затем прибавляем . Это новые значения-кандидаты, умноженные на – на сколько обновить каждое из значений состояния.На последнем этапе определяется то, какая информация будет получена на выходе. Выходные данные будут основаны на нашем состоянии ячейки, к ним будут применены некоторые фильтры. Сначала значения предыдущего выхода
и текущего входа пропускаются через сигмоидальный слой, который решает, какая информация из состояния ячейки будет выведена. Затем значения состояния ячейки проходят через tanh-слой, чтобы получить на выходе значения из диапазона от -1 до 1, и перемножаются с выходными значениями сигмоидального слоя, что позволяет выводить только требуемую информацию.Полученные таким образом
и передаются далее по цепочке.Вариации
Одна из популярных вариаций LSTM, предложенная Герсом и Шмидхубером[6], характеризуется добавлением так называемых “смотровых глазков” (“peephole connections”). С их помощью слои фильтров могут видеть состояние ячейки.
На схеме выше “глазки” есть у каждого слоя, но во многих работах они добавляются лишь к некоторым слоям.
Другие модификации включают объединенные фильтры “забывания” и входные фильтры. В этом случае решения, какую информацию следует забыть, а какую запомнить, принимаются не отдельно, а совместно. Информация забывается только тогда, когда необходимо записать что-то на ее место. Добавление новой информации в состояние ячейки выполняется только тогда, когда забываем старую.
Немного больше отличаются от стандартных LSTM управляемые рекуррентные нейроны (англ. Gated recurrent units, GRU), впервые описанные в работе Cho[7]. В ней фильтры «забывания» и входа объединяют в один фильтр «обновления» (update gate). Кроме того, состояние ячейки объединяется со скрытым состоянием, есть и другие небольшие изменения. Построенная в результате модель проще, чем стандартная LSTM, и популярность ее неуклонно возрастает.
Существует множество других модификаций, как, например, глубокие управляемые рекуррентные нейронные сети (англ. Depth Gated RNNs), представленные в работе Yao[8]. Есть и другие способы решения проблемы долговременных зависимостей, например, Clockwork RNN Яна Кутника[9].
Примеры кода
TensorFlow
Пример кода с библиотекой TensorFlow[10]
# Импорты from __future__ import print_function import tensorflow as tf from tensorflow.contrib import rnn # Импорт MNIST датасета from tensorflow.examples.tutorials.mnist import input_data mnist = input_data.read_data_sets("/tmp/data/", one_hot=True) # Определение параметров обучения learning_rate = 0.001 training_steps = 10000 batch_size = 128 display_step = 200 # Определение параметров сети num_input = 28 timesteps = 28 num_hidden = 128 num_classes = 10 # Входные данные для графа X = tf.placeholder("float", [None, timesteps, num_input]) Y = tf.placeholder("float", [None, num_classes]) # Определение весов weights = { 'out': tf.Variable(tf.random_normal([num_hidden, num_classes])) } biases = { 'out': tf.Variable(tf.random_normal([num_classes])) } def RNN(x, weights, biases): x = tf.unstack(x, timesteps, 1) # Определение LSTM ячейки lstm_cell = rnn.BasicLSTMCell(num_hidden, forget_bias=1.0) # Получение выхода LSTM ячейки outputs, states = rnn.static_rnn(lstm_cell, x, dtype=tf.float32) return tf.matmul(outputs[-1], weights['out']) + biases['out'] logits = RNN(X, weights, biases) prediction = tf.nn.softmax(logits) # Определение функции потерь и оптимизатора loss_op = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits( logits=logits, labels=Y)) optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate) train_op = optimizer.minimize(loss_op) # Оценка модели correct_pred = tf.equal(tf.argmax(prediction, 1), tf.argmax(Y, 1)) accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32)) # Инициализация init = tf.global_variables_initializer() with tf.Session() as sess: sess.run(init) for step in range(1, training_steps+1): batch_x, batch_y = mnist.train.next_batch(batch_size) batch_x = batch_x.reshape((batch_size, timesteps, num_input)) # Запуск оптимизатора (обратное распространение ошибки) sess.run(train_op, feed_dict={X: batch_x, Y: batch_y}) if step % display_step == 0 or step == 1: loss, acc = sess.run([loss_op, accuracy], feed_dict={X: batch_x, Y: batch_y}) print("Step " + str(step) + ", Minibatch Loss= " + \ "{:.4f}".format(loss) + ", Training Accuracy= " + \ "{:.3f}".format(acc)) print("Optimization Finished!") test_len = 128 test_data = mnist.test.images[:test_len].reshape((-1, timesteps, num_input)) test_label = mnist.test.labels[:test_len] print("Testing Accuracy:", \ sess.run(accuracy, feed_dict={X: test_data, Y: test_label}))
Keras
Пример кода с использованием библиотеки Keras.[11]
# Импорты import numpy as np from keras.preprocessing import sequence from keras.models import Sequential from keras.layers import Dense, Activation, Embedding from keras.layers import LSTM from keras.datasets import imdb # Устанавливаем seed для обеспечения повторяемости результатов np.random.seed(42) # Указываем количество слов из частотного словаря, которое будет использоваться (отсортированы по частоте использования) max_features = 5000 # Загружаем данные (датасет IMDB содержит 25000 рецензий на фильмы с правильным ответом для обучения и 25000 рецензий на фильмы с правильным ответом для тестирования) (X_train, y_train), (X_test, y_test) = imdb.load_data(nb_words = max_features) # Устанавливаем максимальную длину рецензий в словах, чтобы они все были одной длины maxlen = 80 # Заполняем короткие рецензии пробелами, а длинные обрезаем X_train = sequence.pad_sequences(X_train, maxlen = maxlen) X_test = sequence.pad_sequences(X_test, maxlen = maxlen) # Создаем модель последовательной сети model = Sequential() # Добавляем слой для векторного представления слов (5000 слов, каждое представлено вектором из 32 чисел, отключаем входной сигнал с вероятностью 20% для предотвращения переобучения) model.add(Embedding(max_features, 32, dropout = 0.2)) # Добавляем слой долго-краткосрочной памяти (100 элементов для долговременного хранения информации, отключаем входной сигнал с вероятностью 20%, отключаем рекуррентный сигнал с вероятностью 20%) model.add(LSTM(100, dropout_W = 0.2, dropout_U = 0.2)) # Добавляем полносвязный слой из 1 элемента для классификации, в качестве функции активации будем использовать сигмоидальную функцию model.add(Dense(1, activation = 'sigmoid')) # Компилируем модель нейронной сети model.compile(loss = 'binary_crossentropy', optimizer = 'adam', metrics = ['accuracy']) # Обучаем нейронную сеть (данные для обучения, ответы к данным для обучения, количество рецензий после анализа которого будут изменены веса, число эпох обучения, тестовые данные, показывать progress bar или нет) model.fit(X_train, y_train, batch_size = 64, nb_epoch = 7, validation_data = (X_test, y_test), verbose = 1) # Проверяем качество обучения на тестовых данных (если есть данные, которые не участвовали в обучении, лучше использовать их, но в нашем случае таковых нет) scores = model.evaluate(X_test, y_test, batch_size = 64) print('Точность на тестовых данных: %.2f%%' % (scores[1] * 100))
См. также
Примечания
- ↑ Sepp Hochreiter, Jurgen Schmidhuber. Long short-term memory (1997). Neural Computation.
- ↑ Backpropagation Through Time
- ↑ Backpropagation Through Time
- ↑ Сигмоида.
- ↑ Гиперболические функции.
- ↑ Gers, Schmidhuber. Recurrent Nets that Time and Count (2000).
- ↑ Cho. Learning Phrase Representations using RNN Encoder–Decoder for Statistical Machine Translation (2014).
- ↑ SeppKaisheng Yao. Depth-Gated Recurrent Neural Networks (2015).
- ↑ Jan Koutnik. A Clockwork RNN (2014).
- ↑ TensorFlow
- ↑ Keras RNN with LSTM layer