Долгая краткосрочная память — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
(Принцип работы)
м (rollbackEdits.php mass rollback)
 
(не показано 29 промежуточных версий 3 участников)
Строка 3: Строка 3:
 
== Описание ==
 
== Описание ==
 
[[File:LSTM.png|650px|thumb|[http://colah.github.io/posts/2015-08-Understanding-LSTMs/ Схема слоев долго-краткосрочной памяти]]]
 
[[File:LSTM.png|650px|thumb|[http://colah.github.io/posts/2015-08-Understanding-LSTMs/ Схема слоев долго-краткосрочной памяти]]]
LSTM разработаны специально, чтобы избежать проблемы долговременной зависимости. Запоминание информации на долгие периоды времени {{---}} это их обычное поведение. Ключом к данной возможности является то, что LSTM-модуль не использует функцию активации внутри своих рекуррентных компонентов.
+
[[:Рекуррентные_нейронные_сети|Рекуррентные нейронные сети]] добавляют память к искуственным нейронным сетям, но реализуемая память получается короткой {{---}} на каждом шаге обучения информация в памяти смешивается с новой и через несколько итераций полностью перезаписывается.
  
Ключевой компонент LSTM {{---}} это состояние ячейки {{---}} горизонтальная линия, проходящая по верхней части схемы.
+
LSTM-модули разработаны специально, чтобы избежать проблемы долговременной зависимости, запоминая значения как на короткие, так и на длинные промежутки времени. Это объясняется тем, что LSTM-модуль не использует функцию активации внутри своих рекуррентных компонентов. Таким образом, хранимое значение не размывается во времени и градиент не исчезает при использовании метода обратного распространения ошибки во времени (англ. Backpropagation Through Time, BPTT)<ref name=BPTT_1>[http://andrew.gibiansky.com/blog/machine-learning/recurrent-neural-networks/ Backpropagation Through Time]</ref><ref name=BPTT_2>[http://www.wildml.com/2015/10/recurrent-neural-networks-tutorial-part-3-backpropagation-through-time-and-vanishing-gradients/ Backpropagation Through Time]</ref> при тренировке сети.
Состояние ячейки напоминает конвейерную ленту. Она проходит напрямую через всю цепочку, участвуя лишь в нескольких линейных преобразованиях. Информация может легко течь по ней, не подвергаясь изменениям.
 
[[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> нейронной сети и операции поточечного умножения.
 
  
 +
Ключевые компоненты LSTM-модуля: состояние ячейки и различные фильтры. О состоянии ячейки можно говорить, как о памяти сети, которая передает соответствующую информацию по всей цепочке модулей. Таким образом, даже информация из ранних временных шагов может быть получена на более поздних, нивелируя эффект кратковременной памяти.
 +
[[File:Lstm-cell-state.png|none|250px|[http://colah.github.io/posts/2015-08-Understanding-LSTMs/ Состояние ячейки]]]
 +
По мере того, как происходит обучение, состояние ячейки изменяется, информация добавляется или удаляется из состояния ячейки структурами, называемыми фильтрами. Фильтры контролируют поток информации на входах и на выходах модуля на основании некоторых условий. Они состоят из слоя сигмоидальной<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/ Фильтры]]]
 
[[File:Lstm-gates.png|none|100px|[http://colah.github.io/posts/2015-08-Understanding-LSTMs/ Фильтры]]]
 
+
Сигмоидальный слой возвращает числа в диапазоне [0; 1], которые обозначают, какую долю каждого блока информации следует пропустить дальше по сети. Умножение на это значение используется для пропуска или запрета потока информации внутрь и наружу памяти. Например, входной фильтр контролирует меру вхождения нового значения в память, а фильтр забывания контролирует меру сохранения значения в памяти. Выходной фильтр контролирует меру того, в какой степени значение, находящееся в памяти, используется при расчёте выходной функции активации.
Сигмоидальный слой возвращает числа от нуля до единицы, которые обозначают, какую долю каждого блока информации следует пропустить дальше по сети. Ноль в данном случае означает “не пропускать ничего”, единица – “пропустить все”.
 
  
 
== Основные компоненты ==
 
== Основные компоненты ==
Строка 25: Строка 22:
 
== Принцип работы ==
 
== Принцип работы ==
  
Вначале LSTM определяет, какую информацию можно выбросить из состояния ячейки. Это решение принимает сигмоидальный слой, называемый “слоем фильтра забывания” (англ. ''forget gate layer''). Он смотрит на <math>h_{t-1}</math> и <math>x_t</math> и возвращает число от 0 до 1 для каждого числа из состояния ячейки <math>C_t{t-1}</math> . 1 означает “полностью сохранить”, а 0 – “полностью выбросить”.
+
Сперва “слой фильтра забывания” (англ. ''forget gate layer'') определяет, какую информацию можно забыть или оставить. Значения предыдущего выхода <math>h_{t-1}</math> и текущего входа <math>x_t</math> пропускаются через сигмоидальный слой. Полученные значения находятся в диапозоне [0; 1]. Значения, которые ближе к 0 будут забыты, а к 1 оставлены.
 
[[File:Lstm-1.png|none|650px]]
 
[[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>, которые можно добавить в состояние ячейки.
+
Далее решается, какая новая информация будет храниться в состоянии ячейки. Этот этап состоит из двух частей. Сначала сигмоидальный слой под названием “слой входного фильтра” (англ. ''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]]
 
[[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> – на сколько мы хотим обновить каждое из значений состояния.
+
Для замены старого состояния ячейки <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]]
 
[[File:Lstm-3.png|none|650px]]
  
На последнем этапе определяется, то, какую информацию мы хотим получать на выходе. Выходные данные будут основаны на нашем состоянии ячейки, к ним будут применены некоторые фильтры. Сначала мы применяем сигмоидальный слой, который решает, какую информацию из состояния ячейки мы будем выводить. Затем значения состояния ячейки проходят через tanh-слой, чтобы получить на выходе значения из диапазона от -1 до 1, и перемножаются с выходными значениями сигмоидального слоя, что позволяет выводить только требуемую информацию.
+
На последнем этапе определяется то, какая информация будет получена на выходе. Выходные данные будут основаны на нашем состоянии ячейки, к ним будут применены некоторые фильтры. Сначала значения предыдущего выхода <math>h_{t-1}</math> и текущего входа <math>x_t</math> пропускаются через сигмоидальный слой, который решает, какая информация из состояния ячейки будет выведена. Затем значения состояния ячейки проходят через tanh-слой, чтобы получить на выходе значения из диапазона от -1 до 1, и перемножаются с выходными значениями сигмоидального слоя, что позволяет выводить только требуемую информацию.
 
[[File:Lstm-4.png|none|650px]]
 
[[File:Lstm-4.png|none|650px]]
  
Строка 40: Строка 37:
  
 
== Вариации ==
 
== Вариации ==
Одна из популярных вариаций 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”). С их помощью слои фильтров могут видеть состояние ячейки.
+
=== Cмотровые глазки ===
 +
Одна из популярных вариаций 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-peephole-connections.png|none|650px]]
 
 
На схеме выше “глазки” есть у каждого слоя, но во многих работах они добавляются лишь к некоторым слоям.
 
На схеме выше “глазки” есть у каждого слоя, но во многих работах они добавляются лишь к некоторым слоям.
  
Другие модификации включают объединенные фильтры “забывания” и входные фильтры. В этом случае решения, какую информацию следует забыть, а какую запомнить, принимаются не отдельно, а совместно. Мы забываем какую-либо информацию только тогда, когда необходимо записать что-то на ее место. Мы добавляем новую информацию с состояние ячейки только тогда, когда забываем старую.
+
=== Объединенные фильтры ===
 +
Другие модификации включают объединенные фильтры “забывания” и входные фильтры. В этом случае решения, какую информацию следует забыть, а какую запомнить, принимаются не отдельно, а совместно. Информация забывается только тогда, когда необходимо записать что-то на её место. Добавление новой информации в состояние ячейки выполняется только тогда, когда забываем старую.
 
[[File:Lstm-mod-1.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, и популярность ее неуклонно возрастает.
+
=== Управляемые рекуррентные нейроны ===
 +
Немного больше отличаются от стандартных LSTM управляемые рекуррентные нейроны (англ. ''Gated recurrent units, GRU''), впервые описанные в работе Кюнгхюна Чо (англ. Kyunghyun 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''). Этот фильтр определяет сколько информации сохранить от последнего состояния, и сколько информации получить от предыдущего слоя. Кроме того, состояние ячейки объединяется со скрытым состоянием, есть и другие небольшие изменения. Фильтр сброса состояния (англ. ''reset gate'') работает почти так же, как фильтр забывания, но расположен немного иначе. На следующие слои отправляется полная информация о состоянии, выходного фильтра нет. В большинстве случаем GRU работают так же, как LSTM, самое значимое отличие в том, что GRU немного быстрее и проще в эксплуатации, однако обладает немного меньшими выразительными возможностями. В результате модели проще, чем LSTM и их популярность неуклонно возрастает. Эффективность при решении задач моделирования музыкальных и речевых сигналов сопоставима с использованием долгой краткосрочной памяти.
 
[[File:Lstm-gru.png|none|650px]]
 
[[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>.
+
=== Глубокие управляемые рекуррентные нейроны ===
 +
Существует множество других модификаций, как, например, глубокие управляемые рекуррентные нейронные сети (англ. ''Depth Gated RNNs''), представленные в работе Каишенга Яо (англ. Kaisheng Yao)<ref name=Yao>[https://arxiv.org/pdf/1508.03790v2.pdf SeppKaisheng Yao. Depth-Gated Recurrent Neural Networks (2015).]</ref>. Глубокие управляемые рекуррентные нейронные сети привносят фильтр глубины для подключения ячеек памяти соседних слоев. Это вводит линейную зависимость между нижними и верхними рекуррентными единицами. Важно отметить, что линейная зависимость проходит через функцию стробирования, которая называется фильтром забывания. Данная архитектура способна улучшить машинный перевод и языковое моделирование.
 +
 
 +
=== Механизм часов ===
 +
Есть и другие способы решения проблемы долговременных зависимостей, например, механизм часов (англ. ''Clockwork RNN'', CW-RNN) Яна Кутника<ref name=Jan>[https://arxiv.org/pdf/1402.3511v1.pdf Jan Koutnik. A Clockwork RNN (2014).]</ref>. CW-RNN {{---}} мощная модификация стандартной архитектуры RNN, в которой скрытый слой разделен на отдельные модули, каждый из которых обрабатывает входные данные со своей временной детализацией, производя вычисления только при заданной тактовой частоте. Стандартная модель RNN не ставновится сложнее, CW-RNN уменьшает количество параметров RNN, улучшает точность и скорость обучения сети в задачах генерации звуковых сигналов.
  
 
== Примеры кода ==
 
== Примеры кода ==
 +
=== Keras ===
 +
Пример кода с использованием библиотеки Keras.<ref name=KerasRNN>[https://keras.io/layers/recurrent/ Keras RNN with LSTM layer]</ref>
 +
 +
  <font color="green"># Импорты</font>
 +
  '''import''' numpy '''as''' np
 +
  '''import''' keras.backend '''as''' K
 +
  '''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
 +
 
 +
  '''def''' f1(y_true, y_pred):
 +
    '''def''' recall(y_true, y_pred):
 +
        true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))
 +
        possible_positives = K.sum(K.round(K.clip(y_true, 0, 1)))
 +
        recall = true_positives / (possible_positives + K.epsilon())
 +
        return recall
 +
 
 +
    '''def''' precision(y_true, y_pred):
 +
        true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))
 +
        predicted_positives = K.sum(K.round(K.clip(y_pred, 0, 1)))
 +
        precision = true_positives / (predicted_positives + K.epsilon())
 +
        return precision
 +
 
 +
    precision = precision(y_true, y_pred)
 +
    recall = recall(y_true, y_pred)
 +
    return 2*((precision*recall)/(precision+recall+K.epsilon()))
 +
 
 +
  <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="red">'f1'</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>))
 +
  print(<font color="red">'F1 на тестовых данных: %.2f%%'</font> % (scores[2] * <font color="blue">100</font>))
 +
 +
Результат:
 +
  Точность на тренировочных данных: 89.64%
 +
  F1 на тренировочных данных: 89.55%
 +
 
 +
  Точность на тестовых данных: 83.01%
 +
  F1 на тестовых данных: 82.48%
 +
 
=== TensorFlow ===
 
=== TensorFlow ===
 
Пример кода с библиотекой TensorFlow<ref>[https://www.tensorflow.org/ TensorFlow]</ref>
 
Пример кода с библиотекой TensorFlow<ref>[https://www.tensorflow.org/ TensorFlow]</ref>
Строка 135: Строка 216:
 
           sess.run(accuracy, feed_dict={X: test_data, Y: test_label}))
 
           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>
+
  Точность на тренировочных данных: 91.40%
 +
  F1 на тренировочных данных: 91.05%
 +
 
 +
  Точность на тестовых данных: 85.15%
 +
  F1 на тестовых данных: 84.28%
  
  <font color="green"># Импорты</font>
+
===Пример на языке Java===
  '''import''' numpy '''as''' np
+
[https://github.com/deeplearning4j/dl4j-examples/blob/master/dl4j-examples/src/main/java/org/deeplearning4j/examples/recurrent/character/LSTMCharModellingExample.java Пример] реализации рекуррентной нейронной сети, использующей механизм LSTM и натренированной на текстах Шекспира, с применением библиотеки <code>deeplearning4j</code>.
  '''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>))
 
  
 
==См. также==
 
==См. также==
Строка 199: Строка 238:
 
[[Категория: Нейронные сети]]
 
[[Категория: Нейронные сети]]
 
[[Категория: Рекуррентные нейронные сети]]
 
[[Категория: Рекуррентные нейронные сети]]
 +
 +
== Источники информации ==
 +
* [http://colah.github.io/posts/2015-08-Understanding-LSTMs/ Understanding LSTM Networks]
 +
* [https://towardsdatascience.com/illustrated-guide-to-lstms-and-gru-s-a-step-by-step-explanation-44e9eb85bf21 Illustrated Guide to LSTM’s and GRU’s: A step by step explanation]
 +
* [https://towardsdatascience.com/the-fall-of-rnn-lstm-2d1594c74ce0 The fall of RNN / LSTM]
 +
* [https://en.wikipedia.org/wiki/Long_short-term_memory Long short-term memory] - статья на Википедии
 +
* [https://www.coursera.org/lecture/nlp-sequence-models/long-short-term-memory-lstm-KXoay Long Short Term Memory (LSTM)] - курс Andrew Ng

Текущая версия на 19:08, 4 сентября 2022

Долгая краткосрочная память (англ. Long short-term memory, LSTM) — особая разновидность архитектуры рекуррентных нейронных сетей, способная к обучению долговременным зависимостям, предложенная в 1997 году Сеппом Хохрайтером и Юргеном Шмидхубером[1].

Описание

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

LSTM-модули разработаны специально, чтобы избежать проблемы долговременной зависимости, запоминая значения как на короткие, так и на длинные промежутки времени. Это объясняется тем, что LSTM-модуль не использует функцию активации внутри своих рекуррентных компонентов. Таким образом, хранимое значение не размывается во времени и градиент не исчезает при использовании метода обратного распространения ошибки во времени (англ. Backpropagation Through Time, BPTT)[2][3] при тренировке сети.

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

Состояние ячейки

По мере того, как происходит обучение, состояние ячейки изменяется, информация добавляется или удаляется из состояния ячейки структурами, называемыми фильтрами. Фильтры контролируют поток информации на входах и на выходах модуля на основании некоторых условий. Они состоят из слоя сигмоидальной[4] нейронной сети и операции поточечного умножения.

Фильтры

Сигмоидальный слой возвращает числа в диапазоне [0; 1], которые обозначают, какую долю каждого блока информации следует пропустить дальше по сети. Умножение на это значение используется для пропуска или запрета потока информации внутрь и наружу памяти. Например, входной фильтр контролирует меру вхождения нового значения в память, а фильтр забывания контролирует меру сохранения значения в памяти. Выходной фильтр контролирует меру того, в какой степени значение, находящееся в памяти, используется при расчёте выходной функции активации.

Основные компоненты

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

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

Сперва “слой фильтра забывания” (англ. forget gate layer) определяет, какую информацию можно забыть или оставить. Значения предыдущего выхода [math]h_{t-1}[/math] и текущего входа [math]x_t[/math] пропускаются через сигмоидальный слой. Полученные значения находятся в диапозоне [0; 1]. Значения, которые ближе к 0 будут забыты, а к 1 оставлены.

Lstm-1.png

Далее решается, какая новая информация будет храниться в состоянии ячейки. Этот этап состоит из двух частей. Сначала сигмоидальный слой под названием “слой входного фильтра” (англ. input layer gate) определяет, какие значения следует обновить. Затем tanh-слой[5] строит вектор новых значений-кандидатов [math]\tilde{C}_t[/math], которые можно добавить в состояние ячейки.

Lstm-2.png

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

Lstm-3.png

На последнем этапе определяется то, какая информация будет получена на выходе. Выходные данные будут основаны на нашем состоянии ячейки, к ним будут применены некоторые фильтры. Сначала значения предыдущего выхода [math]h_{t-1}[/math] и текущего входа [math]x_t[/math] пропускаются через сигмоидальный слой, который решает, какая информация из состояния ячейки будет выведена. Затем значения состояния ячейки проходят через tanh-слой, чтобы получить на выходе значения из диапазона от -1 до 1, и перемножаются с выходными значениями сигмоидального слоя, что позволяет выводить только требуемую информацию.

Lstm-4.png

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

Вариации

Cмотровые глазки

Одна из популярных вариаций LSTM, предложенная Герсом и Шмидхубером[6], характеризуется добавлением так называемых “смотровых глазков” (англ. peephole connections). С их помощью слои фильтров могут видеть состояние ячейки.

Lstm-peephole-connections.png

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

Объединенные фильтры

Другие модификации включают объединенные фильтры “забывания” и входные фильтры. В этом случае решения, какую информацию следует забыть, а какую запомнить, принимаются не отдельно, а совместно. Информация забывается только тогда, когда необходимо записать что-то на её место. Добавление новой информации в состояние ячейки выполняется только тогда, когда забываем старую.

Lstm-mod-1.png

Управляемые рекуррентные нейроны

Немного больше отличаются от стандартных LSTM управляемые рекуррентные нейроны (англ. Gated recurrent units, GRU), впервые описанные в работе Кюнгхюна Чо (англ. Kyunghyun Cho)[7]. У них на один фильтр меньше, и они немного иначе соединены. Фильтры «забывания» и входа объединяют в один фильтр «обновления» (англ. update gate). Этот фильтр определяет сколько информации сохранить от последнего состояния, и сколько информации получить от предыдущего слоя. Кроме того, состояние ячейки объединяется со скрытым состоянием, есть и другие небольшие изменения. Фильтр сброса состояния (англ. reset gate) работает почти так же, как фильтр забывания, но расположен немного иначе. На следующие слои отправляется полная информация о состоянии, выходного фильтра нет. В большинстве случаем GRU работают так же, как LSTM, самое значимое отличие в том, что GRU немного быстрее и проще в эксплуатации, однако обладает немного меньшими выразительными возможностями. В результате модели проще, чем LSTM и их популярность неуклонно возрастает. Эффективность при решении задач моделирования музыкальных и речевых сигналов сопоставима с использованием долгой краткосрочной памяти.

Lstm-gru.png

Глубокие управляемые рекуррентные нейроны

Существует множество других модификаций, как, например, глубокие управляемые рекуррентные нейронные сети (англ. Depth Gated RNNs), представленные в работе Каишенга Яо (англ. Kaisheng Yao)[8]. Глубокие управляемые рекуррентные нейронные сети привносят фильтр глубины для подключения ячеек памяти соседних слоев. Это вводит линейную зависимость между нижними и верхними рекуррентными единицами. Важно отметить, что линейная зависимость проходит через функцию стробирования, которая называется фильтром забывания. Данная архитектура способна улучшить машинный перевод и языковое моделирование.

Механизм часов

Есть и другие способы решения проблемы долговременных зависимостей, например, механизм часов (англ. Clockwork RNN, CW-RNN) Яна Кутника[9]. CW-RNN — мощная модификация стандартной архитектуры RNN, в которой скрытый слой разделен на отдельные модули, каждый из которых обрабатывает входные данные со своей временной детализацией, производя вычисления только при заданной тактовой частоте. Стандартная модель RNN не ставновится сложнее, CW-RNN уменьшает количество параметров RNN, улучшает точность и скорость обучения сети в задачах генерации звуковых сигналов.

Примеры кода

Keras

Пример кода с использованием библиотеки Keras.[10]

 # Импорты
 import numpy as np
 import keras.backend as K
 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
 
 def f1(y_true, y_pred):
   def recall(y_true, y_pred):
       true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))
       possible_positives = K.sum(K.round(K.clip(y_true, 0, 1)))
       recall = true_positives / (possible_positives + K.epsilon())
       return recall
 
   def precision(y_true, y_pred):
       true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))
       predicted_positives = K.sum(K.round(K.clip(y_pred, 0, 1)))
       precision = true_positives / (predicted_positives + K.epsilon())
       return precision
 
   precision = precision(y_true, y_pred)
   recall = recall(y_true, y_pred)
   return 2*((precision*recall)/(precision+recall+K.epsilon()))
 
 # Устанавливаем 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', 'f1'])
 
 # Обучаем нейронную сеть (данные для обучения, ответы к данным для обучения, количество рецензий после анализа которого будут изменены веса, число эпох обучения, тестовые данные, показывать 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))
 print('F1 на тестовых данных: %.2f%%' % (scores[2] * 100))

Результат:

 Точность на тренировочных данных: 89.64%
 F1 на тренировочных данных: 89.55%
 
 Точность на тестовых данных: 83.01%
 F1 на тестовых данных: 82.48%

TensorFlow

Пример кода с библиотекой TensorFlow[11]

 # Импорты
 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}))

Результат:

 Точность на тренировочных данных: 91.40%
 F1 на тренировочных данных: 91.05%
 
 Точность на тестовых данных: 85.15%
 F1 на тестовых данных: 84.28%

Пример на языке Java

Пример реализации рекуррентной нейронной сети, использующей механизм LSTM и натренированной на текстах Шекспира, с применением библиотеки deeplearning4j.

См. также

Примечания

Источники информации