потому что Внимание - все, что вам нужно, буквально!

Одним из важных свойств человеческого восприятия является то, что человек не стремится обрабатывать всю сцену сразу целиком. Вместо этого люди выборочно фокусируют внимание на частях визуального пространства для получения информации, когда и где это необходимо, и объединяют информацию от различных фиксаций с течением времени, чтобы создать внутреннее представление сцены, управляя будущими движениями глаз и принятием решений - Повторяющийся Модели зрительного внимания , 2014

В этом посте я покажу вам, как реализовано внимание. Основное внимание будет уделяться реализации внимания изолированно от более крупной модели. Это потому, что, когда мы реализуем внимание в реальной модели, много внимания уделяется передаче данных и манипулированию многочисленными векторами, а не самими концепциями внимания.

Я буду реализовывать оценку внимания, а также вычислять вектор контекста внимания.

Подсчет внимания:

Давайте начнем с рассмотрения входных данных, которые мы дадим функции оценки. Предположим, мы находимся на первом этапе этапа декодирования. Первым входом в функцию подсчета очков является скрытое состояние декодера (при условии, что игрушечная RNN с тремя скрытыми узлами - непригодна для использования в реальной жизни, но ее легче проиллюстрировать)

dec_hidden_state = [5,1,20]

Представим себе этот вектор:

%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

Давайте визуализируем скрытое состояние нашего декодера:

plt.figure(figsize=(1.5, 4.5))
sns.heatmap(np.transpose(np.matrix(dec_hidden_state)), annot=True, cmap=sns.light_palette(“purple”, as_cmap=True), linewidths=1)

Вы получите что-то вроде этого:

Наша первая функция оценки оценивает одну аннотацию (скрытое состояние кодировщика), которая выглядит следующим образом:

annotation = [3,12,45] #e.g. Encoder hidden state

Представим себе единственную аннотацию:

plt.figure(figsize=(1.5, 4.5))
sns.heatmap(np.transpose(np.matrix(annotation)), annot=True, cmap=sns.light_palette(“orange”, as_cmap=True), linewidths=1)

ВНЕДРЕНИЕ: Оценка отдельной аннотации

Давайте вычислим скалярное произведение одной аннотации.

NumPy's dot () - хороший кандидат для этой операции.

def single_dot_attention_score(dec_hidden_state, enc_hidden_state):
 #return the dot product of the two vectors
 return np.dot(dec_hidden_state, enc_hidden_state)
 
single_dot_attention_score(dec_hidden_state, annotation)

Результат: 927

Матрица аннотаций

Давайте теперь посмотрим, как оценивать сразу все аннотации. Для этого воспользуйтесь нашей матрицей аннотаций:

annotations = np.transpose([[3,12,45], [59,2,5], [1,43,5], [4,3,45.3]])

И это можно визуализировать так (каждый столбец - это скрытое состояние временного шага кодировщика):

ax = sns.heatmap(annotations, annot=True, cmap=sns.light_palette(“orange”, as_cmap=True), linewidths=1)

ВНЕДРЕНИЕ: оценка всех аннотаций одновременно

Давайте посчитаем оценки всех аннотаций за один шаг, используя матричное умножение. Давайте продолжим использовать метод точечной оценки, но для этого нам нужно будет транспонировать dec_hidden_state и матрично умножить его с аннотациями.

def dot_attention_score(dec_hidden_state, annotations):
 # return the product of dec_hidden_state transpose and enc_hidden_states
 return np.matmul(np.transpose(dec_hidden_state), annotations)
 
attention_weights_raw = dot_attention_score(dec_hidden_state, annotations)
attention_weights_raw

Теперь, когда у нас есть оценки, применим softmax:

def softmax(x):
 x = np.array(x, dtype=np.float128)
 e_x = np.exp(x)
 return e_x / e_x.sum(axis=0)
attention_weights = softmax(attention_weights_raw)
attention_weights

Повторное нанесение оценок на аннотации

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

def apply_attention_scores(attention_weights, annotations):
 # Multiple the annotations by their weights
 return attention_weights * annotations
applied_attention = apply_attention_scores(attention_weights, annotations)
applied_attention

Давайте визуализируем, как выглядит вектор контекста теперь, когда мы снова применили к нему оценки внимания:

# Let’s visualize our annotations after applying attention to them
ax = sns.heatmap(applied_attention, annot=True, cmap=sns.light_palette(“orange”, as_cmap=True), linewidths=1)

Сравните это с необработанными аннотациями, визуализированными ранее, и мы увидим, что вторая и третья аннотации (столбцы) почти уничтожены. Первая аннотация сохраняет часть своего значения, а четвертая аннотация наиболее ярко выражена.

Вычисление вектора контекста внимания

Все, что осталось для создания нашего вектора контекста внимания, - это суммировать четыре столбца, чтобы получить единый вектор контекста внимания.

def calculate_attention_vector(applied_attention):
 return np.sum(applied_attention, axis=1)
attention_vector = calculate_attention_vector(applied_attention)
attention_vector
# Let’s visualize the attention context vector
plt.figure(figsize=(1.5, 4.5))
sns.heatmap(np.transpose(np.matrix(attention_vector)), annot=True, cmap=sns.light_palette(“Blue”, as_cmap=True), linewidths=1)

Теперь, когда у нас есть вектор контекста, мы можем объединить его со скрытым состоянием и передать его через скрытый слой, чтобы предоставить результат этого временного шага декодирования.

Итак, в этом сообщении в блоге мы узнали все об оценке внимания, оценке отдельных и всех аннотаций, матрице аннотаций, применении оценок к аннотациям. Я надеюсь, что выделение реализации внимания из более крупной модели сделало концепции внимания немного более ясными.

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