摘要
背景我們已經介紹了,現在我們上篇文章的基礎上面引入比較流行的Attention機制
說下本篇文章的貢獻:
- image captioning中使用同一種框架引入兩種atttention機制。
- 可以洞察模型觀察的點在哪裏where, 以及觀察的是什麼what
- 代碼我只會演示第二種attention 機制
模型
image encoder
第一層還是卷積層來處理圖像信息,但是這裏不同的是,我們不像上一篇提到的那樣直接複用已有的模型,這裏直接處理原始的圖片。
爲什麼要處理原始圖片?因爲如果要做attention,那麼勢必要在decoder階段需要知道聚焦在圖片的哪個位置,這樣我們就不能直接用encoder出來的很高級的單向量了
需要抽取出來一些原始的特徵,每個特徵能夠表徵圖像的某一部分,這樣在做decoder的時候,attention機制可以知道可以聚焦在哪一塊,這樣就提高了decoder描述的準確性
假設我們處理圖片後生成L=196個D=512維的向量:
a=(a1,...,aL),ai∈RD decoder
主要框架我們還是用LSTM,爲了引入attention,我們稍微做下變形,就是在原有的state基礎上面再增加一個圖片的content的信息
假設需要decoder的序列爲:經典的LSTM結構:
it ft ot gt Ct ht=σ(wi.[Eyt−1,ht−1]+bi)=σ(wf.[Eyt−1,ht−1]+bf)=σ(wo.[Eyt−1,ht−1]+bo)=tanh(wc.[Eyt−1,ht−1]+bc)=ft∗Ct−1+it∗gt=ot∗tanh(Ct) _, (c, h) = lstm_cell(inputs=x, axis=1), state=[c, h])
調整後:
it ft ot gt Ct ht =σ(wi.[Eyt−1,ht−1,ẑ t]+bi)=σ(wf.[Eyt−1,ht−1,ẑ t]+bf)=σ(wo.[Eyt−1,ht−1,ẑ t]+bo)=tanh(wc.[Eyt−1,ht−1,ẑ t]+bc)=ft∗Ct−1+it∗gt=ot∗tanh(Ct) _, (c, h) = lstm_cell(inputs=tf.concat([x, context], axis=1), state=[c, h])
ẑ t 就是我們的content
後面文中描述的content就是指這個向量,它代表的是我們decoder到目前這個詞,應該聚焦的圖片信息的哪塊內容。
對比兩組公式可以直接看出,在每一個計算input是原始的input x增加了content , 那麼這樣的content是怎麼生成?以下的計算邏輯和機器翻譯seq2seq的attention模型計算類似,
顯現需要聚焦的區域向量爲
a=(a1,...,aL)
當前hidden output爲:ht−1
針對每一個ai 我們會得到一個它和當前的output的相關性分數
eti=fatt(ai,ht−1) w = tf.get_variable('w', [self.H, self.D], initializer=self.weight_initializer) b = tf.get_variable('b', [self.D], initializer=self.const_initializer) w_att = tf.get_variable('w_att', [self.D, 1], initializer=self.weight_initializer) # features_proj=a_i, h = h_t-1 h_att = tf.nn.relu(features_proj + tf.expand_dims(tf.matmul(h, w), 1) + b) # (N, L, D) out_att = tf.reshape(tf.matmul(tf.reshape(h_att, [-1, self.D]), w_att), [-1, self.L])
這裏
fatt 是一個多層感知機層的attention 模型
然後得到softmax 分數
αti=exp(eti)∑Lk=1exp(etk) alpha = tf.nn.softmax(out_att)
這樣我們可以得到內容向量:
ẑ t=ϕ({ai},{αi}) 這裏
ϕ 將多向量轉化成單向量的函數,具體怎麼計算我們下節介紹:然後是初始的
c0,h0 , 他們是a=(a1,a2,...,aL) 的向量平均分別經過多個線性層
c0 h0=finit,c(1N∑iαi)=finit,h(1N∑iαi) with tf.variable_scope('initial_lstm'): # [batch_size, dim_D] features_mean = tf.reduce_mean(features, 1) w_h = tf.get_variable('w_h', [self.D, self.H], initializer=self.weight_initializer) b_h = tf.get_variable('b_h', [self.H], initializer=self.const_initializer) # [batch_size, dim_hidden] h = tf.nn.tanh(tf.matmul(features_mean, w_h) + b_h) w_c = tf.get_variable('w_c', [self.D, self.H], initializer=self.weight_initializer) b_c = tf.get_variable('b_c', [self.H], initializer=self.const_initializer) c = tf.nn.tanh(tf.matmul(features_mean, w_c) + b_c)
到這裏,常規的做法我們已經得到輸出
ht ,常用的預測yt 的方式是利用softmax
yt=argmaxC(softmax(f(ht)))
f是一個或者多個線性層,然後經過softmax之後,得到概率最大的一個詞就是預測值yt 這裏作者f定義爲:
f=Lo(Eyt−1+Lhht+Lzẑ t)
Lo∈Rk∗m,Lh∈Rm∗n,Lz∈Rm∗D
這樣:
p(yt|a,yt−11=a.exp(Lo(Eyt−1+Lhht+Lzẑ t)))
Attention 機制
本節我們介紹兩種attention機制,隨機attention 和 確定性 attention
隨機attention
顧名思義,隨機性attention的在計算
每次的結果可能不一樣,所以是一種隨機方法。
定義:
它代表在生成第t個詞的時候,聚焦在L中各個區域的參數
這裏用了p=multinoulli多項分佈,那麼
我們從分佈p生成變量
然後:
本節以下可以忽略
上一篇文章我們知道,loss函數我們可以採用l2 loss 或者softmax 交叉熵的方式
這裏因爲參數是sample出來的,顯然它是不能求導,從而不適用於鏈式法則,
下面我們引入對數似然來解決優化可導問題
它的對數似然函數爲:
這裏我們引入了變量
我們令
我們對
其中:
我們替換掉上式,最終得到:
這裏的s是從multinoulli分佈裏面sample出來的,
所以這裏:
整體的優化如上面所示,但是由於是採用採樣的方式得到中間參數,所以算法波動可能很大,
這裏我們使用兩種方式降低波動:
梯度優化裏面動量的機制類似,第k次mini-batch迭代使用大部分的k-1次的對數似然信息,這樣保證足夠穩定
bk=0.9bk−1+0.1log(y|s¯k,a) 另外一種是在後面加上一個熵的量
熵
H(s)=−∑np(sn|a)log(p(sn|a))
最終得到:
∂Ls∂W≈1N∑n=1N[∂logp(s¯n|a)∂W(logp(y|s¯n,a)−b)λr+λe∂H(s¯n)∂W+∂logp(y|s¯n,a)∂W]
這裏要注意點有兩個:
ẑ t=∑ist,iai , 這裏的st,i 是從分佈裏面sample出來的,分佈的參數是αi - 目標函數是最大化似然函數,似然函數有兩種優化形式來降低sample造成的波動
確定性的機制
上面我們使用sample出來的
這裏我們不用sample的方式,直接求期望:
with tf.variable_scope('attention_layer', reuse=reuse):
w = tf.get_variable('w', [self.H, self.D], initializer=self.weight_initializer)
b = tf.get_variable('b', [self.D], initializer=self.const_initializer)
w_att = tf.get_variable('w_att', [self.D, 1], initializer=self.weight_initializer)
# (N, L, D)
h_att = tf.nn.relu(features_proj + tf.expand_dims(tf.matmul(h, w), 1) + b)
# (N, L)
out_att = tf.reshape(tf.matmul(tf.reshape(h_att, [-1, self.D]), w_att), [-1, self.L])
alpha = tf.nn.softmax(out_att)
context = tf.reduce_sum(features * tf.expand_dims(alpha, 2), 1, name='context') #(N, D)
這樣其實整個模型就是可導的
另外:我們可以在這個內容前面增加一個gate常量,這個gate是和
這樣
with tf.variable_scope('selector', reuse=reuse):
w = tf.get_variable('w', [self.H, 1], initializer=self.weight_initializer)
b = tf.get_variable('b', [1], initializer=self.const_initializer)
beta = tf.nn.sigmoid(tf.matmul(h, w) + b, 'beta') # (N, 1)
context = tf.multiply(beta, context, name='selected_context')
return context, beta
當然到了這裏其實就可以了
以下可以忽略
下面的步驟其實是爲了可以和上面的 margin 對數似然
證明了一波,其實它們兩種機制在對數似然的期望上面是一致的
所以統一後,負的對數似然函數爲:
訓練要點
作者用的是RMSProp/Adam,採用的是自適應學習率
這裏還有一個重點是圖像生成的特徵向量
這裏作者直接用VGGNet 在ImageNet上面的預先訓練的模型,每張圖片抽取出 L=196個D=512 維的像向量
效果: