前言
这里学习的注意力模型是我在研究image caption过程当中的出来的经验总结,其实这个注意力模型理解起来并不难,可是国内的博文写的都很不详细或说很不明确,我在看了 attention-mechanism后才彻底明白。得以进行后续工做。git
这里的注意力模型是论文 Show,Attend and Tell:Neural Image Caption Generation with Visual Attention里设计的,可是注意力模型在大致上来说都是相通的。github
先给你们介绍一下我须要注意力模型的背景。vim
I是图片信息矩阵也就是[224,224,3],经过前面的cnn也就是所谓的sequence-sequence模型中的encoder,我用的是vgg19,获得a,这里的a实际上是[14*14,512]=[196,512],很形象吧,表明的是图片被分红了这么多个区域,后面就看咱们单词注意在哪一个区域了,你们能够先这么泛泛理解。经过了本文要讲的Attention以后获得z。这个z是一个区域几率,也就是当前的单词在哪一个图像区域的几率最大。而后z组合单词的embedding去训练。segmentfault
好了,先这么大概理解一下这张图就好。下面咱们来详细解剖attention,附有代码~ruby
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。url
下面咱们打开这个黑盒子来看看里面究竟是在作什么处理。spa
上图中能够看到
这里的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]
这样就能够结合当前单词和图像区域的关系了,我以为注意力模型仍是很巧妙的。