CAM:Learning Deep Features for Discriminative Localization论文翻译笔记(2016-CVPR)

2016-CVPR-CAM 论文笔记

CAM原文:Learning Deep Features for Discriminative Localization


摘要

  • 这项工作中,作者重新审视了之前提出的全局平均池化层(global average pooling, GAP),并阐明了它是如何使只经过图像标签训练CNN具备显著的定位能力(localization ability)
  • 虽然GAP最初是作为规范化训练手段被提出的,但作者发现GAP实际上是构建了一个通用的定位深度表征(generic localizable deep representation),揭示了CNNs在图像上的隐性注意力。
  • 尽管GAP表面比较简单,但是作者在ILSVRC2014上没有经过任何标注框的训练,就实现了top-5 error在37.1%的对象定位。作者在各种实验中也证明了,尽管他们的网络只是为了解决分类任务而训练的,但却能够定位鉴别性图像区域(discriminative image regions)

1. 引言

  • 最近的一项工作表明,CNNs在没有监督对象位置的情况下,实际表现为对象检测器。尽管卷积层有这种非凡的能力,但使用全连接层时这种能力就会丧失。最近提出的网络如Network in Network(NIN)和GoogleNet都在避免使用全连接层,在最小化参数量的同时保持不错的性能。

  • 在训练过程中,GAP作为结构正则器,防止过拟合。在作者的实验中,他们发现对GAP不仅能作为正则器,对其稍加调整,甚至网络最初并没有被训练过的情况下,GAP还能在一次前向传播过程中很容易的识别出鉴别性图像区域,如下图1所示:
    在这里插入图片描述

  • 尽管他们的方法看起来简单,但在ILSVRC的若监督对象定位上,他们的最佳网络达到了了37.1%top-5测试误差,与全监督的AlexNet相近。

  • 此外他们还证明了,他们的方法所提取到的深层特征的定位能力可以很容易的应用在其他数据集上,用于一般分类、定位和概念发现上。

1.1 相关工作

  • CNNs在各种视觉识别任务上的表现非常出色,最近的一项工作表明,虽然是用图像级标签进行训练,但CNNs仍然据偶显著的对象定位能力。在他们的工作中,他们展示了通过使用合适的架构,就可以将这种能力推广,不仅仅能够定位对象,还可识别图像种哪些区域被作为CNN分类的判别依据。

  • 在这里,他们就与该文章最相关的两个领域展开讨论:弱监督的对象定位和可视化CNN的内部表示。

弱监督物体定位
  • 相关研究见原文,不重要,略
  • 通过作者的调查发现,现有方法不是进行端到端的训练,而且需要多次前向传播才能实现对象定位,因此很难扩展到真实世界的数据集。而作者的方法则是端到端训练的,并且在一次正向传播过程中就可以定位对象。
  • 与作者最为相似的工作是Oquab等人使用全局最大池化(global max pooling,GMP)的工作。他们采用的GMP技术只可定位对象边界上的一个点,而不能确定对象的完整范围。作者认为,虽然GAP和GMP相当相似,但使用GAP可以让网络确定对象的完整范围。这背后的直觉是:与最大池化相比,当网络识别出对象的所有判别区域时,平均池化的损失会受益。这一点将在3.2节中进行更详细的解释和实验验证。此外,他们证明了这种定位能力是通用的,甚至对未经训练的网络也有作用。
可视化CNN的内部表示
  • 最近有许多工作都在对CNN的内部表征进行可视化,来更好的理解它的属性。Zeiler等人使用反卷积网络来可视化每个激活单元的模式。Zhou等人表明,CNN在被训练识别场景的同时,还能够学习对象检测,并且证明了同一网络可以在一次正向传递中同时进行场景识别和对象定位。这两篇文章都只分析了卷积层,而忽略了全连接层,因此不能完整地描述整个网络。通过去除全连接层并仍能保留了大部分性能,作者能够从头到尾的理解他们的网络。
  • Mahendran等人和Dosovitskiy等人通过反转不同层的深层特征来分析CNNs的视觉编码。虽然这些方法可以反转全连接层,但它们仅显示深层特征中保留的信息,而没有突出这些信息的相对重要性。与他们不同,作者的方法可以精确地突出图像中哪些区域对辨别重要。

2. 类激活图(CAM)

  • 在这一节中,作者描述了在CNNs中使用GAP生成类激活图(class activation maps,CAM)的详细过程一个特定类别的类激活图表明了哪些判别图像区域被CNN用来鉴别图像类别,如下图2所示:
    在这里插入图片描述

  • 生成这些图的过程见下图3:
    在这里插入图片描述

  • 作者使用的网络结构类似于Network in Network和GoogleNet,这些网络主要由卷积层组成,在最终的输出层(在分类任务下为Softmax)前,他们将GAP应用到卷积特征图上,并将其用作产生所需输出的全连接层的特征。

  • 如上图所示,GAP的输出是最后一个卷积层每个单元的特征图的空间平均值,这些值的加权和生成最终输出。类似的,通过计算最后一个卷积层的特征图的的加权和来获得CAM。在下面将对于softmax的情况进行更为详细的描述。这种技术同样可以应用到回归和其他损失函数中。

  • 给定一幅图像,设 f k ( x , y ) f_k(x,y) fk(x,y) 表示最后一个卷积层中单元 k k k 在空间位置 ( x , y ) (x,y) (x,y) 的激活,对于单元 k k kGAP的输出结果为:

    F k = ∑ x , y f k ( x , y ) F^k=\sum_{x,y}f_k(x,y) Fk=x,yfk(x,y)
    因此,给定类别 c c csoftmax的输入类别评分函数,class score)为:

    S c = ∑ k w k c F k S_c=\sum_kw^{c}_kF_k Sc=kwkcFk

    这里 w k c w_{k}^c wkc 表示单元 k k k 对于类别 c c c 的权重贡献,实际上 w k c w_{k}^c wkc 表明了 F k F_k Fk 对于类别 c c c 的重要程度。

    最终,对于类别 c c csoftmax的输出为:
    P c = e x p ( S c ) ∑ c e x p ( S c ) P_c=\frac{exp(S_c)}{\sum_c{exp(S_c)}} Pc=cexp(Sc)exp(Sc)
    这里作者忽略了偏置项,他们明确地将softmax的输入偏置设置为0,因此这对分类性能没有影响。

    F k = ∑ x , y f k ( x , y ) F^k=\sum_{x,y}f_k(x,y) Fk=x,yfk(x,y) 代入 S c S_c Sc (class score)中,得到:
    S c = ∑ k w k c ∑ x , y f k ( x , y ) = ∑ x , y ∑ k w k c f k ( x , y ) S_c=\sum_kw^{c}_k\sum_{x,y}f_k(x,y)=\sum_{x,y}\sum_k{w_k^{c}f_k(x,y)} Sc=kwkcx,yfk(x,y)=x,ykwkcfk(x,y)
    对于类别 c c c ,作者定义 M c M_c Mc 为类别激活图(CAM),其中CAM的每个空间元素由下式给出:
    M c ( x , y ) = ∑ k w k c f k ( x , y ) M_c{(x,y)}=\sum_k{w_{k}^{c}f_k{(x,y)}} Mc(x,y)=kwkcfk(x,y)
    因此,可以导出类别评分函数与类激活图的关系
    S c = ∑ x , y M c ( x , y ) S_{c}=\sum_{x,y}{M_c{(x,y)}} Sc=x,yMc(x,y)
    因此, M c M_c Mc 就直接反映了空间位置 ( x , y ) (x,y) (x,y) 处的激活对图像被归为 c c c 类的贡献(重要)程度。

  • 基于以前的工作,作者期望每个单元被其感受野内的一些视觉模式激活。因此 f k f_k fk 就是表征这些视觉模式的图。类激活图(CAM)只是这些视觉模式在不同空间位置的线性加权和。通过简单的将CAM上采样到原始图像的大小,就可以识别与特定类别最相关的图像区域。

  • 图2中展示了一些CAMs输出的例子,可以看到不同类别的图像的判别区域被突出显示。下图4展示了当使用不同的类别来生成CAMs时,单张图像的CAMs的差异,可以观察到,即使对于同一幅图像,被划分为不同类别时的判别区域也是不同的。
    在这里插入图片描述

GAP(Golbal average pooling) vs GMP(global max pooling)

  • 考虑到之前的工作是将GMP用于弱监督对象定位,作者认为强调GAP和GMP之间的直观区别是很重要的。他们相信GAP损失鼓励网络去识别对象的范围,而GMP则鼓励网络去仅识别一个判别部分

  • 这是因为,在对特征图做平均时,可以通过发现对象的所有判别部分去最大化该值,因为所有的低激活都会相应的减少特定特征图的输出。另一方面,对于GMP来说,由于只保留了最大值,故除了最具判别性的区域外,整幅图像的低激活区域(low scores)都在输出上得到体现。

  • 作者在ILSVRC 2014上进行了实验验证,也证明了GMP与GAP在实现下你公司分类性能的情况下,GAP的定位能力表现优于GMP

3. 若监督物体定位

  • 在这一部分中,作者评估了当使用ILSVRC 2014数据集进行训练时,CAM的定位能力。他们首先描述了实验的设定和在3.1节使用的各种CNNs。然后,在3.2节中,作者证明了网络在学习定位时,他们的方法不仅不会对分类性能产生不利的影响,而且还能提供弱监督物体定位的精确结果

3.1 实验设置

  • 在下面的实验中,作者评估了使用CAM方法对主流的CNNs的影响,包括:AlexNet、VGGnet和GoogLeNet。通常情况下,对于这些网络,作者移除了最终输出前的全连接层,并且将其替换为一个全连接的softmax层。这里注意一点,移除全连接层可以大大减少网络参数(例如VGGnet中参数量减少了90%),但也会带来一些分类性能的下降。
  • 作者发现,当GAP之前最后一个卷积层具有更高的空间分辨率时(作者称之为映射分辨率,mapping resolution),网络的定位能力可以提高。正因如此,他们在一些网络中移除了几个卷积层。具体来讲:对于AlexNet,移除了conv5之后的层(即pool5到prob),使得输出 13 × 13 13\times13 13×13 的映射分辨率;对于VGGnet,移除了conv5-3之后的层(即pool5到prob),使得输出 14 × 14 14\times14 14×14 的映射分辨率;对于GoogLeNet,移除了inception4e之后的层(即pool4到prob),使得输出 14 × 14 14\times14 14×14 的映射分辨率。对于上述的每一个网络,作者都添加了一个 s i z e = 3 × 3 , s t r i d e = 1 , p a d = 1 , u n i t s = 1024 size=3\times3,stride=1,pad=1,units=1024 size=3×3,stride=1,pad=1,units=1024 的卷积层,之后连接一个GAP层和一个softmax层。然后每个网络都在ILSVRC的1.3M幅训练图像上进行微调,进行1000类的图像目标分类,产生最终的网络:AlexNet-GAP、VGGnet-GAP和GoogLeNet-GAP。
  • 对于分类任务,作者将他们的方法与原始的AlexNet、VGGnet和GoogLeNet进行比较, 还对比了Network in Network。
  • 对于定位任务,作者对比了原始GoogLeNet、NIN,并使用反向传播代替CAMs。此外,为了比较GAP与GMP,他们还提供了使用GMP训练的GoogLenet的结果(GoogLenet-GMP)。
  • 作者使用与ILSVRC的相同的分类和定位误差指标(top-1、top-5)来进行评估。对于分类,只在ILSVRC验证集上进行评估;对于定位,评估验证集和测试集。

3.2 实验结果

  • 分类误差如下表1所示:
    在这里插入图片描述

  • 定位误差如下表2、表3所示:
    在这里插入图片描述
    在这里插入图片描述

4. 通用定位的深度特征

  • CNN的高层响应已经被证明是非常有效的通用特征,在各种图像数据集上具有先进的性能。在这一节中,作者表明他们的GAP CNNs所学习到的特征(不管是作为通用特征还是额外特征),尽管没有针对那些特定的任务进行训练,在分类判别图像区域时表现也都非常良好。

  • 为了获得与原始softmax层相似的权重,只需在GAP层的输出上训练一个线性SVM即可。

    之后的部分翻译意义不大,多为应用领域的拓展, 见原文即可

附:PyTorch实现代码

# simple implementation of CAM in PyTorch for the networks such as ResNet, DenseNet, SqueezeNet, Inception

import io
import requests
from PIL import Image
from torchvision import models, transforms
from torch.autograd import Variable
from torch.nn import functional as F
import numpy as np
import cv2
import pdb

# input image
LABELS_URL = 'https://s3.amazonaws.com/outcome-blog/imagenet/labels.json'
IMG_URL = 'http://media.mlive.com/news_impact/photo/9933031-large.jpg'
# 使用本地的图片和下载到本地的labels.json文件
# LABELS_PATH = "labels.json"

# networks such as googlenet, resnet, densenet already use global average pooling at the end, so CAM could be used directly.
model_id = 1
# 选择使用的网络
if model_id == 1:
    net = models.squeezenet1_1(pretrained=True)
    finalconv_name = 'features' # this is the last conv layer of the network
elif model_id == 2:
    net = models.resnet18(pretrained=True)
    finalconv_name = 'layer4'
elif model_id == 3:
    net = models.densenet161(pretrained=True)
    finalconv_name = 'features'

# 有固定参数的作用,如norm的参数
net.eval()

# 获取特定层的feature map
# hook the feature extractor
features_blobs = []
def hook_feature(module, input, output):
    features_blobs.append(output.data.cpu().numpy())

net._modules.get(finalconv_name).register_forward_hook(hook_feature)

# 得到softmax weight
# get the softmax weight
params = list(net.parameters())                     # 将参数变换为列表
weight_softmax = np.squeeze(params[-2].data.numpy())# 提取softmax层的参数

# 生成CAM图的函数,完成权重和feature相乘操作
def returnCAM(feature_conv, weight_softmax, class_idx):
    # generate the class activation maps upsample to 256x256
    size_upsample = (256, 256)
    # 获取feature_conv特征的尺寸
    bz, nc, h, w = feature_conv.shape               
    output_cam = []
    # class_idx为预测分值较大的类别的数字表示的数组,一张图片中有N类物体则数组中N个元素                          
    for idx in class_idx:          
        # weight_softmax中预测为第idx类的参数w乘以feature_map(为了相乘,故reshape了map的形状)
        cam = weight_softmax[idx].dot(feature_conv.reshape((nc, h*w)))
        # 将feature_map的形状reshape回去
        cam = cam.reshape(h, w)
        # 归一化操作(最小的值为0,最大的为1)
        cam = cam - np.min(cam)
        cam_img = cam / np.max(cam)
        # 转换为图片的255的数据
        cam_img = np.uint8(255 * cam_img)
        # resize 图片尺寸与输入图片一致
        output_cam.append(cv2.resize(cam_img, size_upsample))
    return output_cam

# 数据处理,先缩放尺寸到(224*224),再变换数据类型为tensor,最后normalize
normalize = transforms.Normalize(
   mean=[0.485, 0.456, 0.406],
   std=[0.229, 0.224, 0.225]
)
preprocess = transforms.Compose([
   transforms.Resize((224,224)),
   transforms.ToTensor(),
   normalize
])

# 通过requests库获取图片并保存,若是本地图片则只需要设置本地图片的保存地址,以便后来提取便好
img_path = "sample.jpg"
img_pil = Image.open(img_path)
# get the input image
# response = requests.get(IMG_URL)
# img_pil = Image.open(io.BytesIO(response.content))
# img_pil.save('test.jpg')
# 将图片数据处理成所需要的可用的数据
img_tensor = preprocess(img_pil)
# 处理图片为Variable数据
img_variable = Variable(img_tensor.unsqueeze(0))
# 将图片输入网络得到预测类别分值
logit = net(img_variable)

# download the imagenet category list
# 下载imageNet 分类标签列表,并存储在classes中(数字类别,类别名称)
classes = {
    int(key):value for (key, value)
          in requests.get(LABELS_URL).json().items()
}

# # 使用本地的 LABELS_PATH
# with open(LABELS_PATH) as f:
# data = json.load(f).items()
# classes = {int(key):value for (key, value) in data}
# 使用softmax打分
h_x = F.softmax(logit, dim=1).data.squeeze()
# 对分类的预测类别分值排序,输出预测值和在列表中的位置
probs, idx = h_x.sort(0, True)
# 转换数据类型
probs = probs.numpy()
idx = idx.numpy()

# 输出预测分值排名在前五的五个类别的预测分值和对应类别名称
# output the prediction
for i in range(0, 5):
    print('{:.3f} -> {}'.format(probs[i], classes[idx[i]]))

# generate class activation mapping for the top1 prediction
# 输出与图片尺寸一致的CAM图片
CAMs = returnCAM(features_blobs[0], weight_softmax, [idx[0]])

# render the CAM and output
print('output CAM.jpg for the top1 prediction: %s'%classes[idx[0]])
# 将图片和CAM拼接在一起展示定位结果结果
img = cv2.imread('sample.jpg')
height, width, _ = img.shape
# 生成热度图
heatmap = cv2.applyColorMap(cv2.resize(CAMs[0],(width, height)), cv2.COLORMAP_JET)
result = heatmap * 0.3 + img * 0.5
cv2.imwrite('CAM.jpg', result)
  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值