Learning Semantic-Specific Graph Representation for Multi-Label Image Recognition 学习与复现

7.25

开始通读论文。论文的问题是多标签图像分类任务。论文的主要想法是先通过语义解耦得到针对给定图片的所有类别的特征;再通过语义交互,将类别之间的先验知识融入到预测结果当中,帮助预测。

了解了论文的大体思路就可以开始复现了。
准备工作:先要下载一个论文采用的数据集:Microsoft COCO数据集以及GloVe预训练好的词向量,并要从词向量中提取所有类别词的向量。

7.26

复现的第一部分是语义解耦,semantic decoupling(SD)。SD是基于ResNet-101实现的。由于不太了解具体的ResNet的网络架构,查阅引用论文《Deep Residual Learning for Image Recognition》。

ResNet主要解决的是网络退化的问题,即增加网络深度,训练误差反而增大。ResNet提出了短路机制shortcut connection。短路机制
这种机制将期望学习的映射 H ( x ) \mathcal{H}(\bf{x}) H(x)转化为学习 F ( x ) = H ( x ) − x \mathcal{F}(\bf{x})=\mathcal{H}(\bf{x})-\bf{x} F(x)=H(x)x,并通过 F ( x ) + x \mathcal{F}(\bf{x})+\bf{x} F(x)+x得到原本的映射。

这个结构类似于LSTM的门控机制,两者一定程度上都可以缓解梯度消失和梯度爆炸的问题。不同的是LSTM的门控机制需要进行参数学习,依赖于数据,它需要学习对于上一步的隐藏状态忘记多少,对于当前状态需要输入多少;shortcut connection属于门控机制的特例,它不包含任何参数,直接将输入完全地加入在输出中,需要学习的只有剩余的部分。
lstm机制
ResNet-101采用如下架构:在这里插入图片描述
其模块核心是Bottleneck。如图所示:在这里插入图片描述
ResNet-101的所有结构明晰后,可以开始复现。由于pytorch中已经有ResNet-101代码的官方实现,故复现重点在于理解细节。

复现第一部分遇到的一些问题及解决方案。

  1. ResNet采用BatchNorm2d来帮助训练,缓解梯度消失。BatchNorm采用的是将一个batch内的数据进行统计计算其方差和均值,再归一化后变换。而BatchNorm2d解决的是二维图像问题,图像拥有着多个通道,我并不清楚是将batch内图像中所有的通道的数值计算方差和平均,还是每个图片通道单独统计batch内相同通道的图片数值的方差和平均。不过我的直觉还是告诉我应该是后者,因为图像每个通道的物理含义本就不同,其数值分布也有很大的差别,单独的通道处理更有利于保留本通道内的特征。查阅资料后发现果然如此。
  2. 查看ResNet-101的代码发现其中有nn.ReLU(inplace=True),查看文档得知inplace的操作是直接对原值进行操作,减少内存消耗。
  3. ResNet-34之前的模型采用的是2个33的卷积模块而非像ResNet-101的Bottleneck模块。这并不是随意选择的,由于在ResNet-34之前神经网络层数较少,可以负担的起空间开销,所以可以使用2个33的卷积模块作为building block。而随着网络层数的增加,Bottleneck模块能够有效减少网络参数,所以从ResNet-50之后更深的模型都是使用Bottleneck作为building block。
  4. downsample的操作。在ResNet中,除了第一层Bottleneck层外,每一层Bottleneck层开始都有一个downsample的卷积操作,它让输出图像的长宽均减半。我一开始比较疑惑这一步的操作,因为在每一层Bottleneck层的第一个Bottleneck的3*3卷积核中采用stride=2,已经可以做到输出的长宽均减半,而downsample的操作目的不是很明白。通过阅读downsample详解了解到downsample操作的目的是为了让输入的 x \bf{x} x能够与经过第一个Bottleneck的输出 F ( x ) \mathcal{F}(\bf{x}) F(x)的形状对齐,通过加法操作得到目标映射 H ( x ) \mathcal{H}(\bf{x}) H(x)

7.27

为了验证搭建的ResNet-101的效果,我先尝试复现出论文中Ablative study的baseline效果。
根据论文叙述,先将ResNet-101修改为对应的结构。

阅读理解开源代码并修改使得能够进行ResNet-101的训练。
按照论文上设置的参数编写脚本进行训练。

搭建完图片特征提取的部分,接下来就是构建语义解耦的部分。

按照论文当中叙述的计算方法并不是非常复杂,因为其针对的是单个category。为了发挥GPU并行的效能加快计算,我们必须要将多个category合并成单个矩阵进行计算。

由于涉及大量的矩阵操作,代码运算并不直观,我们需要进行一些分析:

def forward(self,batch_size, img_feature_map, word_features):
		#在使用ResNet进行特征提取的时候,方法将最终的avgPool改为了[2,2]的大小,
		#stride为2,不会将特征变为一个数
		#图片特征形状为[batch_size,out_channel,convsize,convsize]
        convsize = img_feature_map.size()[3]
		
        img_feature_map = torch.transpose(torch.transpose(img_feature_map, 1, 2),2,3)
        #img_feature_map形状变为[batch_size,convsize,convsize,out_channel]
        f_wh_feature = img_feature_map.contiguous().view(batch_size*convsize*convsize, -1)
        #f_wh_feature形状为[batch_size,convsize,convsize,out_channel]
        f_wh_feature = self.fc_1(f_wh_feature).view(batch_size*convsize*convsize, 1, -1).repeat(1, self.num_classes, 1)
		#f_wh_feature形状变为[batch_size*convsize*convsize,num_class,d]
        f_wd_feature = self.fc_2(word_features).view(1, self.num_classes, 1024).repeat(batch_size*convsize*convsize,1,1)
        #f_wd_feature的形状为[batch_size*convsize*convsize,num_class,d]
        lb_feature = self.fc_3(torch.tanh(f_wh_feature*f_wd_feature).view(-1,1024))
        #进行low-rank bilinear pooling得到lb_feature形状[batch_size*convsize*convsize*num_class,d]
        coefficient = self.fc_a(lb_feature)
        #计算注意力值coefficient形状为[batch_size*convsize*convsize*num_class,1]
        coefficient = torch.transpose(torch.transpose(coefficient.view(batch_size, convsize, convsize, self.num_classes),2,3),1,2).view(batch_size, self.num_classes, -1)
		#将注意力值扩展成矩阵方便直接进行对应元素积
		#coefficient形状为[batch_size,num_class,convsize*convsize]
        coefficient = F.softmax(coefficient, dim=2)
        coefficient = coefficient.view(batch_size, self.num_classes, convsize, convsize)
        coefficient = torch.transpose(torch.transpose(coefficient,1,2),2,3)
        #coefficient形状为[batch_size,convsize,convsize,num_class]
        coefficient = coefficient.view(batch_size, convsize, convsize, self.num_classes, 1).repeat(1,1,1,1,self.image_feature_dim)
        #coefficient形状为[batch_size,convsize,convsize,num_class,image_feature_dim]
        #调整形状使得可以进行Hadamard乘积,进行attention的加权求和
        img_feature_map = img_feature_map.view(batch_size, convsize, convsize, 1, self.image_feature_dim).repeat(1, 1, 1, self.num_classes, 1)* coefficient
        #img_feature_map形状变为[batch_size,convsize,convsize,num_classes,out_channel]
        graph_net_input = torch.sum(torch.sum(img_feature_map,1) ,1)
        #沿着convsize进行求和得到最终的语义解耦结果
        return graph_net_input

第二个部分是语义交互. Semantic Interaction。这一部分是将标签的一些先验知识通过图的表示融入到第一部分求出来的类别的特征向量当中,并通过GRU进行特征向量的迭代更新。

7.28

在进行ResNet-101训练时,还是遇到了不少的问题。

  1. 在将COCO的图片载入训练时,开源代码中有一个category.json的类别文件,其功能应该是将原始的category编号进行一次映射,得到最终的用于训练的编号。然而我在网上搜索了很久并没有找到对应的官方文件。我先尝试不经过category的映射能否直接拿原始的COCO类别编号进行训练,结果运行报错。在这里插入图片描述
    奇怪的是COCO的类别总数只有80个,为什么会有图片的category编号超过80会是85?又在网上查阅相关资料coco2017数据集80类别名称与id号的对应关系,并调用coco API查看发现类别并不是完全连续的,而是存在空的编号。我们遍历映射关系得到category.json。在这里插入图片描述
  2. 在载入ResNet的时候,又存在RuntimeError: No CUDA GPUs are available。打印了一下print(torch.cuda.is_available())却是False。而单独在python3的交互界面下该结果又为True。我猜测可能是脚本中设置的针对GPU的环境变量的原因,检查了一下果然是这样,将CUDA_VISIBLE_DEVICES的环境变量再设置好就可以了。
  3. 开源代码中,我们并没有train_label和test_label的文件,我们要先通过annotations统计每一张图片的标签再生成这两个文件。
  4. 由于COCO的图片和ResNet使用的ImageNet图片大小不同,我们将ImageNet架构中的nn.AvgPool2d(avg_pool_kernel_size, stride=1)直接改为nn.AdaptiveAvgPool2d((1,1)),使得最终能生成一个代表图片特征的2048维的特征向量,能够传入全连接层进行最终的预测。
  5. 由于我们是将已经在ImageNet上训练过的模型作为预训练模型,所以我们只更新ResNet-101的第四层以及最后的全连接层的层数。
  6. 模型目前正在训练。
    在这里插入图片描述

7.29

训练比较慢,平均接近1个小时左右才能训练完一轮。目前已训练了15轮,当前mAP为:0.757,离0.80还有一段距离,但还剩余了185epochs,还需要等待结果。
在这里插入图片描述

7.30

在这里插入图片描述
这一轮训练的最好mAP为0.778。观察训练过程:训练集的loss在不断减小,而测试集的loss开始增加了,可以判断目前模型已经过拟合,接着继续训练不会再提高模型的性能。0.77离0.80还存在着不小的距离,我需要分析一些原因再重新训练。
1.缓解过拟合。分析训练过程,我可以知道模型是过拟合了,首先可以想到提高mAP的方法是看看有没有方法可以缓解过拟合。从网上寻找了一些帖子聊一聊过拟合给了我一些想法。我仍然保持网络架构不变化,通过进行HorizontalFlip来缓解过拟合。并且我将ResNet的第四层的参数也冻结不再更新,这不仅可以防止过拟合,也可以加快训练速度。我将batch_size也增大到10来加速训练。

7.31

模型已经收敛,训练误差不再下降,测试误差也不再下降且其值与训练误差基本相当,而模型的mAP只有0.65,这说明模型欠拟合。我将ResNet-101的第四层参数也进行更新以提高模型对于COCO数据集任务的拟合能力。

由于训练速度太慢,我同时尝试多种方法。

  1. 在第一次训练mAP为0.778的best模型上再进行数据增强的训练,包括采用水平翻转、竖直翻转以及旋转等操作进行数据的增强,来缓解过拟合的问题。

    经过几轮训练,mAP有了些许的提升,最高的mAP到达0.781,但之后的训练误差基本不再下降,而测试误差又低于训练误差,很可能模型还有继续拟合的空间,我将ResNet的第三层也进行参数更新以加强模型拟合能力。

    通过一个晚上的训练,该模型的最高mAP已经到达0.796,模型准确率仍在提高。在这里插入图片描述
    目前该模型的mAP已经到达0.800.
    在这里插入图片描述

    我发现这种方法十分有效果,我同时采用更为激进的方式——将ResNet所有参数全部更新以更为贴合COCO的数据集。

虽然终于达到了期望的mAP,但我实际上在这个训练的过程中还是产生了不少的疑问。

  1. 训练的上限究竟在哪里?我之所以会在mAP到达0.78的时候还会继续调参是因为我已经知道目标的mAP应该要到达0.803。但在面对一个未知结果的项目的时候,我们如何知道一个模型是否还能提升?一个模型还能提升多少?

8.2

先固定ResNet所有Res block只调整全连接层之后再放开第四个Res block的方法最终的训练最好准确率停在了0.798,之后的训练又产生了严重的过拟合。
在这里插入图片描述
由于只差一点点,我在0.798最优模型的基础上再次进行优化,采用Adam的weight_decay进行正则化缓解过拟合现象。

8.14

在尝试完原始resnet的图像分类过后,我进行完整的ssgrl模型的复现。

semantic decoupling的部分已经在上面的部分中完成,第二个部分是senmantic interaction。该部分利用类别共同出现的统计信息,构建图结构并利用GRU的结构来迭代地更新类别的向量。

相关代码以及注释:

    def forward(self, input):
        batch_size = input.size()[0]
        input = input.view(-1, self.input_dim)
        node_num = self._in_matrix.size()[0]
        #node_num即为类别的数目
        batch_aog_nodes = input.view(batch_size, node_num, self.input_dim)
        batch_in_matrix = self._in_matrix.repeat(batch_size, 1).view(batch_size, node_num, -1)
        batch_out_matrix = self._out_matrix.repeat(batch_size, 1).view(batch_size, node_num, -1)
        #将输入矩阵和输出矩阵按照batch_size进行重复,方便后续矩阵乘法得到聚合后的结果。
        for t in range(self.time_step):
            # eq(2)
            av = torch.cat((torch.bmm(batch_in_matrix, batch_aog_nodes), torch.bmm(batch_out_matrix, batch_aog_nodes)), 2)
            #批量乘法得到融入了邻域信息的节点
            av = av.view(batch_size * node_num, -1)

            flatten_aog_nodes = batch_aog_nodes.view(batch_size * node_num, -1)

            # eq(3)
            zv = torch.sigmoid(self.fc_eq3_w(av) + self.fc_eq3_u(flatten_aog_nodes))
			#得到更新门
            # eq(4)
            rv = torch.sigmoid(self.fc_eq4_w(av) + self.fc_eq3_u(flatten_aog_nodes))
            #得到重置门
            #eq(5)
            hv = torch.tanh(self.fc_eq5_w(av) + self.fc_eq5_u(rv * flatten_aog_nodes))
			#得到候选隐状态
            flatten_aog_nodes = (1 - zv) * flatten_aog_nodes + zv * hv
            #得到最终结果
            batch_aog_nodes = flatten_aog_nodes.view(batch_size, node_num, -1)
        return batch_aog_nodes

训练的过程中,为了更好地观察模型在训练集和测试集上的loss的变化,我使用tensorboardX进行训练过程的可视化。

在这里插入图片描述
可以看到模型基本上收敛了,训练误差低于测试误差,且没有严重的过拟合现象,模型继续训练对性能的提升也将会非常的小。
ssgrl模型复现的目前的mAP为:0.836,基本上达到了0.838的复现目标。
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值