近期我會(huì)一連幾篇談?wù)刡ert中的關(guān)鍵細(xì)節(jié),這個(gè)position encoding是我看到的bert(實(shí)質(zhì)上是transformer中提出的)中最為驚喜的但是卻被很多人忽略(可以理解為媒體鼓吹最少的)一個(gè)細(xì)節(jié),這里給大家談?wù)劇?/span>
什么是position encoding
顧名思義,就是基于位置的一套詞嵌入方法,說(shuō)得簡(jiǎn)單點(diǎn),就是對(duì)于一個(gè)句子,都有對(duì)應(yīng)的一個(gè)向量。
position encoding的收益
我感覺(jué)要做一個(gè)事情,首先還是要看他的出發(fā)點(diǎn)和收益,說(shuō)白了就是優(yōu)點(diǎn)是啥,做這個(gè)的目標(biāo)是啥,這樣我們才知道怎么做。
回頭看看CNN結(jié)構(gòu)、RNN甚至是transformer的self-attention,其實(shí)都沒(méi)有特別關(guān)注位置信息,而實(shí)際上,我們卻是需要去關(guān)注的,畢竟作為一門(mén)語(yǔ)言,他大都有比較嚴(yán)謹(jǐn)?shù)恼Z(yǔ)法結(jié)構(gòu),特定詞匯還真的會(huì)出現(xiàn)在特定位置,這是非常有意思的,來(lái)看看例子(來(lái)源于知乎):
I like this movie because it doesn't have an overhead history.I don't like this movie because it has an overhead history.
從情感上,上面是正面,下面是負(fù)面,這個(gè)非常顯而易見(jiàn),因?yàn)檫@個(gè)否定句,從實(shí)體提取的角度都有movie和history,無(wú)論是哪個(gè)任務(wù),都可以看到一個(gè)語(yǔ)法結(jié)構(gòu)中存在的位置信息。
對(duì)CNN,只能考慮到固定前后的局部信息,RNN能考慮稍微長(zhǎng)期的信息,LSTM是有重點(diǎn)的記錄,Transformer只能考慮到全局的信息,尤其在bert中,只用了transformer encoder,模型上就完全喪失對(duì)位置信息的描述了,因此引入基于位置的特征就可能在特定任務(wù)中產(chǎn)生收益。
換個(gè)角度再看一個(gè)例子:
I believe I can be the best.
對(duì)于self attention,如果沒(méi)有positional encoding,兩個(gè)i的輸出將會(huì)一樣,但是我們知道,這兩個(gè)i是存在區(qū)別的,不是在指代上,而是含義上,第一個(gè)i是觀點(diǎn)的發(fā)出者,“不要你覺(jué)得,我要我覺(jué)得”,第二個(gè)i是觀點(diǎn)的對(duì)象,“認(rèn)為我會(huì)是最棒的,不是別人”,所以從語(yǔ)義上兩者就有所區(qū)別了,權(quán)重向量完全一樣可就有問(wèn)題了吧。這也是缺少位置信息的缺憾。
position embedding怎么做
首先,最簡(jiǎn)單的模式就是對(duì)詞向量矩陣直接加一層全連接層,就是全連接層。就真的是這么簡(jiǎn)單!
對(duì)于每個(gè)位置的詞向量,都穩(wěn)定的乘以一個(gè)穩(wěn)定的向量,就如上面所示,第1個(gè)位置一定對(duì)應(yīng)positonal embedding的第一個(gè)向量,那這組向量抽出來(lái),不是positional embedding是啥。
但當(dāng)然的,這里就有很大的問(wèn)題,那就是這只是絕對(duì)位置,看上面第一個(gè)例子(我再搬運(yùn)一遍):
I like this movie because it doesn't have an overhead history.I don't like this movie because it has an overhead history.
這里的like 和 don't like可就不是一個(gè)位置了吧,所以絕對(duì)位置肯定是有問(wèn)題的,那么就要引入相對(duì)位置的概念了。來(lái)看看transformer論文里面是怎么說(shuō)的(我把解釋也給大家搬過(guò)來(lái)了):
這里用的是兩種三角函數(shù),可以說(shuō)是非常巧妙了,我們來(lái)慢慢分析。上代碼!
import matplotlib.pyplot as plt
import math
def positional_enc(i,pos):
return math.sin(pos /10000**(i/100))
x = []
for idx in range(10):
tmp_x = list(range(1,100))
tmp_y = [positional_enc(i, idx) for i in tmp_x]
plt.plot(tmp_x,tmp_y,label=str(idx))
plt.legend(loc = 'upper right')
plt.show()
代碼跑出來(lái)是這樣的:
橫坐標(biāo)是維數(shù)上的每個(gè)值,縱坐標(biāo)是對(duì)應(yīng)的sin值,圖例對(duì)應(yīng)句子中的每個(gè)位置。
首先看維數(shù)位置-sin值之間的關(guān)系,很明顯,我們沒(méi)有發(fā)現(xiàn)周期性,最終往0處收斂,我們也可以知道了,在這種emcoding下,其實(shí)維數(shù)沒(méi)必要太高了。
而對(duì)于位置-sini值之間的關(guān)系,可以整個(gè)曲線是會(huì)朝著右邊移動(dòng)的,從權(quán)重角度看,實(shí)質(zhì)上就是每一個(gè)維度都會(huì)有一個(gè)比較看重的句子位置,其他位置說(shuō)白了就是不看了,而前面的甚至可能為負(fù),主要原因是要拋棄以前的信息,這樣多個(gè)維度就能把多個(gè)位置都當(dāng)做了重點(diǎn)來(lái)看。
周期性去了哪里呢,其實(shí)在這里,再來(lái)上代碼:
import matplotlib.pyplot as plt
import math
def positional_emb(i,pos):
return math.sin(pos /10000**(i/100))
tmp_x = list(range(20))
tmp_y = [positional_emb(10, i) for i in tmp_x]
plt.plot(tmp_x,tmp_y)
plt.show()
得到了有周期性的圖。
周期性只體現(xiàn)在位置和整個(gè)函數(shù)結(jié)果的關(guān)系,而具體的波長(zhǎng),其實(shí)是由positional encoding向量決定的。
不得不說(shuō),這個(gè)函數(shù)的設(shè)計(jì)可謂是對(duì)現(xiàn)實(shí)場(chǎng)景有了十分充分的理解,抽象非常精準(zhǔn)。
預(yù)測(cè)效果
首先來(lái)看看兩種positional encoding的具體效果,來(lái)自transformer的對(duì)比。
主要看E、base和big。其實(shí)可以看到posiitional emb本身的效果其實(shí)還行,與base相當(dāng),說(shuō)明還是有不小收益的。
來(lái)看看源碼
原理是看完了,來(lái)看看源碼吧。
def positional_encoding(inputs,
maxlen,
masking=True,
scope="positional_encoding"):
'''Sinusoidal Positional_Encoding. See 3.5
inputs: 3d tensor. (N, T, E)
maxlen: scalar. Must be >= T
masking: Boolean. If True, padding positions are set to zeros.
scope: Optional scope for `variable_scope`.
returns
3d tensor that has the same shape as inputs.
'''
E = inputs.get_shape().as_list()[-1] # static
N, T = tf.shape(inputs)[0], tf.shape(inputs)[1] # dynamic
with tf.variable_scope(scope, reuse=tf.AUTO_REUSE):
# position indices
position_ind = tf.tile(tf.expand_dims(tf.range(T), 0), [N, 1]) # (N, T)
# First part of the PE function: sin and cos argument
position_enc = np.array([
[pos / np.power(10000, (i-i%2)/E) for i in range(E)]
for pos in range(maxlen)])
# Second part, apply the cosine to even columns and sin to odds.
position_enc[:, 0::2] = np.sin(position_enc[:, 0::2]) # dim 2i
position_enc[:, 1::2] = np.cos(position_enc[:, 1::2]) # dim 2i+1
position_enc = tf.convert_to_tensor(position_enc, tf.float32) # (maxlen, E)
# lookup
outputs = tf.nn.embedding_lookup(position_enc, position_ind)
# masks
if masking:
outputs = tf.where(tf.equal(inputs, 0), inputs, outputs)
return tf.to_float(outputs)
本身公式上沒(méi)有想象的復(fù)雜,但是這里面其實(shí)展現(xiàn)了很多python相關(guān)的技巧。
這里的計(jì)算并非全都使用的tf,對(duì)positionenc,前面用numpy進(jìn)行計(jì)算,然后用embeddinglookup的方式引入。
position_enc[:,0::2]和position_enc[:,1::2]來(lái)自numpy語(yǔ)法,避免了寫(xiě)循環(huán)和條件語(yǔ)句就能夠完成奇數(shù)偶數(shù)計(jì)算。
另外是有很多可能在各種教材或者教程中沒(méi)有的函數(shù)工具,大家可以多看看學(xué)學(xué)。
tf.AUTO_REUSE:批量化共享變量作用域的方法。
tf.tile():張量擴(kuò)展,對(duì)當(dāng)前張量?jī)?nèi)的數(shù)據(jù)進(jìn)行一定規(guī)則的復(fù)制,保證輸出張量維度不變。
責(zé)任編輯:xj
原文標(biāo)題:bert之我見(jiàn) - positional encoding
文章出處:【微信公眾號(hào):深度學(xué)習(xí)自然語(yǔ)言處理】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
-
深度學(xué)習(xí)
+關(guān)注
關(guān)注
73文章
5512瀏覽量
121404 -
自然語(yǔ)言
+關(guān)注
關(guān)注
1文章
290瀏覽量
13383 -
nlp
+關(guān)注
關(guān)注
1文章
489瀏覽量
22068
原文標(biāo)題:bert之我見(jiàn) - positional encoding
文章出處:【微信號(hào):zenRRan,微信公眾號(hào):深度學(xué)習(xí)自然語(yǔ)言處理】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論