本文大部份內容翻譯自Illustrated Self-Attention, Step-by-step guide to self-attention with illustrations and code,僅用於學習,若有翻譯不當之處,敬請諒解!python
若是你在想Self-Attention(自注意力機制)是否和Attention(注意力機制)類似,那麼答案是確定的。它們本質上屬於同一個概念,擁有許多共同的數學運算。
一個Self-Attention模塊擁有n個輸入,返回n個輸出。這麼模塊裏面發生了什麼?從非專業角度看,Self-Attention(自注意力機制)容許輸入之間互相做用(「self」部分),尋找出誰更應該值得注意(「attention」部分)。輸出的結果是這些互相做用和注意力分數的聚合。算法
理解分爲如下幾步:網絡
key
,query
和value
;注意:實際上,這些數學運算都是向量化的,也就是說,全部的輸入都會一塊兒經歷這些數學運算。咱們將會在後面的代碼部分看到。
在這個教程中,咱們從3個輸入開始,每一個輸入的維數爲4。app
Input 1: [1, 0, 1, 0] Input 2: [0, 2, 0, 2] Input 3: [1, 1, 1, 1]
每一個輸入必須由三個表示(看下圖)。這些輸入被稱做key
(橙色),query
(紅色)value
(紫色)。在這個例子中,咱們假設咱們想要的表示維數爲3。由於每一個輸入的維數爲4,這就意味着每一個權重的形狀爲4×3。框架
注意:咱們稍後會看到
value
的維數也是output的維數。
爲了獲取這些表示,每一個輸入(綠色)會乘以一個權重的集合獲得keys
,乘以一個權重的集合獲得queries
,乘以一個權重的集合獲得values
。在咱們的例子中,咱們初始化三個權重的集合以下。
key
的權重:ide
[[0, 0, 1], [1, 1, 0], [0, 1, 0], [1, 1, 0]]
query
的權重:函數
[[1, 0, 1], [1, 0, 0], [0, 0, 1], [0, 1, 1]]
value
的權重:學習
[[0, 2, 0], [0, 3, 0], [1, 0, 3], [1, 1, 0]]
注意: 在神經網絡設置中,這些權重一般都是一些小的數字,利用隨機分佈,好比Gaussian, Xavier and Kaiming分佈,隨機初始化。在訓練開始前已經完成初始化。
key
,query
和value
; 如今咱們有了3個權重的集合,讓咱們來給每一個輸入獲取key
,query
和value
。
第1個輸入的key
表示:ui
[0, 0, 1] [1, 0, 1, 0] x [1, 1, 0] = [0, 1, 1] [0, 1, 0] [1, 1, 0]
利用相同的權重集合獲取第2個輸入的key
表示:spa
[0, 0, 1] [0, 2, 0, 2] x [1, 1, 0] = [4, 4, 0] [0, 1, 0] [1, 1, 0]
利用相同的權重集合獲取第3個輸入的key
表示:
[0, 0, 1] [1, 1, 1, 1] x [1, 1, 0] = [2, 3, 1] [0, 1, 0] [1, 1, 0]
更快的方式是將這些運算用向量來描述:
[0, 0, 1] [1, 0, 1, 0] [1, 1, 0] [0, 1, 1] [0, 2, 0, 2] x [0, 1, 0] = [4, 4, 0] [1, 1, 1, 1] [1, 1, 0] [2, 3, 1]
讓咱們用相同的操做來獲取每一個輸入的value
表示:
最後是query
的表示:
[1, 0, 1] [1, 0, 1, 0] [1, 0, 0] [1, 0, 2] [0, 2, 0, 2] x [0, 0, 1] = [2, 2, 2] [1, 1, 1, 1] [0, 1, 1] [2, 1, 3]
注意:實際上,一個偏重向量也許會加到矩陣相乘後的結果。
爲了獲取注意力分數,咱們從輸入1的query
(紅色)和全部keys
(橙色)的點積開始。由於有3個key
表示(這是因爲咱們有3個輸入),咱們獲得3個注意力分數(藍色)。
[0, 4, 2] [1, 0, 2] x [1, 4, 3] = [2, 4, 4] [1, 0, 1]
注意到咱們只用了輸入的query
。後面咱們會爲其餘的queries
重複這些步驟。
對這些注意力分數進行softmax函數運算(藍色部分)。
softmax([2, 4, 4]) = [0.0, 0.5, 0.5]
將每一個輸入(綠色)的softmax做用後的注意力分數乘以各自對應的value
(紫色)。這會產生3個向量(黃色)。在這個教程中,咱們把它們稱做權重化value
。
1: 0.0 * [1, 2, 3] = [0.0, 0.0, 0.0] 2: 0.5 * [2, 8, 0] = [1.0, 4.0, 0.0] 3: 0.5 * [2, 6, 3] = [1.0, 3.0, 1.5]
將權重後value
按元素相加獲得輸出1:
[0.0, 0.0, 0.0] + [1.0, 4.0, 0.0] + [1.0, 3.0, 1.5] ----------------- = [2.0, 7.0, 1.5]
產生的向量[2.0, 7.0, 1.5](暗綠色)就是輸出1,這是基於輸入1的query
表示與其它的keys
,包括它自身的key
互相做用的結果。
既然咱們已經完成了輸入1,咱們重複步驟4-7能獲得輸出2和3。這個能夠留給讀者本身嘗試,相信聰明的你能夠作出來。
這裏有PyTorch的實現代碼,PyTorch是一個主流的Python深度學習框架。爲了可以很好地使用代碼片斷中的@
運算符, .T
and None
操做,請確保Python≥3.6,PyTorch ≥1.3.1。
import torch x = [ [1, 0, 1, 0], # Input 1 [0, 2, 0, 2], # Input 2 [1, 1, 1, 1] # Input 3 ] x = torch.tensor(x, dtype=torch.float32)
w_key = [ [0, 0, 1], [1, 1, 0], [0, 1, 0], [1, 1, 0] ] w_query = [ [1, 0, 1], [1, 0, 0], [0, 0, 1], [0, 1, 1] ] w_value = [ [0, 2, 0], [0, 3, 0], [1, 0, 3], [1, 1, 0] ] w_key = torch.tensor(w_key, dtype=torch.float32) w_query = torch.tensor(w_query, dtype=torch.float32) w_value = torch.tensor(w_value, dtype=torch.float32)
key
,query
和value
keys = x @ w_key querys = x @ w_query values = x @ w_value print(keys) # tensor([[0., 1., 1.], # [4., 4., 0.], # [2., 3., 1.]]) print(querys) # tensor([[1., 0., 2.], # [2., 2., 2.], # [2., 1., 3.]]) print(values) # tensor([[1., 2., 3.], # [2., 8., 0.], # [2., 6., 3.]])
attn_scores = querys @ keys.T # tensor([[ 2., 4., 4.], # attention scores from Query 1 # [ 4., 16., 12.], # attention scores from Query 2 # [ 4., 12., 10.]]) # attention scores from Query 3
from torch.nn.functional import softmax attn_scores_softmax = softmax(attn_scores, dim=-1) # tensor([[6.3379e-02, 4.6831e-01, 4.6831e-01], # [6.0337e-06, 9.8201e-01, 1.7986e-02], # [2.9539e-04, 8.8054e-01, 1.1917e-01]]) # For readability, approximate the above as follows attn_scores_softmax = [ [0.0, 0.5, 0.5], [0.0, 1.0, 0.0], [0.0, 0.9, 0.1] ] attn_scores_softmax = torch.tensor(attn_scores_softmax)
weighted_values = values[:,None] * attn_scores_softmax.T[:,:,None] # tensor([[[0.0000, 0.0000, 0.0000], # [0.0000, 0.0000, 0.0000], # [0.0000, 0.0000, 0.0000]], # # [[1.0000, 4.0000, 0.0000], # [2.0000, 8.0000, 0.0000], # [1.8000, 7.2000, 0.0000]], # # [[1.0000, 3.0000, 1.5000], # [0.0000, 0.0000, 0.0000], # [0.2000, 0.6000, 0.3000]]])
outputs = weighted_values.sum(dim=0) # tensor([[2.0000, 7.0000, 1.5000], # Output 1 # [2.0000, 8.0000, 0.0000], # Output 2 # [2.0000, 7.8000, 0.3000]]) # Output 3
注意:PyTorch已經提供了這個API,名字爲
nn.MultiheadAttention
。可是,這個API須要你提供PyTorch的Tensor形式的key,value,query。還有,這個模塊的輸出會經歷一個線性變換。
如下是筆者本身寫的部分。
對於不熟悉PyTorch的讀者來講,上述的向量操做理解起來有點困難,所以,筆者本身用簡單的Python代碼實現了一遍上述Self-Attention的過程。
完整的Python代碼以下:
# -*- coding: utf-8 -*- from typing import List import math from pprint import pprint x = [[1, 0, 1, 0], # Input 1 [0, 2, 0, 2], # Input 2 [1, 1, 1, 1] # Input 3 ] w_key = [[0, 0, 1], [1, 1, 0], [0, 1, 0], [1, 1, 0] ] w_query = [[1, 0, 1], [1, 0, 0], [0, 0, 1], [0, 1, 1] ] w_value = [[0, 2, 0], [0, 3, 0], [1, 0, 3], [1, 1, 0] ] # vector dot of two vectors def vector_dot(list1: List[float or int], list2: List[float or int]) -> float or int: dot_sum = 0 for element_i, element_j in zip(list1, list2): dot_sum += element_i * element_j return dot_sum # get weights matrix by x, using matrix multiplication def get_weights_matrix_by_x(x, weight_matrix): x_matrix = [] for i in range(len(x)): x_row = [] for j in range(len(weight_matrix[0])): x_row.append(vector_dot(x[i], [_[j] for _ in weight_matrix])) x_matrix.append(x_row) return x_matrix # softmax function def softmax(x: List[float or int]) -> List[float or int]: x_sum = sum([math.exp(_) for _ in x]) return [math.exp(_)/x_sum for _ in x] x_key = get_weights_matrix_by_x(x, w_key) x_value = get_weights_matrix_by_x(x, w_value) x_query = get_weights_matrix_by_x(x, w_query) # print(x_key) # print(x_value) # print(x_query) outputs = [] for query in x_query: score_list = [vector_dot(query, key) for key in x_key] softmax_score_list = softmax(score_list) weights_list = [] for i in range(len(softmax_score_list)): weights = [softmax_score_list[i] * _ for _ in x_value[i]] weights_list.append(weights) output = [] for j in range(len(weights_list[0])): output.append(sum([_[j] for _ in weights_list])) outputs.append(output) pprint(outputs)
輸出結果以下:
[[1.9366210616669624, 6.683105308334811, 1.5950684074995565], [1.9999939663351456, 7.9639915951322156, 0.0539764053125496], [1.9997046127769653, 7.759892254657784, 0.3583892946751152]]
本文主要講述瞭如何一步一步來實現Self-Attention機制,對於想要本身實現算法的讀者來講,值得一讀。 本文分享到此結束,感謝你們的閱讀~