@author : jasperyang
@school : BUPTgit
這裏學習的注意力模型是我在研究image caption過程當中的出來的經驗總結,其實這個注意力模型理解起來並不難,可是國內的博文寫的都很不詳細或說很不明確,我在看了 attention-mechanism後才徹底明白。得以進行後續工做。github
這裏的注意力模型是論文 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去訓練。spa
好了,先這麼大概理解一下這張圖就好。下面咱們來詳細解剖attention,附有代碼~設計
這裏的c其實一個隱含輸入,計算方式以下code
首先咱們這麼個函數:blog
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。ip
下面咱們打開這個黑盒子來看看裏面究竟是在作什麼處理。
上圖中能夠看到
這裏的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]
這樣就能夠結合當前單詞和圖像區域的關係了,我以爲注意力模型仍是很巧妙的。