Изменения

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

Долгая краткосрочная память

19 494 байта добавлено, 02:22, 25 января 2019
Новая страница: «'''Долгая краткосрочная память''' (англ. ''Long short-term memory'', ''LSTM'') {{---}} особая разновидность арх…»
'''Долгая краткосрочная память''' (англ. ''Long short-term memory'', ''LSTM'') {{---}} особая разновидность архитектуры [[:Рекуррентные_нейронные_сети|рекуррентных нейронных сетей]], способная к обучению долговременным зависимостям, предложенная в 1997 году Сеппом Хохрайтером и Юргеном Шмидхубером<ref name=LSTM>[https://www.bioinf.jku.at/publications/older/2604.pdf Sepp Hochreiter, Jurgen Schmidhuber. Long short-term memory (1997). Neural Computation.]</ref>.

== Описание ==
[[File:LSTM.png|650px|thumb|[http://colah.github.io/posts/2015-08-Understanding-LSTMs/ Схема слоев долго-краткосрочной памяти]]]
LSTM разработаны специально, чтобы избежать проблемы долговременной зависимости. Запоминание информации на долгие периоды времени {{---}} это их обычное поведение. Ключом к данной возможности является то, что LSTM-модуль не использует функцию активации внутри своих рекуррентных компонентов.

Ключевой компонент LSTM {{---}} это состояние ячейки {{---}} горизонтальная линия, проходящая по верхней части схемы.
Состояние ячейки напоминает конвейерную ленту. Она проходит напрямую через всю цепочку, участвуя лишь в нескольких линейных преобразованиях. Информация может легко течь по ней, не подвергаясь изменениям.
[[File:Lstm-cell-state.png|none|250px|[http://colah.github.io/posts/2015-08-Understanding-LSTMs/ Состояние ячейки]]]

Тем не менее, LSTM может удалять информацию из состояния ячейки; этот процесс регулируется структурами, называемыми фильтрами.
Фильтры позволяют пропускать информацию на основании некоторых условий. Они состоят из слоя сигмоидальной<ref name=Sigmoid_function>[https://en.wikipedia.org/wiki/Sigmoid_function Сигмоида.]</ref> нейронной сети и операции поточечного умножения.

[[File:Lstm-gates.png|none|100px|[http://colah.github.io/posts/2015-08-Understanding-LSTMs/ Фильтры]]]

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

== Основные компоненты ==
* Состояние ячейки
* Фильтры, контролирующие состояние ячейки
** Забывания
** Входной
** Выходной

== Принцип работы ==

Вначале LSTM {{---}} определяет, какую информацию можно выбросить из состояния ячейки. Это решение принимает сигмоидальный слой, называемый “слоем фильтра забывания” (англ. ''forget gate layer''). Он смотрит на <math>h_{t-1}</math> и <math>x_t</math> и возвращает число от 0 до 1 для каждого числа из состояния ячейки <math>C_t{t-1}</math> . 1 означает “полностью сохранить”, а 0 – “полностью выбросить”.
[[File:Lstm-1.png|none|650px]]

Далее {{---}} решается, какая новая информация будет храниться в состоянии ячейки. Этот этап состоит из двух частей. Сначала сигмоидальный слой под названием “слой входного фильтра” (англ. ''input layer gate'') определяет, какие значения следует обновить. Затем tanh-слой<ref name=Hyperbolic_function>[https://en.wikipedia.org/wiki/Hyperbolic_function Гиперболические функции.]</ref> строит вектор новых значений-кандидатов <math>\tilde{C}_t</math>, которые можно добавить в состояние ячейки.
[[File:Lstm-2.png|none|650px]]

Для замены старого состояния ячейки <math>C_{t-1}</math> на новое состояние <math>C_t</math>. Необходимо умножить старое состояние на <math>f_t</math>, забывая то, что мы решили забыть ранее. Затем прибавляем <math>i_t * \tilde{C}_t</math>. Это новые значения-кандидаты, умноженные на <math>t</math> – на сколько мы хотим обновить каждое из значений состояния.
[[File:Lstm-3.png|none|650px]]

На последнем этапе определяется, то, какую информацию мы хотим получать на выходе. Выходные данные будут основаны на нашем состоянии ячейки, к ним будут применены некоторые фильтры. Сначала мы применяем сигмоидальный слой, который решает, какую информацию из состояния ячейки мы будем выводить. Затем значения состояния ячейки проходят через tanh-слой, чтобы получить на выходе значения из диапазона от -1 до 1, и перемножаются с выходными значениями сигмоидального слоя, что позволяет выводить только требуемую информацию.
[[File:Lstm-4.png|none|650px]]

Полученные таким образом <math>h_t</math> и <math>C_t</math> передаются далее по цепочке.

== Вариации ==
Одна из популярных вариаций LSTM, предложенная Герсом и Шмидхубером<ref name=LSTM-peephole-connections>[ftp://ftp.idsia.ch/pub/juergen/TimeCount-IJCNN2000.pdf Gers, Schmidhuber. Recurrent Nets that Time and Count (2000).]</ref>, характеризуется добавлением так называемых “смотровых глазков” (“peephole connections”). С их помощью слои фильтров могут видеть состояние ячейки.
[[File:Lstm-peephole-connections.png|none|650px]]

На схеме выше “глазки” есть у каждого слоя, но во многих работах они добавляются лишь к некоторым слоям.

Другие модификации включают объединенные фильтры “забывания” и входные фильтры. В этом случае решения, какую информацию следует забыть, а какую запомнить, принимаются не отдельно, а совместно. Мы забываем какую-либо информацию только тогда, когда необходимо записать что-то на ее место. Мы добавляем новую информацию с состояние ячейки только тогда, когда забываем старую.
[[File:Lstm-mod-1.png|none|650px]]

Немного больше отличаются от стандартных LSTM управляемые рекуррентные нейроны (англ. ''Gated recurrent units, GRU''), впервые описанные в работе Cho<ref name=Cho>[https://arxiv.org/pdf/1406.1078v3.pdf Cho. Learning Phrase Representations using RNN Encoder–Decoder for Statistical Machine Translation (2014).]</ref>. В ней фильтры «забывания» и входа объединяют в один фильтр «обновления» (update gate). Кроме того, состояние ячейки объединяется со скрытым состоянием, есть и другие небольшие изменения. Построенная в результате модель проще, чем стандартная LSTM, и популярность ее неуклонно возрастает.
[[File:Lstm-gru.png|none|650px]]

Существует множество других модификаций, как, например, глубокие управляемые рекуррентные нейронные сети (англ. ''Depth Gated RNNs''), представленные в работе Yao<ref name=Yao>[https://arxiv.org/pdf/1508.03790v2.pdf SeppKaisheng Yao. Depth-Gated Recurrent Neural Networks (2015).]</ref>. Есть и другие способы решения проблемы долговременных зависимостей, например, Clockwork RNN Яна Кутника<ref name=Jan>[https://arxiv.org/pdf/1402.3511v1.pdf Jan Koutnik. A Clockwork RNN (2014).]</ref>.

== Примеры кода ==
=== TensorFlow ===
Пример кода с библиотекой TensorFlow<ref>[https://www.tensorflow.org/ TensorFlow]</ref>

<font color="green"># Импорты</font>
'''from''' __future__ '''import''' print_function
'''import''' tensorflow '''as''' tf
'''from''' tensorflow.contrib '''import''' rnn

<font color="green"># Импорт MNIST датасета</font>
'''from''' tensorflow.examples.tutorials.mnist '''import''' input_data
mnist = input_data.read_data_sets("/tmp/data/", one_hot=True)

<font color="green"># Определение параметров обучения</font>
learning_rate = <font color="blue">0.001</font>
training_steps = <font color="blue">10000</font>
batch_size = <font color="blue">128</font>
display_step = <font color="blue">200</font>

<font color="green"># Определение параметров сети</font>
num_input = <font color="blue">28</font>
timesteps = <font color="blue">28</font>
num_hidden = <font color="blue">128</font>
num_classes = <font color="blue">10</font>

<font color="green"># Входные данные для графа</font>
X = tf.placeholder("float", [None, timesteps, num_input])
Y = tf.placeholder("float", [None, num_classes])

<font color="green"># Определение весов</font>
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, <font color="blue">1</font>)
<font color="green"># Определение LSTM ячейки</font>
lstm_cell = rnn.BasicLSTMCell(num_hidden, forget_bias=<font color="blue">1.0</font>)
<font color="green"># Получение выхода LSTM ячейки</font>
outputs, states = rnn.static_rnn(lstm_cell, x, dtype=tf.float32)
return tf.matmul(outputs[<font color="blue">-1</font>], weights['out']) + biases['out']

logits = RNN(X, weights, biases)
prediction = tf.nn.softmax(logits)

<font color="green"># Определение функции потерь и оптимизатора</font>
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)

<font color="green"># Оценка модели</font>
correct_pred = tf.equal(tf.argmax(prediction, <font color="blue">1</font>), tf.argmax(Y, <font color="blue">1</font>))
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))

<font color="green"># Инициализация</font>
init = tf.global_variables_initializer()

with tf.Session() as sess:
sess.run(init)

for step in range(<font color="blue">1</font>, training_steps+<font color="blue">1</font>):
batch_x, batch_y = mnist.train.next_batch(batch_size)
batch_x = batch_x.reshape((batch_size, timesteps, num_input))
<font color="green"># Запуск оптимизатора (обратное распространение ошибки)</font>
sess.run(train_op, feed_dict={X: batch_x, Y: batch_y})
if step % display_step == <font color="blue">0</font> or step == <font color="blue">1</font>:
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 = <font color="blue">128</font>
test_data = mnist.test.images[:test_len].reshape((<font color="blue">-1</font>, 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.<ref name=KerasRNN>[https://keras.io/layers/recurrent/ Keras RNN with LSTM layer]</ref>

<font color="green"># Импорты</font>
'''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

<font color="green"># Устанавливаем seed для обеспечения повторяемости результатов</font>
np.random.seed(<font color="blue">42</font>)

<font color="green"># Указываем количество слов из частотного словаря, которое будет использоваться (отсортированы по частоте использования)</font>
max_features = <font color="blue">5000</font>

<font color="green"># Загружаем данные (датасет IMDB содержит 25000 рецензий на фильмы с правильным ответом для обучения и 25000 рецензий на фильмы с правильным ответом для тестирования)</font>
(X_train, y_train), (X_test, y_test) = imdb.load_data(nb_words = max_features)

<font color="green"># Устанавливаем максимальную длину рецензий в словах, чтобы они все были одной длины</font>
maxlen = <font color="blue">80</font>

<font color="green"># Заполняем короткие рецензии пробелами, а длинные обрезаем</font>
X_train = sequence.pad_sequences(X_train, maxlen = maxlen)
X_test = sequence.pad_sequences(X_test, maxlen = maxlen)

<font color="green"># Создаем модель последовательной сети</font>
model = Sequential()
<font color="green"># Добавляем слой для векторного представления слов (5000 слов, каждое представлено вектором из 32 чисел, отключаем входной сигнал с вероятностью 20% для предотвращения переобучения)</font>
model.add(Embedding(max_features, <font color="blue">32</font>, dropout = <font color="blue">0.2</font>))
<font color="green"># Добавляем слой долго-краткосрочной памяти (100 элементов для долговременного хранения информации, отключаем входной сигнал с вероятностью 20%, отключаем рекуррентный сигнал с вероятностью 20%)</font>
model.add(LSTM(<font color="blue">100</font>, dropout_W = <font color="blue">0.2</font>, dropout_U = <font color="blue">0.2</font>))
<font color="green"># Добавляем полносвязный слой из 1 элемента для классификации, в качестве функции активации будем использовать сигмоидальную функцию</font>
model.add(Dense(<font color="blue">1</font>, activation = <font color="red">'sigmoid'</font>))

<font color="green"># Компилируем модель нейронной сети</font>
model.compile(loss = <font color="red">'binary_crossentropy'</font>,
optimizer = <font color="red">'adam'</font>,
metrics = [<font color="red">'accuracy'</font>])

<font color="green"># Обучаем нейронную сеть (данные для обучения, ответы к данным для обучения, количество рецензий после анализа которого будут изменены веса, число эпох обучения, тестовые данные, показывать progress bar или нет)</font>
model.fit(X_train, y_train,
batch_size = <font color="blue">64</font>,
nb_epoch = <font color="blue">7</font>,
validation_data = (X_test, y_test),
verbose = <font color="blue">1</font>)

<font color="green"># Проверяем качество обучения на тестовых данных (если есть данные, которые не участвовали в обучении, лучше использовать их, но в нашем случае таковых нет)</font>
scores = model.evaluate(X_test, y_test, batch_size = <font color="blue">64</font>)
print(<font color="red">'Точность на тестовых данных: %.2f%%'</font> % (scores[1] * <font color="blue">100</font>))

==См. также==
*[[:Рекуррентные нейронные сети|Рекуррентные нейронные сети]]
*[[:Нейронные_сети,_перцептрон|Нейронные сети, перцептрон]]
*[[:Сверточные_нейронные_сети|Сверточные нейронные сети]]
*[[:Рекурсивные нейронные сети|Рекурсивные нейронные сети]]

==Примечания==
<references/>

[[Категория: Машинное обучение]]
[[Категория: Нейронные сети]]
[[Категория: Рекуррентные нейронные сети]]
38
правок

Навигация