概述
视觉问题回答(VQA)是一种多模式任务,通过字幕或问卷将文本和图像联系起来。例如,有了一条繁忙的高速公路图片,可能会有一个问题:“那里有多少辆红色汽车?”或“摩托车比汽车还多吗?”。这是一项非常具有挑战性的任务,因为它需要对文本和图像以及它们之间的关系有高层次的理解。今天我们将介绍2016年Deep Mind提出的 Relational Network 架构,该架构比以前的所有方法都要大幅度提升。
机器学习中的关系网络(Relational Network)的主要思想不仅在概念上易于理解,而且还广泛适用于其他领域。
数据描述
在本文中,我们将使用CLEVR的简化版本:sort-of-CLEVR3,其中包含10,000个图像,包含20个问题。图像包含来自两个候选形状(正方形或圆形)和6种颜色候选(红色,绿色,蓝色,橙色,灰色和黄色)的随机生成的对象。对于给定的图像,其中10个问题是关系问题,另外10个问题是非关系问题。
即,通过显示的示例图像,我们可以生成非关系问题,例如:
- 绿色物体是否放在图像的左侧?=>是
- 橙色物体是否放在图像的顶部?=>不
和关系问题:
- 最接近红色物体的物体的形状是什么?=>方
- 距离橙色物体最远的物体的形状是什么?=>圈
网络架构
关系网络的通用公式可以写成如下:
首先,我们将数据转换为对象(Oi and Oj)。然后,在将函数g应用于每个对象组合之后,我们在将它们传递给另一个函数f之前将它们相加。
关系网络中最重要的事情是考虑一个对象而不管域。因此,我们可以在一个网络内针对不同的域进行学习。因此,
VQA问题中关系网络的过程如下:
1.生成要素图
- 问题:使用LSTM嵌入
- 图像:CNN(4个卷积层)
2.应用RN(关系网络)步骤
- 生成带有问题的对象对:我们为从问题和图像中提取的对象进行所有可能的组合。
- 添加粒子图像特征的协调位置信息。
- 应用前4层MLP(函数g)
- 对元素g的输出应用元素
- 应用接下来的两层MLP(函数f)
为了更好地理解,让我们来看一个简单的例子。假设有三个3x3 CNN特征映射和一个1x4查询特征向量。然后,我们最终得到9 * 9 = 81个特征向量组合。请注意,由于我们创建了81个新数据对,因此第一个维度将比原始批量大小膨胀81倍。在应用函数g之后,那些膨胀的批次将通过元素总和减少到原始批量大小,然后传递给函数f。假设每个单独的图像对象的长度为3,问题对象的长度为4,我们可以将结构描述如下:
坐标位置矢量是一种提供像素位置信息的矢量。坐标位置的结构如下:
def cvt_coord(i):
return [(i/5-2)/2., (i%5-2)/2.]
Python代码
让我们用Gluon实现关系网络模型。对于数据生成,我们使用此代码(https://github.com/kimhc6028/relational-networks/blob/master/sort_of_clevr_generator.py) 生成输入数据。首先,我们使用sort_of_clevr_generator.py创建输入数据。在执行生成代码之前,我们需要安装必要的包。
pip install opencv-python
python sort_of_clevr_generator.py
运行代码后,您将看到以下消息。生成的图像之一显示在下图中。该问题作为One-hot编码特征向量提供。因此,我们不在示例代码中考虑LSTM编码部分。
building test datasets...
building train datasets...
saving datasets...
datasets saved at ./data/sort-of-clevr.pickle
为了加快培训速度,我们将使用多个gpu。为此,您可以设置context参数,如下所示:
GPU_COUNT = 2
def setting_ctx(GPU_COUNT):
if GPU_COUNT > 0 :
ctx = [mx.gpu(i) for i in range(GPU_COUNT)]
else :
ctx = [mx.cpu()]
return ctx
ctx = setting_ctx(GPU_COUNT)
然后我们需要为关系网络定义一个类。它由三部分组成:卷积,DNN(g)和分类器(f)。卷积部分有4个卷积层,可以定义如下:
class ConvInputModel(nn.HybridSequential):
def __init__(self,**kwargs):
super(ConvInputModel,self).__init__(**kwargs)
with self.name_scope():
self.add(
nn.Conv2D(channels=24, kernel_size=3, strides=2, padding=1, activation='relu'),
nn.BatchNorm(),
nn.Conv2D(channels=24, kernel_size=3, strides=2, padding=1, activation='relu'),
nn.BatchNorm(),
nn.Conv2D(channels=24, kernel_size=3, strides=2, padding=1, activation='relu'),
nn.BatchNorm(),
nn.Conv2D(channels=24, kernel_size=3, strides=2, padding=1, activation='relu'),
nn.BatchNorm()
)
分类器(函数f)非常简单,只有2个全连接层,Python代码如下所示:
class FCOutputModel(nn.HybridSequential):
def __init__(self,**kwargs):
super(FCOutputModel,self).__init__(**kwargs)
with self.name_scope():
self.add(
nn.Dense(256, activation='relu'),
nn.Dropout(0.5),
nn.Dense(10)
)
关系网络体系结构的核心可以分为以下几个步骤:
- 从卷积编码器生成要素图。
- 添加坐标信息以考虑像素位置。
- 连接所有可能的图像特征映射对,并连接问题特征。然后,应用FCN(函数g)。
- 计算元素总和以缩放回原始批量大小并使用分类器(函数f)。
Python代码如下:
class RN_Model(nn.HybridBlock):
def __init__(self, **kwargs):
super(RN_Model, self).__init__(**kwargs)
with self.name_scope():
self.conv = ConvInputModel()
self.g_fc1 = nn.Dense(256, activation='relu', flatten=False)
self.g_fc2 = nn.Dense(256, activation='relu', flatten=False)
self.g_fc3 = nn.Dense(256, activation='relu', flatten=False)
self.g_fc4 = nn.Dense(256, activation='relu', flatten=False)
self.f_fc1 = nn.Dense(256, activation='relu')
self.fcout = FCOutputModel()
def hybrid_forward(self, F, x, qst, coord_tensor):
#input size = (64 * 3 * 75 * 75)
x = self.conv(x) ## x = (64 * 24 * 5 * 5)
x_flat = x.reshape(shape=(0, 0, -3))
x_flat = F.swapaxes(x_flat,1,2) ## (64 * 25 * 24)
##add coordinates
x_flat = F.concat(x_flat, coord_tensor,dim=2)
##add question
qst = qst.expand_dims(1)
qst = F.repeat(qst,repeats=25,axis=1)
qst = qst.expand_dims(2)
# cast all pairs against each other
x_i = x_flat.expand_dims(1)
x_i = F.repeat(x_i,repeats=25,axis=1)
x_j = x_flat.expand_dims(2)
x_j = F.concat(x_j,qst,dim=3)
x_j = F.repeat(x_j,repeats=25,axis=2)
#concatenate all
x_full = F.concat(x_i,x_j,dim=3)
x_ = self.g_fc1(x_full.reshape((0, -1, 63)))
x_ = self.g_fc2(x_)
x_ = self.g_fc3(x_)
x_ = self.g_fc4(x_)
x_g = x_.sum(1)
##### f part #######
x_f = self.f_fc1(x_g)
return self.fcout(x_f)
使用adam优化器和softmax交叉熵损失
#set optimizer
trainer = gluon.Trainer(model.collect_params(),optimizer='adam')
#define loss function
loss = gluon.loss.SoftmaxCrossEntropyLoss()
现在,我们使用split_and_load()函数加载数据,以便轻松使用多个GPU资源。它会自动划分输入数据并将每个子集分配给不同的GPU。
for i, (rel_img, rel_qst, rel_label, norel_img, norel_qst, norel_label) in enumerate(dataloader_train):
# Relational data
rel_imgs = gluon.utils.split_and_load(rel_img, ctx)
rel_qsts = gluon.utils.split_and_load(rel_qst, ctx)
rel_labels = gluon.utils.split_and_load(rel_label, ctx)
# No-relationtional data
norel_imgs = gluon.utils.split_and_load(norel_img, ctx)
norel_qsts = gluon.utils.split_and_load(norel_qst, ctx)
norel_labels = gluon.utils.split_and_load(norel_label, ctx)
with autograd.record():
rel_losses = [loss(model(X,Y, coord),Z) for X, Y, coord, Z in zip(rel_imgs, rel_qsts, coord_tensors, rel_labels )]
for l in rel_losses:
l.backward()
trainer.step(rel_img.shape[0])
实施细节可以在这里找到(https://github.com/seujung/relational-network-gluon/blob/master/relation_reasoning_code_use_multi_gpu.ipynb)。
结果
在关系问题中,我们在非关系问题和91%准确度(对于测试数据)中实现了99%的准确性。
结论
- 关系网络使用简单的算法结构在VQA域中表现出了很好的性能。
- Gluon提供了一个简单直接的API来实现模型训练的多gpu使用。
本文暂时没有评论,来添加一个吧(●'◡'●)