视觉问答——使用预训练模型提取特征以及特征融合的代码学习(未完待续,tensorflow实现)

一、背景

本教程用于记录自己学习视觉问答代码编写的学习过程。

二、VQA关键部分代码

标准VQA模型包括3个模块,分别是图像特征提取模块,文本特征提取模块,以及特征融合后的分类模块。标准VQA模型如下图所示:

视觉问答——使用预训练模型提取特征以及特征融合的代码学习(未完待续,tensorflow实现)_第1张图片

1. 图像特征提取

一般我们用预训练好的CNN模型,这里常用的包括vgg16/19,resnet-152/101,faster rcnn。主要的是这三类,当然你也可以自己写cnn或者用其他模型。

(1)使用预训练的vgg16/19来提取图像特征

这里以vgg16为例。我们需要下载预训练好的vgg16和相应的python文件

常用的预训练好的vgg16有两种格式:

①预训练的vgg16.tfmodel文件和vgg16.py文件:这里的tfmodel可以从这里下载:https://github.com/ry/tensorflow-vgg16,如果你没有安装caffe,那么就用种子下载训练好的文件吧:

视觉问答——使用预训练模型提取特征以及特征融合的代码学习(未完待续,tensorflow实现)_第2张图片

下载上面的几个文件,然后种子文件用迅雷就可以下载。

②vgg16.npy文件和vgg16.py文件:另外一种就是npy文件格式,我个人也是用这种格式比较多。文件我传到了自己的网盘上(链接:https://pan.baidu.com/s/1o2-h5Vq6Ff4mCAHWMV7e8w     提取码:3pcx)。

③vgg19.npy文件和vgg19.py文件:和vgg16类似,文件的下载地址可以参考我这一篇博客:对抗生成网络学习(八)——DeblurGAN实现运动图像的去模糊化(tensorflow实现)。

由于自己不太用tfmodel文件,所以这里先主要介绍npy文件如何提取图像特征。

这里先声明一下,这个vgg16.py文件我简单修改过,初始化的时候需要传入一个config类,这个类里面定义了很多参数,需要用到config的地方包括:

class vgg_16():
    def __init__(self, config):
        self.model_path = config.vgg_path
        self.image_height = config.image_height
        self.image_width = config.image_width

        self.data_dict = np.load(self.model_path, encoding='latin1').item()
        print("vgg16.npy file loaded")

接下来加载vgg16,首先是vgg16初始化,然后用到一个占位符,传入vgg中建立模型。

vgg = vgg_16(config)
images = tf.placeholder("float", [None, image_height, image_width, dim_image])
with tf.name_scope("content_vgg"):
    vgg.build(images)

这时候如果我需要提取一张图中的特征,就可以用下面的方法,这里我取出了‘fc7’层的特征,最终的特征保存在img_vgg_faeture变量中:

img = resize(img, (image_height, image_width))
img = img.reshape((1, image_height, image_width, dim_image))
sess = tf.Session()
sess.run(tf.global_variables_initializer())
img_vgg_feature = self.sess.run(vgg.fc7, feed_dict={images: img})

(2)使用预训练的resnet-152/101来提取图像特征

非常幸运的是,tensorflow里面集成好了resnet-50/101/152,我们可以直接调用。调用需要用到的库包括:

from tensorflow.contrib.slim.nets import resnet_v2
from tensorflow.contrib.slim.python.slim.nets.resnet_utils import resnet_arg_scope

调用方法:

with slim.arg_scope(resnet_arg_scope(is_training=False)):
    net, end_points = resnet_v2.resnet_v2_152(data_set)

resnet-50和101都是一样的调用思路,net是网络最终的返回,大小是(1,1,1,2048),endpoint里面保存了resnet各个网络层节点信息。加入我们要用resnet提取图片特征,抽出中间的一个层,那么可以用如下的方法:

# 随机读取一张图片,将其变成448*448的
image = io.imread('0.png')
image = transform.resize(image, (448, 448))

# 由于一张图像是一个三维立方体,我们要将其变为四维张量
data_set = np.empty((1, 448, 448, 3), dtype="float32")
data_set[0, :, :, :] = image

# 构建resnet网络
with slim.arg_scope(resnet_arg_scope):
    net, end_points = resnet_v2.resnet_v2_152(data_set)

# 运行网络
init = tf.global_variables_initializer()
with tf.Session() as sess:
    sess.run(init)
    sess.run(net)
    
    # 随便记录其中中间的某一层
    show_img = end_points['resnet_v2_152/block1/unit_1/bottleneck_v2/conv1'][0].eval()

# 输出一下这一层的大小,是(112,112,64)
print(show_img.shape)

# 随便拿一个feature map输出一下
io.imshow(show_img[:, :, 0])
io.show()

这个feature map输出的结果和原图如下所示:

视觉问答——使用预训练模型提取特征以及特征融合的代码学习(未完待续,tensorflow实现)_第3张图片

最后还有resnet_arg_scope是可以修改的,比如可以修改成下面的这种方式:

def resnet_arg_scope(is_training=True,                              # 训练标记
                     weight_decay=0.0001,                           # 权重衰减速率
                     batch_norm_decay=0.997,                        # BN的衰减速率
                     batch_norm_epsilon=1e-5,                       # BN的epsilon默认1e-5
                     batch_norm_scale=True):                        # BN的scale默认值

  batch_norm_params = {                                             # 定义batch normalization(标准化)的参数字典
      'is_training': is_training,
      'decay': batch_norm_decay,
      'epsilon': batch_norm_epsilon,
      'scale': batch_norm_scale,
      'updates_collections': tf.GraphKeys.UPDATE_OPS,
  }

  with slim.arg_scope(                                              # 通过slim.arg_scope将[slim.conv2d]的几个默认参数设置好
      [slim.conv2d],
      weights_regularizer=slim.l2_regularizer(weight_decay),        # 权重正则器设置为L2正则
      weights_initializer=slim.variance_scaling_initializer(),      # 权重初始化器
      activation_fn=tf.nn.relu,                                     # 激活函数
      normalizer_fn=slim.batch_norm,                                # 标准化器设置为BN
      normalizer_params=batch_norm_params):
    with slim.arg_scope([slim.batch_norm], **batch_norm_params):
      with slim.arg_scope([slim.max_pool2d], padding='SAME') as arg_sc: # ResNet原论文是VALID模式,SAME模式可让特征对齐更简单
        return arg_sc                                               # 最后将基层嵌套的arg_scope作为结果返回

(3)使用预训练的faster rcnn来提取图像特征

2. 文本特征提取

文本特征常用的提取方法一般有两种,LSTM或者GRU(其实GRU是LSTM的一个变种),但是在做文本特征提取之前,我们需要先将文本转成向量。

(1)embedding嵌入

这一步的目的是将文本转化为向量,当然文本转向量的方法有很多种,一种最简单的API如下:

embeddings = tf.Variable(
            tf.random_uniform([self.vocabulary_size, self.word_embedding_dim], -1.0, 1.0))

也就是我从[-1 1]之间随机初始化,初始化的参数服从均匀分布,生成一个[vocabulary_size, word_embedding_size]的向量。这里的vocabulary_size是所有unique word的数量,word_embedding_size就是每个单词要嵌入为多少长度的向量,一般设置300的比较多,这样做的意思就是说,我现在将所有单词建议一个映射表,每一个单词对应的300维的向量,当然这个映射表是随机建立的。

下面要考虑的就是对unique word建立id表了,也就是对每个单词建立一个编号,根据这个编号,我们才能找到他在映射表中对应的向量:

ques_word_list = []
ques_word_list.append(ques_list[q].split(' '))

上面这个问题就是找到ques_list中第q个问题,然后根据" "将问题中的所有单词分开,添加到ques_word_list中。

添加的ques_word_list一般情况是个二维的,就是[ques_num, word_in_each_question],需要将其变为一维的:

unique_word_in_q = [i for item in ques_word_list for i in item]  # 二维变一维

接下来就是统计这个数组中的唯一单词数量了:

all_word = list(set(list(set(unique_word_in_q)))

最后就可以根据这个all_word构建一个word和id的编号表了:

word2id = {word: index+1 for index, word in enumerate(all_word)}

(2)word2vec可以看:

(3)word2glove可以看:https://nlp.stanford.edu/projects/glove/

三、其他

1. 注意力机制

使用注意力的方式有很多种,下面介绍一些常用的

(1)SAN中的注意力

SAN中的注意力比较简单,就是将问题向量作为一个query,来找图像向量中的关键部分。算法也很简单,两个向量结合在做一个tanh,最后再加上权重和bias做一次softmax就行了,代码如下:

    def compute_attention(self, image_tensor, question_tensor, out_dim, dropout=True):
        # 先将嵌入拉伸为1维向量
        img = tf.nn.tanh(tf.layers.dense(image_tensor, out_dim))
        ques = tf.nn.tanh(tf.layers.dense(question_tensor, out_dim))

        # 连接问题和图像
        ques = tf.expand_dims(ques, axis=-2)
        IQ = tf.nn.tanh(img + ques)

        if dropout:
            IQ = tf.nn.dropout(IQ, self.drop_out_rate)

        # 再将连接好的拉伸为1维向量,再reshape
        temp = tf.layers.dense(IQ, 1)
        temp = tf.reshape(temp, [-1, temp.shape[1]])

        # softmax获得注意力
        p = tf.nn.softmax(temp)
        p_exp = tf.expand_dims(p, axis=-1)
        att_layer = tf.reduce_sum(p_exp * image_tensor, axis=1)

        # 最终的注意力结果
        final_out = att_layer + question_tensor

        return p, final_out

 

你可能感兴趣的:(视觉问答(VQA)相关)