關(guān)鍵詞:Transformer
因妇,位置編碼
內(nèi)容提要
- 位置編碼的目的
- 位置編碼的多種方式
- 從代碼理解sin-cos位置編碼特性
- sin-cos位置編碼如何表達(dá)相對位置信息
位置編碼的目的
注意力Attention這種操作具有排列不變性伐债,輸入元素位置的變動不會對注意力結(jié)果產(chǎn)生影響,從而模型無法感知位置信息,而在自然語言處理場景,字/詞的順序位置關(guān)系信息尤為重要,同樣的字詞不同的順序可能導(dǎo)致句子的語言完全不一樣。
Transformer中采用Self-Attention羽资,每個詞和整個句子所有詞兩兩一對計算相似度權(quán)重,詞與詞的位置隨意變動一下不會導(dǎo)致最終計算該對詞的注意力權(quán)重產(chǎn)生變化瓮栗,進(jìn)一步導(dǎo)致注意力層的整個結(jié)果不變削罩,嚴(yán)格來說是結(jié)果中每一個詞向量計算完全一致,僅僅是詞向量在輸出矩陣的排列隨著詞和詞位置互換而對應(yīng)調(diào)整了一下费奸,舉個例子
[ [0.3, 0.2, 0.1, 0.5]
[我弥激,愛,你] => self attention layers => [0.1, -0.1, -0.2, 0.3],
[0.5, 0.5, 0.1, -0.1] ]
[ [0.5, 0.5, 0.1, -0.1]
[你愿阐,愛微服,我] => self attention layers => [0.1, -0.1, -0.2, 0.3],
[0.3, 0.2, 0.1, 0.5] ]
將[我,愛缨历,你]輸入一組Q以蕴,K,V組成的Self-Attention產(chǎn)出的向量辛孵,和[你丛肮,愛,我] 輸入同一組Q魄缚,K宝与,V組成的Self-Attention產(chǎn)出的向量焚廊,兩者的結(jié)果每個詞/字的embedding輸出一致,僅僅是在矩陣的位置調(diào)換了一下(1和3對調(diào))习劫。
而此時如果下游網(wǎng)絡(luò)是聚合池化操作咆瘟,則池化后的結(jié)果完全一致,徹底失去位置信息诽里,比如transformer之后使用均值池化去做文本分類袒餐,如果不做池化做flatten輸出,也僅僅是特征列和特征列交換位置谤狡,對下游是全連接Dense結(jié)構(gòu)而言是一致的效果灸眼。
因此需要引入位置編碼表達(dá)出每個句子中字/詞的位置信息,配合字/詞本身的embedding一起加入模型進(jìn)行訓(xùn)練豌汇。
位置編碼的多種方式
位置編碼是需要設(shè)計的幢炸,主要有絕對位置編碼和相對位置編碼
- 絕對位置編碼:在輸入層做文章泄隔,為每個輸入的字/詞增加一個對應(yīng)位置編碼拒贱,該位置編碼只與位置k相關(guān),每個字/詞的輸入是自身編碼和位置編碼的融合佛嬉。絕對位置編碼包括可學(xué)習(xí)式逻澳,固定式等方法,比如BERT暖呕、GPT采用可學(xué)習(xí)式將位置編碼當(dāng)作可訓(xùn)練參數(shù)斜做,本文的Transformer采用的是無參數(shù)的固定式三角函數(shù)計算。
- 相對位置編碼:在模型網(wǎng)絡(luò)層做文章湾揽,使得模型的Self-Attention能夠考慮詞和詞之間的相對距離瓤逼,而非每個詞都單獨(dú)標(biāo)注位置和距離,讓模型通過數(shù)據(jù)自己學(xué)習(xí)位置信息库物。
本文主要介紹Transformer中的sin-cos位置編碼
從代碼理解sin-cos位置編碼
先給到Transformer的sin-cos位置編碼公式
其中pos代表句子中詞的位置霸旗,2i或者2i+1代表位置編碼向量的一個分量,2i代表偶數(shù)戚揭,2i+1代表奇數(shù)诱告,由此可見某個詞的位置編碼是一個向量不是一個值,它由詞的位置民晒,以及分量位置兩個共同決定的精居。
進(jìn)一步看代碼實現(xiàn),從結(jié)果看sin-cos位置編碼在做什么
def GetPosEncodingMatrix(max_len, d_emb):
# 位置編碼
pos_enc = np.array([
[pos / np.power(10000, 2 * (j // 2) / d_emb) for j in range(d_emb)]
if pos != 0 else np.zeros(d_emb)
for pos in range(max_len)
])
pos_enc[1:, 0::2] = np.sin(pos_enc[1:, 0::2]) # dim 2i
pos_enc[1:, 1::2] = np.cos(pos_enc[1:, 1::2]) # dim 2i+1
return pos_enc
設(shè)置max_len為每個句子的最大長度為50潜必,d_emb為每個詞的embedding的維度為256靴姿,最終得到一個[50, 256]的位置編碼矩陣,每一行代表一個位置的位置編碼結(jié)果磁滚,每一列代表某個詞在某個位置編碼分量上的值佛吓。所有句子共享這個位置編碼矩陣,也就是說所有句子相同位置的字/詞的位置編碼結(jié)果是一致的,位置編碼矩陣?yán)锩婷總€值用熱力圖畫出來如下
進(jìn)一步深入代碼辈毯,我們重新設(shè)置參數(shù)max_len=6坝疼,d_emb=4把計算結(jié)果全部列出來觀察
其中矩陣第一行是全0向量,當(dāng)輸入序列的元素id是0時直接映射為這個全0向量來表示位置谆沃,我們進(jìn)一步結(jié)合源碼看下原始序列id映射為位置矩陣的代碼
class PosEncodingLayer:
def __init__(self, max_len, d_emb):
# 位置編碼不跟隨訓(xùn)練
self.pos_emb_matrix = Embedding(max_len, d_emb, trainable=False, weights=[GetPosEncodingMatrix(max_len, d_emb)])
def get_pos_seq(self, x):
mask = K.cast(K.not_equal(x, 0), 'int32')
# 這個cumsum在拿pos位置
pos = K.cumsum(K.ones_like(x, 'int32'), 1)
return pos * mask
def __call__(self, seq, pos_input=False):
x = seq
if not pos_input:
# 排除掉padding的钝凶,padding的置為0
x = Lambda(self.get_pos_seq)(x)
return self.pos_emb_matrix(x)
其中K.cumsum在將所有序列元素從1開始自增轉(zhuǎn)化為數(shù)字id,同時mask判斷得到padding為0的位置唁影,將K.cumsum的結(jié)果改為0耕陷,因此輸入序列id中所有padding位置都以全0向量作為位置編碼。
除了第一行以外据沈,位置矩陣的所有值都是三角函數(shù)sin哟沫,cos的結(jié)果,因此所有位置和各分量上的結(jié)果都是介于-1到1之間的锌介,使得位置編碼值固定在一個區(qū)間內(nèi)不會太大或者太小嗜诀,從而使得位置編碼和詞原始embedding相加存在可行性。
進(jìn)一步橫向觀察表格孔祸,每個分量上是sin隆敢,cos輪流交替的,一對相鄰的偶數(shù)和奇數(shù)分量形成一對崔慧,該對的三角函數(shù)輸入相同拂蝎,每一行的POS相同,因此三角函數(shù)輸入的分母相同惶室,差異在分母温自。
再從縱向看,每個分量位置相同皇钞,因此差異在三角函數(shù)的輸入分子悼泌,此時POS不同,POS不斷自增加1鹅士。
總結(jié)
- 位置編碼將詞的位置信息表征為向量券躁,該向量由詞位置和分量位置共同確定
- 位置編碼矩陣所有句子共享,因此不同句子第n個詞的位置編碼結(jié)果相同
- 對輸入padding的位置采用全0向量作為位置編碼
- 采用sin-cos位置編碼保證了位置向量值在-1~1之間掉盅,穩(wěn)定可控
- 在單個詞的位置向量上也拜,sin和cos形成一對交替出現(xiàn)
- 所有POS相同分量上的三角函數(shù)相同(偶數(shù)都是sin,奇數(shù)都是cos)趾痘,差別僅是POS值不同
sin-cos位置編碼如何表達(dá)相對位置距離
從上一節(jié)僅能看出sin-cos位置編碼能夠?qū)ξ恢貌煌M(jìn)行差異化刻畫慢哈,同時輸出結(jié)果穩(wěn)定在-1到1之間,使得和原始embedding相加存在可行性永票,并沒有什么了不起的地方卵贱,實際上由于三角函數(shù)公式的特性滥沫,sin-cos位置編碼具有表達(dá)相對位置的能力,給定距離键俱,任意位置的位置編碼都可以表達(dá)為一個已知位置的位置編碼的關(guān)于距離的線性組合兰绣。
三角函數(shù)具有如下特性
令一個已知位置pos,要表達(dá)距離該pos距離為k的另一個pos的位置編碼编振,套用三角函數(shù)公式如下
求解這組權(quán)重u缀辩,v即可通過已知pos的位置編碼計算出距離k的位置編碼
下面舉例進(jìn)行說明
1.通過位置2計算出位置3的位置編碼
根據(jù)公式,POS=3位置的第i=0的分量可以由POS=2位置的第i=0和i=1兩個結(jié)果的線性組合而計算得到
>>> # 線性組合的結(jié)果
>>> np.cos(1) * np.sin(2 / np.power(10000, 0 / 4)) + np.sin(1) * np.cos(2 / np.power(10000, 0 / 4))
0.1411200080598673
>>> # 直接生成的結(jié)果
>>> np.sin(3 / np.power(10000, 0 / 4))
0.1411200080598672
其中u=cos(1)踪央,v=sin(1)臀玄,線性組合的結(jié)果和直接生成的結(jié)果相同。
同理看一下POS=3的第2個位置分量
>>> np.cos(1 / 10000 ** (2 / 4)) * np.sin(2 / np.power(10000, 2 / 4)) + np.sin(1 / 10000 ** (2 / 4)) * np.cos(2 / np.power(10000, 2 / 4))
0.02999550020249566
>>> np.sin(3 / np.power(10000, 2 / 4))
0.02999550020249566
2.通過位置2計算出位置4的位置編碼
>>> np.cos(1 / 10000 ** (2 / 4) * 2) * np.sin(2 / np.power(10000, 2 / 4)) + np.sin(1 / 10000 ** (2 / 4) * 2) * np.cos(2 / np.power(10000, 2 / 4))
0.03998933418663416
>>> np.sin(4 / np.power(10000, 2 / 4))
0.03998933418663416
計算結(jié)果一致畅蹂,結(jié)論是sin-cos這種位置編碼方式健无,任意位置的位置編碼都可以表達(dá)為一個已知位置的位置編碼的關(guān)于距離的線性組合。也是因為有這個特質(zhì)采用三角函數(shù)表征位置信息液斜,同時由于padding的詞不需要存在這種相對位置表達(dá)性質(zhì)累贤,因此對padding的位置向量做了全0處理。