本人有關自然語言處理和文本挖掘方面的學習和筆記,歡迎大家關注。
往期回顧:
我想現在NLP領域中,不知道bert的已經少之又少了,而bert的講解文章也已經有了很多,這裡我談一下我最近學習得到的理解。事先說明,對bert和transformer完全不懂的人看這個完全不知道咋回事的,想要看下面這些請先對這兩個玩意有初步的理解。(風格依舊,不會重複別人寫過的東西)
今天給大家談的是bert中的attention,通篇可能不會有太多篇幅對著bert講,而是把attention講懂,然後再去看bert中的attention幹了什麼,這樣大家能對bert中的attention,甚至整個注意力機制有更加深刻的理解。
從機器翻譯開始bert的核心之一在於使用了transformer中的encoder,而transformer的架構則來源於機器翻譯中的seq2seq,因此,要完全理解bert,還是要從機器翻譯開始理解。
首先我們看seq2seq。用圖來說比較容易,簡單地,由於我們只要知道基本結構,所以用RNN來解釋更為合適。
從上面的圖其實可以看到,整個seq2seq其實就是一個encoder-decoder的模式,這個就和transformer很像了,這個就是目前機器翻譯目前的一套主流架構。Encoder負責將原始信息進行編碼匯總,整理成模型能夠理解信息,後續如果有了attention之後還能提取關鍵型信息;Decoder則是將信息整合,輸出翻譯結果。
attention機制我認為attention機制談的最清楚的應該是張俊林在2017年寫的《深度學習中的注意力模型》,據說被刊登在《程式設計師》上了,很厲害的亞子。
首先,大家這麼理解,何為注意力,在模型上,大家可以理解為,詞彙比較關鍵的對應位置,權重會比較高,相反不重要的位置權重就比較低。深入地,這個重要性的衡量,在機器翻譯裡,是依賴於翻譯結果中對應的位置的,例如現在翻譯到了一個名詞的位置,那重要性更高的應該就是原句中名詞的部分,因此對於預測句子中的每個的位置,其實都應該有這個位置針對原句所有詞彙的重要性衡量。
按照RNN的邏輯,預測應該是這樣預測的,每個預測點與前面的位置有關,而且在這裡看來是平權的,即C是固定的沒有重點的:
而如果是注意力機制,那就會是這樣的,C是變化的:
至於這個C1,C2,C3是怎麼來的,看這個:
Y1有自己的C1,Y2有自己的C2,於是就造就了注意力機制。
那麼下一個問題就是,怎麼去構造這個根據位置變化的權重向量C了。來看看這個圖:
我對於特定詞彙位置附近的詞進行attention計算,這裡使用的是RNN的輸出,用這個輸出計算了Attention scores之後進行歸一化形成分布。然後我們來看看公式的描述吧。
我們直接先從Decoder的隱含層公式看一下吧。
第i個位置的隱含層的輸出和前一個位置的隱含層輸出、前一個位置的預測結果以及encoder結果結合,然後我們從這個ci往前推。encoder的結果是基於attenton結果導出的權重向量以及encoder的隱含層向量求得的,可以理解為一個加權求和,所以是這樣的:
h是encoder的隱含層向量,這個就與你選用的模型有關了,所以問題就落到了這個alpha上了。然後我們知道這個alpha實質上是一個標準化向量,所以裡面肯定是包裹了一層標準化函數的,所以是這樣的:
一層一層解剖下來,就到了這個e的頭上了,值得注意的是,這裡面需要區分開e對應的兩個下角標,前者是decoder對應的位置,後者是encoder對應的位置。所以問題就到了這個e上了。
首先根據attention定義,對decoder特定位置衡量encoder各個位置的重要性,到了這裡其實就是decoder和encoder之間的相關性了,當然的越相關這裡就越重要對吧,所以說白了就是衡量相關性,硬要嚴謹一些,其實就是去構造兩者的一個得分函數。
這麼看說白了還是相似度吧。這個相似度描述其實就回到了很原始的幾大相似度衡量模型了,此處就不多談啦:
回過頭來,總結一下Attention的思想,就成了這樣:
衡量輸入和輸出兩者的相似度作為權重,做隱含層的加權平均,就這麼簡單。來看個直觀點的圖吧,這麼看大家是不是就知道怎麼回事了:
這裡就引出了attention的三個重要角色,query、key、value,query是原句,key是翻譯句,value是隱含層向量。後續討論attention模型,就只需要搞清楚這三個是啥,這個模型你就理解了一大半了(額,其實我倒是感覺很多文章裡反而沒在各種應用,包括self attention,裡面把這三個角色分別是什麼說清楚)。
TransformerTransformer就是BERT發明的一大功臣,這裡面,實際上就是使用了self-attetion,即自注意力機制。
何為自注意力機制,就是自己對自己,這個非常好理解,但是,自己對自己裡面的計算又是什麼樣的,大家有仔細想過嗎?是每個位點自己對自己,還是自己這句對應自己這句?很明顯,是後面的,用機器翻譯的方式理解,attention說白了就是把輸入句和輸出句都當做是自己,那麼這裡計算的重要性權重,就是每個單詞在整個句子中的重要性了(我的天這不就是term weighting嗎?)
然後現在回頭來看,k、q、v就很明確了。
k、q、v對應的其實都是一套,而不是一個,都是一個向量空間裡面的,只不過計算的時候取的不是一個位點而已。
這裡也可以看到,大家理解了k、q、v之後,attention模型的應用你就非常明白了。
這裡也告訴大家一個看k、q、v很快的技巧,那就是——看!源!碼!
tranformer的源碼中(https://github.com/Kyubyong/transformer/blob/master/model.py),對encodeer的attentiion是這樣的,非常一目了然。
enc = multihead_attention(queries=enc,
keys=enc,
values=enc,
key_masks=src_masks,
num_heads=self.hp.num_heads,
dropout_rate=self.hp.dropout_rate,
training=training,
causality=False)
而decoder的是這樣的。
dec = multihead_attention(queries=dec,
keys=dec,
values=dec,
key_masks=tgt_masks,
num_heads=self.hp.num_heads,
dropout_rate=self.hp.dropout_rate,
training=training,
causality=True,
scope="self_attention")
# Vanilla attention
dec = multihead_attention(queries=dec,
keys=memory,
values=memory,
key_masks=src_masks,
num_heads=self.hp.num_heads,
dropout_rate=self.hp.dropout_rate,
training=training,
causality=False,
scope="vanilla_attention")
可以看到這裡整了兩次,而這兩者的輸入是不同的,每層的decoder裡面實際上有兩個attention,第一個很明顯就是self-attention了,第二個的key和values是memory,至於這個memory是什麼,我們往前看。
# memory: encoder outputs. (N, T1, d_model)
這句話就在decoder的函數定義下的一行注釋裡,看到這個完全足夠了。由此你其實就非常明白transformer的attention機制是怎麼用的了,看看這圖是不是匹配的,而裡面怎麼整的是不是也更清楚了。
bert中的attention終於談到bert了,這裡就可以開始談bert中的attention了,這裡用源碼來講更清楚,實際上,我們關注的就是這個代碼塊:
self.all_encoder_layers = transformer_model(
input_tensor=self.embedding_output,
attention_mask=attention_mask,
hidden_size=config.hidden_size,
num_hidden_layers=config.num_hidden_layers,
num_attention_heads=config.num_attention_heads,
intermediate_size=config.intermediate_size,
intermediate_act_fn=get_activation(config.hidden_act),
hidden_dropout_prob=config.hidden_dropout_prob,
attention_probs_dropout_prob=config.attention_probs_dropout_prob,
initializer_range=config.initializer_range,
do_return_all_layers=True)
它實際上就是引入了一個transformer_model。那麼transformer裡面有啥呢,繼續看:
attention_head = attention_layer(
from_tensor=layer_input,
to_tensor=layer_input,
attention_mask=attention_mask,
num_attention_heads=num_attention_heads,
size_per_head=attention_head_size,
attention_probs_dropout_prob=attention_probs_dropout_prob,
initializer_range=initializer_range,
do_return_2d_tensor=True,
batch_size=batch_size,
from_seq_length=seq_length,
to_seq_length=seq_length)
不多放,大部分代碼都是才處理各種輸入和輸出的參數,實質上我們就關注attention,它的應用就在這裡(這裡是構造multi-head attention中的其中一個)。於是我們就要看這個attention_layer是什麼了。可以看到,他這裡並沒有直接給出q、k、v是什麼,所以我們還要繼續往裡面去深挖。
# `query_layer` = [B*F, N*H]
query_layer = tf.layers.dense(
from_tensor_2d,
num_attention_heads * size_per_head,
activation=query_act,
name="query",
kernel_initializer=create_initializer(initializer_range))
# `key_layer` = [B*T, N*H]
key_layer = tf.layers.dense(
to_tensor_2d,
num_attention_heads * size_per_head,
activation=key_act,
name="key",
kernel_initializer=create_initializer(initializer_range))
# `value_layer` = [B*T, N*H]
value_layer = tf.layers.dense(
to_tensor_2d,
num_attention_heads * size_per_head,
activation=value_act,
name="value",
kernel_initializer=create_initializer(initializer_range))
找到了函數裡的這個,可以看到的是query用的是fromtensor2d,key和value用的是totensor2d,那我們回過頭來看這兩個是啥,其實就能看到他們都是layer_input,說白了就哈市self attention,而且沒有別的attention結構了,這也就印證了bert中的用的就是transformer中的encoder。
attention源碼然後我們來看看attention的源碼吧其實不是很長:
def scaled_dot_product_attention(Q, K, V, key_masks,
causality=False, dropout_rate=0.,
training=True,
scope="scaled_dot_product_attention"):
'''See 3.2.1.
Q: Packed queries. 3d tensor. [N, T_q, d_k].
K: Packed keys. 3d tensor. [N, T_k, d_k].
V: Packed values. 3d tensor. [N, T_k, d_v].
key_masks: A 2d tensor with shape of [N, key_seqlen]
causality: If True, applies masking for future blinding
dropout_rate: A floating point number of [0, 1].
training: boolean for controlling droput
scope: Optional scope for `variable_scope`.
'''
with tf.variable_scope(scope, reuse=tf.AUTO_REUSE):
d_k = Q.get_shape().as_list()[-1]
# dot product
outputs = tf.matmul(Q, tf.transpose(K, [0, 2, 1])) # (N, T_q, T_k)
# scale
outputs /= d_k ** 0.5
# key masking
outputs = mask(outputs, key_masks=key_masks, type="key")
# causality or future blinding masking
if causality:
outputs = mask(outputs, type="future")
# softmax
outputs = tf.nn.softmax(outputs)
attention = tf.transpose(outputs, [0, 2, 1])
tf.summary.image("attention", tf.expand_dims(attention[:1], -1))
# # query masking
# outputs = mask(outputs, Q, K, type="query")
# dropout
outputs = tf.layers.dropout(outputs, rate=dropout_rate, training=training)
# weighted sum (context vectors)
outputs = tf.matmul(outputs, V) # (N, T_q, d_v)
return outputs
點乘等各種操作,注釋其實寫的很好了,大家根據代碼翻譯為公式。
參考文獻CS224N,Lecture Notes: Part VI, Neural Machine Translation, Seq2seq and Attention.
張俊林,深度學習中的attention機制:https://zhuanlan.zhihu.com/p/37601161
Attention機制詳解(二)——Self-Attention與Transformer:https://zhuanlan.zhihu.com/p/47282410
注意力機制在自然語言處理中的應用:https://www.cnblogs.com/robert-dlut/p/5952032.html
一文讀懂bert(原理篇):https://blog.csdn.net/sunhua93/article/details/102764783
【NLP】徹底搞懂BERT:https://www.cnblogs.com/rucwxb/p/10277217.html
transformer源碼:https://github.com/Kyubyong/transformer
bert源碼:https://github.com/google-research/bert