前言
這里學習的注意力模型是我在研究image caption過程中的出來的經驗總結,其實這個注意力模型理解起來並不難,但是國內的博文寫的都很不詳細或說很不明確,我在看了 attention-mechanism后才完全明白。得以進行后續工作。
這里的注意力模型是論文 Show,Attend and Tell:Neural Image Caption Generation with Visual Attention里設計的,但是注意力模型在大體上來講都是相通的。
先給大家介紹一下我需要注意力模型的背景。
I是圖片信息矩陣也就是[224,224,3],通過前面的cnn也就是所謂的sequence-sequence模型中的encoder,我用的是vgg19,得到a,這里的a其實是[14*14,512]=[196,512],很形象吧,代表的是圖片被分成了這么多個區域,后面就看我們單詞注意在哪個區域了,大家可以先這么泛泛理解。通過了本文要講的Attention之后得到z。這個z是一個區域概率,也就是當前的單詞在哪個圖像區域的概率最大。然后z組合單詞的embedding去訓練。
好了,先這么大概理解一下這張圖就好。下面我們來詳細解剖attention,附有代碼~
attention的內部結構是什么?
這里的c其實一個隱含輸入,計算方式如下
首先我們這么個函數:
def _get_initial_lstm(self, features): with tf.variable_scope('initial_lstm'): 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) 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) return c, h
上面的c你可以暫時不用管,是lstm中的memory state,輸入feature就是通過cnn跑出來的a,我們暫時考慮batch=1,就認為這個a是一張圖片生成的。所以a的維度是[1,196,512]
y向量代表的就是feature。
下面我們打開這個黑盒子來看看里面到底是在做什么處理。
上圖中可以看到
這里的tanh不能替換成ReLU函數,一旦替換成ReLU函數,因為有很多負值就會消失,會很影響后面的結果,會造成最后Inference句子時,不管你輸入什么圖片矩陣的到的句子都是一樣的。不能隨便用激活函數!!!ReLU是能解決梯度消散問題,但是在這里我們需要負值信息,所以只能用tanh
c和y在輸入到tanh之前要做個全連接,代碼如下。
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) h_att = tf.nn.relu(features_proj + tf.expand_dims(tf.matmul(h, w), 1) + b) # (N, L, D)
這里的features_proj是feature已經做了全連接后的矩陣。並且在上面計算h_att中你可以看到一個矩陣的傳播機制,也就是relu函數里的加法。features_proj和后面的那個維度是不一樣的。
def _project_features(self, features): with tf.variable_scope('project_features'): w = tf.get_variable('w', [self.D, self.D], initializer=self.weight_initializer) features_flat = tf.reshape(features, [-1, self.D]) features_proj = tf.matmul(features_flat, w) features_proj = tf.reshape(features_proj, [-1, self.L, self.D]) return features_proj
然后要做softmax了,這里有個點,因為上面得到的m的維度是[1,196,512],1是代表batch數量。經過softmax后想要得到的是維度為[1,196]的矩陣也就是每個區域的注意力權值。所以
out_att = tf.reshape(tf.matmul(tf.reshape(h_att, [-1, self.D]), w_att), [-1, self.L]) # (N, L) alpha = tf.nn.softmax(out_att)
最后計算s就是一個相乘。
context = tf.reduce_sum(features * tf.expand_dims(alpha, 2), 1, name='context') #(N, D)
這里也是有個傳播的機制,features維度[1,196,512],后面那個維度[1,196,1]。
最后給個完整的注意力模型代碼。
def _attention_layer(self, features, features_proj, h, reuse=False): 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) 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]) # (N, L) alpha = tf.nn.softmax(out_att) context = tf.reduce_sum(features * tf.expand_dims(alpha, 2), 1, name='context') #(N, D) return context, alpha
如果大家想研究整個完整的show-attend-tell模型,可以去看看github鏈接
以上我們講的是soft_attention,還有一個hard_attention。hard_attention比較不適合於反向傳播,其原理是取代了我們之前將softmax后的所有結果相加,使用采樣其中一個作為z。反向傳播的梯度就不好算了,這里用蒙特卡洛采樣方式。
ok,回到我們的image_caption中,看下圖
這個圖其實不太准確,每一個z其實還會用tf.concat連接上當前這個lstm_cell的單詞embedding輸入。也就是維度變成[512]+[512]=[1024]
這樣就可以結合當前單詞和圖像區域的關系了,我覺得注意力模型還是很巧妙的。
https://segmentfault.com/a/1190000011744246