ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MA-CNN详解:多注意力卷积网络攻克细粒度图像识别

MA-CNN详解:多注意力卷积网络攻克细粒度图像识别 简介MA-CNN多注意力卷积神经网络复现资源基于ICCV 2017论文《Learning Multi-Attention Convolutional Neural Network for Fine-Grained Image Recognition》实现面向对细粒度图像识别、注意力机制和视频分析感兴趣的Python开发者。项目设计思路围绕全连接层聚类生成的注意力矩阵与通道损失函数展开能够强化模型对局部判别区域的关注从而完成细粒度识别任务也可用于视频流中的关键帧提取。代码全部基于PyTorch编写并明确要求GPU运行环境作者将数据接口、卷积骨干训练、聚类训练和分类模块分离其中数据接口通过改写Pytorch数据类函数即可适配新数据集卷积部分可以替换主流网络并调整输出通道数。资源包共7个文件以4个Python脚本为主要内容辅以README说明、License和Git忽略配置压缩后仅19KB结构精简方便逐文件对照论文理解核心模块。目前已有1153人学习下载适合希望快速跑通注意力模型流程的初学者也适合在复现基础上继续扩展应用的进阶开发者。 细粒度图像识别一直是计算机视觉里比较尴尬的一类任务。说它难不是难在要把猫和狗分开而是难在要把“一脸严肃的哈士奇”和“同样一脸严肃的阿拉斯加”分开难在区分不同品种的鸟、不同款式的车、不同种类的花。这类任务里类别之间的差异往往只集中在很小一片区域上——鸟喙的弧度、车轮的辐条、花瓣的纹理剩下的整张图对分类几乎都是干扰。几年前我第一次拿通用分类网络去跑CUB-200-2011鸟类数据集top-1准确率只能做到六成出头而同一个网络在ImageNet上表现并不差差距完全是“注意力放错了地方”造成的。MA-CNNMulti-Attention Convolutional Neural Network多注意力卷积神经网络就是针对这个痛点提出来的一种经典思路不靠人工标注部件位置让网络自己学会“该看哪里”把多个局部部件特征聚合起来再做细粒度分类。这篇论文发表于ICCV 2017到现在回头看它的很多设计思路依然值得拿出来认真拆一遍。1. 细粒度识别为什么难MA-CNN到底在解决什么通用图像分类任务里一张图里的苹果和香蕉差异巨大网络只要抓住“红色圆形的区域”基本就能做出正确判断。但细粒度识别完全不是这个量级同一个大类下的子类比如不同种类的鸟整体轮廓、颜色分布、姿态都可能非常接近真正区分它们的往往是“初级飞羽边缘的颜色”或者“眼周是否有白色斑块”这种局部细节。这意味着模型需要在正确的空间位置上提取到足够的判别性特征差之毫厘谬以千里。在MA-CNN之前主流做法分成两类。一类是强监督方案先训练目标检测器去定位鸟的头部、翅膀、尾巴再分别对这些局部区域做分类。这类方法效果不差但需要大量部件级标注人工成本高得离谱一个CUB数据集就标了上万个关键点换个新任务就得重新标。另一类是单水平的全局特征直接分类省事但上限低因为全图池化会把关键局部信息“稀释”在背景和无关区域里。MA-CNN走的是第三条路弱监督下的多部件注意力学习。它不要求任何部件标注只给图像级别的类别标签但通过网络内部的注意力机制自动发现多个判别性局部区域。打个比方不是让模型盯着一整只鸟看而是让它学会先看头、再看翅膀、再看尾巴最后把这些信息汇总起来做判断。这个“多”字很关键因为在细粒度任务里几乎没有哪个单一局部区域是永远足够的不同类别的判别部位可能分布在完全不同的位置。所以MA-CNN要解决的问题可以概括成两点一是如何让网络自动找到多个关键局部区域二是如何把这些局部区域的特征高效聚合起来辅助分类。理解了这两个问题再看它的网络结构就顺理成章了。2. 拆解MA-CNN多注意力是怎么“长”出来的MA-CNN的整体结构并不复杂可以拆成三个模块来看特征提取主干、注意力建议网络、部件特征聚合分类器。核心思想一句话就是从主干网络的特征图中“挖”出多个注意力图再用这些注意力图去加权池化特征形成多个部件级别的描述符。2.1 从特征图通道到注意力图先看特征提取主干。论文里用的基础网络是VGG-16去掉最后的全连接分类层保留到conv5_3输出的特征图尺寸一般是14x14通道数512。也就是说一张任意尺寸的输入图片经过主干网络后会变成一个[B, 512, 14, 14]的张量这个张量里既包含了空间位置信息也包含了语义信息。注意力建议网络就在这个特征图上面做文章。它的结构很简单接一个3x3卷积加ReLU再通过一个1x1卷积把输出通道数变成N这里的N就是设定的部件数量。最后得到的是N张与特征图同分辨率的响应图经过softmax归一化后每一张就是对应一个部件的注意力图。我第一次看到这里时有个疑问为什么几个卷积层就能让不同通道学会关注不同部位后来想明白了这跟softmax和后续约束有关。注意力图在通道维度上做了softmax相当于让N个通道在空间位置上形成竞争关系一个像素位置只能归属到与之最匹配的那个部件上。再加上后面要讲的重叠正则不同通道会被“逼”着去寻找彼此不重叠的空间区域。所以不是卷积层天然有这种能力而是整个损失函数的设计把网络推向了“每个通道关注一个不同部位”的解。2.2 注意力图如何变成部件特征有了N张注意力图后下一步是把它们用到特征提取上。具体做法是对每个注意力图把它和主干特征图逐元素相乘然后做全局平均池化得到一个512维的部件特征向量。这个过程可以理解为一种“软裁剪”——注意力图取值大的地方对应区域的特征被保留取值接近0的地方特征被压制。相比用检测框做硬裁剪这种软注意力的好处很明显不需要额外的检测分支也不会有框偏了或者框大了导致信息污染的问题而且全过程都是可微的梯度可以直接回传到注意力建议网络里。同时全局特征分支也没有丢掉主干特征图再做一次全局平均池化得到整个图像的全局特征。聚合方式上MA-CNN把全局特征和N个部件特征拼接或相加之后再送入全连接分类层。全局特征提供基础性信息部件特征补充细节。两者结合做分类时相当于模型既有全局视野又能精准放大关键局部这对细粒度识别的提升非常明显。2.3 重叠正则为什么需要多个注意力互相监督多注意力网络最容易出现的一个问题就是“注意力崩坏”N个通道最后学出来的注意力图几乎一模一样全都聚焦在整张图上响应最强的那个位置。一旦发生这种情况多注意力就退化成单注意力了即使N设得再大也没用。MA-CNN为了预防这个问题在损失函数里加了一项“部件间互斥正则”让不同部件的注意力图之间尽量不重叠。实现上可以这样理解把任意两张注意力图逐元素相乘再对所有空间位置求和。如果两张图关注的是同一片区域这个值就会很大如果关注区域完全不重叠这个值趋近于0。优化目标就是让所有两两组合的这个值都尽量小。这种约束本质上是给注意力通道之间建立了一种“分工机制”。它没有规定哪个通道必须关注哪个部位只是告诉网络你们几个最好各看各的别扎堆。结果就是网络自动把鸟的头部、翅膀、尾巴等具有强判别力的部位分摊到了不同通道上。这一设计的意义不只在MA-CNN本身后来很多弱监督细粒度方法都沿用了类似的互斥思想。3. 关键实现细节与训练策略很多照着论文复现的人会在训练阶段翻车原因往往不是结构理解错了而是损失函数和优化策略没处理好。MA-CNN的训练方式里有几个细节是决定成败的关键。3.1 损失函数构成MA-CNN的总损失由两部分组成分类损失用标准的交叉熵直接监督最终聚合特征的分类结果。这部分梯度能同时传到全局分支、部件分类分支和特征主干上是整个模型优化的主驱动。互斥正则项则作用在注意力图上。具体到实现常见做法是计算每对注意力图的归一化内积或者用它们在空间位置上的逐元素乘积之和加在一起作为惩罚项。为了方便控制强度通常会乘以一个权重系数λ论文和复现中λ一般取一个小到中等的值比如0.1到1之间。λ太小互斥约束起不到作用注意力容易扎堆λ太大模型会过度关注“区域分离”而忽略分类性能导致注意力图分裂成碎片。实际调的时候我习惯把λ先设成0.1训练几轮之后看一眼注意力图的可视化结果。如果发现两张图明显重叠再把λ往上调如果注意力图看起来像噪声就降下来。这个值对数据集比较敏感换数据集后值得重新搜一遍。3.2 交替优化不是一次反向传播到底MA-CNN论文里有一个让很多人容易忽略的点训练不是简单地把所有模块端到端一起反传而是采用了一种分阶段、交替优化的策略。大致过程是先单独用全图分类损失训练主干网络和全局分类器让它先具备一个合理的特征提取能力然后固定主干只训练注意力建议网络让注意力图慢慢涌现出来再固定注意力模块用部件聚合特征训练分类器最后再整网联合微调。为什么要这么绕一圈因为注意力建议网络刚初始化时是毫无章法的如果从一开始就让它和主干一起端到端训练注意力图会给主干回传大量混乱的梯度主干特征很容易被带偏最后陷入一个“特征差—注意力差—特征更差”的恶性循环。先让主干在常规分类任务上稳住注意力模块学起来就容易得多。我自己复现时采用过一个简化版本前20个epoch只优化主干的分类损失注意力模块的梯度停用20个epoch后再放开注意力模块的梯度联合训练整个网络。实测效果跟完全交替优化接近但实现更简单。如果你追求论文效果的精确复现还是老老实实按交替优化来。3.3 代码级复现PyTorch核心模块下面给出一个简化的MA-CNN核心模块实现主干用VGG-16的conv特征部分注意力建议网络和部件聚合都在这里。代码只保留核心逻辑方便理解流程。import torch import torch.nn as nn import torch.nn.functional as F class MACNN(nn.Module): def __init__(self, base_net, num_parts8, num_classes200): super(MACNN, self).__init__() self.num_parts num_parts self.base_net base_net # 输出特征图 [B, 512, 14, 14] # 注意力建议网络从特征图生成num_parts张注意力图 self.attention_net nn.Sequential( nn.Conv2d(512, 512, 3, padding1), nn.ReLU(inplaceTrue), nn.Conv2d(512, num_parts, 1) ) self.cls_global nn.Linear(512, num_classes) self.cls_parts nn.Linear(512 * num_parts, num_classes) def forward(self, x): feature self.base_net(x) # [B, 512, H, W] # 注意力图并在部件维度做softmax att_logits self.attention_net(feature) # [B, num_parts, H, W] att_map F.softmax(att_logits, dim1) # 全局特征 global_feat F.adaptive_avg_pool2d(feature, 1).view(feature.size(0), -1) # 部件特征注意力加权池化 part_feats [] for i in range(self.num_parts): a att_map[:, i:i1, :, :] part_feat torch.sum(feature * a, dim[2, 3]) # [B, 512] part_feats.append(part_feat) part_feats torch.cat(part_feats, dim1) # [B, 512 * num_parts] logits self.cls_global(global_feat) self.cls_parts(part_feats) return logits, att_map这段代码里有一个值得注意的地方部件特征是在全分辨率特征图上做加权池化没有像很多后续方法那样先对注意力图做上采样再裁剪原图区域因此训练和推理时的计算量都在可控范围内。互斥正则损失可以这样写def part_overlap_loss(att_map, eps1e-8): # att_map: [B, num_parts, H, W]已经过softmax B, num_parts, H, W att_map.shape att_map_flat att_map.view(B, num_parts, -1) # [B, num_parts, H*W] overlap torch.bmm(att_map_flat, att_map_flat.transpose(1, 2)) mask torch.eye(num_parts, deviceatt_map.device).bool() overlap overlap[:, ~mask] # 去掉对角线 return overlap.mean()用矩阵乘法计算两两注意力图的内积比显式循环快很多。代码虽然简单但已经是MA-CNN压缩到极致后的核心了。实际工程项目里基于这个骨架改造成多尺度版本或者替换主干网络都很方便。4. 训练中的坑与调参心得这部分是我反复复现和改造MA-CNN过程中踩出来的经验很多坑在论文里不会写但实际跑起来几乎每个人都会遇到。4.1 注意力崩坏所有注意力图都指向同一片区域最经典的问题前面也提到过。现象是训练几轮之后可视化注意力图发现8张图长得基本一样。除了调整互斥正则权重λ之外还有一个容易忽略的原因softmax的归一化维度。如果代码里在空间维度上做softmax而不是在部件通道维度上做效果会完全不同。MA-CNN的注意力图在部件维度上做softmax是为了让同一位置的不同部件响应之和等于1形成“这一个像素点属于哪个部件”的竞争。如果误在空间维度上做softmax每个通道的空间响应被单独归一化互斥损失的梯度方向就变了注意力崩坏会更严重。复现前先检查这一点。4.2 部件数量N怎么选N的选择没有标准答案完全取决于数据集的判别区域复杂度。CUB鸟类数据集通常8到16个部件就能覆盖主要判别区域车型数据集可以设少一点因为车的主要判别区域就集中在前脸、车灯、轮毂如果是行人重识别这类任务N设16甚至24也没问题因为人体部件更分散。一个土办法先设一个较大的N比如32训练少量轮次后看哪些注意力通道对应的区域长期没有有效响应。如果一个通道的注意力图始终是一张接近零的低响应图说明它“分不到地”这时候再把N往下降。反过来如果可视化发现多个关键区域没有被任何通道覆盖就说明N太小。4.3 注意力图分辨率的影响VGG-16的conv5输出是14x14对224x224的输入来说每个像素对应原图16x16的区域。这个分辨率做细粒度定位只能说勉强够用偶尔会出现注意力图激活区域稍微偏一点导致部件特征里混入相邻部位信息的情况。我的经验是如果换用ResNet做主干尽量别直接在stride32的最后一层特征图上做注意力那样分辨率只剩7x7细粒度定位基本废了。比较实用的做法是在stage4或者stage3的输出上接注意力分支分辨率能到14x14或28x28定位精度明显提升。代价是注意力分支的计算量增加但换来的是更准确的部件发现。4.4 数据增强和输入尺度对注意力一致性的影响MA-CNN对输入尺度比通用分类网络更敏感。原因是注意力图最终要映射到原图空间训练时如果随机裁剪比例变化太大注意力模块看到的目标尺度分布会很广学出来的注意力图容易“漂”。我建议训练时先用固定尺度比如448x448或256x256随机裁剪不要一上来就用多尺度训练等模型收敛后再用多尺度微调注意力图会更稳定。另外推理时如果直接resize到训练尺度可能会出现整体表现比训练还差的情况因为传统resize对长宽比拉伸会影响空间对应关系。最好用先等比resize再中心裁剪的方式尽量保持目标形态一致。5. 后续扩展与MA-CNN留下的“遗产”MA-CNN虽然发表于2017年但它的设计思想在后来几年的工作里被反复引用和演化理解它对读懂很多新方法都很有帮助。比较直接的后继者是NTS-Net它把“注意力发现”升级成了“区域信息熵引导的有监督区域发现”用信息量来筛选最有判别力的区域。还有WS-DAN在MA-CNN的部件注意力基础上引入了双线性池化让部件特征之间的二阶交互信息参与分类进一步提升了细粒度识别的上限。另外像PCB这类行人重识别网络里对特征图做水平方向的分块注意力思路也明显受到MA-CNN“多个部件注意力”的影响。往大一点说MA-CNN里“多个注意力图 互斥约束 交替优化”的组合和Transformer中multi-head self-attention的直觉是一致的多头注意力让不同head关注不同语义关系避免所有注意力坍缩到同一模式。虽然两者的数学形式完全不同但目的异曲同工。区别在于Transformer直接靠大规模数据和残差结构来避免注意力崩坏而MA-CNN需要用显式的互斥正则来约束。这在一定程度上也解释了为什么注意力机制在CNN里始终没有像Transformer那样“无条件好用”因为缺少了天然的多样性保证机制。在应用层面MA-CNN的思路已经被迁移到很多任务里细粒度车型识别、农产品外观分级、工业缺陷检测等等。凡是“整体长得很像只有局部有差异”的场景都可以借鉴这种多注意力部件的思路。我现在做一个零件表面划痕检测项目时就是拿MA-CNN的框架做底把分类头换成了多标签输出用注意力图定位多处可能同时出现的缺陷区域效果比单纯用目标检测稳不少。回到MA-CNN本身我最深的体会是它的成功不在于网络结构有多精妙而在于把“细粒度识别难在何处”这个问题分析得很透彻然后针对性地设计了注意力生成、部件特征池化、互斥正则这三个环环相扣的模块。任何一个模块单独拿出来都不是什么惊天设计但组合在一起就形成了一套完整的弱监督部件学习方案。这也是很多经典论文的共同特质——结构简单逻辑严密每一块设计都有明确的问题指向。到现在我给学生讲细粒度识别时还是会先让ta们把MA-CNN完整复现一遍。这个过程中能踩的坑基本就是做弱监督视觉任务所有可能踩的坑。本文还有配套的精品资源点击获取
返回列表