[Из песочницы] Attention для чайников и реализация в Keras

О статьях по искусственному интеллекту на русском языке

Не смотря на то что механизм Attention описан в англоязычной литературе, в русскоязычном секторе достойного описание данной технологии я до сих пор не встречал. На нашем языке есть много статей по Искусственному Интеллекту (ИИ). Тем не менее, те статьи, которые удалось найти, раскрывают только самые простые модели ИИ, например, свёрточные сети, генеративные сети. Однако, по передовым новейшим разработками в области ИИ статей в русскоязычном секторе крайне мало.

Отсутствие статей на Русском языке по новейшим разработкам стали проблемой для меня, когда я входил в тему, изучал текущее состояние дел в области ИИ. Я хорошо знаю английский, читаю статьи на английском по тематике ИИ. Однако, когда выходит новая концепция или новый принцип ИИ, его понимание на не родном языке бывает мучительным и долгим. Зная английский, вникнуть на неродном в сложный всё же предмет стоит значительно больше сил и времени. После прочтения описания задаёшь себе вопрос: на сколько процентов ты понял? Если бы была статья на Русском, то понял бы на 100% после первого прочтения. Так произошло с генеративными сетями, по которым есть великолепный цикл статей: после прочтения всё стало понятно. Но в мире сетей есть множество подходов, которые на описаны только английском и с которыми приходилось разбираться днями.

Я собираюсь периодически писать статьи на родном языке, привнося в нашу языковую область знания. Как известно, лучший способ разобраться в теме это объяснить её кому-нибудь. Так что кому как не мне начать цикл статей по самым современным, сложным, передовым архитектурном ИИ. К концу статьи сам пойму на 100% подход, и будет польза для кого-то, кто прочитает и улучшит своё понимание (кстати я люблю Gesser, но лучше Blanche de bruxelles).

Когда разбираешься в предмете можно выделить 4 уровня понимания:


  1. ты понял принцип и входы и выходы Алгоритма / Уровня
  2. ты понял сходы выходы и в общих чертах как оно работает
  3. ты понял всё вышеперечисленное, а также устройство каждого уровня сети (например, в модели VAE ты понял принцип, а ещё ты понял суть трюка с репараметризацией)
  4. понял всё включая каждый уровень, так ещё понял, за счёт чего это всё обучается, при чём так, что стал способен подбирать гиперпараметры под свою задачу, а не копипастить готовые решения.

По новым архитектурам переход от уровня 1 к уровню 4 часто сложен: авторы делают упор на то, что им ближе описывая разные важные детали поверхностно (поняли ли они их сами?). Или твой мозг не содержит какие-то конструкции, так что даже прочитав описание оно не расшифровалось и не перешло в навыки. Это бывает, если ты в студенческие годы спал на том самом уроке матана, после ночной вечерники  где давали нужный мат. аппарат. И как раз тут нужны статьи на родном языке, раскрывающие нюансы и тонкости каждой операции.


Концепция Attention и применение

Выше приведён сценарий уровней понимания. Чтобы разобрать Attention, начнём с первого уровня. Перед описанием входов и выходов разберём суть: на какие базовые, понятные даже ребёнку, понятия опирается данная концепция. В статье будем использовать английский термин Attention, потому что в таком виде он является также вызовом функции библиотеки Keras (он не реализован в ней прямо, требуется дополнительный модуль, но об этом ниже). Чотбы читать дальше вы должны иметь понимание библиотеки Keras и python, потому что будет приводиться исходный код.

lfnv-ayy8tlpfgkiwcrgiinkme4.pngС английского языка Attention переводится как «внимание». Этот термин правильно описывает суть подхода: если вы автомобилист и на фотографии изображён генерал ГИБДД, вы интуитивно придаёте ему важность, вне зависимости от контекста фотографии. Вы скорее всего пристальнее взглянете на генерала. Вы напряжёте глаза, рассмотрите погоны внимательнее: сколько у него там конкретно звёзд. Если генерал не очень высокий, проигнорируете его. В противном случае учтёте как ключевой фактор при принятии решений. Так работает наш мозг. В Русской культуре мы натренированы поколениями на внимание к высоким чинам, наш мозг автоматически ставит высокий приоритет таким объектам.

Attention представляет собой способ сообщить сети, на что стоит обратить больше внимания, т.е. сообщить вероятность того или ионного исхода в зависимости от состояния нейронов и поступающих на вход данных. Реализованный в Keras слой Attention сам выявляет на основе обучающей выборки факторы, обращение внимания на которые снижает ошибку сети. Выявление важных факторов осуществляется через метод обратного распространения ошибки, подобно тому как это делается для свёрточной сетей.

При обучении, Attention демонстрирует свою вероятностную природу. Сам по себе механизм формирует матрицу весов важности. Если бы мы не обучали Attention, мы могли бы задать важность, например, эмпирически (генерал важнее прапорщика). Но когда мы обучаем сеть на данных, важность становится функцией вероятности того или иного исхода в зависимости от поступивших на вход сети данных. Например, если бы проживая в Царской России мы встретили генерала, то вероятность получить шпицрутенов была бы высока. Удостоверившийся в этом можно было бы через несколько личных встреч, собрав статистику. После этого наш мозг выставит соответствующий вес факту встречи данного субъекта и поставит маркеры на погоны и лампасы. Надо отметить, что выставленный маркер не является вероятностью: сейчас встреча генерала повлечёт для вас совершенно иные последствия чем тогда, кроме того вес может быть больше единицы. Но, вес можно привести к вероятности, нормировав его.

Вероятностная природа механизма Attention при обучении проявляется в задачах машинного перевода. Например, сообщим сети, что при переводе с русского на английский слово Любовь переводится в 90% случаев как Love, в 9% случаях как Sex, в 1% случаях как иное. Сеть сразу отметёт множество вариантов, показав лучшее качество обучения. При переводе мы сообщаем сети: «при переводе слова любовь обрати особое внимание на английское слово Love, также посмотри может ли это всё же быть Sex».

Подход Attention применяться для работы с текстом, а также звуком и временными рядами. Для обработки текста широко используются рекуррентные нейронные сети (RNN, LSTM, GRU). Attention может либо дополнять их, либо заменять их, переводя сеть к более простым и быстрым архитектурам.

Одно из самых известных применений Attention это применение его для того, чтобы отказаться от рекуррентной сети и перейти к полносвязной модели. Рекуррентные сети обладают серией недостатков: невозможность осуществлять обучение на GPU, быстро наступающее переобучение. С помощью механизма Attention мы можем выстроить сеть способную к изучению последовательностей на базе полносвязной сети, обучить её на GPU, использовать droput.

Attention широко применяется для улучшения работы рекуррентных сетей, например, в области перевода с языка на язык. При использовании подхода кодирование / декодирование, которое достаточно часто применяется в современном ИИ (например, вариационные автокодировщики). При добавлении между кодировщиком и декодирокщиком слоя Attention результат работы сети заметно улучшается.

В этой статье я не привожу конкретные архитектуры сетей с использованием Attention, этому будут посвящены отдельные работы. Attention применяется в работе со звуком, с временными последовательностями. Перечисление всех возможностей применения attention достойно отдельной статьи.


Реализация Attention в Keras из коробки

Когда разбираешься каком-от подходе, усвоить базовый принцип очень полезно. Но, часто полное понимание приходит только посмотрев на техническую реализацию. Видишь потоки данных, составляющие функцию операции, становится ясно что именно вычисляется. Но сначала его нужно запустить и написать «Attention hello word».

В настоящее время механизм Attention не реализован в самом Keras. Но уже есть сторонние реализации, например attenytion-keras который можно установить с github. Тогда ваш код станет предельно простым:

from attention_keras.layers.attention import AttentionLayer
attn_layer = AttentionLayer(name='attention_layer')
attn_out, attn_states = attn_layer([encoder_outputs, decoder_outputs])

Данная реализация поддерживает функцию визуализации весов Attention. Обучив Attention можно получить матрицу, сигнализирующую, что по мнению сети особенно важно примерно такого вида (картинка с github со страницы библиотеки attenytion-keras).


juhryjvb8eedahp2h77q07p_pwm.png

В принципе вам больше ничего не нужно: включите этот код вашу сеть как один из уровней и наслаждайтесь обучение вашей сети. Любая сеть, любой алгоритм проектируется на первых этапах на концептуальном уровне (как и база данных кстати), после чего реализация до воплощения уточняется в логическом и физическом представлении. Для нейросетей пока данного метода проектирования не разработано (о да, это будет тема моей следующей статьи). Вы же не разбираетесь, как внутри работают слои свёртки? Описан принцип, вы их используете.


Реализация Attention в Keras на низком уровне

Чтобы окончательно разобраться в теме, ниже подробно разберём реализацию Attention под капотом. Концепция хороша, но как именно это работает и почему результат получается именно таким как заявлено?

Простейшая реализация механизма Attention в Keras заминает всего 3 строки:

inputs = Input(shape=(input_dims,))
attention_probs = Dense(input_dims, activation='softmax', name='attention_probs')(inputs)
attention_mul = merge([inputs, attention_probs], output_shape=32, name='attention_mul', mode='mul'

В данном случае в первой строке декларируется слой Input, далее идёт полносвязный слой с функцией активации softmax с количеством нейронов равным количеству элементов в первом слое. Третий слой умножает результат полносвязного слоя на входные данные поэлементно.

Ниже приведён целый класс Attention, реализующий немного более сложный механизм self-attention, который может быть использован как полноценный уровень в модели, класс наследует класс Keras layer.

# Attention
class Attention(Layer):
      def __init__(self, step_dim,
                   W_regularizer=None, b_regularizer=None,
                   W_constraint=None, b_constraint=None,
                   bias=True, **kwargs):
          self.supports_masking = True
          self.init = initializers.get('glorot_uniform')

          self.W_regularizer = regularizers.get(W_regularizer)
          self.b_regularizer = regularizers.get(b_regularizer)

          self.W_constraint = constraints.get(W_constraint)
          self.b_constraint = constraints.get(b_constraint)

          self.bias = bias
          self.step_dim = step_dim
          self.features_dim = 0
          super(Attention, self).__init__(**kwargs)

      def build(self, input_shape):
          assert len(input_shape) == 3

          self.W = self.add_weight((input_shape[-1],),
                                   initializer=self.init,
                                   name='{}_W'.format(self.name),
                                   regularizer=self.W_regularizer,
                                   constraint=self.W_constraint)
          self.features_dim = input_shape[-1]

          if self.bias:
              self.b = self.add_weight((input_shape[1],),
                                       initializer='zero',
                                       name='{}_b'.format(self.name),
                                       regularizer=self.b_regularizer,
                                       constraint=self.b_constraint)
          else:
              self.b = None

          self.built = True

      def compute_mask(self, input, input_mask=None):
          return None

      def call(self, x, mask=None):
          features_dim = self.features_dim
          step_dim = self.step_dim
          eij = K.reshape(K.dot(K.reshape(x, (-1, features_dim)),                           K.reshape(self.W, (features_dim, 1))), (-1, step_dim))
          if self.bias:
              eij += self.b
          eij = K.tanh(eij)
          a = K.exp(eij)

          if mask is not None:
              a *= K.cast(mask, K.floatx())

          a /= K.cast(K.sum(a, axis=1, keepdims=True) + K.epsilon(), K.floatx())

          a = K.expand_dims(a)
          weighted_input = x * a 
          return K.sum(weighted_input, axis=1)

      def compute_output_shape(self, input_shape):
          return input_shape[0],  self.features_dim

Здесь мы видим примерно то же самое что было реализовано выше через полносвязный слой Keras, только выполненное через более глубокую логику на более низком уровне. В функции создаётся параметрический уровень (self.W) который потом скалярно умножается (K.dot) на входной вектор. Логика зашитая в этом варианте немного более cложна: к входному вектору, помноженному на веса self.W, применяется сдвиг (если раскрыт параметр bias), гиперболический тангенс, экспонирование, маска (если она задана), нормирование, после чего входной вектор снова взвешивается по полученному результату. У меня нет описания логики заложенной в этом примере, воспроизвожу операции по чтению кода. Кстати, напишите пожалуйста в комментариях, если вы узнали в этой логике какую-то математическую высокоуровневую функцию.

У класса есть параметр «bias» т.е. предвзятость. Если параметр активирован, то после применения Dense слоя итоговый вектор будет сложен с вектором параметров слоя «self.b» что даст возможность не только определять «веса» для нашей функции внимания, но также смещать уровень внимания на число. Пример из жизни: мы боимся привидений, но ни разу не встречали их. Таким образом мы вносим поправку на страх -100 баллов. То есть только если страх зашкалит за 100 баллов, мы примем решения о защите от привидений, вызове агентства ловцов привидений, покупке отпугивающих устройства и т.д.


Заключение

У механизма Attention есть вариации. Самый простой вариант Attention, реализованный в классе, приведённом выше, называется Self-Attention. Self-attention это механизм предназначенный для обработки последовательных данных с учётом контекста каждой метки времени. Он чаще всего применяется для работы с текстовой информацией. Реализацию self-attention можно взять из коробки, импортировав библиотеку keras-self-attention. Есть и другие вариации Attention. Изучая англоязычные материалы удалось насчитать более 5 вариаций.

При написании даже этой относительно короткой статьи я изучил более 10 англоязычных статей. Безусловно мне не удалось загрузить все данные из всех этих статей в 5 страниц, я сделал лишь выжимку, с целью создать «руководство для чайников». Чтобы разобрать все нюансы механизма Attention потребуется книжечка страниц на 150–200. Очень надеюсь, мне удалось раскрыть базовую сущность этого механизма чтобы те, кто только начинает разбираться в машинному обучении, поняли как это всё работает.


Источники


  1. Attention mechanism in Neural Networks with Keras
  2. Attention in Deep Networks with Keras
  3. Attention-based Sequence-to-Sequence in Keras
  4. Text Classification using Attention Mechanism in Keras
  5. Pervasive Attention: 2D Convolutional Neural Networks for Sequence-to-Sequence Prediction
  6. How to implement the Attention Layer in Keras?
  7. Attention? Attention!
  8. Neural Machine Translation with Attention

© Habrahabr.ru