nptel-dlcv-merge-0
NPTEL 深度学习计算机视觉笔记(一)
038:解释CNN - 可视化方法


在本节课中,我们将学习如何理解、可视化和解释卷积神经网络(CNN)或其他神经网络的模型预测。我们将从可视化方法开始,探讨CNN中不同卷积核或滤波器、特定层的激活值以及其他相关技术。本讲内容主要基于斯坦福大学CS 231N课程的第13讲,并参考了IIT Madras的Mitesh Khapra教授的精彩讲座。
可视化卷积核
上一节我们介绍了课程概述,本节中我们来看看最简单的可视化形式——可视化CNN中的滤波器或卷积核本身。
在CNN的每个卷积层中,都有一定数量的滤波器。例如,在AlexNet的第一个卷积层中,有64个大小为11x11的滤波器,每个滤波器包含三个颜色通道(R、G、B)。我们可以将这些滤波器本身当作图像来可视化。
以下是可视化第一层卷积核的步骤:
-
将每个滤波器矩阵(例如11x11x3)视为一个小图像。
-
将每个通道的值归一化到合适的显示范围(如0-255)。
-
将所有滤波器排列在一个网格中进行显示。
当这样操作时,我们会发现第一层的滤波器通常呈现出类似Gabor滤波器的模式,它们能捕捉不同方向的边缘、颜色块、棋盘格图案和条纹等低层图像特征。这种现象在多种CNN模型(如AlexNet、ResNet、DenseNet、VGG)的第一层中普遍存在,因此被称为“类Gabor滤波器疲劳”。
可视化表示空间
了解了单个滤波器的可视化后,我们来看看CNN学习到的整体表示空间。
我们可以提取CNN倒数第二层(通常是最后一个全连接层,如AlexNet中的fc7层)的输出向量。对于一批测试图像,我们前向传播得到这些高维特征向量(例如4096维),然后使用降维技术将其映射到二维空间以便可视化。
以下是进行表示空间可视化的方法:
-
使用主成分分析(PCA)或t-SNE等降维方法。
-
将降维后的二维点根据其真实类别用不同颜色标注。
-
观察不同类别的点是否在二维空间中形成分离的簇。
例如,在MNIST手写数字数据集上,经过t-SNE降维后,不同数字的样本会清晰地聚集在不同的区域。这表明CNN学习到的表示具有良好的语义区分能力,相似语义的物体在表示空间中距离更近。
可视化激活与神经元
除了静态的权重,我们还可以动态地观察CNN在处理输入时的内部状态。
我们可以选择CNN中间层的某个特定神经元,然后观察哪些输入图像或图像中的哪些区域会最大程度地激活该神经元。通过分析这些“激发”该神经元的图像块,我们可以推断该神经元所响应的视觉模式。
以下是分析特定神经元的步骤:
-
准备一个大型图像数据集。
-
将每张图像输入训练好的CNN。
-
记录目标神经元在每张图像上的激活值。
-
找出激活值最高的那些图像,并定位导致高激活的图像区域(通过计算感受野)。
在实践中,我们可能发现某些神经元专门对人脸、文本、动物眼睛或特定纹理(如红色斑点、镜面反射)产生响应。这有助于我们理解CNN各层是如何逐步构建对复杂特征的表示的。
遮挡实验
最后,我们来探讨一种旨在理解CNN决策依据的方法——遮挡实验。
为了验证CNN是否真正关注目标物体本身(例如,因为图像中有猫而预测为“猫”,而不是因为背景中的草地),我们可以系统地遮挡图像的不同部分,并观察模型预测概率的变化。
以下是进行遮挡实验的步骤:
-
选择一张输入图像及其真实标签。
-
使用一个灰色方块(或其他遮挡物)依次遮挡图像中的每个区域。
-
每次将遮挡后的图像输入CNN,并记录模型对真实标签的预测概率。
-
为每个被遮挡的位置生成一个概率值,从而形成一个热力图。
在这个热力图中,概率显著下降的区域(通常显示为蓝色)就是模型做出决策所依赖的关键区域。例如,在一张狗的图像中,当狗的脸部被遮挡时,模型预测为“博美犬”的概率会大幅下降,这证明模型确实是通过识别狗的脸部特征来做出判断的。
总结
本节课中我们一起学习了多种理解和可视化CNN的“不干扰模型”方法。我们首先介绍了如何可视化卷积核,特别是第一层中普遍存在的类Gabor滤波器。接着,我们探讨了如何通过降维技术(如t-SNE)来可视化CNN学习到的高维表示空间。然后,我们学习了如何通过分析最大化激活特定神经元的图像块来理解神经元的功能。最后,我们介绍了遮挡实验,该方法通过扰动输入并观察输出变化,来揭示模型决策所依赖的图像区域。
这些方法帮助我们窥探CNN的“黑箱”,增强我们对模型为何以及如何做出特定预测的理解,是模型可解释性研究的重要基础。


推荐阅读与资源:
-
CS 231N课程讲义。
-
Jason Yosinski的深度可视化工具包演示网页和视频。
-
关于t-SNE降维技术的详细介绍和可视化示例。
039:解释CNN - 早期方法


在本节课中,我们将继续探讨几种帮助我们理解卷积神经网络及其预测的方法。我们将学习如何通过优化输入图像来最大化特定类别的得分,如何可视化数据梯度,以及如何使用引导反向传播技术来获得更清晰的激活可视化。这些方法为我们打开了理解CNN内部工作机制的窗口。
最大化类别得分的图像生成 🖼️
上一节我们讨论了如何通过可视化来理解CNN。本节中,我们来看看一个不同的问题:我们能否找到一张能最大化某个类别得分的图像?
例如,一个在ImageNet上训练好的AlexNet模型见过很多猫的图像。那么,它是否知道一只“平均”的猫长什么样?如果我们要求它重建一张猫的图像,它能做到吗?这就是我们试图回答的问题。
以下是实现这一目标的核心步骤:
-
初始化输入:首先,我们取一个训练好的CNN模型(如AlexNet)。我们提供一个初始图像作为输入,这个图像可以是全黑或全灰的零图像。
-
定义目标:我们希望网络的最终输出是一个独热向量,其中我们感兴趣的类别(如“猫”)位置为1,其余位置为0。
-
计算梯度:由于输入零图像不会得到目标输出,我们计算损失。关键的一步是,我们不更新网络权重,而是计算损失相对于输入图像的梯度,即
dL/dx。 -
梯度上升更新:我们使用梯度上升来最大化目标类别的得分。这意味着我们沿着梯度的正方向更新输入图像,使其越来越能激活目标类别。更新公式可以表示为:
I_new = I_old + η * (dL/dI)其中
η是学习率,dL/dI是损失相对于输入图像的梯度。 -
迭代优化:重复步骤3和4,不断更新输入图像,直到收敛。最终得到的图像就是模型认为最能代表目标类别的“平均”图像。
通过这种方法生成的图像可能看起来不像我们日常认知中的物体,但它们揭示了模型内部学到的特征模式。例如,最大化“洗衣机”类别的图像可能包含重复的圆形和矩形纹理。
可视化数据梯度 📊
除了生成图像,我们还可以直接可视化损失相对于输入图像的梯度(dL/dI)。这个梯度图可以粗略地显示图像中哪些像素对提高特定输出神经元的得分贡献最大。
由于输入图像有RGB三个通道,梯度在每个通道上都有一个值。一种常见的可视化方法是取每个像素点上三个通道梯度绝对值的最大值。
以下是几个可视化数据梯度的例子。可以看到,对于“帆船”图像,高梯度区域集中在帆和船体的轮廓上;对于“狗”的图像,高梯度区域则突出了狗的头部和身体。这表明梯度图能够定位与类别判断相关的图像区域。
结合图割生成对象掩码 ✂️
仅仅有梯度图还不够直观。我们可以将数据梯度与一种称为图割的图像分割算法结合,来生成更精确的对象掩码。
具体流程如下:
-
给定一张输入图像,我们首先计算其对于某个预测类别(如“鸟”)的数据梯度。
-
将这个梯度图作为输入,应用图割算法。图割算法能够根据像素间的相似性(由梯度等特征定义)和差异性,将图像分割成前景(对象)和背景。
-
算法输出一个二值掩码,清晰地标出了模型认为导致该类别预测的图像区域。
这种方法能有效地从背景中分离出对象,为进一步的分析(如目标定位)提供了可能。
从特征编码重建图像 🔄
另一个有趣的问题是:给定CNN中间层(如AlexNet的FC7层)的特征编码,我们能否重建出原始图像?这相当于一个“解码”或“反演”过程。
我们可以通过解决一个优化问题来实现:
-
目标函数:我们希望找到一张图像
x,使其FC7层的特征φ(x)尽可能接近我们给定的目标编码φ_target。同时,我们希望重建的图像看起来自然,因此需要加入图像先验正则化项(如惩罚图像的高频噪声)。 -
优化过程:目标函数可以形式化为:
x* = argmin_x ||φ(x) - φ_target||² + λ * R(x)其中
R(x)是正则化项,λ是平衡两项的系数。我们通过梯度下降法迭代更新图像x来最小化这个目标函数。 -
结果:从不同的初始图像开始优化,可能会得到不同的局部最优解,但它们都与原始图像在语义上具有整体相似性。这证明了CNN的高层特征确实捕获了图像的核心语义信息。
引导反向传播 🧭
在反向传播计算梯度以可视化时,标准的ReLU激活函数在反向过程中,会将前向传播时输入为负的神经元的梯度置零。然而,梯度本身可能仍包含正值和负值。
引导反向传播 在2015年被提出,它对标准过程做了一个关键修改:在反向传播时,不仅屏蔽掉前向传播中被ReLU抑制(输入为负)的神经元的梯度,还额外屏蔽掉所有负的梯度值。也就是说,只允许正的梯度传递回前面的层。
这样做的直观理解是:我们只关心那些对激活目标神经元有正面贡献的像素。屏蔽掉负梯度(即那些会降低激活值的像素)后,得到的可视化图像噪声更少,对象轮廓更加清晰。例如,在可视化“猫”的激活时,引导反向传播能产生比标准方法更干净、更易辨认的猫的轮廓。
这种方法已成为许多现代CNN解释方法的基础组件。
总结与练习 📝
本节课我们一起学习了几种理解CNN的早期重要方法:
-
图像生成:通过梯度上升优化输入,生成能最大化特定类别或神经元激活的图像。
-
梯度可视化:直接绘制损失相对于输入的梯度,以识别重要像素区域。
-
图割分割:结合数据梯度和图割算法,生成目标对象的二值掩码。
-
特征反演:从网络中间层的特征编码出发,通过优化重建语义相似的图像。
-
引导反向传播:通过仅反向传播正梯度,获得更清晰、噪声更少的激活可视化图像。
这些方法为我们提供了工具,去窥探CNN这个“黑箱”内部的运作机制,理解模型究竟根据图像的哪些部分做出了决策。
推荐阅读与练习:
-
课程CS231N中关于可视化CNN的讲义。
-
论文《Deep Inside Convolutional Networks》、《Visualizing and Understanding Convolutional Networks》、《Striving for Simplicity: The All Convolutional Net》。
-
练习:请通过此链接学习图割算法,并尝试理解如何将其与数据梯度结合生成对象掩码。


040:类别归因图方法 🧠

在本节课中,我们将学习几种用于解释卷积神经网络预测的类别归因图方法。我们将从基础的类别激活映射开始,逐步探讨其改进版本,理解它们如何通过可视化网络关注的图像区域来解释模型的决策过程。
概述
我们已经了解了多种利用CNN模型不同方面(如不同层的激活值、梯度、输出概率等)来解释其预测的方法。现在,我们将关注一类被称为类别判别性显著图或类别归因图的方法。
所谓“显著图”,指的是对于给定预测而言,图像中那些显著的区域。而“类别判别性显著图”,则特指那些有助于区分不同类别的显著图。例如,在一张同时包含猫和狗的图片中,我们想知道是图像的哪部分导致模型将其预测为猫,又是哪部分导致其被预测为狗。
接下来的内容将对此进行更详细的探讨。
类别激活映射
我们持续追问的问题是:能否知道网络在预测某个类别时,正在“看”图像的哪部分?但接下来要介绍的方法,与我们之前所见有所不同。
这方面最早的方法之一是类别激活映射,发表于2015-2016年。该方法利用卷积神经网络,并引入全局平均池化的概念来实现目标。
工作原理
让我们详细了解一下。假设你的网络架构中有五到六个卷积层。你取最后一个卷积层的输出。对于该卷积层中的每个特征图(假设有100个滤波器,你就有100个特征图),你对其进行全局平均池化。
全局平均池化具体做什么呢?它取一个特定的特征图(例如图中绿色的那个),将该图中所有强度值平均为一个单一的标量(即图中的绿色圆圈)。同样地,对红色特征图进行平均得到红色标量,对蓝色特征图进行平均得到蓝色标量。
现在,我们如何处理这些全局平均值呢?每个标量代表一个特征图。接着,我们学习一个简单的回归模型(或线性模型),将这些标量映射到最后一层的每个类别标签上。
具体来说,对于最后一层的每个类别标签,我们学习一组权重:w1 * 第一个特征图的平均值 + w2 * 第二个特征图的平均值 + ... + wn * 第n个特征图的平均值,其中n是最后一个卷积层中特征图的数量。
如何生成显著图
对于一张给定的图像(例如这里看到的图像),如果你将其前向传播通过一个训练好的AlexNet,直到最后一个卷积层,你会得到一组n个激活图。假设这些就是图中所示的激活图。
现在,我们使用在GAP层和分类层之间学到的权重,对每个激活图进行加权。当你对所有激活图进行加权求和时,就得到了这些激活图对某个特定类别标签的贡献。
在这个例子中,假设我们想预测这张图像属于“澳大利亚梗犬”类。那么,你学习对应于“澳大利亚梗犬”类的每个激活图的权重,并以相同的方式对每个激活图进行加权。这个卷积层激活图的加权组合,就对应了“澳大利亚梗犬”类。
如果你想预测图像中的“人”类,那么你会得到一组不同的权重(对应“人”类)。因此,同一组激活图的不同加权组合,会告诉你图像的哪部分对应于“人”类。
这种方法为我们提供了一种获取类别判别性显著图的途径。
视觉示例与优势
以下是该方法的一些视觉示例。在前三张图片中,是“比熊犬”类的例子;后三张是“杠铃”类的例子。你可以看到,在每种情况下,CNN模型都关注特定区域(比如比熊犬的脸部、杠铃的配重片)来做出决策。
你可能会问:如果我改变类别会怎样?这里也有一个例子。一张图像及其基于该图像预测的前五个类别的显著图(类别激活图)。前五个预测类别是:圆顶、教堂、祭坛、修道院。可以看到,当模型预测“圆顶”时,它只关注圆顶部分;预测“教堂”时,关注靠近圆顶的立面;预测“祭坛”和“修道院”时,则关注可能使其认为是该类别的特定部分。
这为我们提供了类别特定的激活图,在实践中非常有用。
方法背后的直觉
在大多数CNN中,我们通常有几个卷积层,后面跟着一些全连接层。卷积层在一定程度上保持了物体定位的能力。然而,全连接层(例如Fc7表示)会丢失这种空间信息。这正是开发卷积层本身的原因之一。
通过基于CAM的方法,我们实际上可以检索第五层的激活图,并利用它们来解释分类层的决策。
不同模型比较与优缺点
这里比较了不同模型上的CAM图。可以看到,在GoogleNet、VGG、AlexNet、网络中的网络等架构上应用CAM或使用GAP。并与AlexNet和GoogleNet上的反向传播(即数据梯度)进行了比较。显然,CAM提供了比数据梯度更强、更有用的显著图。
CAM的优点:
-
具有类别判别性。
-
无需任何位置监督即可定位物体。
-
不需要像引导反向传播或反向传播到图像那样,通过整个模型进行反向传递。
CAM的缺点:
-
关键缺点在于,为了获得GAP后的那些权重,实际上需要重新训练模型。在训练完AlexNet后,你仍然需要进行全局平均池化,并在最后一层学习那些线性模型,以理解激活图与每个类别标签之间的关系。这在你训练好的CNN模型之上,为解释增加了额外的计算负担。
-
我们通过强制引入全局平均池化层来对架构施加了约束,这可能在你希望将此类方法推广到许多视觉任务时造成问题。
-
模型可能会为了可解释性而牺牲准确性。为了获得更好的可解释性,如果直接使用GAP模型及其相应权重进行分类,最终可能会达到较低的准确率。
梯度加权类别激活映射
现在,让我们看看如何解决这些缺点。这在一个后续方法中得以实现,该方法发表于2017年ICCV(顶级计算机视觉会议)。
梯度加权类别激活映射是一个非常巧妙的方法,它重新利用了CNN中已有的量来实现CAM的功能。
整体思路与架构
以下是整体思路和架构,我们将在接下来的幻灯片中描述每个组件。
-
输入图像通过CNN。
-
得到卷积特征图。这些特征图后面可以接任何特定任务的网络(例如我们目前看到的分类,也可以是图像描述或视觉问答等后续课程会讲的任务)。
-
无论任务如何,我们都假设有一个最后一层、一个损失函数和一个梯度(任何神经网络中都可以假设存在)。
-
一旦你有了关于任何任务的损失梯度,你就有了关于所有特征图或激活图的梯度。
-
将这些梯度组合起来,它们会自动成为每个特征图的权重。
-
在Grad-CAM中,为了确保最终显著图中只显示正相关性,我们在激活图的加权组合上应用一个ReLU函数,这就得到了我们最终的Grad-CAM显著图。
-
该方法还讨论了加入引导反向传播来创建一个变体,称为引导Grad-CAM。
数学推导:从CAM到Grad-CAM
从数学上讲,CAM中我们有类别得分 Yc,由对所有K个特征图的求和给出:Yc = Σk Wck * Fk,其中 Fk 是第k个特征图的全局平均池化结果。
如果我们现在计算 Yc 对 Fk 的梯度,∂Yc/∂Fk 正是 Wck。进一步推导可以发现,Wck 实际上等于 Yc 对特征图 Ak 中每个像素的梯度之和的平均(乘以一个归一化因子Z)。
这告诉我们一个重要信息:CAM模型中我们实际学习的权重 Wck,实际上就是每个类别得分相对于特征图中每个像素的梯度之和。因此,我们实际上不需要像CAM那样进行重新训练。你不需要全局平均池化,也不需要重新训练那些权重;你只需要计算最后一层得分相对于任何你想计算显著图的特征图的梯度,就可以得到这些权重。
最终公式
因此,我们现在可以将 Wck 写为:Wck = (1/Z) * Σi Σj (∂Yc/∂Aijk),其中Z是归一化因子(特征图像素总数)。我们最终的显著图或定位图 Lc 由下式给出:Lc = ReLU( Σk Wck * Ak )。应用ReLU是为了只保留对类别有正面贡献的特征。
示例与引导Grad-CAM
现在看一些例子。对于原始图像,这里是“猫”的Grad-CAM,它聚焦在猫身上。同样,在一个ResNet模型上,它定位了猫的位置;对于“狗”,它关注狗的区域来做出判断。
但这里有一个仔细的观察:网络实际上预测这是“虎斑猫”而不仅仅是“猫”。我们能进一步阐述这一点,看看为什么它是虎斑猫吗?这就是该方法提出引导Grad-CAM变体的地方。
引导Grad-CAM将我们在之前课程中看到的引导反向传播和Grad-CAM结合在一起,通过进行逐像素乘积将它们叠加起来。现在我们可以看到,取Grad-CAM指出的特定区域,并将其与引导反向传播的输出(即图像中哪些像素是显著的)相结合,我们得到了一个更清晰的估计,知道模型在判断为“狗”时关注的是什么。引导反向传播本身不一定具有类别判别性,所以如果单独使用它,你也会看到其他类型的活跃像素。但通过与Grad-CAM结合,我们得到了一个更局部化的理解。
现在你明白为什么模型称它为“虎斑猫”了:将Grad-CAM显著图与引导反向传播的输出再次结合,你实际上得到了这种输出,它显示了猫身体上的条纹,这解释了为什么它是虎斑猫。
反事实解释
Grad-CAM还展示了该方法如何用于反事实解释。即,能否找出图像中哪个区域最大程度地影响了模型将图像判断为属于“狗”类?或者,哪些其他像素可能抑制了模型输出“狗”的概率?
这可以通过使用完全相同的Grad-CAM程序来完成,唯一的区别是使用负梯度而不是正梯度。这将告诉我们哪些特征图的组合(或加权组合)对提高某个类别概率产生了负面影响,从而为我们提供了反事实解释。最终,你可以通过某种方式移除或抑制这些特征来提高模型的置信度(如果你希望以这种方式使用它)。
Grad-CAM的局限性与Grad-CAM++
然而,Grad-CAM存在一些局限性,特别是在图像中存在多个物体实例或遮挡时。
例如,在一张有多只狗的图片中,Grad-CAM给出的输出似乎没有捕捉到图像中所有的狗。当物体数量较少时,它可能做得还不错,但这里仍然有三只狗,Grad-CAM捕捉到了两只,但错过了中间的那只——这是Grad-CAM的局限性之一。另一个例子是在存在遮挡的情况下:一只鸟的腿隐藏在水下,并且它有一个喙;这里还有一只刺猬,它也有一个类似喙的结构。在这两种情况下,Grad-CAM似乎没有捕捉到那些实际上是该物体类别判别性显著方面的特征。
我们能否对此做些改进?Grad-CAM本身的公式是否存在可以改进的局限性?这在一项名为Grad-CAM++的工作中得以实现。
主要动机
Grad-CAM++的主要动机在于观察到:Grad-CAM计算了类别得分 Yc 相对于激活图中每个像素的梯度,然后取所有这些梯度的平均值来得到其最终权重 Wkc。从某种意义上说,它在获取最终权重时,通过取平均值来平等地加权每个像素。
Grad-CAM++的想法指出:也许对类别贡献更大的像素应该获得比平等分配更多的权重。这尤其可以抑制那些空间足迹较小的激活图(正如上一张幻灯片中的例子:当有三只狗时,较小的那只狗获得的梯度较少,因为它的像素更少,因此对输出的贡献也较小)。当可视化中存在这种偏差时,一些较小的物体可能根本不会被显著图捕捉到。
方法改进
Grad-CAM++建议保留Grad-CAM的基本公式,但这次在计算最终权重 Wkc 时,我们将为每个激活图中的每个像素分配一定的权重(称为 α_ijk^c),以决定它们应如何贡献于该显著图。Grad-CAM++还在此权重的计算中添加了一个ReLU,以确保只考虑正梯度。
但核心问题是:在Grad-CAM中,平均所有梯度得到 Wkc 相对简单。现在,如何计算这些像素级的 α_ijk 呢?
直观理解
让我们再次直观地理解Grad-CAM++。这里注意到,如果一张图像中有三个不同的物体(比如狗),分别占据大、中、小的空间足迹。假设不同的特征图捕捉不同的狗(也可以是其他物体)。在Grad-CAM中计算梯度时,占据最大面积的区域最终获得了大部分梯度,而其余像素的梯度较小,因为它们像素较少,因此对输出的贡献较小。Grad-CAM++试图通过进行像素级加权来克服这个问题。可以看到,在使用这种方法时,梯度处于相同的范围内,最终显著图也处于相同的范围内。
如何计算像素级权重
我们不会在这里推导这个公式,这可能比较冗长,并将作为你的作业一部分。但通过重组梯度表达式并利用一些算术,Grad-CAM++表明,α_ijk^c 实际上可以作为一个闭式表达式获得,该表达式由你已经拥有的几个梯度项组成。一旦完成这个推导,其余部分与Grad-CAM非常相似:在Grad-CAM++中,你仍然在最后对 Σk Wck * Ak 应用ReLU,其中 Wck 由 Σi Σj α_ijk^c * ReLU(∂Yc/∂Aijk) 给出。
效果展示
这带来了什么帮助呢?现在可以看到,给定同一张多只狗的图片,虽然Grad-CAM没有有效地定位所有狗,但Grad-CAM++似乎得到了一个更好的、围绕狗的显著图。即使有三只狗,它也能工作得更好,并且还能捕捉到存在遮挡(如喙或腿)结构的情况。
Grad-CAM++还表明,与Grad-CAM相比,从Grad-CAM++获得的显著图提供了更好的定位效果(与图像提供的边界框相比)。你可以在这里看到几个结果:左侧块的第一列是原始图像,然后是对应类别的Grad-CAM可视化(头发、美洲龙虾、灰鲸、项链),以及似乎更好的Grad-CAM++定位(尤其是对于灰鲸等物体)。在另一组图像(灰鲸、风筝、山羊、鳗鱼)中,Grad-CAM++通过考虑像素级权重策略,其定位效果优于Grad-CAM。
这里还有更多Grad-CAM++针对物体多次出现情况的例子,再次显示了相对于Grad-CAM的改进性能。
总结与作业
在本节课中,我们一起学习了三种重要的类别归因图方法:类别激活映射、梯度加权类别激活映射及其改进版Grad-CAM++。我们了解了它们如何利用网络内部的激活图和梯度信息,生成能够解释CNN决策过程的视觉显著图。从需要修改网络结构的CAM,到利用现有梯度的Grad-CAM,再到进一步优化权重分配的Grad-CAM++,这些方法在保持或提升解释能力的同时,不断增强其适用性和准确性。

作业:
有三篇论文:CAM、Grad-CAM和Grad-CAM++。你的任务是阅读它们。
另一个练习是推导出Grad-CAM++中 α 权重的闭式表达式。
041:解释CNN - 最新方法 - 第一部分

概述 📖
在本节课中,我们将学习近年来用于解释卷积神经网络(CNN)乃至更广泛的神经网络行为的最新方法。我们将探讨传统梯度可视化方法的局限性,并介绍几种旨在解决这些问题的先进技术,包括DeepLIFT、积分梯度及其变体。
从梯度可视化到其局限性 🔍
上一节我们介绍了通过计算输出对输入的梯度(dy/dx)并可视化来生成归因图的方法。归因图显示了每个输入像素对最终输出分数的贡献程度。
然而,这种方法并不总是足够有效。让我们来看一个反例,说明仅使用梯度的方法可能会失败。
梯度方法的饱和问题
考虑一个简单的神经网络场景,有两个输入 i1 和 i2。它们经过一个使用ReLU激活函数的层,输出 h,最终输出 y 定义如下:
h = max(0, 1 - i1 - i2)
y = 1 - h
当 i1 + i2 > 1 时,h 恒为0,因此 h 对 i1 和 i2 的梯度也为0。同时,y 对 h 的梯度为负值。在引导反向传播等方法中,负梯度会被置零。综合来看,这导致最终计算出的梯度处处为零,无法提供有效的解释。这就是所谓的饱和问题。
解决饱和问题:DeepLIFT方法 🧠
DeepLIFT方法在2017年被提出,旨在解决上述饱和问题。其核心思想是不再使用传统的(无穷小)梯度,而是使用一种基于参考点的“梯度变体”。
DeepLIFT的核心概念
DeepLIFT不再考察输入发生无穷小扰动时输出的变化,而是考察:当输入从某个参考输入(或基线输入)变化到当前输入时,输出相对于参考输出的变化量。它为每个输入属性(如图像像素)分配一个贡献分数。
对于输入属性 i,其贡献分数 C_Δx_i Δt 满足:
Σ_i (C_Δx_i Δt) = Δt
其中 Δt 是输出相对于参考输出的总变化量。这样,每个输入对输出变化的贡献就被量化了。
通过始终与一个固定的参考点(例如全零图像)进行比较,即使函数进入饱和区,输入的变化仍能导致输出的可测量变化,从而避免了梯度为零的问题。
DeepLIFT的“重缩放规则”
DeepLIFT提出了几条规则,其中“重缩放规则”概括了其核心思想。该规则通过层间反向传播“相关性”来实现。
定义:
-
z_ji^(l+1) = w_ji^(l+1) * a_i^(l):当前输入下,第l层神经元i对第l+1层神经元j的贡献。 -
z_ji‘^(l+1) = w_ji^(l+1) * a_i‘^(l):参考输入下,相应的贡献。 -
R_i^(l):第l层神经元i的相关性分数。
计算过程:
-
输出层:最终层的相关性是当前输出与参考输出之差。
R^(L) = F(x) - F(x‘)。 -
中间层:对于第
l层的神经元i,其相关性由它连接到第l+1层的所有神经元j的相关性反向传播而来:R_i^(l) = Σ_j [ (z_ji - z_ji‘) / Σ_i (z_ji - z_ji‘) ] * R_j^(l+1) -
将此过程从输出层反向传播至输入层,即可得到每个输入像素的归因分数。
这个过程与梯度反向传播类似,但传播的是基于参考点的贡献差异,而非瞬时梯度。
积分梯度:沿路径累积 📈
积分梯度是另一种广泛使用的方法,它同样受解决梯度饱和问题的动机驱动。
动机与直观理解
下图展示了对一张“消防船”图像使用普通梯度方法得到的归因图。归因信号分散,未能清晰突出关键特征(如船体和水流),这正是饱和问题导致的。
积分梯度通过累积输入图像从基线(如黑色图像)到当前图像路径上多个点的梯度来解决这个问题。具体做法是,创建一系列图像,这些图像是基线图像和原始图像在不同比例(α)下的线性插值:
x_interpolated = x‘ + α * (x - x‘), 其中 α 从 0 渐变到 1。
对每一个插值图像计算梯度,然后将所有这些梯度进行积分(或求和平均)。
数学定义与实现
输入 x 在第 i 维上的积分梯度 IG_i 定义为:
IG_i(x) = (x_i - x_i‘) * ∫_{α=0}^{1} [ ∂F(x‘ + α*(x - x‘)) / ∂x_i ] dα
其中 F 是神经网络函数,x‘ 是基线输入。
在实际操作中,我们无法进行连续积分,因此采用离散近似。将区间 [0, 1] 分成 M 步,计算公式变为:
IG_i(x) ≈ (x_i - x_i‘) * (1/M) * Σ_{k=1}^{M} [ ∂F(x‘ + (k/M)*(x - x‘)) / ∂x_i ]
通过这种方式得到的积分梯度归因图,能够更清晰、更集中地突出对预测类别至关重要的特征,例如消防船的船身和喷出的水流。
平滑梯度:通过添加噪声来平滑归因图 🌫️
平滑梯度采用了一种与积分梯度思想相似但更启发式、更简单的方法。它不进行路径积分,而是通过对输入图像添加多次像素级的高斯噪声,然后平均这些噪声版本图像的梯度。
核心操作:
-
创建原始图像的多个副本。
-
对每个副本添加不同的高斯噪声。
-
分别计算每个噪声图像通过模型后的数据梯度。
-
将所有梯度进行平均,得到最终的平滑梯度归因图。
一个有趣的观察是:这种方法通过向输入添加噪声,反而能在归因图上减少噪声(即得到更平滑、更集中的显著性区域)。这种方法在实践中效果良好,并且催生了许多“平滑”变体,例如平滑积分梯度。
扩展到图像区域:XRAI方法 🧩
XR AI是积分梯度的一个近期变体,发表于2019年。其核心思想是将归因分析从像素级别提升到图像区域(分割块)级别,这通常更符合人类的认知习惯,也更具实用性。
XRAI的工作流程
以下是XRAI方法的主要步骤:
-
计算初始归因图:首先使用积分梯度等方法,得到像素级的归因热图。
-
过分割图像:使用图像分割算法将输入图像分割成许多小区域(超像素)。
-
迭代构建掩码:
-
从一个空的区域掩码开始。
-
在所有的分割区域中,选择区域内像素归因值总和最高的那个区域。
-
将该区域添加到掩码中。
-
重复此过程,可以依次添加归因值总和次高的区域,以此类推。
-
-
生成区域级归因:通过上述过程,我们可以得到一系列按重要性排序的图像区域。例如,可以可视化“对预测贡献最大的前10%的区域”。
效果展示
通过XRAI,归因结果不再是散乱的像素点,而是连贯的、有意义的图像区域。例如,对于“热气球”图像,XRAI能够清晰地标出气球本身所在的区域;对于“鸟”的图像,它能依次找出鸟喙、头部、身体等关键部位。这使得模型的决策过程对人类来说更加直观和可理解。
总结 🎯
本节课我们一起学习了多种用于解释神经网络决策的最新方法。
-
我们首先指出了传统梯度可视化方法存在的饱和问题。
-
接着,我们探讨了DeepLIFT如何通过引入参考输入和输出,使用贡献差异而非瞬时梯度来规避饱和问题。
-
然后,我们介绍了积分梯度,它通过沿从基线到输入的路径积分梯度,获得了更鲁棒、更集中的归因图。
-
之后,我们了解了平滑梯度这种更简单的启发式方法,它通过向输入添加噪声并平均梯度来平滑归因结果。
-
最后,我们看到了XRAI如何将像素级归因提升到区域级,通过图像分割和区域选择,提供了更符合人类直觉的解释。

这些方法都是在模型训练完成后,用于分析和理解其内部决策机制的工具,对于构建可信、可靠的AI系统至关重要。
042:解释CNN - 最新方法 - 第二部分

概述
在本节课中,我们将继续学习解释卷积神经网络(CNN)决策的最新方法。我们将重点介绍三种核心方法:LIME、SHAP以及评估解释质量的量化指标。这些方法旨在帮助我们理解模型为何做出特定预测,这对于建立对AI系统的信任至关重要。
LIME:局部可解释模型无关解释
上一节我们介绍了基于梯度和类激活映射的解释方法。本节中,我们来看看一种模型无关的解释方法——LIME。
LIME代表“局部可解释模型无关解释”。该方法的核心思想是:对于一个给定的复杂模型(如CNN)和特定的输入实例,LIME通过在该实例附近构建一个简单的、可解释的模型(如线性模型)来近似原始模型的局部行为。
以下是LIME方法的工作原理:
-
选择待解释的实例:例如,一张被模型分类为“猫”的图片。
-
生成扰动样本:通过遮盖、删除或修改原图片的部分区域(如超像素),生成一系列略微不同的新图片。
-
获取原始模型预测:将这些扰动后的图片输入到待解释的原始CNN模型中,记录其预测结果(例如,是“猫”还是“狗”)。
-
拟合局部解释模型:根据这些扰动样本及其对应的原始模型预测结果,训练一个简单的、可解释的模型(如线性回归模型)。这个简单模型只在该特定输入实例的“局部”有效。
-
解释权重:简单模型的权重(系数)大小,指示了原图中哪些区域对模型的最终预测贡献最大。权重高的区域被认为是重要的。
其目标函数公式如下:
解释模型G = argmin [ Σ (π_x(z) * (F(z) - G(z))^2 ) + Ω(G) ]
其中,F是原始复杂模型,G是待学习的简单解释模型,π_x(z)衡量扰动样本z与原始样本x的接近程度,Ω(G)是解释模型G的复杂度(如非零权重数量),用于鼓励模型简洁。
保真度与可解释性的权衡
在应用LIME时,我们需要在保真度和可解释性之间进行权衡。
-
保真度:指局部解释模型
G在多大程度上准确反映了原始模型F在局部区域的行为。我们希望G尽可能接近F。 -
可解释性:指解释模型
G本身对人类而言是否容易理解。线性模型因其直观性(权重代表重要性)而被广泛使用。
目标函数中的两项正对应了这种权衡:
-
第一项
Σ (π_x(z) * (F(z) - G(z))^2 )追求高保真度。 -
第二项
Ω(G)追求高可解释性(低复杂度)。
通常,提高保真度可能需要更复杂的G,但这会降低可解释性。LIME通过选择线性模型并施加稀疏性约束(如L1正则化),在实践中取得了较好的平衡。
SHAP:基于博弈论的解释方法
接下来,我们介绍另一种强大的解释方法——SHAP,它源于博弈论中的沙普利值概念。
想象一个足球队,你想量化梅西对巴塞罗那队价值的贡献。SHAP的思想是:计算某个“玩家”(在机器学习中即某个特征)加入所有可能的“联盟”(特征子集)时所带来的边际贡献的平均值。
形式上,对于特征i的SHAP值(即其重要性得分)计算公式为:
φ_i(v) = Σ [ |S|! (|N|-|S|-1)! / |N|! ] * [ v(S ∪ {i}) - v(S) ]
其中:
-
N是所有特征的集合。 -
S是N中不包含特征i的任意一个子集(即一个“联盟”)。 -
v(S)是衡量特征子集S价值的函数。 -
[ v(S ∪ {i}) - v(S) ]就是特征i加入联盟S带来的边际贡献。 -
求和项遍历所有可能的子集
S,前面的系数用于对所有可能的排列顺序进行平均。
在神经网络中,关键是如何定义价值函数v(S)。一种常见定义是:v(S) = E[ F(x) | 已知特征子集S的值 ],即,在已知特征子集S取值的情况下,模型输出F(x)的期望值。这需要通过采样未知特征S'(S的补集)的值来计算。
SHAP的一个扩展是DeepSHAP,它结合了SHAP和DeepLIFT的思想,通过考虑一组基线输入并计算平均归因来定义价值函数v(S)。
如何评估解释方法?
我们已经学习了多种生成解释的方法,一个自然的问题是:如何客观地评估这些解释的好坏?我们不能仅靠肉眼观察定性结果。
以下是几种常用的量化评估指标:
1. 交并比
如果数据集中包含真实的目标边界框(例如,图片中猫的精确位置),我们可以将解释方法生成的显著图进行阈值化,得到一个区域,然后计算该区域与真实边界框的交并比。IoU越高,说明解释方法定位关键区域越准确。
IoU = (Area of Overlap) / (Area of Union)
2. 忠实性
该指标衡量归因分数与输出变化之间的相关性。具体做法是:对输入进行扰动(如修改像素),观察模型输出的变化。如果某个特征(像素)的归因分数高,那么扰动它应该引起较大的输出变化。两者相关性越高,说明解释越忠实于模型。
Faithfulness = Corr( R_i, Δ_i )
其中R_i是像素i的归因分数,Δ_i是扰动像素i后的输出差异。
3. 删除与插入指标
-
删除指标:按照解释方法给出的重要性顺序,从最重要到最不重要,依次删除(如置黑)图像中的像素。同时监控模型对该类别的预测概率。性能(如AUC)下降得越快,说明解释方法找出的区域越关键。
-
插入指标:与删除指标相反,从最不重要到最重要,依次向空白图像中插入像素。性能上升得越快,说明解释方法越有效。
4. 移除与重训练
这是一种更严格的评估方式:使用解释方法为训练集中的每张图片生成显著图,然后根据显著图扰动(如移除)最重要的像素,并用这个被修改过的训练集重新训练模型。如果解释方法有效,那么新模型在测试集上的准确率应该有显著下降。
此外,研究社区还提出了解释方法的公理(如完整性、敏感性、实现不变性等)和显著性图的健全性检查,作为评估解释方法理论合理性的基础。建议读者将这些作为延伸阅读材料。
总结
本节课我们一起学习了多种解释CNN决策的高级方法与评估标准:
-
LIME是一种模型无关的局部解释方法,它通过拟合一个局部线性模型来近似复杂模型的行为。其优点是通用性强,可用于图像、文本等多种数据,但需要为每个输入实例单独训练解释模型,速度较慢。
-
SHAP基于坚实的博弈论基础,通过计算特征在所有可能子集中的平均边际贡献来分配重要性。它提供了统一的理论框架,但实际计算可能需要近似。
-
评估解释需要客观的量化指标,如交并比、忠实性、删除/插入指标和移除与重训练等,这些指标帮助我们超越定性观察,科学地比较不同解释方法的优劣。
不同的解释方法各有侧重和适用场景。在实践中,选择哪种方法取决于具体需求:是追求速度、理论完备性,还是需要模型无关的灵活性。理解这些方法的原理和评估方式,是构建可信、可解释AI系统的重要一步。
课后作业建议:
-
阅读关于“归因公理”和“显著性图健全性检查”的论文,理解可靠解释方法应满足的理论性质。
-
使用Captum(PyTorch)或类似的可解释性工具库,对你之前构建的CNN模型进行解释,并可视化显著图。
-
探索OpenAI Microscope等模型可视化工具,从不同层次理解神经网络的行为。

扩展资源:
-
Christoph Molnar所著《Interpretable Machine Learning》在线电子书。
-
Captum库官方教程与文档。
043:超越CNN解释


在本节课中,我们将学习如何超越简单的CNN解释,探索如何利用之前学到的梯度等概念,实现一些有趣的应用。我们将回顾上周留下的关于归因方法公理的作业,然后重点介绍两个应用:Deep Dream和神经风格迁移。
📚 回顾:归因方法的公理
上一节我们介绍了用于解释CNN的各种方法。本节中,我们来看看评估这些解释方法(或称归因方法)质量的一些理论公理。希望你已经阅读了相关论文。
以下是论文中提出的几个公理,所有归因方法(或称显著性方法、视觉解释方法)都应尽量满足:
-
完备性公理:对于任何输入 X,所有特征归因值的总和应等于神经网络的输出 f(x)。这类似于DeepLift方法中,贡献值之和等于输出与参考输出的差值 Δy。
-
敏感性公理:如果输入 X 中只有一个特征非零,且神经网络输出 f(x) 不为零,那么该特征的归因值也应为非零。基于梯度的方法通常不满足此公理。
-
实现不变性公理:如果两个神经网络实现了完全相同的数学函数 f(x),那么无论它们的内部结构(如层数、激活函数)如何不同,对所有特征的归因值应该始终相同。DeepLift方法违反了此公理。
-
对称性保持公理:对于任何输入 X,如果两个对称特征(即交换后不影响网络输出的特征)的值相同,那么它们的归因值也应该相同。
关于这些公理的更多细节,请阅读文末引用的论文。
🎨 应用一:Deep Dream
了解了归因方法的基本要求后,我们来看看第一个超越解释的有趣应用。Deep Dream于2015年提出,它以一种新颖的方式使用了我们本周讨论的概念。
Deep Dream通过修改给定图像,来增强网络中任意层的激活,从而形成一个反馈循环。例如,如果网络中有一个检测狗脸的滤波器,你可以将这个滤波器的激活反向传播到一张天空图片上,不断将“狗脸”特征叠加到云朵图像中,最终生成一幅充满艺术感的图像。
让我们详细研究这个方法:
-
选择输入图像与目标:你有一张输入图像(如天空)。然后,在预训练的CNN中选择一个特定的层和该层中的一个特定滤波器。
-
前向传播:将输入图像输入CNN,前向传播到选定的层。
-
反向传播至图像:将该选定滤波器的激活值反向传播回输入图像。这与我们之前“最大化激活”实验类似,但关键区别在于,这里的起点不是随机噪声或灰度图,而是一张真实的图像。
-
梯度上升更新图像:计算出的梯度指明了如何修改输入图像才能增强该滤波器的激活。我们使用梯度上升法:将梯度乘以学习率,然后加到原始输入图像上。公式表示为:
新图像 = 旧图像 + 学习率 * 梯度。 -
迭代:将更新后的图像再次输入网络,可以重复使用相同或不同的滤波器,持续叠加特征,从而创造出复杂的、如梦似幻的图像。
以下是该过程的一些关键观察:
-
较低层的滤波器倾向于增强边缘和纹理。
-
较高层的滤波器能产生更复杂、更具语义的特征(如建筑、动物部位)。
-
通过组合不同层的滤波器,可以创造出丰富的视觉效果。
以下是Deep Dream生成的一些示例:
-
将地平线图像通过叠加“塔”滤波器,转化为城堡般的景象。
-
将树木图像通过叠加“建筑”滤波器,转化为宝塔状结构。
-
将树叶图像通过叠加“鸟”和“昆虫”滤波器,转化为充满生物特征的奇幻图像。
🖼️ 应用二:神经风格迁移
看过了Deep Dream如何创造性地修改图像内容后,我们来看看另一个类似但目标不同的应用:神经风格迁移。它同样于2015年被提出,其目标是将一幅图像的内容与另一幅图像的风格相结合。
例如,如何让你的照片看起来像是梵高画的作品?简单地叠加两张图像效果很差,而人工绘制又非我们所求。神经风格迁移提供了解决方案。
其核心思想是,利用一个预训练的CNN(如VGG)分别提取内容图像的内容特征和风格图像的风格特征,然后通过优化生成一张新图像,使其同时匹配这两组特征。
以下是具体步骤:
-
提取内容特征:将内容图像(如你的照片)输入预训练网络,记录某些中间层的激活值。这些激活值捕获了图像的内容信息。
-
提取风格特征:将风格图像(如梵高的画)输入同一个网络。为了捕获风格,我们不是直接使用激活值,而是计算这些激活值的Gram矩阵。
- Gram矩阵的计算:对于某一层,将其每个滤波器的输出(特征图)展平为向量,然后计算所有向量两两之间的内积(或协方差)。这个矩阵反映了不同特征之间的相关性,被认为编码了图像的纹理、色彩等风格信息。
-
生成新图像:我们从一个随机噪声图像(或内容图像的副本)开始。
-
定义并优化损失函数:通过反向传播优化这张噪声图像,使其满足两个目标:
-
内容损失:使生成图像在网络特定层的激活值与内容图像的激活值尽可能接近。
-
风格损失:使生成图像在各层的Gram矩阵与风格图像的Gram矩阵尽可能接近。
-
总损失是内容损失和风格损失的加权和:
总损失 = α * 内容损失 + β * 风格损失。
-
-
梯度下降更新图像:通过最小化总损失,不断更新生成图像的像素值。最终得到的图像既保留了内容图像的结构,又融入了风格图像的艺术风格。
神经风格迁移产生了许多令人印象深刻的结果,例如将人物肖像、城市风景等内容与各种绘画风格相结合,生成全新的艺术作品。
📖 延伸学习与练习
为了加深理解,这里有一些推荐资源:
-
关于神经风格迁移的直观解释(可访问Towards Data Science等平台)。
-
关于Deep Dream的详细教程及代码实现(例如Hacker Noon上的文章)。
-
本周练习:观看YouTube上关于将Deep Dream应用于视频的趣味视频。思考并尝试理解其实现原理:如何将静态图像的生成过程扩展到动态视频序列中?

🎯 总结

本节课中,我们一起学习了如何超越单纯的CNN可视化解释,探索了两种基于梯度等概念的有趣应用。我们首先回顾了评估归因方法的几个重要公理。然后,深入探讨了Deep Dream,它通过反向传播特定滤波器的激活并叠加到输入图像上,创造出梦幻般的艺术图像。接着,我们学习了神经风格迁移,它通过分别匹配内容图像的特征激活和风格图像的Gram矩阵,成功地将一幅图像的风格转移到另一幅图像的内容上。这两种方法都展示了深度学习模型在创造性任务中的强大潜力。
044:用于目标检测的CNN I - 第一部分


在本节课中,我们将要学习目标检测任务,这是计算机视觉中最常用和重要的任务之一。我们将从深度学习时代之前的方法开始,了解其基本原理,然后探讨如何将卷积神经网络(CNN)应用于目标检测,并介绍一些早期的成功案例。
概述:从分类到检测
到目前为止,我们已经学习了神经网络的最简单形式,即前馈神经网络。我们学习了反向传播,了解了如何使用不同变体的梯度下降法。我们还学习了一些训练神经网络时的实用技巧。
随后,我们转向了卷积神经网络(CNN),这是计算机视觉的基石。我们看到了如何使用反向传播训练CNN,了解了CNN中的各种架构,并且在上周学习了如何可视化和理解CNN所学的内容,以及如何解释它们的预测。
然而,在迄今为止的所有讲座中,我们主要关注的任务是分类。给定一张图像,我们的目标是分类图像中包含什么物体。我们可以通过将交叉熵损失改为均方误差,将其扩展到回归任务,预测实数值。
但是,计算机视觉中还有许多其他类型的任务。从本周开始,我们将转向这些任务。我们将从当今最常用的任务之一——目标检测开始。
分类、定位与检测的区别
分类、定位和检测之间的区别如下:
-
分类:给定一张包含猫的图像,目标是判断图像中存在一只猫。
-
定位:除了判断存在一只猫,还需要用一个边界框框出猫在图像中的位置。
-
目标检测:更进一步,需要检测出给定图像中属于你设定类别的所有可能出现的物体,并对它们进行定位。
在定位任务中,通常只定位一个物体。而在目标检测中,可能存在多个物体,甚至是同一物体的多个实例。例如,图像中可能有一只猫和一只狗,或者两只狗和一只猫。目标检测的目标是识别出每一个物体,并使用边界框对它们进行定位。
最后是分割任务,其工作是为每个像素分配一个标签,标记其属于C个类别中的哪一个(取决于我们拥有的类别数量)。
我们将从检测开始本讲座,然后在本周晚些时候讨论分割以及其他任务。
深度学习前的目标检测方法
在深度学习兴起之前,人们使用其他方法在各种设备上部署计算机视觉进行目标检测。例如,即使在15年前深度学习尚未成熟时,目标检测和人脸检测功能就已经出现在傻瓜相机中。
这些方法大多使用了Viola-Jones算法的变体。该算法由Viola和Jones开发,因此得名,并于2001年在CVPR上发表。这是一个用于实时目标检测的框架,最初主要用于检测正面人脸,后来也被调整用于检测其他类型的物体或物体的部分(如人脸中的眼睛检测)。
该工作的主要贡献包括:
-
使用Haar-like特征进行弱分类。
-
引入了积分图像这一强大概念,可以显著加快Haar-like特征的计算速度。
-
使用AdaBoost作为分类器,但其使用方式与传统机器学习中的AdaBoost略有不同。
-
使用了级联分类器。
Haar-like特征
Haar特征是基于Haar小波的矩形特征。它们看起来像这样:通常包含一个黑色区域和一个白色区域,但可以有不同组合。你可以有水平或垂直排列的黑白两区域,也可以有被白色区域夹在中间的黑色区域(水平或垂直),或者棋盘格图案等等。
Haar滤波器的核心思想是:特征值等于黑色矩形区域内像素值的总和减去白色矩形区域内像素值的总和。记住这是卷积操作,滤波器会翻转,因此与Haar滤波器卷积的输出实际上是黑色矩形区域像素和减去白色矩形区域像素和。
为了适应不同尺度的图像,这些特征可以通过高度、宽度进行缩放,或者采用不同的模式。
积分图像
积分图像是一个基本概念,并非专门用于人脸检测,而是一种加速计算的方法,尤其是在使用Haar-like特征时。它由Viola和Jones在这项工作中引入。
积分图像的定义是:对于积分图像中的任意像素点 (x, y),其值等于从图像左上角原点 (0, 0) 到该点 (x, y) 所围成的矩形区域内所有原始图像像素值的总和。
用数学公式表示为:
II(x, y) = Σ_{x'≤x, y'≤y} I(x', y')
其中,II 是积分图像,I 是原始图像。
积分图像可以通过递推关系高效计算:
s(x, y) = s(x, y-1) + I(x, y)
II(x, y) = II(x-1, y) + s(x, y)
其中 s(x, y) 是行累加和。为了使递推成立,需要定义 s(x, -1) = 0 和 II(-1, y) = 0。
积分图像的强大之处在于,计算任意矩形区域的像素和只需要四次查表运算。假设矩形的左上角坐标为 (x1, y1),右下角坐标为 (x2, y2),则该矩形区域的像素和 Sum 为:
Sum = II(x2, y2) + II(x1-1, y1-1) - II(x2, y1-1) - II(x1-1, y2)
对于Haar特征计算,例如一个两矩形特征(一个黑矩形紧邻一个白矩形),计算其卷积输出(黑区和减白区和)只需要六次积分图像查表(因为需要确定三个矩形的六个角点)。作为练习,可以思考一下,对于一个三矩形特征(例如中间黑,两边白)和一个四矩形特征(例如棋盘格),分别需要多少次查表?
使用AdaBoost进行特征选择
一旦有了这些Haar滤波器的输出,Viola-Jones算法建议每个特征都可以被视为一个弱分类器。给定一个特征 f_j,一个阈值 θ_j 和一个指示符号的奇偶性 p_j,弱分类器 h_j(x) 定义为:
h_j(x) = 1, if p_j * f_j(x) < p_j * θ_j
否则为0。
这样,每个Haar特征都对应一个弱分类器。我们知道,弱分类器的组合可以形成强分类器。但这里存在一个问题:即使在一个简单的24x24滑动窗口内,可能的Haar特征数量也超过16万个。在每个滑动窗口内计算16万个弱分类器是不现实的。
这时就需要AdaBoost。Viola-Jones算法中使用AdaBoost的方式如下(简述自论文):
-
初始化所有训练样本的权重为均匀分布。
-
对于每一个可能的Haar特征
j,训练一个仅使用该特征的弱分类器h_j。 -
计算该弱分类器的加权误差
ε_j。 -
从所有特征对应的弱分类器中,选择误差
ε_t最小的那个,记为h_t。 -
根据
h_t的误差更新所有样本的权重(增加错分样本的权重,降低正确分类样本的权重)。 -
重复步骤2-5,共进行
T轮迭代。 -
最终的强分类器是这
T个弱分类器的加权组合。
通过这种方式,AdaBoost从海量特征中自动选择了最具判别力的 T 个特征,大大提升了效率。
分类器级联
Viola-Jones算法的第三个组成部分是分类器级联。其动机是:图像中只有极少数子窗口真正包含目标(如人脸)。如果对每个可能的窗口都进行完整的强分类器评估,计算量巨大,且假阳性率可能很高。
级联分类器的思想是:尽早排除负样本窗口,从而减少在它们身上花费的计算时间。具体来说:
-
所有可能的子窗口首先进入第一级分类器(由少量特征构成,计算快)。
-
只有被第一级分类器判定为“可能包含目标”的窗口,才会进入第二级分类器(可能使用更多特征,更精确)。
-
以此类推,经过多级筛选。
-
在任何一级被拒绝的窗口将不再参与后续处理。
为什么级联很重要?对于目标检测,假阳性率(False Positive Rate, FPR)至关重要。假设一张100万像素(1兆像素)的图像,至少有百万量级的候选窗口。理想的FPR需要低于 10^-6 量级。
如果只使用少数几个Haar特征,可能达到100%的检测率,但FPR高达50%。使用200个特征,可能达到95%检测率,FPR约为 1/14000,仍然不够。
级联分类器解决了这个问题。最终的总检测率是各级检测率的乘积,总FPR是各级FPR的乘积。例如,一个10级级联,如果每一级的检测率为0.99,FPR为0.3,那么总FPR就是 0.3^10 ≈ 6e-6,达到了我们的要求。而每一级使用少量特征(如200个)就可以实现0.99的检测率和0.3的FPR。通过级联,我们以可接受的计算成本获得了极低的总体FPR。
非极大值抑制
在检测中,一个物体周围可能有多个窗口都被分类为包含该物体。我们需要选择其中最合适的一个。这通常通过非极大值抑制(Non-Maximum Suppression, NMS)来完成,其核心是交并比(Intersection over Union, IoU)。
IoU定义为两个边界框 B1 和 B2 的交集面积除以它们的并集面积:
IoU(B1, B2) = Area(B1 ∩ B2) / Area(B1 ∪ B2)
IoU越接近1,表示两个框重叠度越高。
NMS的基本步骤是:
-
获取所有预测框及其置信度分数。
-
选择置信度最高的框。
-
计算该框与剩余所有框的IoU。
-
移除所有IoU超过某个阈值(如0.5)的框(因为它们很可能是对同一物体的重复检测)。
-
在剩余的框中重复步骤2-4,直到没有框剩下。
我们稍后会看到更完善的NMS方法,它不仅考虑IoU,还会结合框的置信度。
方向梯度直方图
另一个在深度学习之前流行的检测方法是方向梯度直方图(Histogram of Oriented Gradients, HOG),我们之前在讨论手工特征提取时提到过它。这里我们主要看它如何用于行人检测任务。
HOG检测流程如下:
-
给定一张图像,一个检测窗口在图像上滑动。
-
在每个窗口内,计算图像的梯度(例如使用Sobel滤波器)。
-
将窗口划分为小的单元格(cells)。
-
在每个单元格内,计算梯度方向的直方图(将360度或180度划分为若干个区间)。
-
将相邻的单元格组合成块(blocks,例如2x2个单元格),对块内所有单元格的直方图进行归一化,以增强对光照和阴影的鲁棒性。
-
将所有块的归一化直方图连接起来,形成最终的HOG描述符。
得到HOG描述符后,可以训练一个支持向量机(SVM)分类器,来判断一个窗口是否包含行人。该方法由Dalal和Triggs于2005年提出。
HOG也可以在多分辨率下进行,即构建图像金字塔,在每一层金字塔图像上执行上述HOG+SVM检测,然后通过某种方式(如加权、投票)整合各层的检测结果。
以上是深度学习兴起之前用于检测的主要方法。
使用CNN进行目标检测的最简单方法
现在,让我们看看如何以最简单的方式使用CNN进行目标检测。
假设在训练时,我们有汽车的图像。我们会训练一个CNN(使用标准的交叉熵损失)来学习一个汽车分类器。
在测试时,当得到一张汽车可能位于图像中任何位置的图片时,我们需要:
-
在图像上以多个尺度提取滑动窗口(可能产生数十万个窗口)。
-
将每个窗口调整到CNN输入所需的大小。
-
将每个窗口依次输入训练好的CNN。
-
CNN对每个窗口进行分类,判断其是否包含汽车。
-
根据分类结果,在原始图像上标记出检测到的汽车位置。
同样,可以结合非极大值抑制来提升性能。具体来说,在获得所有可能包含汽车的边界框列表后:
-
选择置信度最高的边界框(例如,图中红色框,置信度0.93)。
-
计算该框与列表中所有其他框的IoU。
-
移除所有与该框IoU超过阈值(如0.5)的其他框,因为它们很可能检测的是同一物体。
-
在剩余的框中重复此过程。
这种方法存在几个明显问题:
-
边界框可能不够紧密地贴合物体。
-
需要将成千上万个滑动窗口逐个输入CNN进行评估,计算成本极高。
我们将在后续讨论克服这些问题的更先进方法。
目标定位
在深入讨论检测之前,我们先看看目标定位,正如之前所说,它是检测的前置任务。在定位中,我们假设图像中只有一个物体。
实现定位的一种方法是修改CNN结构:
-
CNN的骨干网络部分(卷积层)保持不变,用于提取特征。
-
在最后的全连接层之后,网络分成两个分支(头):
-
分类头:输出物体的类别概率,使用交叉熵损失训练。
-
回归头:输出边界框的坐标,通常是四个值
(x, y, w, h),分别表示边界框的中心点(或左上角点)坐标、宽度和高度。使用L2损失(均方误差)训练。
-
-
网络的总损失是分类损失和回归损失的加权和。
通过这种方式,网络被训练为同时进行分类和定位。
OverFeat:一个成功的定位器
OverFeat是早期成功将CNN用于定位的典范,它是2013年ImageNet定位挑战赛的冠军。
OverFeat实现定位的关键思想是:将整个网络设计成全卷积的,避免使用全连接层。这样做的好处是:
-
可以对任意大小的输入图像直接进行前向传播。
-
通过一次前向传播,就能在特征图上得到对应于输入图像不同位置的预测,避免了在多个滑动窗口上重复计算,极大提升了效率。
具体来说,OverFeat用全局平均池化或1x1卷积层替代了全连接层。例如,一个14x14的输入,经过一系列卷积和池化操作后,特征图尺寸逐渐减小,最后通过1x1卷积直接输出每个空间位置对应的类别和边界框偏移量。
这种“全卷积”的思想后来被许多先进的检测和分割模型所采用,我们将在后续课程中看到。

总结

本节课中,我们一起学习了目标检测的基础知识。我们首先回顾了深度学习时代之前的经典方法,包括Viola-Jones算法(使用Haar-like特征、积分图像、AdaBoost和级联分类器)和HOG描述符结合SVM的方法。然后,我们探讨了如何将CNN应用于目标检测的最朴素方式——滑动窗口法,并指出了其计算效率低下的问题。接着,我们介绍了目标定位任务,以及如何通过修改CNN结构(添加回归头)来实现单物体定位。最后,我们了解了OverFeat模型,它通过全卷积网络结构显著提升了定位的效率,为后续更先进的检测模型奠定了基础。下一节,我们将深入探讨基于CNN的现代目标检测算法。
045:用于目标检测的CNN I - 第二部分


在本节课中,我们将要学习基于深度学习的现代目标检测方法。我们将首先了解目标检测方法的两种主要分类,然后深入探讨基于区域提议的方法,特别是R-CNN系列模型的发展历程、工作原理及其优缺点。
上一节我们介绍了深度学习时代之前的目标检测方法以及如何将基础CNN用于目标检测。本节中,我们来看看当今基于CNN的现代目标检测方法。
现有的方法主要可以分为两大类:基于区域提议的方法和基于密集采样的方法。我们将在本讲中介绍基于区域提议的方法,并在下一讲中讨论基于密集采样的方法。
🧩 基于区域提议的方法概述
在基于区域提议的方法中,检测框架通常由两个阶段组成。
-
第一阶段:一个网络或模块负责提出可能包含物体的不同候选区域。
-
第二阶段:一个分类器(在我们的案例中是CNN)对这些候选区域进行分类,判断其属于某个物体类别还是背景。
这种两阶段方法性能更稳健,但由于其分步处理的特性,速度较慢。另一方面,基于密集采样的方法是单阶段方法,它通过密集采样程序一次性完成区域提议和区域分类/检测。这种方法更简单、更快,但有时在精度上可能不及基于区域提议的方法。
🚀 R-CNN:区域卷积神经网络
现在我们来讨论基于区域提议的方法。最早的方法是R-CNN(基于区域的卷积神经网络),由Ross Girshick等人在2014年CVPR会议上提出。
以下是R-CNN的工作流程:
-
区域提议:使用一种名为“选择性搜索”的图像处理方法(不使用神经网络)来生成多个可能包含物体的候选区域。
-
区域处理:将每个候选区域缩放(扭曲)到相同尺寸,以适应CNN的固定输入要求。
-
特征提取与分类:将每个区域输入CNN(如AlexNet或VGG)提取特征。特征图通过全连接网络后,分为三个部分:
-
Softmax分类器:将区域分类为已知的物体类别之一。
-
SVM分类器:实际上用于最终分类决策(稍后解释)。
-
边界框回归模块:预测当前候选区域与真实边界框之间的偏移量(如左上角坐标的Δx, Δy,以及高度和宽度的Δh, Δw)。
-
R-CNN的训练流程
R-CNN的训练是一个多阶段的流程:
-
微调CNN:使用经过扭曲的区域图像,通过Softmax和交叉熵损失(也称为对数损失)来微调CNN及其后的全连接网络权重。交叉熵损失公式为:
L_cls = -log(p_gt)其中
p_gt是模型预测的真实类别的概率。 -
训练SVM:将全连接网络的输出作为每个区域的表征,针对检测问题中的每个类别(例如20个类别)训练一个独立的SVM分类器。
-
训练边界框回归器:训练一个全连接层用于边界框回归,使用L2损失(均方误差损失)。
R-CNN的局限性
尽管R-CNN是开创性的工作,但它存在几个主要缺点:
-
多阶段训练流程复杂:需要依次训练CNN、多个SVM和边界框回归器。
-
速度慢,存储需求大:每个候选区域都需要独立通过CNN进行前向传播。例如,在Pascal VOC数据集(约5000张图像)上训练需要约2.5个GPU天和数百GB的存储空间。
-
检测速度慢:测试时,对每张图像进行目标检测大约需要47秒,无法达到实时性。
⚡ Fast R-CNN:快速区域卷积神经网络
为了解决R-CNN的瓶颈,同一批作者在2015年ICCV上提出了Fast R-CNN。
Fast R-CNN引入了几个关键创新:
-
共享特征计算:不再将每个候选区域单独输入CNN。而是将整张图像输入CNN一次,得到特征图。然后,将选择性搜索得到的候选区域坐标映射到这些共享的特征图上。这避免了2000次(候选区域数量)重复的CNN前向传播,极大地提升了效率。
-
统一的多任务网络:移除了独立的SVM分类器。网络末端有两个并行的“头”:
-
分类头:使用Softmax和交叉熵损失直接输出类别概率。
-
边界框回归头:输出边界框偏移量。
-
-
ROI池化层:由于映射到特征图上的候选区域尺寸不一,Fast R-CNN引入了ROI(感兴趣区域)池化层,将任意尺寸的区域特征图转换为固定尺寸(如7x7),以供后续的全连接层处理。
ROI池化工作原理
ROI池化将尺寸为 h x w 的输入区域,划分为 H x W 个网格(例如目标尺寸为2x2)。对每个网格子窗口执行最大池化操作,最终输出固定尺寸 H x W 的特征图。
Fast R-CNN的损失函数
Fast R-CNN使用一个多任务损失函数,结合了分类损失和定位损失:
L = L_cls + λ * L_loc
-
L_cls是分类损失,即交叉熵损失(对数损失)。 -
L_loc是定位损失,仅对正样本(真实类别)计算。Fast R-CNN建议使用平滑L1损失代替L2损失。
平滑L1损失公式如下:
smooth_L1(x) = {
0.5 * x^2, if |x| < 1
|x| - 0.5, otherwise
}
平滑L1损失对异常值的敏感度低于L2损失(原因将在课后思考题中探讨)。
Fast R-CNN的性能提升
得益于这些改进,Fast R-CNN在训练和测试速度上显著超越R-CNN:
-
训练时间从84小时减少到8.75小时(约10倍提升)。
-
测试时间从47秒减少到2.3秒(若不计入区域提议时间,则为0.3秒)。
然而,区域提议步骤(选择性搜索)仍然占据了大部分时间(约2秒),这成为了新的瓶颈。
🚀 Faster R-CNN:更快的区域卷积神经网络
为了彻底解决区域提议的速度问题,作者们在2015年NIPS上提出了Faster R-CNN。
Faster R-CNN的核心思想是:用神经网络(区域提议网络,RPN)替代耗时的选择性搜索算法,将区域提议也集成到端到端的深度学习框架中。
Faster R-CNN架构
-
共享卷积层:输入图像通过一个基础CNN(如VGG)得到特征图。
-
区域提议网络(RPN):RPN在特征图的每个位置上预设一组不同尺度和长宽比的锚框。RPN的任务是判断每个锚框内是否包含物体(二分类:物体/背景),并初步预测边界框的偏移量以微调锚框位置。
-
ROI池化与检测网络:RPN提出的候选区域(经过微调后的锚框)被映射到共享特征图上,然后通过ROI池化层和后续的全连接网络,最终由分类头和边界框回归头输出检测结果。
RPN的工作原理
RPN是一个全卷积网络。在特征图的每个滑动窗口中心,它会评估 k 个预定义的锚框。对于每个锚框,RPN输出两个预测:
-
物体性得分:该锚框包含物体的概率。
-
边界框偏移量:4个值,用于调整锚框的位置和尺寸。
Faster R-CNN的训练流程
Faster R-CNN采用四步交替训练法:
-
训练RPN(使用ImageNet预训练的CNN权重固定)。
-
使用RPN生成的提议训练Fast R-CNN检测网络(此时CNN权重固定)。
-
固定检测网络,用其权重初始化RPN的共享卷积层,并微调RPN独有的层。
-
固定共享卷积层,微调检测网络独有的全连接层。
Faster R-CNN的性能
通过将区域提议步骤融入网络并实现参数共享,Faster R-CNN实现了接近实时的检测速度:
- 测试时间从Fast R-CNN的2.3秒大幅降低到0.2秒每图像。
📚 总结与课后任务
本节课中我们一起学习了基于区域提议的现代目标检测CNN方法。我们从两阶段检测框架讲起,详细分析了R-CNN、Fast R-CNN和Faster R-CNN的演进过程。每一次改进都致力于解决前一代模型的瓶颈:R-CNN解决了如何用CNN进行检测的问题;Fast R-CNN通过共享卷积计算和统一网络提升了效率;Faster R-CNN则通过引入区域提议网络(RPN)实现了近乎端到端的训练,大幅提升了速度。
课后思考题:在Fast R-CNN中,为什么平滑L1损失(Smooth L1 Loss)比L2损失对异常值更不敏感?请尝试分析原因,我们将在下一讲开始前进行讨论。
延伸阅读与练习:
-
回顾经典的Viola-Jones目标检测框架。
-
阅读博客“Object Detection for Dummies”以加深理解。
-
观看关于目标检测模型演进的视频系列。
下一讲,我们将探讨目标检测的另一大类方法:基于密集采样的方法(如YOLO, SSD等)。


046:用于目标检测的CNN II

概述
在本节课中,我们将继续学习目标检测中的密集采样方法。我们将详细介绍几种主流的单阶段检测框架,包括YOLO、SSD、特征金字塔网络以及RetinaNet,并解释它们的工作原理、损失函数和优缺点。
平滑L1损失与L2损失
上一节我们介绍了目标检测的基本概念。在深入新内容之前,我们先回顾一下上节课留下的思考题:为什么平滑L1损失比L2损失对异常值更不敏感?
当预测输出与真实值之间的偏差很大时(即存在异常值),平方损失或L2损失会夸大这种偏差。这也会导致梯度被夸大,可能引发梯度爆炸问题。L1损失缓解了这个问题。因为L1损失在零点不可微,所以我们使用其平滑版本,即平滑L1损失,以减轻异常值引起的问题。
密集采样方法简介
我们之前提到,现代目标检测方法大致可分为区域提议方法和密集采样方法。现在,我们将进入密集采样方法的学习,这些都属于单阶段检测框架。
YOLO (You Only Look Once) 🎯
目前仍被广泛使用的最流行的单阶段检测器是YOLO。YOLO的第一个版本于2016年提出。它是一个单阶段检测器。可以说,所有这些单阶段检测器都大致基于OverFeat的思想,它们都只使用卷积层,没有全连接层,其主要目标是速度和良好的性能。
高层次流程是:调整图像大小,运行卷积网络,得到一组输出,然后进行非极大值抑制以确定最终的边界框。
让我们更详细地了解它。以下是整体流程。给定输入图像,首先将图像划分为S×S的网格。每个网格单元负责其中心重合的物体。例如,如果取这里的特定网格单元,它将负责这里的狗这个物体,并需要预测该边界框的偏移量。
因此,每个网格单元预测B个边界框(B是另一个超参数)以及每个框的置信度分数。所以,每个网格单元可以预测多个边界框和每个框的置信度。同时,每个网格单元为每个类别预测一个概率,总共C个类别意味着C个总概率。可以认为这是在计算一个条件概率:给定存在物体时,属于类别I的概率。
最终的预测可以编码为:你有一个S×S的网格。在每个网格位置,为每个边界框预测5个值。这五个值是什么?是表示物体中心的坐标、宽度和高度,以及每个边界框的置信度。这总共是5个值。然后,每个网格单元有C个类别概率。这样,在应用非极大值抑制之前,输出层的总输出数量为 S × S × (5B + C)。
每个边界框给出四个坐标:x, y, w, h。x, y是表示物体中心相对于网格单元的坐标,w, h是物体相对于整个图像的宽度和高度。每个边界框给出的置信度分数反映了模型对该边界框包含物体的置信程度,以及框的准确性。可以将置信度视为存在物体的概率(此时还不知道属于哪个类别)乘以预测框与真实框的交并比。因此,置信度同时考虑了这两个量。
每个网格单元有每个类别的条件类别概率,无论每个网格单元预测的边界框数量B是多少。YOLO每个网格单元只预测C个类别概率。因此,可以假设这些C个类别概率就是这里的条件概率:给定存在物体时,属于类别I的概率。
所以,如果你将这个置信度与这些类别条件概率相乘,你会得到:给定存在物体时属于类别I的概率(即条件类别概率)乘以置信度(即存在物体的概率乘以真实框与预测框的IOU),这等于类别I的概率乘以真实框与预测框的IOU。因此,对于每个单元格,你是在说这个类别出现在这个网格单元的概率是多少,以及预测框与真实框的IOU是多少。
用于训练YOLO的损失函数是什么? 损失函数有效地结合了多个组成部分,以确保预测的每个量都接近真实值。这里有多项。首先解释这些指示符:1_{ij}^{obj} 表示单元格i中的第j个边界框预测器是否负责该预测。记住,每个单元格预测B个边界框。所以这里的I_{ij}对应于第i个单元格预测的第j个边界框。1_{i}^{obj} 表示物体是否出现在单元格i中。
现在看看每一项的作用。如果你看一个特定单元格预测的特定边界框,首先试图确保网络预测的x_i, y_i接近期望的x_i_hat, y_i_hat。然后确保网络预测的宽度和高度接近原始的宽度和高度,这里取了平方根,因为宽度和高度是二维的。C和C_hat是置信度值,所以也试图确保当置信度较低时(相对于应有的置信度,如果有物体,应为1),这个差异也被最小化。所有这些求和是针对所有网格单元和每个网格单元预测的所有边界框进行的。
即使物体不存在,你也会做同样的事情,希望置信度匹配期望的置信度,此时应为0。所以这两项本质上是针对正类和负类的互补项。最后,最后一项表示条件类别概率匹配期望的条件类别概率。这些就是用于训练网络的损失函数中的不同项。
这种方法有哪些局限性? YOLO V1有一些局限性。首先,它只能检测少量物体。由于方法本身的空间限制以及可以呈现的重叠物体的数量,它会漏检小物体或彼此靠近的物体。这导致了较高的定位误差和相对较低的召回率。
为了克服这些问题,作为YOLO v1扩展提出的YOLO v2将锚框的概念引入了YOLO框架。这些锚框类似于你在Faster R-CNN中看到的。让我们看看这些锚框如何工作。
每个锚框预测五个坐标:t_x, t_y, t_w, t_h 和 p_o。让我们看看每一个。如果单元格距离图像左上角的偏移为(c_x, c_y),并且锚框具有先验宽度和高度p_w和p_h,那么对应于锚框的预测由以下公式给出:
-
b_x = σ(t_x) + c_x -
b_y = σ(t_y) + c_y -
b_w = p_w * e^{t_w} -
b_h = p_h * e^{t_h} -
物体概率乘以IOU,即我们在YOLO v1中看到的量,是
σ(t_o)
这些是网络预测的不同量,它们与网格位置试图指向的实际边界框相关。记住,每个锚框只预测这五个值。b_x, b_y, b_w, b_h是由锚框给出的这些值对应的实际边界框。
SSD (Single Shot MultiBox Detector) 📦
继续前进,有另一种单阶段检测方法,称为单次多框检测器(SSD)。SSD再次使用了类似OverFeat的方法。你可以看到,给定输入图像,网络的初始部分是一个类似VGG的网络。然后,卷积层不断减小尺寸,你可以看到有一些跳跃连接将你从卷积层直接带到分类器。
让我们更详细地看看它们。你拥有用于检测的多尺度特征图,因为你将这些卷积层的特征图直接发送到输出,这些直接到输出,这些直接到输出。所以输出层接收来自不同尺度卷积层的特征图,这就是多尺度特征图。
我们还看到,对于每个卷积层,都有一组不同的卷积滤波器将其连接到输出层。例如,这里的初始层经过一个 3×3×(4+类别数) 的卷积滤波器,这是它拥有的输出数量,也就是卷积滤波器的大小,从而在输出层给出相应的输出,依此类推,对于每个中间卷积层都是如此。
如果你取这些特征图中的任何一个,例如一个大小为m×n、具有C个通道的特征图层,那么它将给出m×n个位置。因此,这些特征图中每个像素位置都可能是物体的中心。所以,每个位置都像一个网格单元,你可以将其与YOLO进行比较。边界框偏移值相对于该网格单元位置。
记住,每个卷积图预测 (类别数 + 4) 个值,这是它们将预测的值的数量,即每个类别的类别概率加上对应于这些卷积特征图中每个像素位置的边界框偏移的四个值。
因此,当通过网络的后面的部分时,特征图变得越来越小。你可以在这里看到,如果这是原始图像中的真实框,你可以看到一个8×8的特征图,接着是一个4×4的特征图。在8×8的特征图中,如果你查看每个网格单元位置的锚框,每个锚框将预测一组值,我们在上一张幻灯片中讨论过。如果你有K个具有不同宽高比的锚框,SSD将预测C个类别特定分数加上4个锚框偏移,这就是我们在前面幻灯片中看到的C+4,即每个卷积层连接到输出层时的通道数。
因此,对于一个m×n的特征图,你总共有 (C+4) × K × m × n 个输出,因为你现在假设每个像素都可能是物体的中心,并且每个像素周围有K个锚框,每个锚框预测C个类别概率和4个边界框偏移。这就是为什么在SSD框架中,每个m×n特征图会有这么多输出。
这里使用的损失函数是什么? 与我们目前看到的损失函数非常相似,SSD实现了一个定位损失函数和一个置信度损失函数。置信度损失函数比较预测的类别概率和真实值。真实值的写法是:x_{ij}^p 根据是否存在物体而给定为1或0,如果第i个默认框(或锚框,这里默认框和锚框是同义词)与类别p的第j个真实框匹配。
L和G是预测的和真实的框参数。让我们更详细地看看每个损失函数。
SSD的定位损失再次由平滑L1损失给出。它开头也有一个因子x_{ijk}来表示是否涉及某个类别,因为你只想在涉及类别时评估这个量(此时它会变为1)。这些L是预测的偏移量,G_hat是真实的偏移量。这些真实偏移量由以下公式给出:
-
ĝ_{cx} = (g_{jx} - d_{ix}) / d_{iw} -
ĝ_{cy} = (g_{jy} - d_{iy}) / d_{ih} -
ĝ_w = log(g_{jw} / d_{iw}) -
ĝ_h = log(g_{jh} / d_{ih})
这里ĝ_{cx}是真实框中心x坐标减去当前锚框中心x坐标,然后除以锚框宽度,确保你计算的是相对于当前锚框的正确偏移,并对x和y维度用宽度和高度进行归一化。这里的ĝ_w, ĝ_h是指数因子,我们用来缩放宽度和高度,而不是加性因子。我们说YOLO对宽度和高度使用乘法因子,因为那里有一个指数项,所以这里使用对数来反转操作,这将是你的网络应该预测的真实缩放因子。当你对其取指数时,对数项会消失,你会得到正确的期望宽度和高度。
SSD的另一个损失,即置信度损失,是类别置信度概率的softmax损失。它由正边界框的第一项(即标准交叉熵损失)和负边界框的第二项给出,其中有一个对应于背景类别的类标签,你希望最大化它。这里的c_0对应于一个称为背景的类标签,它被视为你想要读取的类别之一。c_hat是你的标准softmax激活函数。
在实际实现中,YOLO和SSD都有这个问题:与Faster R-CNN相比,大多数锚框很可能是负样本。为了应对这个问题,你选择具有最高置信度损失的负样本,使得负样本与正样本的比例保持在约3:1。因为否则,请记住,即使在一张图像中只有100个物体,如果你采用SSD,每个像素你将有K个不同的锚框,就负样本且其中没有物体的框的数量而言,这可能是一个巨大的数字。然后学习会受到影响,这就是你这样做的原因。困难负样本挖掘:在训练时,你只选择那些具有非常高置信度损失的框,并且只在我们讨论的损失函数中使用它们。记住,损失函数也考虑了那些负框。
SSD还使用了数据增强策略。给定原始图像,原始图像也被使用。它还从图像中随机采样 patches,试图确保与实际物体的最小IOU在预定义的一组范围内,以确保网络暴露于图像中不同种类的 patches 以及不同种类的物体及其重叠。
通过这种方法,SSD的性能可以超越YOLO和Faster R-CNN。
评估指标:mAP
所有检测方法都使用一个称为mAP(平均精度均值)的评估指标进行测量。精度指的是机器学习方法中使用的标准精度指标。平均精度指的是在所有类别上获得的平均精度,而均值是针对所有边界框的。
因此,通常在一个特定的IOU下测量mAP。那么,你如何确认是否预测了一个边界框?你预先指定一个特定的IOU,例如0.5,并说只要我的预测框与我的真实框至少有0.5的IOU,我就认为我的预测是正确的。这就是定义正确性的方式,以获得你的精度,然后你在预测的类别和框上取平均值。
你可以看到,SSD在mAP上匹配了Faster R-CNN,但以显著更高的FPS(每秒帧数)速率,这是使单阶段方法在实践中更快的主要目标。你还可以看到,输出框的数量明显更高,显然对于SSD来说是这样,因为你为每个像素都做,并且他们还表明这在不同的输入分辨率下也相当有效。
特征金字塔网络 (FPN) 🗼
第三种单阶段检测器方法称为特征金字塔网络(FPN)。FPN使用的思想是,来自初始层的特征图中的特征层可能并不真正适合检测。当你通过卷积网络时,初始层是高分辨率的,然后随着你深入,分辨率变得越来越低。但是,尽管初始层是高分辨率的,我们从CNN的可视化中看到,它们可能并没有真正捕捉到那些初始特征图中的物体语义。然而,它们是分辨率较高的。而较低分辨率的特征图具有更丰富的特征用于检测。我们如何弥合这个差距,正是特征金字塔网络试图做的。
以下是特征金字塔网络如何尝试弥合这一差距的可视化。如果你要使用图像金字塔来进行检测,你可以做的一件事是对图像进行下采样,并为每个图像分辨率构建一个特征图,并为每个特征图进行预测。或者,你可以获取输入图像,构建越来越低分辨率的特征图,最后在最低分辨率进行预测。或者,给定输入图像,在构建许多卷积层时在不同分辨率构建特征图,并在每个分辨率进行预测。
特征金字塔网络建议的是,当你通过卷积网络时,你确实在不同分辨率构建特征图,但现在你进行上采样并获取更高分辨率的特征图,然后进行预测。这样,你试图在最低分辨率获取语义,但上采样回来将语义传递到更高分辨率。
让我们看看这在架构中是如何完成的。这是整体架构。你有一张图像。你有一个步长为2的卷积层,然后0.5x表示一个下采样最大池化层(2×2池化)。然后你有一个步长为4的卷积层,一个最大池化,一个步长为8的卷积层,一个最大池化,一个步长为16的卷积层,一个最大池化,以及一个步长为32的卷积层。你可以认为这就像一个在FPN中使用的ResNet。所有卷积特征图C1到C5都经过一个1×1卷积处理(你在这些箭头上看到,有256个通道)。然后,M5被上采样2倍以获得下一个M4,但在获得M4之前,你从C4获取信号(在应用1×1卷积之后),并将C4的输出与1×1卷积和M5结合以获得M4。你类似地继续这样做以获得M3和M2。一旦完成,在M4、M3和M2上应用3×3卷积,这样做是为了减少M中的混叠效应。记住,当我们从M5到M4、M4到M3、M3到M2时,我们正在进行上采样,而回想一下,上采样可能导致混叠。因此,为了平滑这些混叠因素,我们使用一个3×3卷积,将我们从M4带到P4,从M3到P3,从M2到P2,依此类推。最后,你得到所有这些P,它们被提供给单独的目标检测器以获得最终预测。
RetinaNet 与 Focal Loss ⚖️
一种更近期的目标检测方法侧重于用于训练这些检测器的损失函数。这就是RetinaNet。提出的损失函数称为Focal Loss,于2017年ICCV提出。它基于这样的直觉:两阶段检测器被认为比一阶段检测器更准确。一阶段检测器显然更快。据推测,两阶段检测器更准确,是因为背景或负类与包含物体的正提议之间的类别不平衡较少。记住,当你进行选择性搜索时,我们将区域提议限制在只有2000个。而在一阶段检测器中,你可能要处理100,000个区域,因为在SSD中,你在特征图的每个像素上都有多个锚框。即使在YOLO中,你可能为每个S×S网格单元预测B个边界框,这可能是一个非常大的数量。那么,我们如何解决负类或背景类与实际正类之间的这种不平衡?
每当存在这样的问题,即有许多负样本(它们对模型没有真正帮助)而只有少数正样本时,可能会发生某些情况:
-
训练可能变得低效,因为简单的负样本并没有给出任何有用的信号。
-
损失可能被负样本淹没,而不是正样本,这可能会使训练过程退化,导致模型在一定程度上退化。我们在SSD中讨论的困难负样本挖掘,我们试图确保最终损失只使用那些具有显著置信度损失的样本,并确保负样本与正样本的比例为3:1,这确实缓解了这些问题,但问题仍然存在。
因此,这篇论文提出,用于检测分类分支的交叉熵损失可能本质上是不好的。让我们试着看看为什么。记住,交叉熵损失在多类设置中定义为 -log(p),正如我们提到的,它就是负对数损失。根据他们的实证研究,他们观察到了这个图表。如果你注意到,当γ等于0时,他们引入的称为Focal Loss的损失将变成,这个系数在γ=0时将变为1,损失将变成你的标准对数损失或交叉熵损失。所以当γ=0时,蓝色是你的标准交叉熵损失,你可以看到,即使网络对真实类别预测了高概率,损失值也相当不小,即使模型对正确类别预测了高概率,你也会得到相当高的损失。这可能会违背学习的目的。
那么,使用Focal Loss的RetinaNet提出了什么?一种方法是你可以做一个平衡交叉熵,其中负对数损失由某个量α加权,α可以给定为某个逆类别频率,这是一种方法。但RetinaNet方法也提出了Focal Loss,它考虑预测概率本身来微调损失。所以你用 (1 - p_t)^γ 来加权你的对数损失,其中γ是一个可调的聚焦参数。因此,γ是训练网络时必须提供的一个超参数。最终的Focal Loss给出这个量。假设γ是某个值,比如5。那么当p高时,这个量会变低,你降低了整体损失,因为当p高时你希望损失低。当p低时,比如0.1,因为γ是5,这个量仍然是一个合理的量,并且当你的预测概率p_t对于真实类别较低时,对数损失将保持在高水平。这就是Focal Loss的主要思想。
RetinaNet架构在其他方面使用FPN(我们讨论过的特征金字塔网络)以及Focal Loss,你可以看到这里的第一部分是特征金字塔网络本身,然后对于每个尺度,你有一个分类子网络和一个边界框回归子网络,分类子网络使用Focal Loss进行学习。
实现与总结
所有这些现代检测方法(包括密集采样方法和基于区域提议的方法)都在一个名为Detectron的流行库中实现。Detectron由Facebook AI Research提供,特别是为了促进目标检测算法的使用。因此,如果你有兴趣为任何项目实现这些目标检测算法中的任何一个,可以查看Detectron或Detectron2以获取更多细节。
你的阅读材料是《目标检测入门》的延续,这次是第4部分,关于密集采样方法。这里有一个关于整个YOLO系列方法的教程,以及理解SSD、FPN和RetinaNet的教程。
课后练习
我们在这节课中只涵盖了YOLO v1和YOLO v2。YOLO还有一个YOLO9000,讨论了将YOLO扩展到9000个类别,以及一个YOLO v3,其思想非常接近YOLO9000。然而,这些与YOLO v2的不同之处将是你的作业,请务必阅读上一张幻灯片阅读部分中给出的链接以理解YOLO。
还有几个更简单的问题:
-
给定图像中的两个边界框,一个左上框为2×2,一个右下框为2×3,重叠区域为1×1。两个框之间的IOU是多少?
-
考虑在19×19的网格上使用YOLO,处理一个具有20个类别和5个锚框的检测问题。在训练期间,你必须构建一个输出体积y作为神经网络的目标值。这个输出体积的维度是多少?记住,在YOLO中,我们设定为
S × S × (5B + C)。尝试在这里使用该公式找出这个特定YOLO目标检测器的输出体积应该是多少。
请完成这些练习,我们将在下一讲继续讨论。
总结

本节课中,我们一起学习了目标检测中的几种核心密集采样方法。我们从YOLO的基本原理和损失函数开始,了解了其网格预测机制。接着,探讨了SSD如何利用多尺度特征图进行高效检测,并引入了困难负样本挖掘。然后,我们学习了FPN如何通过自上而下的路径和横向连接来融合不同分辨率的语义信息,以提升对小物体的检测能力。最后,我们分析了类别不平衡问题,并介绍了RetinaNet提出的Focal Loss如何通过调整损失权重来聚焦于困难样本,从而提升单阶段检测器的精度。这些方法共同推动了实时、高精度目标检测技术的发展。
047:用于分割的CNN 🖼️

在本节课中,我们将学习如何使用卷积神经网络进行图像分割。我们将从回顾检测任务开始,然后深入探讨用于语义分割、实例分割和全景分割的各种CNN架构。课程将涵盖FCN、SegNet、U-Net、PSPNet、DeepLab和Mask R-CNN等核心模型,并解释它们的设计原理、关键技术和应用场景。
回顾与引言
上一节我们介绍了目标检测。现在,我们将继续学习使用CNN架构进行图像分割。
首先快速回顾上一讲的练习。给定图像中的两个边界框:一个左上角框,大小为2x2;一个右下角框,大小为2x3;重叠区域为1x1。那么交并比是多少?这应该很简单。
你有一个左上角2x2的框,一个右下角2x3的框。重叠区域是一个1x1的框。这里总共有9个框,其中交集是1个框。因此,总的IOU是1/9。
第二个问题:考虑在19x19的网格上使用YOLO,有20个类别和5个锚框,输出体积是多少?记住,我们说过它是 S x S x (5 * (B + C)),其中B是边界框信息(通常是4个坐标),C是类别数。这就是你得到的最终答案。这里我们假设类别数C也包括背景。如果你想将背景作为一个单独的类别,那么C需要是21。
图像分割基础
首先,让我们回顾一下图像分割。在课程早期,我们讨论过不同的图像分割方法,如分水岭、图割、归一化割、均值漂移等。在某种程度上,这些方法启发了用于检测和分割任务的早期CNN架构。事实上,R-CNN使用选择性搜索来获取区域提议,就使用了一种称为约束参数最小割的分割方法来生成区域提议。
但现在,我们将转向用于分割的深度神经网络。因此,我们首先要处理的任务是语义分割,我们希望为图像中的每个像素分配一个类别标签。
如何用神经网络,特别是卷积神经网络来解决这个问题?我们将把它视为一个像素级分类问题。
到目前为止,我们讨论的是图像级分类,即我们只在图像级别有一个标签。然后我们学习了检测,在边界框级别有标签,我们不仅给出类别标签,还给出边界框的偏移量来进行最终预测。但在语义分割中,我们将在每个像素的级别进行分类。因此,对于每张图像,每个像素都必须用一个语义类别进行标记。可以想象,为语义分割创建数据集需要非常密集的标注工作。
近年来出现了各种架构,如FCN、SegNet、U-Net、PSPNet、DeepLab等,我们将在本讲座中逐一介绍。
全卷积网络
我们从用于语义分割的全卷积网络开始,它于2015-16年提出。它们将各种分类网络(如VGGNet、GoogleNet等)改编为全卷积网络。
通过将全连接层转换为1x1卷积层,我们在单次检测方法中看到了相同的思路。这里也是如此,因为对于语义分割,你仍然需要在像素级别进行分类,所以你不想脱离卷积机制。
为了获得每个像素的分类,我们可以在任何CNN的末端添加一个1x1卷积层,并在最终的输出体积中拥有C+1个通道,其中C是类别数,加1是用于背景类。
但这种方法有什么问题吗?使用卷积层时存在一个问题:所有标准的CNN随着层数加深都会进行下采样,无论是通过卷积还是池化。这意味着你的输出特征图尺寸不会与输入相同。但我们最终希望以输入图像的分辨率给出像素级标签。
那么我们该怎么做?我们已经知道答案:我们可以在某个阶段后进行上采样。
那么如何上采样呢?我们之前讨论过的方法之一是转置卷积。
让我们快速回顾一下转置卷积。这是我们讨论卷积神经网络时谈到的一维例子,其中输出可以大于输入。这是我们为转置卷积提供的图示,当你在输入图像上应用滤波器时,卷积中存在膨胀。因为有膨胀,转置卷积也被称为膨胀卷积,有时也被称为空洞卷积,我们稍后会看到。因为在进行卷积时它会膨胀。
现在,在FCN中,这就是在某个层级之后用于上采样的思路。
让我们看一个带有VGG骨干网络的FCN架构。如果你有一个VGG16骨干网络,FCN中做的第一件事是移除所有全连接层,所以你现在只有VGG16的卷积层。你可以看到所有标准的VGG16特征图。三个FC层被替换为1x1卷积层,最后一个1x1卷积层有C+1个滤波器。你可以看到这里最后一个有21维滤波器,这是应用于一个有20个类别的数据集,所以是21维。
然而,你注意到这里:虽然有21个通道,但输出尺寸仍然比输入分辨率小。因此,最后你有一个上采样层,使用转置卷积来恢复到原始尺寸。这就是在全卷积网络中所做的。
但是,转置卷积是获得上采样的唯一方法吗?不一定。我们可以做几件事,包括我们稍后会看到的跳跃连接。但在那之前,我们将讨论另一个与FCN大约同时期出现的方法,称为SegNet。
SegNet 架构
SegNet就是你在图像中看到的:有一个输入图像,一个输出。该架构是一个全卷积的编码器-解码器架构。
这与FCN有何不同?与FCN非常相似,编码器是没有FC层的VGG16。解码器将低分辨率的编码器特征图映射到输入分辨率。这里与FCN有一个区别:FCN只在最后有一个上采样层,而SegNet有一系列解码器层,它们镜像了编码器的架构。因此,编码器和解码器架构是彼此镜像的。
SegNet的一个重要贡献是避免在上采样中使用转置卷积。那么,SegNet在这种情况下如何进行上采样呢?它实际上使用在编码器层中获得的池化索引来进行上采样。
让我们看看这是如何完成的。如果你有一个来自特定解码器阶段的输出图,我们使用原始编码器的最大池化索引。假设最大池化索引是00、10、10、01,那么A会去到00位置(左上角),B会去到10位置(左下角),C会去到10位置(左下角),D会去到01位置(右上角)。现在,这成为解码器中前一层输出图的上采样版本。可以继续这样做以获得最终的上采样图像。
编码器中的最大池化索引是什么意思?每当你有池化层时,无论是2x2还是3x3的池化,你都会跟踪该补丁中哪些条目的索引是最大的,并将这些获胜索引存储在一个单独的内存缓冲区中。我们将使用这些最大池化索引上采样回更高的分辨率。
为什么这很好?存储最大池化索引比存储编码器的完整特征图更高效。在解码器的每个阶段,使用相应编码器的最大池化索引来生成上采样的特征图。如果你看完整的图像,这个编码器特征图的池化索引会进入这个解码器,这个编码器的特征图会进入这个解码器,依此类推。
损失函数
现在我们已经看到了几种架构,FCN和SegNet。训练这样的网络应该使用什么损失函数?
很简单,就是像素级交叉熵损失的总和。记住,在FCN和SegNet中,你的输出将是一个体积,其尺寸与输入分辨率相同,但通道数将是类别数加一(背景)。因此,实际上你可以为每个像素在输出体积中有一个概率向量。现在,你可以计算每个像素的像素级交叉熵,将它们全部加起来,这就成为你的损失函数。因为它是交叉熵的总和,所以仍然是可微的。
U-Net 架构
第三种架构,也出现在2015-16年,被称为U-Net。U-Net也是一个全卷积的编码器-解码器架构,但它以一种不同于SegNet的方式引入了跳跃连接的概念。
在收缩路径(编码器路径)中,是一个移除了全连接层的现有分类网络,类似于SegNet和FCN。但在扩展路径中,除了特征图的上采样外,网络还接收收缩路径的相应特征图,以及进一步的卷积层。最后,在末端,有一个1x1卷积层,具有C+1个通道,其中C是类别数。
U-Net中的上采样是使用步长为2、填充为0的转置卷积来执行的。在每个上采样步骤中,特征通道数减半,这意味着总的输入尺寸将加倍。现在,通过2x2的转置卷积和通道数减半,此时输入图的尺寸将加倍。
所以原始的U-Net架构执行的是无填充卷积,这意味着输出分割图比输入小一个恒定的边框宽度。
让我们看一下图示,这就是U-Net。它的形状告诉你为什么它被称为U-Net架构。你可以再次看到收缩路径类似于任何其他没有FC层的架构。在扩展路径中,除了在每个步骤进行转置卷积外,你还接收收缩路径的相应特征图,这些特征图被添加到你的扩展路径中。这样,除了上采样之外,你还能从编码器的相应层获得定位信息。因此,你从编码器的相应层获得额外信息,而不是像SegNet或FCN那样仅仅依赖上采样。
U-Net最初是为生物医学图像分割提出的。事实上,它发表在一个医学会议MICCAI上。你认为这会如何影响U-Net模型的学习?
生物医学图像分割有一些独特的挑战。首先,通常你没有像一百万甚至几万那样数量级的训练数据集。可能只有很少的训练图像可用。因此,这项特定工作通过对可用的训练图像应用弹性形变来进行过度的数据增强,这是其贡献之一。医学领域的另一个挑战是,同一类别的许多物体可能彼此接触。因为可能有彼此相似的物体相邻放置。因此,U-Net还提出了一种损失函数,对靠近边缘的像素的惩罚比对远离边缘的像素的惩罚更重。边缘对于医学图像分割至关重要。
多年来,出现了U-Net的几种变体,主要是通过用密集块替换卷积块、增加U-Net的深度等来改变架构。
PSPNet 架构
另一个用于分割的网络被称为PSPNet。PSPNet首先提出了一个问题:如果在复杂场景上应用FCN,可能会面临哪些挑战?
如果你深入思考,你会注意到FCN没有学习到上下文模式。例如,我们知道汽车在路上,而船在河流附近。FCN也可能将物体的部分预测为不同的类别。例如,它可能看着一座摩天大楼,认为像素属于不同的建筑物,因为该物体的规模巨大。第三,根据架构和所使用的感受野,FCN可能无法检测到大物体或小物体。
那么我们如何解决这些限制呢?PSPNet假设应该使用全局上下文信息来提高分割性能。
那么如何获取全局上下文信息呢?在多个尺度上进行分割。当你在较低分辨率下分割时,你实际上是在尝试将更多的全局信息带入分割结果中。
因此,PSPNet引入了金字塔池化模块。给定一张图像,通过CNN进行前向传播。然后有一个池化层,在多个尺度上进行池化。有哪些不同的尺度?有一个粗糙的尺度,就是简单的全局平均池化,所以这将是1x1xC,其中C是通道数。每个连续的池化级别提供增加的信息定位信息。因此,你的池化输出将是1x1xC(全局平均池化)、2x2xC、3x3xC和6x6xC,这是PSP采用的四个尺度。记住,当你进行池化时,通常通道数不会减少,你是按通道操作的。
一旦完成了这四个尺度的池化,PSPNet现在对每个来自池化操作的映射应用一个1x1卷积,以减少输出中的通道数。每个1x1卷积将通道大小减少了多少?减少了四分之一。这样做是有原因的。原因是,一旦执行了这些1x1卷积,所有这些映射都会被上采样并连接起来。因此,每个池化后的映射使用简单的双线性插值进行上采样,然后它们被连接起来,这个连接后的体积将具有与原始特征图相同数量的通道。完成后,将其送入卷积层以获得最终的分割预测。
DeepLab 架构
另一种方法是DeepLab,这是一种流行的语义分割方法,与PSPNet类似。在这种情况下,图像经过几个卷积块,然后,不是有一个将图像分成四个不同尺度的池化层,而是执行空洞空间金字塔池化,因此这个模块被称为ASPP模块。记住,空洞卷积与膨胀卷积或转置卷积相同,有时甚至被称为分数步长卷积,所以当你的步长小于1时,你会得到与在多个尺度上进行池化相同的效果。因此,可以说空间金字塔池化是实现PSPNet中金字塔池化模块的不同方式。
因此,一旦你进行了空洞空间金字塔池化,你再次上采样、连接,并最终在连接后的体积上得到结果。
DeepLab多年来经历了一些不同的版本,其中一个较新的版本称为DeepLab V3。它在基本的DeepLab架构中添加了一些模块。在这个DeepLab V3模块中,图像级特征也被传递到ASPP模块。使用了批归一化以方便训练,还有一个完整的解码器架构用于细化分割结果。你可以看到这里有ASPP模块,使用它执行1x1卷积、3x3卷积等,你得到一组特征图,这些图被连接起来。进行1x1卷积以减少这些通道的深度,然后上采样4倍,并与ASPP模块的输出一起传递给解码器。然后将这些连接起来,最后再进行一次卷积,然后上采样并在最后进行预测。记住,当我们说预测时,它将再次是一个输出体积,其分辨率与输入图像相同,通道数将是C+1,其中C是类别数,加1是背景。
实例分割
从语义分割继续,实例分割是一个比语义分割更具挑战性的任务。在语义实例分割,或通常所说的实例分割中,我们的目标不仅是为每个像素提供一个类别标签,还要为每个对象分配一个对象ID。
因此,如果有多个行人、多把椅子或多只狗,你还需要区分这些行人、狗的实例,而基本的语义分割任务没有考虑这一点。在语义分割中,狗就是狗像素,无论有两只狗、三只狗还是四只狗,它们都只会被分类为狗。但在实例分割中,我们想要狗1、狗2、狗3和狗4。这里的挑战是你可能不知道狗的数量,这与检测问题类似。这就是为什么最流行的实例分割方法之一,称为Mask R-CNN,实际上试图改进Faster R-CNN来实现实例分割。
因此,Mask R-CNN于2017年在ICCV上提出,它使用类似Faster R-CNN的架构,但增加了一个分支来掩码化并获得实例级分割。所以你可以把Mask R-CNN看作是在感兴趣区域上带有FCN的Faster R-CNN。它添加了一个并行的头部,除了你的RPN对象分类器(或简单的分类模块)和边界框偏移回归器之外,你现在添加了第三个头部——掩码预测器。
Mask R-CNN中能够准确实现掩码的一个重要贡献是一个称为RoI Align的模块。由于Mask R-CNN在Faster R-CNN的框架内运行,这个模块解决的主要限制是:当你将对象提议映射到特征空间时,回想一下在Fast R-CNN和Faster R-CNN中,你有一些对象提议,并将它们映射到某个特征空间。而特征空间的分辨率可能与你获得对象提议的分辨率不同。在Fast R-CNN和Faster R-CNN中,我们只是将其变形到所需的大小,然后将你的对象提议带到那个维度。然而,当你这样做时,你可能会遇到由于量化而导致的误差。
为什么这很重要?这很重要,因为特征空间中的一个像素可能相当于图像上的许多像素,因为与初始输入相比,后面卷积层的特征空间更小。当你试图获得一个掩码时,这几个像素可能会在最终性能中带来显著的误差。
那么我们如何克服这个问题呢?RoI Align执行双线性插值,以获取对象提议与给定特征图匹配的确切坐标。这保留了掩码的平移等变性。我们是什么意思?我们的意思是,给定一张图像,其中一个物体可能位于这个黄色框或红色框中,两者都可能是有效的检测,但每个边界框中的掩码位置是不同的。因此,通过平移等变性,我们的意思是,如果物体在边界框内移动了一定的量,掩码也必须移动相同的量。这可以通过RoI Align层来实现。
因此,RoI Align的做法是:当你将对象提议转换为特征图上的位置时,如果该位置落在特征图上的几个像素之间,那么你对特征图周围的像素执行双线性插值。然后你在对象提议指向的特定点获得特定的像素值,这就是然后被池化以获得固定维度表示的内容,该表示将进入Faster R-CNN架构的后续层。
因此,除了具有分类损失和边界框损失的标准Faster R-CNN架构外,我们还有带有Mask R-CNN的掩码头。Mask R-CNN所做的是使用一个类似FCN的分支,这意味着它是全卷积的。对于每个RoI,掩码大小为28x28,正如你在这里看到的。在推理过程中,它最终被缩放到边界框大小,然后叠加在图像上。因此,正如你在这里看到的,这个掩码是为每个RoI获得的,然后在推理时最终叠加回输入图像上。
全景分割
最后一种更近期的流行分割形式被称为全景分割。全景分割结合了语义分割和实例分割。因此,正如你所看到的,语义分割是你分割所有对象的像素,但所有实例保持不变。实例分割是你只关心你类别中对象的实例,但你分别标记每个实例。而全景分割是两者的结合,你标记每个像素属于不同的对象,并且你还确保每个对象的实例获得完全不同的标签。
那么,如何进行全景分割呢?这项工作于2019年在CVPR上首次提出,大约一年前。人们可以使用几种架构来实现全景分割。你可以使用Mask R-CNN来获得实例分割结果,可以使用膨胀FCN来获得语义分割结果,然后将两者合并以获得全景分割,这是一种方法。因此,这项特定工作实际上实现了几种方法,然后研究了如何以不同的方式对其进行评估。
另一种方法是,你可以使用特征金字塔网络,而不是我们在这里看到的膨胀FCN或Mask R-CNN,你可以使用FPN,然后将这些检测结果通过Mask R-CNN头部以获得实例级分割,你也可以将这些不同的特征图通过像素级识别头部进行语义分割。因此,在这两种方法中,都有一个实例分割路径和一个语义分割路径,它们结合起来给出你的全景分割。
这里的损失函数是什么?推荐的损失函数是语义分割和实例分割损失函数的组合。记住,实例分割,正如我们在Mask R-CNN中看到的,有三个损失:分类损失、边界框损失和掩码损失。而对于语义分割,你有标准的像素级交叉熵损失。
全景分割的挑战之一是,它不能使用用于语义分割或实例分割的指标进行评估。为什么会这样?在语义分割中,我们使用IoU和每像素精度。因此,你可以尝试看看,如果你画出了一组属于特定对象的像素,你可以尝试获得该对象与真实标注的交并比。你也可以获得每像素精度,因为你将在每个像素级别为你拥有的所有类别预测概率,你也可以将该精度作为一个指标。
另一方面,对于实例分割,它类似于检测,你会有不同IoU阈值下的平均精度。
为什么我们不能将这些用于全景分割?因为这可能导致对有或没有实例级标注的类别产生不对称性。记住,在全景分割中,你可能有一些类别有多个实例,例如行人或狗。也可能有一些类别,如天空、道路,可能没有很多实例。因此,在不考虑这些不对称性的情况下合并这些指标可能不明智。
因此,这项特定工作推荐了一个称为PQ指标的新指标。PQ指标如何工作?你可以看这里的例子,让我们假设这是你正确的真实标注。所以有天空,有草地,有三个人(这是其实例分割部分),然后有一只狗。
现在让我们假设,无论我们提出什么模型,都这样预测它。天空,它预测对了。草地,它预测对了。狗被预测成了人。并且不是三个人,它只预测了两个人,它把其中两个人合并了。
那么你现在如何衡量这种系统的误差呢?因此,这种方法推荐的方式是:你首先写出所有的真正例,这将是你的真实标注和预测之间的两个真正例的人。有一个假阴性,即你错过的那个人。还有一个假阳性,即你添加的而不是狗的那个人。
因此,对于一个真实标注段G和一个预测段P,PQ指标计算为:对所有属于真正例的PGs的IoU求和,除以真正例的数量加上二分之一假阳性的数量加上二分之一假阴性的数量。
如果你分析这个,它可以写成:分子保持不变,让我们乘以并除以真正例的数量。那么你会得到第一项是这样,第二项是真正例的数量除以真正例的数量加上二分之一假阳性加上二分之一假阴性。
因此,这里的第一项给出了分割质量:在所有真正例中,你的所有分割的IoU是多少。第二部分给出了识别质量:在所有可能的分割中,我们有多少得到了真正例。因此,你有一部分检查分割质量,有一部分检查识别质量,这就是为什么这个指标对全景分割有用。
总结
在本节课中,我们一起学习了用于图像分割的各种卷积神经网络架构。我们从基础的语义分割任务开始,介绍了全卷积网络、SegNet和U-Net等编码器-解码器结构,它们通过上采样和跳跃连接来恢复空间分辨率并融合多尺度信息。接着,我们探讨了PSPNet和DeepLab,它们通过金字塔池化或空洞卷积来捕获全局上下文,以提升复杂场景下的分割性能。然后,我们深入到更精细的实例分割任务,了解了Mask R-CNN如何扩展Faster R-CNN,并引入RoI Align来精确预测每个实例的掩码。最后,我们介绍了全景分割这一前沿任务,它旨在统一语义分割和实例分割,并讨论了其特有的评估指标PQ。

这些架构的发展体现了深度学习在像素级理解图像方面的不断进步,从粗略的类别划分到精确的实例区分,再到全面的场景解析。掌握这些模型是进入计算机视觉高级应用领域的重要一步。
048:用于人类理解的CNN - 面部识别 - 第一部分

在本节课中,我们将要学习卷积神经网络在计算机视觉中的一个重要应用领域:面部识别。我们将探讨面部识别的基本概念、发展历程、标准处理流程以及两种核心任务:身份验证与身份识别。
发展历程与重要性
面部识别作为生物识别技术的一部分,在安全、金融、医疗保健等多个社会领域有着重要应用,其研究已持续数十年。无约束条件下的面部识别(即在自然场景中识别人脸)是一个极具挑战性的问题,因为它需要处理光照、遮挡、姿态、表情等多种变化。
从算法发展来看,面部识别的研究始于20世纪60年代。随后在70年代、90年代(以特征脸算法为代表)以及21世纪初,出现了基于局部特征分析、弹性束图匹配、局部二值模式等手工设计特征的方法。自2014年起,基于深度神经网络的方法成为主流,这也是本节课的重点。
从硬件发展来看,摄像头技术从1915年的相机发展到90年代的数码相机,再到后来的监控摄像头、智能手机、Kinect等设备,为面部识别的广泛应用奠定了基础。
标准面部识别流程
一个标准的、采用深度学习的面部识别流程如下图所示。流程始于输入图像,首先需要进行面部检测以从图像中定位并裁剪出人脸区域。
检测完成后,在将裁剪后的人脸送入识别系统前,通常需要进行一些预处理步骤。第一步是面部对齐,即将人脸对齐到预定义的几何标准。第二步是活体检测(可选),用于判断输入是真实人脸还是伪造的图片(如照片)。如果检测为伪造,流程终止;否则,进入下一阶段。
下一阶段是面部处理,主要分为“一对多”增强和“多对一”归一化两种方式,具体取决于应用需求。我们稍后会详细讨论。
经过上述处理步骤得到训练数据后,将其输入卷积神经网络进行特征提取。最后,使用欧几里得距离、角距离等不同的损失函数来训练CNN。
在测试时,测试图像会经过类似的预处理流程。处理完成后提取其特征,并通过与阈值比较、最近邻匹配、度量学习或稀疏表示等方法,将最终的人脸嵌入向量进行匹配,以完成识别。
核心处理步骤详解
上一节我们介绍了标准流程,本节中我们来看看其中的关键处理步骤。
面部处理:增强与归一化
面部处理旨在提升后续识别的鲁棒性和准确性,主要有两种策略:
1. 一对多增强
这是标准的数据增强技术。通过对单张人脸图像进行各种变换(如旋转以模拟不同姿态),生成多个训练样本。这有助于使面部识别系统对姿态变化等更具鲁棒性。
2. 多对一归一化
此方法旨在将同一个人在不同条件下(如不同光照、姿态、时间点或视频帧)捕获的多张图像,归一化到一个标准的、通常是正面的规范视图。这种方法有助于在保留身份信息的同时,消除光照、表情和背景变化的影响。该领域也有大量研究利用3D模型进行面部归一化。
网络架构演进
在深度学习的时代,用于面部识别的网络架构也随着CNN主流架构的发展而演进。
以下是近年来一些代表性工作及其使用的骨干网络:
-
2014年 DeepFace: 使用了 AlexNet。
-
2015年 FaceNet: 使用了 GoogleNet。
-
2015年 VGG Face: 使用了 VGGNet。
-
2017年 SphereFace: 使用了 ResNet。
-
2017年 VGG Face 2: 使用了 SENet。
总体而言,面部识别架构的骨干网络主要采用了同期在通用视觉任务上表现优异的网络,如AlexNet、VGG、ResNet等,研究人员有时会对其进行微调以适应特定任务。
核心任务:验证与识别
从应用角度看,整个面部识别领域可大致分为两种核心任务:验证和识别。在这两种情况下,通常只有架构的最后阶段有所不同。流程都是:输入图像 -> 检测与对齐 -> 深度CNN特征提取 -> 验证或识别系统。
面部识别
面部识别的任务是为给定的输入图像,从数据库中的多个身份里分配一个对应的人名或身份ID。这是一个一对多匹配问题。
这非常类似于我们之前讨论的图像分类任务(如ImageNet)。给定一张图像,将其与数据库中的多个类别(身份)进行匹配,找出最匹配的类别。这通常被表述为一个 K+1 多类分类问题(额外的“+1”类用于处理数据库中不存在的人脸)。
损失函数:在此场景下,通常使用标准的 Softmax 激活函数和交叉熵损失。
设 x 为倒数第二层的特征表示,W 为最后一层线性层的权重,则未经过Softmax的最终输出(即逻辑值 logits)为 W^T * x。随后应用Softmax函数得到概率分布,并计算交叉熵损失。
该损失函数的目标是增大类间方差,即将不同身份的特征在嵌入空间里分离开。
面部验证
面部验证的任务是确认两张图像是否属于同一个身份。这是一个一对一匹配问题。
例如,在出入境检查时,官员需要核实护照上的照片与持证人当前的面部是否匹配。系统不需要将当前图像与整个数据库比对,只需验证其与声称的身份是否一致。
因此,这是一个二分类问题,输出为“匹配”或“不匹配”。
流程:输入图像通过CNN得到特征表示,同时从数据库中提取声称身份对应的特征表示。然后,使用一个相似性度量(如距离计算)来比较这两个特征,最终通过一个阈值或分类器做出二元决策。
验证与识别的关系
一个有趣的观点是,可以将识别任务转化为一系列验证任务。
具体做法是:对于测试图像,提取其特征表示,然后将其与数据库中的每一个身份进行逐一验证(匹配/不匹配判断)。
这样做的好处是,当数据库中加入新身份时,无需重新训练整个CNN模型。因为模型只需要学习如何提取好的特征表示,而新身份的匹配可以通过外部的相似性度量来完成,这使得系统更具可扩展性。
潜在的缺点是,识别需要进行多次验证,如果验证系统本身存在误差,这些误差可能会在多次比较中被放大。因此,构建一个高精度、高效率的验证系统至关重要,这样才能有效地将其用于识别任务。
早期验证架构:孪生网络
最早的验证架构之一是1994年提出的孪生网络,最初用于签名验证任务。
其架构如其名(源于“连体双胞胎”),包含两个结构相同、权重共享的CNN分支。一个分支处理当前输入的签名(或人脸),另一个分支处理数据库中存档的签名(或人脸),分别得到特征表示 F1 和 F2。
然后,通过一个距离度量(如欧氏距离)来计算这两个特征的相似度,最后送入一个二分类器判断是否匹配。
这种方法的一个重要启示是:网络学习到的特征表示必须与后续用于比较的距离度量方式相兼容。这个思想在后续的研究中得到了诸多改进和发展。

本节课中我们一起学习了面部识别的基本概念、标准流程、关键预处理技术、网络架构的演进,并深入探讨了验证与识别这两大核心任务及其相互关系。我们还了解了将识别转化为验证任务的思路,以及孪生网络这一早期验证架构的基本原理。在接下来的课程中,我们将继续探讨更先进的损失函数和训练方法。
049:用于人类理解的CNN - 面部识别(第二部分)👤

概述
在本节课中,我们将学习卷积神经网络在人脸识别与验证任务中的近期应用。我们将从DeepFace模型开始,逐步探讨对比损失、三元组损失等核心概念,并了解如何通过改进损失函数来提升模型性能,最终区分闭集与开集人脸识别场景。
近期基于CNN的人脸识别与验证工作

上一节我们介绍了CNN在人脸理解中的基础应用。本节中,我们来看看自AlexNet成功以来,一些利用CNN进行人脸识别与验证的早期重要工作。
其中一项开创性工作是DeepFace,发表于2014年的CVPR会议。该工作首先执行了一个预处理步骤,包括人脸定位、关键点检测和对齐,以获得人脸的正面裁剪图。

左侧图像展示了获得正面裁剪图的步骤:
-
给定输入图像,第一步是检测人脸并从图像中裁剪出人脸区域。
-
然后在2D裁剪图上检测约67个面部关键点(如鼻尖、眼角、嘴角等),并进行三角剖分。
-
接着,将这些关键点投影到一个3D模型上,该模型对应这张特定的人脸图像。
-
然后对这个3D模型进行归一化,生成一个带有对应关键点的2D人脸图像,即正面裁剪图。
-
最后,由于我们现在有了同一个人脸的3D模型,我们还可以生成该人物的其他姿态图像。
因此,我们在步骤1中讨论的正面裁剪图就是图像G,与图像B相比,可以看到它被很好地归一化了。
这个正面裁剪图被送入一个深度CNN模型。该模型使用标准的Softmax层和交叉熵损失,通过一个K路Softmax来区分K个不同的类别。
验证任务与孪生网络
对于验证部分,DeepFace采用了一种类似孪生网络的方法。从识别任务中学到的表示被冻结用于验证,即这些架构参数被固定。
给定一张人脸图像I1和其声称身份对应的记录图像I2,我们得到两个表示F1和F2,即G(I1)和G(I2)。网络的其余部分通过取F1和F2的绝对差值,后接一个全连接层,最后使用Sigmoid激活函数进行二元决策来训练。
因此,在这种特定场景中诱导的距离是 |F1 - F2| * α,其中α是在该特定层中学到的权重。最终输出是匹配或不匹配(相同或不同)。
DeepFace提出的这种方法,源于90年代的孪生网络思想,此后在多个方面得到了扩展。其中一个主要思想是使用所谓的对比损失。
对比损失与度量学习
对比损失是一种基于机器学习中度量学习范式来训练神经网络的损失函数。机器学习中的度量学习方法试图学习一个距离度量,而不是使用欧几里得距离度量或余弦距离度量。
同样的思想现在被用来为特定任务学习具有区分性的特征表示。在人脸验证场景中,我们的目标是将输入映射到一个嵌入空间,其中点之间的距离对应于语义相似性。
欧几里得距离可以做到这一点,但如果我们能学习一个更有效地实现此目标的距离,那将更有用,这也是大多数方法试图做到的。已有多种公式化方法,如成对对比损失、排序损失、三元组损失等。我们将在本讲座的剩余部分看到其中一些。
成对对比损失源于Hadsell等人2006年的工作,其中引入了一种学习对复杂变换不变的映射的方法。正如我们刚才所说,其思想是让相似的点在嵌入流形上彼此靠近。因此,从CNN输出得到的这种表示也称为嵌入。
如果我们现在假设这些是低维嵌入(通常这些嵌入的维度低于图像本身,图像可能是200x200,而嵌入可能只是一个1000维或4000维的向量),我们理想地希望这些位于(例如)1000维或4000维流形上的嵌入,对于相似实体彼此接近,对于不相似实体彼此远离。

我们的目标是学习神经网络权重W,使得给定输入x1和x2时,距离 Dw = Gw(x1) - Gw(x2) 能够近似输入之间的语义相似性。让我们看看这是如何实现的。

以下是该算法的总体思想:
-
给定一个训练数据集,首先识别彼此相似的样本,并将它们放入一个集合中,例如给定输入Xi,我们识别与Xi相似的样本Xj。在人脸验证上下文中,这可能是同一个人的其他照片。我们设定如果这些点相似,则Yij = 0。
-
同样地,识别不相似的点,这可能是两个不同人的图像,此时设定标签Yij = 1。
-
训练时,对于训练集中的每一对(Xi, Xj):如果Yij = 0(相似图像),则更新W以减小距离Dw;如果Yij = 1(不相似图像),则更新W以增大距离Dw。
现在,让我们看看如何使用单个损失函数来实现这一点。
给定x1和x2为一对高维输入向量。令y为一个二元标签,如果x1和x2相似则为0,否则为1。那么,成对对比损失 L_contrastive 定义如下:
L_contrastive = (1 - y)/2 * Dw² + y/2 * max(0, m - Dw)²
第一项仅在y=0时激活,第二项仅在y=1时激活。
-
当y=0(图像相似)时,我们最小化Dw²,这正是我们想要的。
-
当y=1(输入图像不相似)时,我们最小化max(0, m - Dw)²。这里m是用户定义的边界,我们希望不相似的图像至少被此边界值隔开。
-
如果Dw² > m,则第二项max(0, m - Dw)²变为0,意味着无需操作,因为不相似图像之间的距离已经大于边界m。
-
如果Dw² < m,则我们最小化(m - Dw)²,这等价于最大化Dw²,即增大这些不相似图像之间的距离。
-
DeepID2:联合识别与验证
这个思想被用于后续的网络DeepID2,该网络于2014年提出。与DeepFace类似,DeepID2训练一个深度CNN来联合执行识别和验证任务。可以认为,识别任务增加了人际差异(试图将不同身份的图像或嵌入分开),而验证任务通过拉近同一身份的特征或嵌入来减少个人内部差异。

这是如何实现的?在DeepID2中,交叉熵损失用于识别参数,成对对比损失用于学习验证参数。因此,我们有对应识别模块权重的θ_ID,和对应验证模块权重的θ_verification。
识别损失由交叉熵给出,其中θ_ID是Softmax层的参数(用于识别的最后一层参数)。类似地,验证损失由成对对比损失给出,其中验证损失参数由θ_verification表示。

但DeepID2使用了一种略有不同的方式来学习整个流程。让我们看看这个算法:
-
从训练数据中采样两个训练样本Xi和Xj。
-
令F和Fj分别为Xi和Xj经过参数为θ_C的主干CNN(基础CNN)卷积层后的输出。
-
识别参数的梯度由两部分组成:对F的识别损失(交叉熵损失)关于θ_ID的梯度,加上对Fj的识别损失关于θ_ID的梯度。
-
验证参数的梯度由系数λ(用于加权)乘以验证损失关于θ_verification的梯度给出,其中验证损失由成对对比损失定义(在此特定情况下,如果身份相同则Yij设为1,如果身份不同则Yij设为-1)。
-
类似地,F和Fj的梯度(即关于嵌入F和Fj的梯度,它们是CNN的输出)由对F的识别梯度加上对F的验证梯度给出,对Fj同理。
-
整个CNN参数的最终梯度由以下公式给出:
dF_i * d(CNN(Xi))/dθ_c + dF_j * d(CNN(Xj))/dθ_c。这确保了CNN的权重更新首先使用Xi和Xj输出的梯度进行更新,然后相同的权重被共享给应用于Xi和Xj的两个CNN架构。
总结来说,θ_ID和θ_verification通过各自的目标分别更新,而θ_C(在分支到验证和识别之前的主干CNN的权重)则通过识别和验证目标的加权贡献进行训练。这使得主干网络能够同时学习良好的人际特征和个人内部特征。
FaceNet:直接学习嵌入表示
随后出现了FaceNet,于2015年的CVPR会议上提出。它的前提是,现有的人脸识别架构(包括DeepFace和DeepID2)通常学习整个神经网络,然后使用其中一个倒数第二层(称为瓶颈层)的嵌入来进行最终的匹配决策。
这种方法存在问题吗?FaceNet声称存在,即存在间接性。因为我们不知道来自中间层、倒数第二层或瓶颈层的那些嵌入在后续任务中能用于什么。并且,因为它没有针对后续任务(那些嵌入被用于的任务)进行明确训练,所以可能效率低下。
因此,FaceNet试图确保其目标是输出一个良好的表示,而不再有基于学习到的表示进行单独考虑的分类目标。事实上,FaceNet使用一种称为三元组损失函数的新损失函数来学习一个128维的嵌入,该损失函数如今在深度学习的多个场景中被广泛使用。
三元组损失
三元组损失的思想是:它是对比损失的扩展。对比损失使用相似和不相似的样本对,而三元组损失使用一个三元组。
三元组由什么构成?一个锚点、一个正样本点和一个负样本点。给定任何输入(作为锚点),我们有一个与锚点身份相同的正样本点,和一个与锚点身份不同的负样本点。
我们的目标是在学习过程中使用所有这三个点,通过一个损失函数来确保锚点与负样本之间的距离至少比锚点与正样本之间的距离大一个特定的边界值。
如何用损失函数实现这一点?我们有一个三元组约束,规定:
||f(x_a) - f(x_p)||² + α ≤ ||f(x_a) - f(x_n)||²
这意味着锚点到负样本的距离必须至少比锚点到正样本x_p的距离大一个正值α。
你可以将其可视化:有一个作为输入给CNN模型的锚点图像,数据集中有该锚点图像的正例和负例。在获得表示(作为CNN架构的输出)后,首先确保这些表示被归一化,使得每个表示的||f(x)|| = 1。这相当于这些点位于你所考虑的128维空间中的单位超球面或单位球体上。
现在的目标是根据所有这些三元组来调整神经网络权重θ,以确保 ||f(x_a) - f(x_p)||² + α ≤ ||f(x_a) - f(x_n)||²。
这是通过使用三元组损失来实现的,其定义为:
L_triplet = max(0, ||f(x_a) - f(x_p)||² - ||f(x_a) - f(x_n)||² + α)
很容易看出,这将试图确保在锚点到负样本的距离与锚点到正样本的距离之间至少有一个边界α。
一个关键问题是:给定一个锚点图像,如何选择这些三元组?可能有所谓的简单三元组,其中正样本和负样本非常明确。然而,简单三元组可能无法真正促进学习,它们可能无法让网络学习到有区分性的特征。因此,选择最初违反三元组约束的三元组可能很重要,这样网络才能学习,并可能随着时间的推移最终改变三元组的性质。
因此,你可以首先选择所谓的困难三元组。给定一个用于训练神经网络的三元组样本小批量,你可以只选择困难正样本和困难负样本。
-
困难正样本是那些使
||f(x_a) - f(x_p)||最大化的样本(即与正样本距离最远的样本)。 -
困难负样本是那些使
||f(x_a) - f(x_n)||最小化的样本(即与负样本距离最近的样本)。
因此,我们在给定的小批量中选择这类样本作为困难三元组来训练网络。FaceNet实际上使用在线三元组采样,从每个小批量中采样困难正样本和负样本。
但这里有一个问题:如果早期选择非常困难的负样本,可能会导致糟糕的局部极小值,训练可能崩溃。因此,想法是在初始阶段选择半困难负样本,然后在训练后期转向更困难的负样本。
如何选择半困难负样本?理想情况下,我们希望选择位于边界α内的负样本。因此,我们想从这类样本开始选择,它不一定是最小距离的样本,但我们希望选择一个在边界内的负样本。如何选择这样的半困难样本?你可以选择满足 ||f(x_a) - f(x_p)||² < ||f(x_a) - f(x_n)||² 的样本(忽略我们之前提到的α),这样你最终可能会选择在初始训练时位于边界内的样本。
角Softmax损失及其他改进
进一步关于三元组损失,2017年出现了一种称为角Softmax损失的方法,旨在提高人脸识别和验证的性能,也可用于其他领域。论文中的这些图表阐明了整体思想。
如果你有一个原始的Softmax损失,并考虑在应用Softmax损失之前神经网络的嵌入(即在应用Softmax激活函数和之后的交叉熵损失之前的倒数第二层的嵌入),你可能得到类似这样的嵌入:所有黄色点属于一个类别,所有粉色或紫色点属于另一个类别。可以看到,如果现在归一化这些向量并获得单位范数,使用两者之间的余弦距离来区分身份可能有点困难,因为有些地方黄色和紫色点几乎重叠。
这可以通过使用改进的Softmax损失来改善,即在获得这些嵌入之前先归一化权重向量。这项特定工作提出了角Softmax,它增强了正类嵌入和负类嵌入之间的区分度,从而可以使用余弦距离度量来有效区分身份或获得身份之间的相似性。
为什么这在上下文中很重要?请记住,对于大多数这些识别或验证方法,最后一步是使用距离度量进行匹配,而可用于匹配的一个距离度量是余弦相似性或这些表示之间的角度。
现在我们看看这些改进的Softmax损失的不同定义。
-
原始Softmax损失可以写为:
L_softmax = -log(e^{W_{y_i}x_i + b_{y_i}} / Σ_j e^{W_j x_i + b_j}),其中W_{y_i}x_i + b_{y_i}是神经网络对应真实类别y_i的逻辑值。点积Wx可以写成||W|| * ||x|| * cos(θ_{y_i}) + b_{y_i}。 -
改进的Softmax损失:在神经网络的倒数第二层之后,应用Softmax之前,对获得的权重进行归一化。在这种情况下,连接到最后层的每个神经元的权重(W1, W2等)的范数被归一化为1,同时偏置b也被设为0。因此,新的改进损失看起来像这样,其中||W||变为1,偏置变为0。
-
角Softmax损失扩展了这一点,使对应正类的角度缩放m倍,而对应所有其他类的角度不按相同的m值缩放(m是一个正常数)。这确保了正类和其他类之间的角度被进一步分离。请记住,检查一个特征是否属于一个类别而非另一个类别的最终匹配将由
cos(θ_i) > cos(θ_j)给出,此时我们会说该点属于第i个标签而非第j个标签。通过进行角Softmax,我们确保现在有cos(m * θ_i),它可能大于cos(θ_j)。我们试图确保对于正类,余弦值可以进一步远离与其他类角度的余弦值。
以下是来自同一论文的另一可视化:
-
Softmax决策边界由
(W1 - W2)x + (b1 - b2) = 0给出。 -
在改进的Softmax边界中,你归一化权重,因此所有点都位于一个单位球体上。你现在试图确保得到这样的分离:属于正类的点位于球体的一侧,属于负类的点位于球体的另一侧。
-
角Softmax试图通过让W1在球体一侧的弧上更远,W2在球体另一侧的弧上更远,来确保这种分离被进一步最大化。这种进一步的分离有助于在最后一步实现更好的分类。
其他损失函数改进
过去几年,还有其他改进这些损失函数的想法,其中许多已用于人脸分析领域。
- 中心损失是2016年提出的另一种方法。它是在交叉熵损失之外添加的一个损失,除了标准的交叉熵损失外,我们还获得每个类标签的一个质心。对于一个属于特定类的点,我们还最小化该表示到该特定类质心的距离。这些质心在学习过程中在线更新。右侧图表显示,随着调整加权到质心距离的系数λ,当λ值较小时,嵌入与质心有某种分离;当λ权重越来越大时,特定类的所有嵌入最终都接近该类质心(由这些类中的白点表示)。当你想最终决定是否与某个身份匹配时,这允许类之间有良好的分离。

- L2 Softmax 是另一项改进。它试图确保在Softmax之前的特征的L2范数位于一个固定半径α的超球面上。这种方法不是将特征归一化以确保它们具有单位范数,而是试图确保它们具有特定的范数α。这种方法的直觉来自他们的观察:具有高L2范数的特征易于分类,而具有低L2范数的特征(因为它们可能接近原点)可能更难以分类(当点更接近原点时,它们可能看起来更接近;当点具有高L2范数时,它们离原点更远,因此可能更容易分离)。在这种情况下,优化结果是:最小化交叉熵损失,使得在应用Softmax之前获得的表示满足
||f(x_i)|| = α,α是预定义的。


- 环损失于2018年提出,进一步改进了这一点。这是一个与L2 Softmax非常相似的想法,然而,即使在这种情况下,你也是最小化交叉熵损失使得
||f(x_i)|| = R,但与L2 Softmax的区别在于,在此特定情况下,你还将R作为训练过程的一部分来学习。因此,神经网络除了学习神经网络的权重外,还决定L2范数值应该是多少。这被称为环损失,因为你试图将Softmax层之前的特征表示投影到一个环上,该环的半径也由神经网络决定。

基于这些方法,出现了一系列努力:
-
CosFace(2018年提出)提出了一种基于大间隔的余弦损失用于人脸识别。
-
UniformFace(2019年提出)提出了一种均匀损失来学习等分布表示,从而利用整个特征空间,而不是专注于原点周围的一个球体。
-
RegularFace(2019年再次提出)提出了一种显式正则化来显式增大不同实体之间的角度距离。
-
GroupFace(2020年最近提出)使用多个组感知表示来提高嵌入质量。
-
CurricularFace(同样是2020年的近期工作)提出了自适应课程学习,以调整不同训练阶段简单和困难样本的相对重要性。课程学习是训练深度神经网络的一个方面,即最初让模型接触简单的训练样本,然后逐渐增加训练样本的难度,以帮助神经网络更好地学习。我们在三元组挖掘中看到了这一点,我们说在转向困难负样本之前,先从半困难负样本开始。这种称为CurricularFace的方法进一步提出了自适应课程学习损失。
闭集与开集人脸识别
总结本讲座,人脸识别还有所谓的闭集和开集设置。
-
在闭集设置中,你有一组预先给定的身份,必须将输入图像与之匹配。可以使用Softmax加交叉熵损失、中心损失、L2损失、环损失、角Softmax损失等来实现闭集人脸识别。
-
另一方面,在开集人脸识别中,可能会遇到不在你数据库中的人(即系统未知的人)的图像,神经网络必须至少能够说此人不属于数据库且对当前系统是未知的。你使用什么样的损失函数来训练这样的网络?有一种称为双边界对比损失的方法可以被重新用于实现这一点,甚至三元组损失也可用于实现开集人脸识别。更多细节,可以参考名为《Deep Face Recognition: A Survey》的论文。

总结
本节课中,我们一起学习了CNN在人脸识别与验证中的演进。我们从DeepFace的预处理和孪生网络架构开始,深入探讨了用于学习判别性嵌入的核心思想——对比损失。接着,我们分析了DeepID2如何联合优化识别与验证任务,以及FaceNet如何通过三元组损失直接学习高质量的嵌入表示。然后,我们了解了包括角Softmax、中心损失、L2 Softmax和环损失在内的多种改进损失函数,它们通过不同的几何约束(如角度间隔、特征范数)来提升特征空间的分离度。最后,我们区分了闭集与开集人脸识别场景,并指出了适应开集任务的潜在损失函数方向。
课后阅读与作业

本讲的课后阅读将是一篇关于理解排序损失、对比损失、边界损失、三元组损失等的非常好的博客(如果它们都让你感到困惑的话)。还有一篇由Masi等人撰写的关于深度人脸识别的详细教程。如果你有兴趣,可以查看使用OpenCV进行人脸识别的Python代码库,以及关于深度人脸识别综述的整篇论文。
我们留下的问题是:什么是双边界对比损失?以及它如何用于开集人脸识别?给你的提示是阅读Masi等人的这篇教程以找到答案。


050:用于人体理解的CNN - 人体姿态估计与人群计数

概述
在本节课中,我们将学习卷积神经网络在人体理解任务中的两个重要应用:人体姿态估计和人群计数。我们将了解这些任务的定义、面临的挑战、评估指标以及基于深度学习的核心方法。
超越人脸理解
上一节我们介绍了人脸理解任务。实际上,卷积神经网络已被用于许多其他人体理解任务,例如手势识别、情绪识别和步态识别等。
为了展示任务的多样性,我们现在将探讨另外两个任务:使用深度学习和CNN进行人体姿态估计和人群计数。
人体姿态估计任务
人体姿态估计的任务是在图像和视频中定位人体关节(如肘部、手腕等)的问题。
这种任务可用于体育分析,或用于微软Xbox等设备,通过检测你的姿态来控制屏幕上的虚拟角色进行特定的网球击球或高尔夫挥杆动作。
现有方法分类
现有的人体姿态估计方法,特别是基于深度学习的方法,大致分为两类:
-
单人姿态估计流程:旨在获取图像帧中单个人的姿态。
-
多人姿态估计流程:图像帧中可能存在多个人,需要获取每个人的姿态。
下图展示了姿态估计模型的工作原理示意图。理想情况下,我们寻找的是图像中每个关节的位置。

任务挑战
这个任务与我们目前看到的任务(如图像级分类、检测、分割或人脸验证)不同。我们将在本讲座中了解如何使用CNN来完成此任务。
此外,当出现遮挡(尤其是自遮挡,即身体的一部分遮挡了另一部分)时,这个任务会变得非常具有挑战性。
评估指标
在讨论具体方法之前,我们首先需要知道如何评估人体姿态估计模型的性能。目前有一些公认的评估指标。
以下是几个关键指标:
-
PCP(正确部位百分比):如果一个检测到的关节与真实关节之间的距离小于该肢体长度的一半,则认为该肢体被正确检测。根据所建模的身体部位不同,肢体有长有短。如果预测关节位置与正确关节位置之间的距离小于该肢体长度的一半,则认为这是一个相当正确的预测。这被称为PCP@0.5。如果考虑四分之一肢体长度,则称为PCP@0.25。
-
PDJ(检测关节百分比):如果预测关节与检测关节之间的距离在躯干直径的某个分数范围内,则认为检测到的关节是正确的。可以将躯干直径作为所建模人物的中心尺度。例如,PDJ@0.2意味着要求预测关节与真实关节之间的距离小于所考虑人物躯干直径的0.2倍。
-
基于OKS的mAP(目标关键点相似度平均精度):这可以看作是人体姿态估计中IOU的等效指标。其公式如下:
OKS = Σ_i [exp(-d_i² / (2 * s² * k_i²)) * δ(v_i > 0)] / Σ_i [δ(v_i > 0)]让我们解释一下公式中的各个量:
-
d_i是检测到的关键点与对应真实关节之间的欧几里得距离。 -
v_i是真实关节的可见性标志。某些关节可能被其他关节遮挡,尝试获取该关节的正确估计可能是一项不可能完成的任务。通过为每个关节设置可见性标志,可以知道如果某个关节不可见,则该关节上的任何误差都可以被赋予较低的权重。 -
s是目标尺度。检测点与真实点之间的距离必须通过该特定目标的尺度进行归一化。如果图像中该目标非常大,这个距离可能相对较大;而对于同一图像中尺度较小的人物,这个距离则较小。 -
k_i是每个关键点的用户定义常数,可以视为控制衰减的超参数。
可以看到,这个指标是距离的负指数函数,意味着它给出了预测好坏的程度,但同时考虑了关节的尺度和可见性来评估最终性能。
-
深度姿态估计方法
在明确了这些评估指标后,我们现在来讨论使用深度神经网络模型进行人体姿态估计的方法。
所有此类模型的先驱是2014年提出的 DeepPose。这可能是第一个启动基于深度学习的人体姿态估计的工作。
在该方法中,图像被输入模型,当时的架构自然是受AlexNet启发的,因为在2014年这是实践中最常见的模型之一。
与原始AlexNet架构的唯一区别在于输出空间。现在,不是预测整个图像的类别标签,而是预测一组关节的2D位置坐标 (x_i, y_i)。因此,输出 y 是一组 y_i,每个 y_i 包含对应关节的x、y坐标。
这里使用的损失函数是什么?损失函数将是每个关节位置相对于其真实位置的均方误差或L2范数。
级联回归器
该方法并未止步于此。为了进一步提高性能,它还使用了级联回归器来提高每个关节位置的精度。
这意味着什么?一旦在初始阶段识别出关节位置,就在关节位置周围裁剪出一个区域。该区域被缩放到整个CNN的输入大小,并再次预测该区域内关节位置的细化值,这有助于在多个阶段微调关节位置。
因此,这些裁剪后的图像在后续阶段被输入网络,这确保了在更精细的图像尺度上,可以获得精确的关键点位置作为输出。
迭代误差反馈方法
另一种在2016年提出的方法采用了不同的方向,也是一种基于迭代误差反馈的方法。
整个预测从一个平均姿态骨架开始,然后在多个步骤中进行迭代更新。给定一张输入图像和一个平均人体姿态骨架,神经网络的任务仅是预测与平均姿态骨架的偏差。
更详细地看这个过程:
-
在第一阶段,
x_t是图像I本身。 -
该图像被输入神经网络,神经网络为每个关节预测一个偏差
ε_t。 -
这个
ε_t被加到前一步的姿态y_t上,得到一个新的姿态y_{t+1}。 -
现在,将这个
y_{t+1}覆盖在图像上,并围绕它裁剪出一个区域,再次用于细化误差和偏差,得到一个新的ε_t。 -
这个过程反复迭代,以获得姿态的最终估计。
视觉上,这里有几个示例。可以看到,在图像上覆盖了平均姿态(通常是直立的人),在每一步中,平均姿态都被调整以接近特定人物的姿态。经过四步后,预测的姿态变得接近最后一列显示的真实姿态。
这里还有更多具有挑战性的示例。同样,从站立姿态开始,经过几步,得到了人物蹲在特定位置的姿态。
基于检测的方法
我们之前看到的基于回归的方法试图预测关节位置。人体姿态估计的另一类方法是基于检测的方法,即一次性尝试获取每个关节区域的热图。
使用这种方法的工作之一在2015年提出,它还采用了从粗到细的多尺度策略来帮助细化这些热图,以获得更好的关节定位。
让我们看看这种方法。在该方法中:
-
输入图像被输入一个粗热图模型,该模型给出关节位置的热图。这可能是一个经过微小修改的标准CNN骨干网络。
-
对于预测的每个关节(可能位于特定热图的中心),在其周围裁剪出一个区域,然后使用一个精细热图模型来改进该特定位置的关节定位。
让我们更详细地看看这个精细热图模型。对于粗热图中的每个关节位置(可能是热图特定区域的中心),有一个多尺度金字塔。如果原始图像是64x64,则有128x128和256x256版本。相应地,裁剪出9x9、18x18和36x36的区域。
现在,这三个区域分别通过不同的卷积流程。9x9区域通过一个流程,18x18区域通过两个具有不同卷积滤波器的流程,36x36区域通过另一个不同的卷积+ReLU层流程。然后,所有这些特征图都被上采样到36x36(显然,最上面的不需要上采样),所有特征图被连接起来以进行最终预测,该预测对于该特定关节更加精确。这个细化过程有助于提高最终性能。
多人姿态估计
正如我们提到的,另一类方法是当图像中有多个人,需要估计每个人的姿态时。
这里主要有两种不同的流程,方法与我们之前看到的非常相似:
-
自上而下流程:在这种情况下,我们希望检测给定图像中所有人的姿态。因此,我们首先检测给定图像中的所有人物。然后,对于检测到的每个人的边界框,运行我们在前几张幻灯片中看到的单人姿态估计方法。如果需要,可以使用一些全局上下文信息来帮助细化姿态估计。
这是一个示例:使用人体检测器检测到两个人,然后裁剪出这两个人,并使用基于回归或检测的方法运行单人姿态估计器,得到骨架。同样处理另一个人,然后将这两个骨架覆盖在输入图像上。
-
自下而上流程:这个过程相反,首先检测图像中的所有关键点,而不考虑它们属于谁。因此,可以使用基于检测的方法获得整个图像的热图,这些热图区域的中心可能是不同的关键点,但不知道哪个关键点属于哪个人。一旦检测到这些关键点,然后使用不同的方法将这些关键点关联到不同的人物实例。
有关这些方法的更多细节,可以参考名为“基于深度学习的2D人体姿态估计”的综述。显然,在这种方法中,推理可能更快,因为同时处理所有人的信息,而不是通过不同的流程处理每个人的边界框。
人群计数任务
我们将探讨的另一个任务是人群计数。人群计数对于城市规划、公共安全、安保、治理等是极其重要的任务。然而,这在实践中可能是一项非常具有挑战性的任务。
你可能面临几个挑战,例如:
-
遮挡:如图所示,在这张图像的单个区域中,有许多不同的面孔(用绿点表示),彼此之间严重遮挡。
-
复杂背景
-
尺度变化:取决于相机拍摄的角度。
-
图像中人物分布不均匀
-
透视畸变
-
旋转问题
-
光照变化:例如在阴影中计数人数。
-
天气变化
所有这些都使得人群计数问题变得极其困难。
人群计数方法分类
使用CNN进行人群计数的现有方法可分为三种:
-
基础CNN方法:使用基本的CNN架构来实现目的。输入图像通过一个简单的CNN,输出一个密度图。密度图热图中的峰值可以估计给定图片中的人数。
-
多列方法:该方法的假设是,为了能够计数不同尺度的人脸(大人脸、小人脸等),需要使用多列方法,其中每一列关注不同尺度的人脸。可以看到,给定输入后,将其转换为多尺度金字塔形式,在每个单独的流程中尝试检测特定尺度的人脸。
-
单列方法:这种方法再次采用单列,但试图观察多列方法的性能,并在一定程度上简化它们,以确保网络架构不会太复杂。
基础CNN方法
基础CNN方法将人群计数问题视为回归问题,给定图像,必须预测一个数字作为输出。
以下是一些训练数据点:给定一组人群正例图像及其计数,以及其他场景(人群计数为0)的负例图像。每个训练样本都通过CNN架构进行拟合,输出是该图像中的人数,可以直接通过回归(如L2损失或平滑L1损失)来解决。
这里可以扩展负样本集,因为获取没有人的图像(其真实计数为零)可能更容易,这有助于减少干扰并提高在正样本上的性能。
然而,可以看出这是一种粗略的方法,可能对人群密度分布、人物尺度等敏感。
多列CNN方法
这引出了基于多列的方法,这些方法试图解决人脸的尺度在图像中可能不同的问题。
在这种多列CNN中,每个单独的流程以不同尺度查看输入图像。可以看到这里有9x9、7x7和5x5的卷积滤波器,它们查看输入中的多个尺度。这有助于在最后获得更好的性能:在每个流程的最后几层之后,特征图被合并,然后通过一个1x1卷积层获得与输入相同分辨率的密度图。
这里有一些示例:给定测试图像和相应的真实密度图,可以看到估计的热图。如果需要计数,可以考虑使用这些热图的峰值来估计数量;在某些应用中,密度图本身可能就足够了。
带切换分类器的多列CNN
2017年的一项工作在多列CNN的基础上进行了扩展,引入了切换分类器的概念。
输入图像的每个区域被输入一个切换层,该层决定哪个分辨率是该区域的最佳尺度。因此,切换分类器(可以在最右侧看到)获取图像区域,然后输出一个结果,指示在CNN的三个尺度列中,哪个尺度适合在该区域检测人脸。现在,该区域被输入相应的CNN,并使用该尺度的密度图来获得最终结果。
为什么这有道理?这是因为在图像的局部区域内,人脸的尺度很可能保持在相同范围内,而在图像的另一个区域,尺度可能非常不同。因此,这种方法利用人群尺度的局部性,为每个区域切换相应的CNN流程,从而最终获得良好的性能。最后,它使用加权平均来融合可以在全局使用的特征。
单列CNN方法
单列CNN是从多列CNN衍生而来的,基于对其性能的一些观察。
2018年的首次努力观察到,在多列CNN中,单列在某些数据集上保留了约70%的准确率。那么,为什么要使架构复杂化呢?
因此,这里的单列CNN最初使用一组标准的卷积层,然后将来自较早层的特征图传递到经过反卷积后获得的较深层。可以看到,在卷积层6之后,连接了前一层的特征图,然后进行反卷积(相当于这里的上采样)以获得更高分辨率的图像。然后,将来自较早层的特征图添加到这个上采样图像中,得到新的特征图,这些特征图通过一组特定的卷积层,最后通过一个1x1卷积层获得最终的密度图。
在这个特定案例中,使用了反卷积而不是上采样或逐元素求和。此外,这项工作使用了基于密度图的损失和基于计数的损失来训练神经网络。因此,所有这些方法都假设真实密度图和总人数是已知的,因此可以使用与这两者相对应的损失进行反向传播并训练神经网络的其他部分。对于密度图,可以使用L2损失;对于计数,可以直接使用计数的L1损失(即预测计数与正确计数之差的绝对值)。
更近期的单列CNN
2019年提出的另一个更近期的单列CNN观察到,在多列CNN中,低级特征在人群计数上下文中具有非常相似的功能和特征。
因此,他们建议保留CNN初始层的相同流程,当进入较深层时,使用一个多尺度金字塔(称为尺度金字塔模块),该模块结合不同尺度的特征以获得密度图的最终输出。
在这个特定案例中,尺度金字塔模块是使用不同尺度的扩张卷积实现的,以便能够分析不同尺度的人脸,并将其放置在VGG16架构的conv4_3和conv5_1层之间。
这里有几个示例:给定图像、估计计数和正确计数。可以看到,在大多数情况下,估计计数在一定的误差容忍范围内与真实估计相当接近。
本课作业与思考
本课的作业是关于人体姿态估计的一篇很好的博客文章(来自Nanonets),以及2020年最新发布的关于密度估计和人群计数的综述。
如果你有兴趣,也可以阅读这篇关于2D人体姿态估计的综述。
本课的练习或思考实验是:CNN,特别是用于人体理解时,可能会受到数据集中偏见的影响。因此,根据特定数据集中占主导地位的种族(用于人体姿态估计或人脸识别),决策可能会受到这些统计数据的偏见影响。
在讨论如何解决这些偏见之前,你首先如何发现为基于人体的任务训练的模型是否存在偏见?请思考一下。

总结
在本节课中,我们一起学习了卷积神经网络在人体理解中的两个高级应用:人体姿态估计和人群计数。我们了解了它们的任务定义、核心挑战、评估指标(如PCP、PDJ、OKS),并深入探讨了多种深度学习方法,包括DeepPose、迭代误差反馈、基于检测的热图方法、多人姿态估计的流程(自上而下与自下而上),以及人群计数的基础CNN、多列CNN和单列CNN方法。最后,我们提出了关于模型偏见的思考,这是实际应用中需要关注的重要问题。
051:用于其他图像任务的CNN


在本节课中,我们将简要了解深度卷积神经网络(CNN)在几个其他图像任务中的应用,包括深度估计、超分辨率和异常检测。我们将探讨这些任务的基本概念和实现方法。
深度估计
上一节我们介绍了CNN在分类和检测等任务中的应用。本节中,我们来看看如何利用CNN从单张2D图像中估计深度。
深度估计的目标是确定场景中每个像素点距离相机的距离。传统方法(包括人类视觉系统)通常依赖双目立体视觉。然而,研究试图仅从单张图像中获取深度信息。
以下是使用CNN进行深度估计的两种方法:
1. 早期方法(Eigen et al., 2014)
该方法使用一个粗糙级CNN对输入图像进行前向传播,在最后一层输出每个像素的深度估计值。这与姿态估计等任务类似。为了提高精度,该方法引入了一个细化步骤:将原始图像以不同分辨率和滤波器处理后,输入到一个精细深度估计网络。同时,粗糙网络的估计结果也会在中间层与精细网络的输入进行拼接,共同生成更精确的细化深度估计。
公式表示:
-
粗糙估计:
Depth_coarse = CNN_coarse(Image) -
细化估计:
Depth_refined = CNN_fine(Image, Depth_coarse)
训练时,使用像素级真实深度图,通过均方误差(L2损失) 作为损失函数进行优化。
2. 当代方法(Genet)
一个更现代的方法是Genet,它在一个联合框架中同时进行深度估计、相机运动估计和姿态估计。我们主要关注其深度估计部分。
该方法假设可以从一个图像序列(例如自动驾驶中连续的视频帧)中更好地估计深度,而非单张图像。网络将不同时间点的帧作为输入体积(Volume) 的不同通道,类似于之前将RGB作为不同通道。
-
深度网络估计每个时间通道的深度图。
-
这些时序深度图之间的相互作用产生了刚性流的估计,即场景中静止物体的运动。
-
同时,一个非刚性运动定位器使用光流方法来处理场景中的动态内容。
-
最后,结合刚性流和非刚性运动定位器的信息,预测整个场景的最终深度/光流,并进行一致性检查以处理遮挡和非朗伯表面等问题。
超分辨率
接下来,我们探讨CNN在超分辨率任务中的应用。超分辨率旨在从低分辨率图像中恢复出高分辨率图像。
这在电视等领域有广泛应用,例如通过AI增强技术将内容提升至更高分辨率显示。
以下是使用CNN进行超分辨率的一种方法:
基于CNN的超分辨率网络
该方法将低分辨率图像输入一系列卷积层。其核心思想是,利用像素周围的图像块(Patch) 信息,来提升输出图像中该像素区域的分辨率。
网络结构详解:
-
输入低分辨率图像
y。 -
第一卷积层生成
N1个特征图:F1(y) = W1 * y + B1 -
第二卷积层基于
F1(y)生成N2个特征图:F2(y) = ReLU(W2 * F1(y) + B2)(假设使用ReLU激活函数) -
最后一层通过线性加权重建出高分辨率图像:
HR_Output = W3 * F2(y) + B3
训练:如果有成对的低分辨率-高分辨率图像数据,可以使用像素级损失函数(如L1或L2损失)来训练整个CNN。
工作流程:以蝴蝶翅膀图像为例,第一层卷积提取边缘、纹理等基础特征图;这些特征传递到第二层,被组合成更复杂的特征;最终,所有特征信息被整合,重建出细节更丰富、感知质量更高的输出图像。
异常检测
最后,我们介绍异常检测任务。目标是从给定数据分布中找出异常样本或分布外图像。
例如,在训练好的猫狗分类CNN中,输入一张汽车图片,网络需要识别出这不是它见过的类别。
直接使用分类网络的Softmax输出概率来判断并不可靠。一种2018年提出的方法引入了两个关键改进:
1. 温度缩放(Temperature Scaling)
修改Softmax激活函数,引入温度参数 T:
Softmax(z_i) = exp(z_i / T) / Σ_j exp(z_j / T)
温度 T 的作用:缩放输入到指数函数的逻辑值 z_i。降低 T 可以放大类间差异,使得输出概率分布更“尖锐”,有助于区分分布内和分布外样本。
2. 输入扰动(Input Perturbation)
对于输入图像 x,假设模型预测其为“狗”:
-
计算损失函数相对于输入的梯度符号:
sign(-∇_x L) -
根据该符号,对原始输入添加一个小的扰动
ε:x_perturbed = x + ε * sign(-∇_x L)
直觉:
-
如果
x确实是狗,添加扰动后,网络应更确信其是狗(对应类的Log-Softmax输出会增大)。 -
如果
x是分布外图像,添加扰动后,网络的输出会变得更加不确定或混乱(Log-Softmax输出可能减小)。
检测步骤:
-
将扰动后的图像
x_perturbed再次输入网络。 -
获取其Log-Softmax输出值。
-
若输出值大于某个阈值,则判定为正常(分布内)图像。
-
若输出值小于阈值,则判定为异常(分布外)图像。
这种方法无需改变网络架构或重新训练,即可有效检测异常。
总结
本节课中我们一起学习了CNN在三个重要图像任务中的应用:
-
深度估计:学习了从单目图像估计深度图的方法,包括早期的多级细化网络和利用时序信息的现代联合估计框架。
-
超分辨率:了解了基于CNN的超分辨率网络如何通过卷积层提取特征并重建高分辨率图像。
-
异常检测:掌握了一种利用温度缩放的Softmax函数和输入扰动技术,来检测分布外图像的实用方法。
这些应用展示了CNN在解决超越简单分类的复杂视觉问题上的强大能力和灵活性。


🎼
052:循环神经网络 - 介绍 🧠

在本节课中,我们将要学习循环神经网络的基本概念。我们将了解RNN为何被设计用于处理序列数据,其核心架构如何运作,以及如何通过计算图来理解其前向传播和反向传播过程。
概述
到目前为止,我们已经学习了卷积神经网络的工作原理、如何使用反向传播训练它们、如何将其用于图像分类、使用哪些损失函数,以及如何可视化和理解CNN。我们还看到了CNN如何扩展到其他任务,如检测、分割、人脸识别以及其他各种任务。
本周,我们将转向深度学习领域的另一个支柱——循环神经网络。循环神经网络旨在处理时间序列,这意味着在计算机视觉中,当输入是视频时,它们就变得相关。
在开始本主题之前,我们有一个上周遗留的问题,当时我们完成了关于CNN用于人类理解的讲座。在当今时代,一个重要的问题是:如何发现模型中的偏见? 如果你有一个面部识别模型,如何知道该模型是否对特定人群或数据集存在偏见?希望你有机会思考过这个问题。在当今这类技术被大规模部署的时代,这是一个重要问题。
一个简单的检查方法是,如果你认为可能存在某个特定特征或属性(例如某个人的背景),那么你可以在输入中改变该属性,并观察预测是否发生变化。如果预测发生变化,则表明模型依赖该特征进行预测。如果你认为这种依赖不应该存在,你应该尝试基于该属性进行正则化,使用数据增强方法来改变该属性的值,并确保模型的预测不发生变化。
我们现在将转向循环神经网络。本讲座幻灯片基于Fili的CS231N课程和Mihikara的IIT Madras课程。
序列学习问题
到目前为止,无论是前馈神经网络还是卷积神经网络,输入的大小始终是固定的。给定一个CNN,你总是输入一个固定尺寸的图像(例如32x32)进行分类。每个输入也独立于之前或未来的输入。两个连续图像的计算、输出和决策完全相互独立。
这是什么意思?这也与机器学习数据集中常见的IID属性相关。IID代表独立同分布。如果我们有一个图像数据集,我们假设数据集中的每个图像都是独立同分布的。这意味着无论你以何种顺序提供这些图像,你的模型最终都可能学习到相同的预测函数。因此,无论输入是这张图像还是那张图像,显然类别标签会改变,但模型参数或架构中的其他内容不会改变。
然而,还有另一类问题被称为序列学习问题,它们需要比我们目前所见更多的东西。
以文本自动补全为例,这是你可能每天都会遇到的情况。如果你输入“Google Autocomplete A”,你会得到一组推荐;如果你输入“Google autocomplete B”,你会得到另一组推荐;对于“C”也是如此。在这类问题中,连续的输入不再独立。为了在特定状态下进行预测,你需要查看之前时间步的输入。这是我们在讨论前馈网络或CNN时不需要的。
在这类问题中,输入的长度和需要进行的预测数量也可能不固定。例如,如果你输入“Google autocomplete C”,最后一个词可能是“code”或“country”,它们的长度不同。你希望你的模型不受此类约束的影响。
这类问题的另一个重要启示是:无论你的输入是什么,无论“C”之前有两个词、三个词还是一个词,模型都必须执行相同的任务。在所有上下文中,给定一个先前的短语,它必须预测下一组字符。无论你在哪个时间步尝试进行预测,该任务都不会改变。如果用户输入了一个词,你仍然需要预测下一组字符;如果用户输入了两个词,你的上下文(时间上下文)长度增加了,但任务需要实现的目标保持不变。
这类问题被称为序列学习问题。为处理此类序列学习问题而设计的神经网络架构被称为循环神经网络,我们很快就会看到它们为何如此命名。
RNN的架构变体
以下是RNN的几种主要架构变体。
一对多架构
如果你将一个标准的前馈神经网络可视化为具有输入、一组隐藏层和输出的结构,那么循环神经网络的变体可以可视化为:你可以有一个一对多的变体。其中,你的输入在一个时间步,但你的输出实际上是跨多个时间步的输出序列。
示例:图像描述生成。输入是单张图像,输出是一个单词序列,一个单词必须跟随前一个单词以构成完整的句子。这被称为“一对多”,即一个输入,多个输出。
多对一架构
类似地,你可以有一个多对一架构。其中,你在多个时间步有输入,但只在某个特定时间步进行一次预测。
示例:动作预测。输入是一系列视频帧,模型在看完完整的输入序列后给出一个特定的类别。例如,在看完一段足球比赛的完整视频片段后,模型应预测该片段是否为进球事件。
多对多架构
还有一种多对多设置,其中给定一组跨时间步的多个输入,输出也必须具有跨时间步的多个输出。
示例:视频描述生成。输入是一个视频(即随时间变化的一系列帧),输出也是随时间变化的一系列单词。
同步多对多架构
你可能还有一个略有不同的多对多设置,其中你有一组随时间变化的输入和一组随时间变化的输出,但输出和输入在每个输入处是同步的。
示例:逐帧视频分类。你有一个视频作为模型的输入(记住,视频具有时间成分)。但同时,你希望在每一帧级别进行预测。在这种特定上下文中,这将是一个多对多设置。
如何为序列建模
那么,我们如何为涉及序列的任务建模呢?我们看到了高级架构,但实际上如何用这些架构学习模型?如何考虑时间序列数据中常见的输入之间的依赖性?如何考虑可变数量的输入?如何确保,例如在图像描述生成中,给定一张图像,描述可能有七个词;给定另一张图像,描述可能有十个词?你如何允许多个输出、可变数量的输出,或者类似地,为另一个应用允许可变数量的输入?
第三个考虑因素是,如何确保在每个时间步执行的函数是相同的?为什么我们需要这个?无论一帧在视频中的哪个位置,你都希望使用相同的逻辑来执行预测。这类似于我们在卷积中所说的:无论猫在图像的左上角还是右下角,我们都想说图像中存在一只猫。我们通过卷积的思想克服了那个问题。但现在,我们如何在时间序列和序列学习问题上做到这一点,这就是我们接下来要讨论的内容。
RNN的高级抽象
循环神经网络的高级抽象如下图所示。你有一个输入 x,一个输出 y。在两者之间,有一个被称为循环神经网络的东西。顾名思义,这里存在一个循环,现在由这个单元循环表示。
RNN的关键思想是它们有一个内部状态,该状态在序列被处理时更新。人们可以将这个内部状态视为某种记忆,它保留了同一时间序列过去数据的一些信息,这个想法我们本周稍后也会讨论。
如果展开这个循环,你会得到一个这样的网络:你有一组随时间变化的输入 x1 到 xt。这就是你拥有的输入序列,这就是为什么我们将其视为序列学习问题。对于每个输入,你可以将该输入提供给一个RNN块。这个RNN块可以是单个隐藏层,也可以是多个隐藏层,可以具有跳跃连接,可以在此RNN块内具有任何其他架构,但我们将把它视为一个单一的抽象。该RNN的输出是模型在该时间步的输出。
这将是整个展开的RNN。当你使用RNN时,你必须事先决定要展开多少。这是什么意思?对于特定问题,你必须决定这个 T 必须是多少。你是想查看过去的20帧来做决定,还是想查看过去的100帧,或者200帧?这是在你开始学习RNN之前必须决定的事情。
这里的 T 将对应于你在进行预测时希望查看的序列长度。一旦 T 被决定,你就可以这样展开RNN,其中RNN块内的相同操作用于每个时间步的输入。正如我们之前所说,根据你使用RNN的应用,你可以在每个时间步都有输出,或者只在最后一个时间步有输出,或者也可以在最后一个时间步之后有一个跟随输入的输出序列。所有这些可能性都是我们在屏幕上看到的这个抽象的不同变体。
循环操作详解
现在让我们深入更多细节。我们说过它是一个循环神经网络,并且我们也希望这个RNN块对每个时间步的输入重复相同的操作。那么,我们实际想要使用的循环操作是什么?
对我们来说,每个时间步的循环公式是:给定特定时间步的输入 Xt,以及 ht-1(RNN在前一个时间步的状态),然后你将应用某个函数。这个函数是RNN内部带有一些权重 U 和 W 的参数。你会得到该时间步的输出 h,它被传递给RNN的下一个状态,以处理下一个时间步的输入。
这是一个可视化:在时间步 T1,给定输入 x1 和初始状态 h0,RNN使用权重矩阵 U 和 W 处理这两个输入,并输出 y1(如果该时间步需要输出)以及状态 h1,该状态被传递给RNN的下一个时间步。在下一个时间步,RNN接收 x2 和 h1 作为输入,使用与前一个时间步相同的权重 U 和 W(记住我们说过,我们希望RNN将每一帧视为类似类型的输入并执行类似的操作,尽管它们在时间上位置不同)。因此,第二个时间步的RNN输出 y2(根据问题需要)和其隐藏状态 h2,该状态被传递给第三个时间步的RNN,依此类推,直到最后一个时间步。
所以,重复一下循环公式:ht = F_UW(Xt, Ht-1),其中 U 和 W 是那些权重矩阵。这里的关键观察是,相同的函数和相同的参数集在每个时间步都被使用。这不会改变,而这正是循环神经网络中“循环”所在之处。
现在,你可以考虑根据任务使用激活函数。我们刚才写的循环关系现在可以是:H = tanh(U * Xt + W * Ht-1),其中 tanh 是循环神经网络中常用的激活函数。U 是乘以 Xt 的权重矩阵,W 是乘以输入 Ht-1 的权重矩阵。Yt 可以是 softmax(V * Ht)。所以,如果你有一个从RNN输出的 Ht,那么你有一组权重 V 乘以 V*Ht,然后如果你处理的是分类问题,你可以在 V*Ht 上应用 softmax 来给出RNN的最终输出。
这里的权重,从 x 到RNN的是 U,查看 Ht-1 并给出 Ht 的是 W。所以你可以看到有三组权重矩阵(当然,W 可以根据RNN块内有多少层而变化),否则你这里有作用于输入、隐藏状态和隐藏状态到输出的三组权重。这有时也被称为Elman RNN,以Jeffrey Hinton(应为Jeffrey Elman,此处原文有误)命名,他是这些思想的发起者。
计算图简介
为了继续前进并理解RNN实际上是如何训练的,我们将退一步,看看所谓的计算图的概念。
计算图是实现神经网络(包括推理和反向传播)的一种非常常见的方法,如今在PyTorch或TensorFlow等框架中也是如此。因此,我们将首先回顾一般的计算图,然后讨论如何为RNN定义计算图。
计算图是一个有向图,其节点对应于操作或变量。输入节点和从节点输出的值是张量。张量是多维数组,通常是大于二维或二维的。向量的一般化是矩阵,矩阵的一般化通常被称为张量。张量通常包含标量、向量、矩阵等,是表示这些量的更通用术语。
正如我刚才提到的,计算图可以实例化以执行神经网络的前向传播以及反向传播。
让我们在讨论神经网络之前举一个简单的例子,谈谈如何使用计算图来创建表达式并计算它们。如果你有表达式 E = (a + b) * (b + 1),这是一个算术表达式。这里有三个操作:两个加法和一个乘法。
你可以将整个表达式或操作写为一系列中间操作。那些中间操作是什么?你可以首先说 C = a + b,D = b + 1,然后 E = C * D,这是你的最终输出。所以,你说这里的第一个项是 C,第二项是 D,然后你最终将两者相乘以得到 E。这看起来很简单,我们为什么要讨论这个?
现在可以将其写为计算图。你有作为输入给出的值 A 和 B。你计算由 a + b 给出的 C。然后你计算 D,它只依赖于 B,即 b + 1。然后你组合 C 和 D 得到最终输出 E,即 C * D。
如何评估这些表达式?假设给你特定的 A 和 B 值,你如何使用计算图评估这个表达式?你会将输入变量设置为给定的特定值,例如 a=2,b=1,然后你会从这些变量实例值开始,沿着图向上计算节点,直到根节点,得到最终答案。
所以它可能看起来像这样:设置 a=2,b=1。首先计算 c = a + b,得到 c=3。类似地,d=2。然后组合它们,得到 E 的最终答案,即 E=6。这很好很简单。
但是你如何使用这样的计算图进行反向传播?或者换句话说,如何使用计算图计算梯度/导数?这里的关键是,要理解梯度,你需要理解计算图的边,因为边表示一个特定变量如何影响另一个变量。
例如,如果你有相同的计算图,你在这个叶节点有 a,并且在那个叶节点的父节点有 c = a + b。现在,可以写出 ∂c/∂a。因为 c = a + b,所以 ∂c/∂a = 1。类似地,在连接 B 和 C 的这条边上,∂c/∂b 也是 1,因为 c = a + b,依此类推,对于图中的每一组边都是如此。
那么,我们如何做到这一点?我们正在将标准求和规则和乘积规则适当地应用于梯度,以计算每条边上的每个梯度。为了得到你的最终梯度,你可以对所有从一个节点到另一个节点的可能路径求和,将路径上每条边的导数相乘。
例如,如果我们想要 ∂E/∂a,我们查看所有将我们从 a 带到 E 的可能路径。在这个特定情况下,那将只是这条路径。你将那条路径上的所有梯度相乘,所以在这种情况下是 1 * 2,这给了你梯度 ∂E/∂a。类似地,如果你必须计算 ∂E/∂b,你必须考虑所有将你从 B 带到 E 的路径。你将每条路径上的梯度相乘,并将这两条路径的贡献相加,你将得到最终梯度。
所以再次,在这个例子中,∂E/∂b 将是 1 * 2 + 1 * 3。这就是你如何使用计算图来获取特定表达式中的梯度。
在PyTorch中的应用
为了给你一个更具体的例子,在你可能使用的框架(即PyTorch)中,PyTorch在前向传播期间动态跟踪变化,允许创建这样的计算图,称为动态图。当你说 loss.backward() 时(当你在PyTorch中训练神经网络时,如果你计算损失然后调用 .backward()),或者如果你只是评估一个表达式,你可以在这个我们看到的特定例子中说 c.backward()。当你执行 c.backward() 时,PyTorch框架会触发梯度的计算,然后你可以打印出表达式中每个边或每个梯度的梯度。
神经网络的计算图
那么神经网络的计算图呢?前馈神经网络?假设你在神经网络中有几层,给定输入 X,假设你的隐藏层 H 是 tanh(W * X + b),W * X + b 是你的输入 x 的线性组合,让我们假设你在每个节点上有一个非线性激活函数 tanh,这给你一个向量 H。然后你在 H 上应用另一组权重 V 来给出你的最终输出 y,其中 b 和 a 是你的偏置。
如果这是你对前馈神经网络的数学表示,那么你对应的计算图将如下所示:你的第一个输入是 W 和 x,基于此,你在这里得到某个输出。然后你将其与 + b 组合,然后你可以得到某个输出。最后,你取那个并应用 tanh 得到你的 tanh(U)。所以这里的第一步可以表示为一个计算图,它是一个将 W、X 和 b 作为输入并输出 H 的序列。类似地,对于下一个操作,你现在可以将 h 视为输入,v 视为另一个输入,a 视为另一个输入,此时的计算图将是这组操作。显然,此计算图中某个节点的输入是作为前一个计算图的输出而来的边。
RNN的计算图视角
现在,回到RNN,由于RNN稍微复杂一些,让我们尝试从计算图的角度来看待RNN。给定输入 x1 和 H0,输入被提供给 F_UW(这是我们之前讨论过的),F_UW 输出 h1。然后 h1 和 x2 再次进入 F_UW,这是我们在每个时间步评估的相同函数。然后第二个时间步的 F_UW 给你 H2。这再次循环地在下个时间步给出,依此类推,直到你得到该序列最后一个时间步的最终隐藏状态。
所以从计算图的角度来看,这里的输入 U、W(权重矩阵)在每个时间步都是相同的,这就是这些箭头所显示的,它们进入每个时间步的 F_UW。

浙公网安备 33010602011771号