笔记参考了 https://zhuanlan.zhihu.com/p/540389017

上一节课总结:

数据集划分:
  • 数据集可以分成训练集、开发集、测试集三种。
  • 数据量小时按比例划分,数据量大时只需要选少量数据用作开发集、测试集。
  • 开发集和测试集的区别:开发模型时不能偷看测试集的评估结果。

方差与偏差
  • 面对偏差问题,常见的解法是使用的更复杂的模型提升参数量,并延长训练时间。
  • 面对方差问题,常见的解法是增加数据(数量和质量)和正则化。
  • 改变模型结构往往能同时解决这两个问题。
正则化
  • 正则化的作用:缓解过拟合。
  • 正则化的通用思想:防止网络过分依赖少量的某些参数。

  • dropout的思想:训练时随机丢掉某些激活输出。
  • dropout的实现:由随机数矩阵和失活概率算出一个bool矩阵,以此bool矩阵为mask与激活输出相乘。
 参数初始化
  • 了解梯度数值异常的原因:中间计算结果随网络层数指数级变化。
  • 参数初始化可以令数据的方差尽可能靠近1,防止梯度异常问题。
梯度检查
  • 知道梯度检查的数学公式。
  • 实现简单模型的梯度检查。
高级梯度下降算法

前置知识:指数加权移动平均

Momentum

  • 大致理解Momentum的思想。
  • 掌握公式,能用代码实现,知道一般情况下超参数。

RMSProp

  • 掌握公式,能用代码实现,知道有哪些超参数。

一.组织深度学习项目

1. 如何降低误差以达到优化目标

经过了之前的学习,我们学会了许多改进深度学习模型的方法,比如:

  • 收集更多数据
  • 收集更多样化的数据
  • 延长训练时间
  • 用高级梯度下降算法
  • 缩小/扩大网络
  • 使用正则化
  • ……

这么多方法,如果只是一个一个试过去,开发效率就太低了。接下俩,我们会学习一些改进机器学习的策略。这些策略会给我们一些启发性的指导,让我们在改进模型时更明确下一步该做什么。

1.1正交化

首先,是调整老式电视机的例子。老式电视机的画面不一定恰好能端端正正地填满屏幕,需要人为地调整画面的位置。一般这些电视机都有很多按钮,每个按钮各负责一项调整功能,比如调整上下位置、左右位置、缩放、旋转等。每个按钮之间的功能互不干扰。

另外,还有一个开汽车的例子。汽车最少有三种操作:转方向盘、加速、减速。只需要组合这三种操作,我们就能让汽车沿着某一路线跑起来。而如果汽车只有两个可以左右调整的按钮,一个按钮控制0.3倍的角度和-0.8倍的速度,另一个按钮控制2倍的角度和0.9倍的速度,那司机控制汽车时肯定会倍感吃力。

以上两个例子显示了正交化的好处。正交可以指数学里两条直线垂直,这里指的是两个调整方向互不干扰。通过调整正交的参数,我们可以把事物的“坐标分量”逐个调整到我们期待的“位置”。

类似地,在改进机器学习项目时,也可以使用正交化。

在机器学习项目中,大概有4个“坐标分量”需要调整:拟合训练集、拟合开发集、拟合测试集、提升时机应用中的表现。对于这每一项的目标,我们都应该使用相互正交的策略去调整,比如

拟合训练集-用更大的网络

拟合开发集-正则化

拟合测试集-用更大的开发集

提升实际应用中的表现-更换损失函数

值得一提的是,提前停止是一个即会影响训练误差,又会影响开发误差的方法。这个方法不满足正交化的要求,使用此方法时需要多多注意。

1.2设置目标

1.21单一指标

在分类任务中,一般有下面这两种评价指标:

  • 精确率(precision, 又称查准率):所有识别为猫的图片中,究竟有多少确实是猫?
  • 召回率(recall, 又称查全率):所有猫的图片中,有多少猫被正确识别了?

“ 注意,我们之前代码实战中用的准确率(accuracy)和精确率(precision)不是一个指标 

现在,假设有两个模型,它们在开发集上的评估结果如下:

  • 模型1:精确率95%,召回率90%。
  • 模型2:精确率98%,召回率85%。

二者在精确率和召回率上各有优劣,该怎么从中选一个更好的模型出来呢?

设置目标的一个原则是:只使用单一实数作为评价标准。因此,我们要想办法用一个指标把这两个指标都考虑进来。比如使用F1-score,它的公式如下:

                                                         F_{1}score = \frac{2}{\frac{1}{P} + \frac{1}{R}}

再看一个例子,假如我们开发好了几个算法,我们要用来自不同国家的数据去测试他们。不同算法在不同国家的数据上表现较好。为了快速选取一个最好的算法,我们可以去计算每个算法的表现平均值。

有了单一评价标准,我们就可以快速比较各个模型在开发集上的表现,并选择一个更好的模型。这样,我们开发的迭代速度也变快了。

1.22满足指标与优化指标 

在有多个评价指标时,不是总能挑选出一个最恰当的综合指标的。比如评价某算法时既要考虑到准确率,又要考虑到运行时间。用一个综合指标来组合它们显然不太现实。这时,我们可以把指标分成满足指标优化指标

比如说,我们有这样几个算法:

分类器 准确率 运行时间
A 90% 80ms
B 92% 95ms
C 95% 1500ms

算法C是挺好的,但是它相较A,B实在跑的太慢了。因此,我们可以设置以下的评价标准:

满足运行时间≤100ms的前提下,最大化准确率。

这个标准既保证了运行时间不会太长,又能选出准确率较高的算法。按照这个标准,B应该是最优的分类器。

在这个例子中,准确率就是优化指标,运行时间就是满足指标。

这种新的选取指标的方法应该和之前提到的单一指标原则结合起来。准确来说,应该只有一个优化指标,外加若干个满足指标。

1.23 训练/开发/测试的分布

开发集和评价指标,共同决定了我们的优化目标。因此,我们应该谨慎地选择各数据集的数据分布,防止优化目标跑偏。

举个例子,假如我们收集了来自不同地区的数据,有亚洲、欧洲……。假如我们令亚洲的数据为开发集,欧洲的数据为测试集,我们就可能会训练出一个在开发集上表现优秀,却在测试集上表现糟糕的模型。正确的做法是,我们把来自不同地区的数据打乱,把数据随机分成开发集和测试集。

还有一个改编自真实故事的例子。一个团队想开发根据某人的邮政编码预测他同意贷款的概率的算法。他们以中等收入地区的邮政编码为开发集,却以低收入地区的邮政编码为测试集。显然,在这两个地区的人同意贷款的概率会差很多。最后,这个团队花了3个月优化了算法在开发集上的表现,却发现模型在测试集上表现奇差,不得以推倒重来。

也就是说,我们应该让训练集和测试集能够反映我们将来实际应用时的数据,并且训练集和测试集都得来自同一个分布。设置开发集和评估指标,就像立了一个靶子一样。训练,就是让射出的箭更靠靶心。而测试集,应该反映我们期望箭射到的位置。我们既要知道箭应该射在哪里,还要把靶子摆对。

1.24开发集和测试集的大小

数据量小的时候(比如说数量级在万以下),我们可以按6:2:2的比例划分训练/开发/测试集。但数据量大的时候,就不用考虑比例了,按固定大小选择差不多大小的开发集和测试集即可。

那么,测试集要多大才够呢?从统计学的眼光来看,把测试集当成实际应用数据中的一个采样结果的话,我们应该保证测试集有很高的置信度能反映模型在实际应用中的综合表现。当然,对于简单的数据分布,我们可以用统计学知识严谨地算出置信度。而对于人工智能任务中用到的海量数据,数学工具就难以派上用场了。我们只能根据经验选择一个足够大的测试集。比如有百万级数据的话,一万个测试样本就够了。

 1.25何时更换开发/测试集与评价指标

在算法投入应用后,我们可能会发现新的评价角度。比如对于小猫分类模型,我们本来只期望它能正确识别小猫。可是,随着使用应用的人变多,我们发现有的用户会上传色情图片。这时,我们不仅希望模型能只找出小猫,还要能过滤掉色情图片。

这样,我们就引入了一个新的评价指标。这样,之前辨认小猫能力强的模型,可能会在辨认色情图片上较差。

为了考虑这个新的评价指标,我们可以修改误差函数,用更高的权重加大色情图片分类错误的惩罚。

总结来说,当我们发现使用当前指标得出来的最优模型,与考虑到某些新因素后得到的最优模型不同时,我们就应该更换开发/测试集与评价指标了。

1.3 与人类级表现比较 

在许多任务中,人类的表现都非常出色。当AI超过了人类后,往往也达到了这类问题的最优精度。在机器学习模型超过人类前,与人类比较有以下好处:

  1. 获取人类标注的数据。
  2. 从手动误差分析中获得启发:为什么人就能做对?
  3. 更好地分析偏差与方差。

其中,第1条是显然的,第2条会在下周介绍。接下来,我们看看第3条是怎么回事。

1.31 可规避偏差

在判断一件事时,有可能因为信息的缺乏,最优的准确率也达不到100%,总会存在一些误差。这样的最小的误差叫做贝叶斯误差。人类的表现,通常可以用作贝叶斯误差的一个估计。

在刚才那个例子中,如果人类误差是1%,那么模型的训练误差还有7%的提升空间;而如果人类误差是7.5%,那说不定模型的训练误差只有0.5%的提升空间了。对于前者,我们应该关注偏差;关于后者,我们应该关注方差。这里讲到的7%, 0.5%的提升空间,可以称作可规避偏差

如果一个模型的训练误差是8%,开发误差是10%,我们不一定说模型就存在这个偏差问题。有可能模型在训练集上已经几乎达到了最优的表现;

1.32 理解人类级表现 

  

假如让人类来完成医学图片分类任务,人们得到了以下的分类误差:

从一个普通人,到一群有经验的医生,误差逐渐降低。那么,哪个误差算是人类级表现呢?

回顾上一节的内容,人类误差是贝叶斯误差的一个估计。因此,人类最优的表现,才应该被视作是人类误差。

当然,获取人类级表现的目的还是为了做偏差和方差分析。如果当前的训练误差是5%,那不管人类误差是1%,0.7%,还是5%,都差不多。而如果训练误差到了1%,甚至更低,那就要仔细地获取人类误差了

1.33 提升模型表现

最后,再一次回顾一下如何减少偏差和方差。

机器学习有两大假设:模型能够很好地拟合训练集、模型能够泛化到开发/测试集上。它们分别对应偏差问题和方差问题。

训练误差和人类级表现之间的差是可规避偏差,开发集和训练集之间的差是方差。

训练更大的模型、训练更久/用更好的优化算法能够解决偏差问题。

使用更多数据、正则化能解决方差问题。

用更好的架构、超参数能同时解决这两个问题。

总结

这节课涉及的新知识很少,大家就权当是复习了一下之前的知识。这节课大概学了这些东西:

  • 正交化
  • 目标 
    • 单一指标
    • 满足指标与优化指标
  • 开发集与测试集 
    • 分布
    • 大小
  • 人类级表现 
    • 贝叶斯误差
    • 可规避偏差
    • 提升模型表现的思路

2. 错误分析、数据集匹配、多任务学习、端到端学习

2.1错误分析

2.11分析具体错误

当我们想提升模型的准确率时,一种做法是统计模型输出错误的样例,看看哪类数据更容易让模型出错。

比如,在提升一个小猫分类器的准确率时,我们可以去看看分类器最容易把其他哪种动物错分类成小猫。经过调查后,我们可能会发现一些小狗长得很像小猫,分类器在这些小狗图片上的表现不佳:

这时,我们可以考虑去提升模型在小狗图片上的表现。

但是,在决定朝着某个方向改进模型之前,我们应该先做一个数据分析,看看这样的改进究竟有没有意义。我们可以去统计100张分类错误的开发集图片,看看这些图片里有多少张是小狗。如果小狗图片的数量很小,比如说只有5张,那么无论我们再怎么提升模型辨别小狗的能力,我们顶多把10%的错误率降到9.5%,提升微乎其微;但如果错分为小狗图片的数量很多,比如有50张,那么我们最优情况下可以把错误率从10%降到5%,这个提升就很显著了。

更系统地,我们可以建立一套同时分析多个改进方向的数据分析方法。比如说,在小猫的错误样例中,一些输入样本是很像小猫的小狗,一些输入样本是其他大型猫科动物,一些输入样本过于模糊。我们可以挑一些错误的样例,分别去记录这些错误样例的出现情况:

在这个表格中,我们可以记录每张分类错误的图片是由哪一种错误引起的,并留下一些备注。

调研已有问题的同时,我们还可以顺便去发现一些新的问题。比如我们可能会发现某些错分类的图片加了滤镜。发现这个新问题后,我们可以去表格中新建“滤镜”这一列。

手动分析完所有样例后,我们统计每种错误的百分比,看看改进哪种问题的价值更大。

2.12 清理标错的数据

在有监督学习中,标注数据往往是人工完成的,数据的标签有误也是情理之中的事。那么,如果数据中有标错的数据,它们会对模型的表现有什么影响呢?

首先,来看训练集有误的影响。事实上,深度学习算法对随机错误的容忍度很高。如果有少量样本是不小心标错的,那么它们对训练结果几乎没有影响。但是,如果数据中有系统性错误,比如白色的小狗全部被标成了小猫,那问题就大了,因为模型也会学到数据集中这种错误的规律。

接着,我们来看开发集有误的影响。为了确认标错数据的影响,我们应该用刚刚的表格统计法,顺便调查一下标错数据的比例:

在开发集误差不同时,标错数据产生的影响也不同。假设我们分别有一个开发集误差为10%的分类器和一个误差为2%的分类器,如上图下面部分。

对于第一个分类器,总体占比0.6%的错标数据相对于10%的开发集错误率几乎可以忽略。但是,对于第二个误差为2%的分类器,0.6%的错标数据就显得占比很大了。在这种情况下,假如有同一个模型有两个权重记录点,一个误差为2.1%,一个误差为1.9%。由于误差的存在,我们不好说第二个记录点就优于第一个记录点。回想一下,开发集本来的目的就是帮助我们选择一个在开发集上表现更好的模型。分辨不出更好的模型,开发集就失效了。因此,我们必须要去纠正一下这些开发集中的错标数据。

在纠正错标数据时,我们要注意以下几点:

  • 由于开发集和测试集应来自同一个分布,纠正数据的过程应该在开发集和测试集上同步进行。
  • 不仅要检查算法输出错误的样本中的错标样本,还要考虑那些标注错误却输出正确的样本。
  • 不一定要去训练集上纠正错标样本,因为训练集和开发集/测试集可以来自不同的分布。

吴恩达老师建议道,尽管很多人会因为检查数据这件事很琐碎而不愿意去一个一个检查算法输出错误的样本,但他还是鼓励大家这样做。他在自己领导的深度学习项目中,经常亲自去检查错误样本。检查错误样本往往能够确认算法之后的改进方向,在这件事上花时间绝对是值得的。

 2.13 快速构建第一个系统,再迭代更新

在面对一个全新的深度学习问题时,我们不应该一上来就花很多时间去开发一个复杂的系统,而是应该按照下面的步骤尽快开始迭代:

  • 快速建立开发集、测试集和评估指标以树立一个目标。
  • 快速构建一个初始的系统。
  • 使用偏差和方差分析、错误分析来获取后续任务的优先级。
  • 简而言之,就是:快速构建第一个系统,再迭代更新。

    当然,如果你在这个问题上已经很有经验了,或者这个问题已经有很多的科研文献,那么一上来就使用一套较为复杂却十分成熟的系统也是可以的。

    这种快速迭代的思想同样适用于人生中的其他任务。比如,软件开发中,敏捷开发指的就是快速开发出原型,再逐步迭代。同样,我们在计划做一件事时,不必事先就想得面面俱到,可以尽快下手,再逐渐去改良做法。

2.2 不匹配的训练集与开发/测试集

 2.21 在不同分布上训练与测试

到目前为止,我们已经多次学习过,开发集和测试集的分布必须一致,但是它们与训练集的分布不一定要一致。让我们来看一个实际的例子:

假设我们要开发一个小猫分类的手机程序。我们有两批数据,第一批是从网站上爬取的高清图片,共200,000张;第二批是使用手机摄像头拍摄上传的图片,有10,000张。最终,用户在使用我们的手机程序时,也是要通过拍照上传。

现在,有一个问题:该如何划分训练集、测试集、开发集呢?

一种方法是把所有数据混在一起,得到210,000张图片。之后,按照某种比例划分三个集合,比如按照205,000/2,500/2,500的比例划分训练/测试/开发集。

这种方法有一个问题:我们的开发集和测试集中有很多高清图片。但是,用户最终上传的图片可能都不是高清图片,而是模糊的收集摄像图片。在开发集和测试集中混入更简单的高清图片会让评估结果偏好,不能反映模型在实际应用中的真正表现。

另一种方法是只用手机拍摄的图片作为开发集和测试集。我们可以从手机拍摄的图片里选5,000张放进训练集里,剩下各放2,500张到开发/训练集里。这样的话,开发集和测试集就能更好地反映模型在我们所期望的指标上的表现了。

总结来说,如果我们有来自不同分布的数据,我们应该谨慎地划分训练集与开发/测试集,尽可能让开发/测试集只包含我们期待的分布中的数据,哪怕这样做会让训练集和开发/测试集的分布不一致。

2.22不同数据分布下的偏差与方差问题

在之前的学习中,我们一直把机器学习模型的改进问题分为偏差问题和方差问题两种。而在使用不匹配的数据分布后,我们会引入一个新的分布不匹配问题。

还是在刚刚提到的小猫分类模型中,我们用第二种方法设置了分布不一致的训练集和开发/训练集。假设我们得到了1%的训练误差和10%的开发误差。但是,我们使用了不同分布的数据,开发/测试集的数据可能比训练数据要难得多。我们难以分辨更高的开发误差是过拟合导致的,还是开发集比训练集难度更高导致的。

为了区分这两种问题,我们需要划分出一个只评估一种问题的新数据集——训练开发集(Training-dev set)。训练开发集的用法和我们之前用的开发集类似,但是其数据分布和训练集一致,而不参与训练。通过比较模型在训练集和训练开发集上的准确度,我们就能单独评估模型的方差,进而拆分过拟合问题和数据不匹配问题了。

加入了这个数据集后,让我们对几个示例进行改进问题分析。

假设人类在小猫分类上的失误率是0%。现在,有以下几个不同准确率的模型:

误差/样本 1 2 3 4
训练误差 1% 1 10% 10%
训练开发误差 9% 1.5% 11% 11%
开发误差 10% 10 12% 20%
问题诊断 高方差 数据不匹配 高偏差 高偏差、数据不匹配

也就是说,在多出了数据不匹配问题后,我们可以通过加入一个训练开发集来区分不同的问题。

当然,数据不匹配不一定会加大误差。如果开发/测试集上的数据更加简单,模型有可能取得比训练集还低的误差。

首先,我们要知道训练集上人类的表现,以此为贝叶斯误差的一个估计。之后,我们要测训练误差和训练开发误差。训练误差和人类表现之间的差距为可规避偏差,训练开发误差和训练误差之间的差距为方差。最后,我们计算开发/测试集误差,这个误差和训练开发误差之间的差距为数据不匹配造成的差距。

一般来说,只把上述内容填入表格即可明确当前模型存在的问题。不过,如果我们能够获取开发/测试数据分布上的人类误差和训练误差,把上表填满,我们就能获取更多的启发。比如上表中,如果我们发现在开发/测试数据上人类的表现也是6%,这就说明开发/测试数据对于人类来说比较难,但是对模型来说比较简单。

 3. 完成多个任务

3.1迁移学习

深度学习的一大强大之处,就是一个深度学习模型在某任务中学习到的知识,能够在另一项任务中使用。比如在计算机视觉中,目标检测等更难的任务会把图像分类任务的模型作为其模型组成的一部分。这种技术叫做迁移学习

假如我们有一个通用图像识别的数据集和一个医学图像识别数据集,我们可以先训好一个通用的图像识别模型,再对模型做一些调整,换医学图像数据上去再训练出一个医学图像识别模型。

具体来说,以上图中展示的情况为例,我们可以在训练完通用图像识别模型后,删掉最后一个输出层,初始化一个符合医学图像识别任务要求的输出层。之后,我们使用医学图像来训练。在这个过程中,如果新数据较少,我们既可以只训练最后的输出层,而保持其他层参数不变;如果新数据够多,我们可以让所有参数都参与训练。

这里还要介绍两个重要的深度学习名词。如果换新数据后要训练所有参数,则换数据前的训练过程称为预训练(pre-training) ,换数据后的训练过程称为微调(fine-tuning) 。

在上面的例子中,我们只是删掉了一个输出层,加了一个输出层。实际上,删哪些层换哪些层都没有一定的标准。如果任务变得更难了,我们可以删一个输出层,再加几个隐藏层和一个输出层。

迁移学习最常见的场合,是我们想完成训练数据较少的B任务,却在相似的A任务中有大量的训练数据。这时,我们就可以先学A任务,再迁移到B任务上。如果A、B任务的数据量差不多,那迁移学习就没什么意义了,因为同样是一份数据,对于B任务来说,一份B任务的数据肯定比一份A任务的数据要有用得多。

另外,迁移学习之所以能有效,是因为神经网络的浅层总能学到一些和任务无关,而之和数据相关的知识。因此,A任务和B任务要有一样的输入,且A任务的浅层特征能够帮助到任务B。

3.2多任务学习

在刚刚学的迁移学习中,模型会先学任务A,再学任务B。而在另一个面向多个任务的学习方法中,模型可以并行地学习多个任务。这种学习方法叫做多任务学习

还是来先看一个例子。在开发无人驾驶车时,算法要分别识别出一张图片中是否有人行道、汽车、停止路牌、红绿灯……。识别每一种物体是否存在,都是一个二分类问题。使用多任务学习,我们可以让一个模型同时处理多个任务,即把模型的输出堆叠起来:

这里,一定要区分多个二分类问题和多分类问题。多分类中,一个物体只可能属于多个类别中的一种;而多个二分类问题中,图片可以被同时归为多个类别。

使用多任务学习时,除了输出数据格式需要改变,网络结构和损失函数也需要改变。多个二分类任务的网络结构和多分类的类似,都要在最后一层输出多个结果;而误差和多分类的不一样,不使用softmax,而是使用多个sigmoid求和(每个sigmoid对应一个二分类任务)。

此外,多个二分类任务和多分类任务还有一个不同。在执行多分类学习时,由于所有任务都用统一的数据,数据的标注可能有缺失。比如某几张图片可能没有标出红绿灯,另外几张图片又没有标出人行道。在多任务学习中,我们是允许数据中出现“模糊不清”的现象的,可以把没有标注的数据标成"?"。这样,碰到标注是"?"的数据时,我们就不对这一项进行损失函数的计算。

和迁移学习一样,多任务学习在使用上有一些要求。

首先,所有任务都必须受益于相同的浅层特征。这是显而易见的。

其次,每类任务的数据集都要差不多大。在迁移学习中,我们有比较大的数据集A和比较小的数据集B。而在迁移学习中,假如我们有100项任务,每种数据有1000条数据。对于每一项任务来说,其他99项任务的99000条数据就像数据集A一样,自己的1000条数据就像数据集B一样。

最后,经研究,只有当神经网络模型足够大时,使用多任务学习才至少不比分别学习每个任务差。

在实践中,迁移学习比多任务学习常见得多。

 4 端对端深度学习

深度学习的另一大强大之处,就是端到端(end-to-end)学习。这项技术可以让搭建学习算法简单很多。让我们先看看端到端学习具体是指什么。

不使用深度学习的话,一项任务可能会被拆成多个子步骤。比如在NLP(自然语言处理)中,为了让电脑看懂人类的语言,传统方法会先提取语言中的词语,再根据语法组织起词语,最后再做进一步的处理。而在端到端学习中,深度学习可能一步就把任务完成了。比如说机器翻译这项NLP任务,用深度学习的话,输入是某语言的句子,输出就是另一个语言的句子,中间不需要有其他任何步骤。

相较于多步骤的方法,端到端学习的方法需要更多的数据。仅在数据足够的情况下,端到端学习才是有效的。下面,我们来看一个反例。

在人脸识别任务中,输入是一张图片,输出是图片中人脸的身份。这里有一个问题:识别人脸之前,算法需要先定位人脸的位置。如果使用端到端学习的话,学习算法要花很长时间才能学会找到人脸并识别人脸的身份。

相比之下,我们可以把这个人物拆成两个阶段:第一阶段,算法的输入是图片,输出是一个框,框出了人脸所在位置;第二阶段,输入是框里的人脸,输出是人脸的身份。学习算法可以轻松地完成这两个子问题,这种非端到端的方法反而更加通用。

 总结一下,非端到端学习想要优于端到端学习,必须满足两个条件:每个子任务都比较简单;每个子任务的数据很多,而整个任务的数据很少。

那么,具体哪些情况下该用端到端学习,哪些情况下不用呢?我们来看看端到端学习的优缺点:

优点:

  • 让数据说话。相较于手工设计的某些步骤,端到端学习能够从海量数据中发现于更适合计算机理解的统计规律。
  • 减少手工设计的工作量,让设计者少花点精力。

缺点:

  • 可能需要大量的数据。
  • 排除了可能有用的手工设计的东西。比如人脸识别中,显然,找出人脸是一个绕不过去的子步骤。

 归根结底,还是数据量决定了是否使用端到端学习。在复杂的任务中,要达成端到端需要非常非常多的数据,在不能够获取足够数据之前,还是使用多阶段的方法好;而对于简单的任务,可能要求的数据不多,直接用端到端学习就能很好地完成任务了。

总结

这周的知识点如下:

  • 错误分析 
    • 用表格做错误分析
    • 统计错标数据

  • 数据不匹配 
    • 何时使用数据分布不同的训练集和开发/测试集
    • 训练开发集
    • 如何诊断数据不匹配问题
  • 完成多个任务 
    • 迁移学习的定义与常见做法
    • 预训练、微调
    • 多任务学习的定义
    • 多个二分类任务
    • 迁移学习与多任务学习的优劣、使用场景

二. 卷积神经网络

上一阶段回顾:

首先,我们应该设置好任务的目标。选取开发/测试集时,应参考实际应用中使用的数据分布。设置优化指标时,应使用单一目标。可以设置一个最优化目标和多个满足目标。

在搭建模型时,我们可以根据现有的数据量、问题的难易度,选择端到端学习或者是多阶段学习。

训练模型前,如果有和该任务相似的预训练模型,我们可以采取迁移学习,把其他任务的模型权重搬过来;如果我们的模型要完成多个相似的任务,可以同时训练多个任务的模型。

有了目标,搭好了模型之后,就可以开始训练模型了。有了训练好的模型后,我们可以根据模型的训练误差、训练开发误差、开发误差来诊断模型当前存在的问题。当然,在诊断之前,我们可以先估计一下人类在该问题上的最低误差,以此为贝叶斯误差的一个估计。通过比较贝叶斯误差和训练误差,我们能知道模型是否存在偏差问题;通过比较训练误差和训练开发误差,我们能知道模型是否存在方差问题;通过比较训练开发误差和开发误差,我们能知道模型是否存在数据不匹配问题。

另一方面,如果在改进模型时碰到了问题,不妨采取错误分析技术,看看模型究竟错在哪。我们可以拿出开发集的一个子集,统计一下模型的具体错误样例,看看究竟是模型在某些条件下表现得不好,还是标错的数据太多了。

这些内容可能比较偏向于工程经验,没有过多的数学理论。但是,相信大家在搭建自己的深度学习项目时,这些知识一定能派上用场。

卷积神经网络预览:

第一周,我们会学习卷积神经网络的基本构件,建立对卷积神经网络的基本认识,为后续的学习做准备。具体的内容有:

  • 卷积操作 
    • 从卷积核到卷积
    • 卷积的属性——填充、步幅
    • 卷积层
  • 池化操作
  • 卷积神经网络示例

最简单的计算机视觉任务是图像分类。第二周,我们将学习一系列图像分类网络。这些网络不仅能在图像分类上取得优秀的成绩,还是很多其他计算机视觉任务的基石。通过学习它们,我们不仅能见识一些经典网络的架构,更能从中学习到搭建卷积神经网络的一般规律。其内容有:

  • 早期神经网络 
    • LeNet-5
    • AlexNet
    • VGG
  • 残差神经网络
  • Inception 网络
  • MobileNet
  • 搭建卷积网络项目 
    • 使用开源代码
    • 迁移学习
    • 数据增强

第三周,我们将学习计算机视觉中一个比较热门的任务——目标检测。目标检测要求算法不仅能辨别出图片中的物体,还要能把物体精确地框出来。我们会一步一步学习如何搭建完成目标检测的卷积神经网络:

  • 目标定位与关键点检测
  • 使用卷积神经网络的目标检测 
  • YOLO 算法 
    • 结合目标定位与滑动窗口
    • 交并比(IoU)
    • NMS(非极大值抑制)
    • 锚框(Anchor boxes)
  • R-CNN 系列算法简介

此外,这周还会稍微提及另一个计算机视觉任务——语义分割的基本知识:

  • 基于U-Net的语义分割 
    • 反卷积
    • U-Net架构

最后一周,第四周,我们又会认识两个新任务:人脸检测与神经网络风格迁移。具体的内容有:

  • 人脸检测 
    • 人脸检测问题与一次性学习
    • 孪生神经网络
    • 三元组误差
    • 转化成二分类问题
  • 神经网络风格迁移 
    • 风格迁移简介
    • 利用神经网络学到的东西
    • 风格迁移中的误差
    • 推广到1维和3维

1. 卷积神经网络的基础构件

CV(Computer Vision, 计算机视觉)是计算机科学的一个研究领域。该领域研究如何让计算机“理解”图像,从而完成一些只有人类才能完成的高级任务。这些高级任务有:图像分类、目标检测、风格转换等。

现在,大多数前沿CV算法是用深度学习实现的。 

 但是,在CV任务上使用我们之前学的经典神经网络,会碰到一个问题:神经网络输入层的通道数与输入图像尺寸正相关。对于一幅的图像,输入的通道数是;而对于一幅的图像,输入的通道数就高达了。而网络第一层的参数量又与输入的通道数正相关。对于一个通道数高达的输入,假设网络第一个隐藏层有个神经元,那么这一层的将有个参数。有这么多参数,除非有海量的数据,不然网络非常容易过拟合。现有的数据量和计算资源还是跑不动参数这么多的网络的。

因此,在CV中,我们一般不使用之前学的经典神经网络架构,而是使用一种新的网络架构——CNN(Convolutional Neural Network, 卷积神经网络)。让我们从卷积神经网络最简单的构件——卷积学起,一步一步认识卷积神经网络。

1.1边缘检测 

卷积是一种定义在图像上的操作。在深度学习时代之前,它最常用于图像处理。让我们来看看卷积在图像处理中的一个经典应用——边缘检测,通过这个应用来学习卷积。

边缘检测的示意图如上所示。输入一张图片,我们希望计算机能够检测出图像纵向和横向的边缘,把有边缘的地方标成白色,没有边缘的地方标成黑色。

我们可以用卷积实现边缘检测。让我们来看看卷积运算是怎么样对数据进行操作的。

卷积有两个输入:一幅图像和一个卷积核(英文是kernel,也叫做filter滤波器),其中卷积核是一个二维矩阵。我们这里假设图像是一幅单通道的矩阵,卷积核是一个的矩阵。经过卷积后,我们会得到一个的单通道图像(稍后会介绍是怎么算出来的)。

卷积操作会依次算出输出图像中每一个格子的值。对于输出左上角第一个格子,它的计算方法如下:

以此类推,我们可以填完所有格子。大家明白了为什么输出是的图像吗?没错,把的卷积核放到的图像上,只有个位置能放得下。

学会了卷积,该怎么用卷积完成边缘检测呢?我们可以看下面这个例子:

来看左边那幅图像,它左侧是白的,右侧是灰的。很明显,中间有一条纵向的边缘。当我们用图中那个卷积核对图像做卷积操作后,输出的图像中间是白色的(非0值),两侧是黑色的。输出图像用白色标出了原图像的纵向边缘,达到了边缘检测的目的。

刚刚那个卷积核只能检测纵向的边缘。大家应该能猜出,如果我们把卷积核转一下,就能检测横向的边缘了。

实际上,不仅是横向和纵向,我们还可以通过改变卷积核,检测出图像45°,30°的边缘。同时,卷积核里面的数值也不一定是1和-1,还有各种各样的取值方法。如果大家感兴趣,可以参考数字图像处理中有关边缘检测的介绍。 

 1.2卷积和交叉相关

其实,现在我们在课堂上学的和编程框架里用的卷积,在数学上叫做“交叉相关(cross-correlation)”。数学中真正的那个卷积,要先对卷积核做一个旋转180°的操作,再做我们现在的那个卷积的操作。相比交叉相关,数学中的那个卷积能够满足交换律、结合律等一些实用的性质。

但是,在图像处理中,我们是从工程的角度而不是理科的角度使用卷积。要实现多次卷积的操作,只要拿图像多卷几次就好了,不用考虑结合律等复杂的性质。对于计算机来说,旋转卷积核180°是一个费时而多余的操作。因此,我们现在说到的卷积,实际上是一个简化版的卷积,即交叉相关。

1.3填充

卷积后,图像的边长会变小。比如刚刚那个6*6的图像经卷积后,会得到一个4*4的图像。这是因为原图像中只有个位置放得下4*4卷积核。

原图像大小为n\times n,卷积核大小为f\times f,卷积后的图像为(n-f+1)\times (n-f+1)

卷积操作导致的这种“缩水”现象有两个缺点:1)图像的分辨率会越来越小。最坏的情况下,图像变成了1*1的大小,再也无法进行卷积操作了。2)图像中间的数据会被算到多次,而边缘处数据被算的次数较少。

填充(padding) 操作可以解决这些的问题:在做卷积操作之前,我们可以往图像四周填充一些像素,使得卷积操作后的图像大小不变。比如6*6的图像做卷积时,可以先把图像填充成8*8。这样,卷积后的图像还能保持6*6的大小。

padding 两个参数:填充的数据和向四周填充的宽度。对于填充的数据,一般情况下,全部填0即可。而对于填充宽度,其取决于卷积核的大小。为了让图像大小不变,我们应该让填充宽度p满足n+2p-f+1 = n,解得p=\frac{f-1}{2}。为了让p是整数,卷积核边长最好是奇数。 

加入了填充操作后,我们可以把卷积分成两类:有效卷积等长卷积。前者不做填充操作,只对图像的有效区域做卷积。而后者会在卷积前做一次填充,保证整个操作的前后图像大小不变

1.4跨步卷积 

之前,每做完一次卷积后,我们都会让卷积核往右移1格;每做完一行卷积后,我们都会让卷积核往下移1格。但实际上,我们可以让卷积核移动得更快一点。卷积核每次移动的长度称为步幅(stride).

可以看到,步幅改变后,输出图像的边长也改变了。一般地,卷积后图像边长满足下面这个公式,

\left \lfloor \frac{n+2p-f}{s} +1 \right \rfloor

其中\left \lfloor x\right \rfloor表示去掉的小数部分,只保留其x整数部分,即向下取整。

1.5在3D数据体上卷积 

之前我们学的卷积都是定义在一个二维单通道图像上的。在一个三通道的图像上,应该怎么进行卷积呢?

其实,对3D数据体的卷积是类似的。对于一个有3个通道的图像,卷积核也应该有3个通道。这样,图像和卷积核就从面变成了体。和2D时一样,我们把两个数据体对应位置的元素相乘,最后再把乘法的结果加起来,放到输出图像对应的格子中。

 既然输入都可以是多通道图像了,输出图像是不是也可以有多个通道呢?是的,我们只要用多个卷积核来卷图像,就可以得到一个多通道的图像了

总结一下,假如输入图像的形状是n\times n\times n_{c},卷积核形状是 f\times f\times n_{c}注意这个n_{c}必须是同一个数,假如 n_{c}^{'}个卷积核,则输出图像的形状是(n-f+1)\times (n-f+1)\times \left ( n_{c}^{'} \right )

1.6卷积神经网络中的卷积层

现在,我们已经掌握了卷积的基本知识,让我们来看看卷积神经网络中的卷积层长什么样。

卷积在卷积层中的地位,就和乘法操作在传统神经网络隐藏层中的地位一样。因此,在卷积层中,除了基础的卷积操作外,还有添加偏移量、使用激活函数这两步。注意,每有一个输出通道,就有一个b。

现在,我们可以总结一下一个卷积层中涉及的所有中间变量以及它们的形状了。 

        1.7 池化层与全连接层 

池化层执行的池化操作和卷积类似,都是拿一个小矩阵盖在图像上,根据被小矩阵盖住的元素来算一个结果。因此,池化也有池化边长f和池化步幅s这两个参数。而与卷积不同的是,池化是一个没有可学习参数的操作,它的结果完全取决于输入。比如对于最大池化,每一步计算都会算出被覆盖区域的最大值。

比如上图中,我们令池化边长为2,步幅为2。这样,就等于把一个4*4的图像分成了2*2个等大的区域。对于每一个区域,我们算一个最大值。

一般情况下,最常用的池化就是这种边长为2,步幅为2的池化。做完该操作后,图像的边长会缩小至原来的1/2。

除了最大池化,还有计算区域内所有数平均值的平均池化。但现在几乎只用最大池化,不用平均池化了。

没有人知道池化层究竟为什么这么有用。一种可能的解释是:池化层忽略了细节,保留了关键信息,使后续网络能够只关注之前输出的最值/平均值。

全连接层其实就是我们之前学的经典神经网络中的层。前一层的每一个神经元和后一层的每一个神经元直接都有连接。当然,在把图像喂入全连接层之前,一定别忘了做flatten操作,把图像中所有数据平铺成一个一维向量。

 1.8 CNN示例

学完了CNN所有的基础构件,我们或许会感到疑惑:每个卷积层、池化层、全连接层都有那么多超参数,而且层与层之间可以随意地排列组合。该怎么搭建一个CNN呢?来看一个CNN的实例:

这个网络是经典网络LeNet-5的改进版,它被用于一个10-分类任务。我们会在下周正式学习这个网络。现在,让我们通过概览这个网络来找出一些搭建CNN的规律。

网络按照“卷积-池化-卷积-池化-全连接-全连接-softmax”的顺序执行。通常情况下,CNN都是执行若干次卷积,后面跟一次池化。等所有卷积核池化做完,才会做全连接操作。全连接之后就是由softmax激活的输出层。

另外,图像的形状也有一些规律。在卷积核池化的过程中,图像的边长不断变小,而通道数会不断变大。

 1.9为什么用卷积

首先,卷积最大的优势就是需要的参数量少。回想这周开头讲的参数量问题。对于图像数据,如果用全连接网络的话,网络的参数会非常多。而卷积的两个性质,使得需要的参数量大大降低。这两个性质是权重共享与稀疏连接。

权重共享:对于输入图像的所有位置来说,卷积核的参数是共享的。这种设计是十分合理的。比如在边缘检测中,只要我们用同样一个[[1, 0, -1], [1, 0, -1], [1, 0, -1]]的卷积核卷网络,就能检测出垂直方向的边缘。这样,卷积操作的参数量就只由卷积核参数决定,而与图像大小无关。

稀疏连接:卷积核的大小通常很小,也就是卷积操作的一个输出只会由少部分的输入决定。这样,相比一个输出要由所有输入决定的全连接网络,参数量得到进一步的减少。

除了减少参数量外,这两个特性还让网络更加不容易过拟合。回想之前学过的dropout,卷积的这些特性就和扔掉了部分激活输出一样。

另外,卷积操作还适合捕捉平移不变性(translation invariance)。这个词的意思是说,如果一张图里画了一个小猫,如果你把图片往右移动几格,那么图片里还是一个小猫。由于同样的卷积操作会用在所有像素上,这种平移后不变的特性非常容易被CNN捕捉。

总结

在这堂课中,我们认识了CNN的三大基础构件:卷积、池化、全连接。其中,卷积和池化是新学的知识。这堂课的内容非常多,也非常重要,让我们来回顾一下。

  • CNN 的优点 
    • CNN 与全连接网络的参数比较
    • 权重共享、稀疏连接
  • 卷积操作 
    • 基本运算流程
    • 填充
    • 步幅
    • 示例:边缘检测
  • 卷积层 
    • 对多通道图像卷积
    • 输出多通道图像
    • 加上bias,送入激活函数
  • 池化层 
    • 运算流程
    • 最大池化与平均池化
  • CNN 示例 
    • 如何组合不同类别的层:卷积接池化,最后全连接。
    • 图像边长变小,通道数变大。

由于深度学习编程框架通常会帮我们实现好卷积,卷积的实现细节倒没有那么重要。在这周的课里,最重要的是一些宏观的知识。我们要知道卷积有哪些参数、哪些超参数,了解卷积的优点。同时,还要知道卷积和其他构件是如何组成一个CNN的。

2.CNN示例学习:VGG, ResNET, MobileNet

 2.1 经典网络

2.1.1 LeNet-5

LeNet-5是用于手写数字识别(识别0~9的阿拉伯数字)的网络。它的结构如下:

 网络的输入是一张[32,32,1]灰度图像,输入经过四个卷积+池化层,再经过两个全连接层输出一个0~9的数字,这个网络和我们上周见过的网络十分相似,数据体的宽和高在不断变小,而通道数在不断变多。

这篇工作是1998年发表的,当时的神经网络架构和现在我们学的有不少区别:

  • 当时padding还没有得到广泛使用,数据体的分辨率会越降越小。
  • 当时主要使用平均池化,而现在最大池化更常见。
  • 网络只输出一个值,表示识别出来的数字。而现在的多分类任务一般会输出10个值并使用softmax激活函数。
  • 当时激活函数只用sigmoid和tanh,没有人用ReLU。
  • 当时的算力没有现在这么强,原工作在计算每个通道卷积时使用了很多复杂的小技巧。而现在我们直接算就行了。

LeNet-5只有6万个参数。随着算力的增长,后来的网络越来越大了。

2.1.2 AlexNet

发表于2012年的有关图像分类的CNN结构,他的输入是[227, 227, 3]的图像,输出是一个1000类的分类结果。

AlexNet和LeNet-5在架构上十分接近。但是,AlexNet做出以下改进:

  • Alex用了更多的参数,一共有约6000万个参数
  • 使用ReLU作为激活函数

AlexNet还提出了其他一些创新,但与我们要学的知识没有那么多关系:

  • 当时算力还是比较紧张,AlexNet用了双GPU训练。论文里写了很多相关的工程细节。
  • 使用了Local Response Normalization这种归一化层。现在几乎没人用这种归一化。

AlexNet中的一些技术在今天看来,已经是常识般的存在。而在那个年代,尽管深度学习在语音识别等任务上已经初露锋芒,人们还没有开始重视深度学习这项技术。正是由于AlexNet这一篇工作的出现,计算机视觉的研究者开始关注起了深度学习。甚至在后来,这篇工作的影响力已经远超出了计算机视觉社区。

 2.1.3 VGG-16

VGG-16是一个图像分类网络。VGG出发点是:为了简化网络结构,只用3*3等长(same)卷积和2*2最大池化。 

可以看出,VGG也是经过一系列的卷积和池化层,最后使用全连接层和softmax输出结果。

顺带一提,VGG-16的16表示有16个带参数的层。

VGG非常庞大,有1.38亿个参数,但是它简洁的结构吸引了很多人关注。

2.2 ResNets(基于残差的网络)

非常非常深的神经网络是很难训练的,这主要是由梯度爆炸/弥散问题导致的。在这一节中,我们要学一种叫做“跳连(skip connection)”的网络模块连接方式。使用跳连,我们能让浅层模块的输出直接对接到深层模块的输入上,进而搭建基于残差的网络,解决梯度爆炸和弥散的问题,训练深度达100层的网络。

2.2.1残差块

在全连接网络中,假如我们有中间层的输出:

 也就是,a^{[l]} 要经过一个线性层、一个激活函数、一个线性层、一个激活函数,才能到a^{[l+2]},这条路非常长。

在残差块(Residual block)中,我们使用了一个新的连接方法:

a^{[l]}的值被直接加到了第二个ReLU层之前的线性输出上,这是一种类似电路中短路的连接方法(又称跳连)。这样,浅层的信息能更好的传递到深层了。

使用这种方法之后,计算公式变为:

z^{[l+1]} = W^{[l+1]}a^{[l]}+b^{[l+1]}

a^{[l+1]}=g(z^{[l+1]})

z^{[l+2]}=W^{[l+2]}a^{[l+1]}+b^{[l+2]} 

a^{[l+2]} = g(z^{[l+2]}+a^{[l]})

残差块中还有一个要注意的细节。a^{[l+2]}=g(z^{[l+2]}+a^{[l]})这个式子能成立,实际上默认了a^{[l+2]}a^{[l]}维度相同。而一旦a^{[l+2]}的维度发生了变化,就需要用下面这种方式来调整了。

a^{[l+2]} = g(z^{[l+2]}+W^{'}a^{[l]})

我们可以用一个W^{'}来完成维度的转换。为了方便理解,我们先让所有a 都是一维向量,W^{'}是矩阵。这样,假设a^{[l+2]}的长度是256,a^{[l]}的长度是128,则W^{'}形状是256*128。

但实际上,a是一个三维的图像张量,三个维度的长度都可能发生变化。因此,对于图像,上式中的W^{'}应该表示的是一个卷积操作。通过卷积操作,我们能够减小图像的宽高,调整图像的通道数,使得a^{[l]}a^{[l+2]}的维度完全相同。

2.2.2 残差网络

在构建残差网络ResNet时,只要把这种残差块一个一个拼接起来即可。或者从另一个角度来看,对于一个“平坦网络”("plain network", ResNet论文中用的词,用于表示非残差网络),我们只要把线性层两两打包,添加跳连即可。

 残差块起到了什么作用呢?让我们看看在网络层变多时,平坦网络和残差网络训练误差的变化趋势如上图。

理论上来说,层数越深,训练误差应该越低。但在实际中,对平坦网络增加深度,反而会让误差变高。而使用ResNet后,随着深度增加,训练误差起码不会降低了。

正是有这样的特性,我们可以用ResNet架构去训练非常深的网络。

为什么ResNet是有这样的特性呢?我们还是从刚刚那个ResNet的公式里找答案。

假设我们设计好了一个网络,又给它新加了一个残差块,即多加了两个卷积层,那么最后的输出可以写成:

a^{[l+2]} = g(z^{[l+2]}+a^{[l]})

a^{[l+2]} =g(W^{[l+2]}a^{[l+1]}+b^{[l+2]}+a^{[l]})

由于正则化的存在,所有W和b都倾向于变得更小。极端情况下,W,b都变成0了

a^{[l+2]} =g(a^{[l]})

再不妨设g=ReLU 则因为a^{[l]}也是Relu的输出,有

a^{[l+2]} =g(a^{[l]})

a^{[l+2]} =a^{[l]}

这其实是一个恒等映射,也就是说,新加的残差块对之前的输出没有任何影响。网络非常容易学习到恒等映射。这样,最起码能够保证较深的网络不比浅的网络差。

 所以完整的ResNet长什么样?

ResNet有几个参数量不同的版本。这里展示的叫做ResNet-34。完整的网络很长,我们只用关注其中一小部分就行了。

一开始,网络还是用一个大卷积核大步幅的卷积以及一个池化操作快速降低图像的宽度,再把数据传入残差块中。

一开始,网络还是用一个大卷积核大步幅的卷积以及一个池化操作快速降低图像的宽度,再把数据传入残差块中。和我们刚刚学的一样,残差块有两种,一种是维度相同可以直接相加的(实线),一种是要调整维度的(虚线)。整个网络就是由这若干个这样的残差块组构成。经过所有残差块后,还是和经典的网络一样,用全连接层输出结果。

这里,我们只学习了残差连接的基本原理。ResNet的论文里还有更多有关网络结构、实验的细节。最好能读一读论文

2.3 Inception网络 

我们已经见过不少CNN的示例了。当我们仿照它们设计自己的网络时,或许会感到迷茫:有3x3, 5x5卷积,有池化,该怎么选择每一个模块呢?Inception网络给了一个解决此问题的答案:我全都要。

Inception网络用到了一种特殊的1x1卷积。我们会先学习1x1卷积,再学习Inception网络的知识。

2.3.1 1x1卷积

用1x1的卷积核去卷一幅图像,似乎是一件很滑稽的事情。假设一幅图像的数字是[1, 2, 3],卷积核是[2],那么卷出来的图像就是[2, 4, 6]。这不就是把每个数都做了一次乘法吗?

对于通道数为1的图像,1X1卷积确实没什么大用。二档通道数多起来后,1X1卷积的意义就逐渐显现出来了,对多通道图像做1X1卷积,就是把某像素所有通道的数字各乘一个数,求和,加一个bias,再通过激活函数。这是计算一个输出结果的过程,而如果有多个卷积核,就可以计算出多个结果。(下图中,蓝色的数据体是输入图像,黄色的数据体是1x1的卷积核。两个数据体重合部分的数据会先做乘法,再求和,加bias,经过激活函数。)

 这个过程让你想起了什么?没错,正是最早学习的全连接网络。1x1卷积,实际上就是在各通道上做了一次全连接的计算。1x1卷积的输入通道数,就是全连接层上一层神经元的数量;1x1卷积核的数量,就是这一层神经元的数量。

1x1卷积主要用于变换图像的通道数。比如要把一个192通道数的图像变成32通道的,就应该用32个1x1卷积去卷原图像。

2.3.2 Inception块的原理

在Inception网络中,我们会使用这样一种混合模块:对原数据做1x1, 3x3, 5x5卷积以及最大池化,得到通道数不同的数据体。这些数据体会被拼接起来,作为整个模块的输出。

在实现这样一种模块时,会碰到计算量过大的问题。比如把上面的28x28x192数据体用5x5卷积卷成的28x28x32数据体,需要多少次乘法计算呢?对每个像素单独考虑,一个通道上的卷积要做5x5次乘法,192个通道的卷积要做192x5x5次乘法。32个这样的卷积在28x28的图片上要做次120M乘法。这个计算量太大了。

为此,我们可以巧妙的先利用1x1卷积减少通道数,再做5x5卷积。这样计算量就少多了。

这样一种两头大,中间小的结构被形象地称为瓶颈(bottlenect)。这种结构被广泛用在许多典型网络中。

2.3.3 Inception网络

有了之前的知识,我们可以看Inception模块的完整结构了。1x1卷积没有什么特别的。为了减少3x3卷积和5x5卷积的计算量,做这两种卷积之前都会用1x1卷积减少通道数。而为了改变池化结果的通道数,池化后接了一个1x1卷积操作。

实际上,理解了Inception块,也就能看懂Inception网络了。如下图所示,红框内的模块都是Inception块。而这个网络还有一些小细节:除了和普通网络一样在网络的最后使用softmax输出结果外,这个网络还根据中间结果也输出了几个结果。当然,这些都是早期网络的设计技巧了。 

2.4 MobileNet

MobileNet,顾名思义,这是一种适用于移动(mobile)设备的神经网络。移动设备的计算资源通常十分紧缺,因此,MobileNet对网络的计算量进行了极致的压缩。

2.4.1减小卷积运算量

一次卷积操作计算量这么大,主要问题出在每一个输出通道都要与每一个输入通道“全连接”上。为此,我们可以考虑让输出通道只由部分的输入通道决定。这样一种卷积的策略叫逐深度可分卷积(Depthwise Separable Convolution)。 

这里的depthwise是“逐深度”的意思,但我觉得“逐通道”这个称呼会更容易理解一点。

逐深度可分卷积分为两步:逐深度卷积(depthwise convolution),逐点卷积(pointwise convolution)。逐深度卷积生成新的通道,逐点卷积把各通道的信息关联起来。

之前,要对下图中的三通道图片做卷积,需要3个卷积核分别处理3个通道。而在逐深度卷积中,我们只要1个卷积核。这个卷积核会把输入图像当成三个单通道图像来看待,分别对原图像的各个通道进行卷积,并生成3个单通道图像,最后把3个单通道图像拼回一个三通道图像。也就是说,逐深度卷积只能生成一幅通道数相同的新图像。

下一步,是逐点卷积,也就是1x1卷积。用来改变图片的通道数。

之前的卷积有2160次乘法,现在只有432+240=672次,计算量确实减少了不少。实际上,优化后计算量占原计算量的比例是:

 2.4.2 网络结构
Mobile Net v1

13个逐深度可分卷积模块,之后接通常的池化、全连接、softmax。

Mobile Net v2 

两个改进:

  1. 残差连接
  2. 扩张(expansion)操作

残差连接和ResNet一样。这里我们关注一下第二个改进。

在MobileNet v2中,先做一个扩张维度的1x1卷积,再做逐深度卷积,最后做之前的逐点1x1卷积。由于最后的逐点卷积起到的是减小维度的作用,所以最后一步操作也叫做投影。

 这种架构很好地解决了性能和效果之间的矛盾:在模块之间,数据的通道数只有3,占用内存少;在模块之内,更高通道的数据能拟合更复杂的函数。

2.5 EfficientNet

EfficientNet能根据设备的计算能力,自动调整网络占用的资源。

让我们想想,哪些因素决定了一个网络占用的运算资源?我们很快能想到下面这些因素:

  • 图像分辨率
  • 网络深度
  • 特征的长度(即卷积核数量或神经元数量)

在EfficientNet中,我们可以在这三个维度上缩放网络,动态改变网络的计算量。EfficientNet的开源实现中,一般会提供各设备下的最优参数。

2.6 卷积网络实现细节

2.6.1 使用开源实现

由于深度学习项目涉及很多训练上的细节,想复现一个前人的工作是很耗时的。最好的学习方法是找到别人的开源代码,在现有代码的基础上学习。

深度学习的开源代码一般在GitHub上都能找到。如果是想看PyTorch实现,可以直接去GitHub上搜索OpenMMLab。

2.6.2 使用迁移学习

如第三门课第二周所学,我们可以用迁移学习,导入别人训练好的模型里的权重为初始权重,加速我们自己模型的训练。

还是以多分类任务的迁移学习为例(比如把一个1000分类的分类器迁移到一个猫、狗、其他的三分类模型上)。迁移后,新的网络至少要删除输出层,并按照新的多分类个数,重新初始化一个输出层。之后,根据新任务的数据集大小,冻结网络的部分参数,从导入的权重开始重新训练网络的其他部分:

2.6.3 数据增强

由于CV任务总是缺少数据,数据增强是一种常见的提升网络性能的手段。

常见的改变形状的数据增强手段有:

  • 镜像
  • 裁剪
  • 旋转
  • 扭曲

此外,还可以改变图像的颜色。比如对三个颜色通道都随机加一个偏移量。

数据增强有一些实现上的细节:数据的读取及增强是放在CPU上运行的,训练是放在CPU或GPU上运行的。这两步其实是独立的,可以并行完成。最常见的做法是,在CPU上用多进程(发挥多核的优势)读取数据并进行数据增强,之后把数据搬到GPU上训练。

2.6.4 计算机视觉的现状与相关建议

一般来说,算法从两个来源获取知识:标注的数据,人工设计的特征。这二者是互补的关系。对于人工智能任务来说,如果有足够的数据,设计一个很简单的网络就行了;而如果数据量不足,则需要去精心设计网络结构。

像语音识别这种任务就数据充足,用简单的网络就行了。而大部分计算机视觉任务都处于数据不足的状态。哪怕计算机视觉中比较基础的图像分类任务,都需要设计结构复杂的网络,更不用说目标检测这些更难的任务了。

如果你想用深度学习模型参加刷精度的比赛,可以使用以下几个小技巧:

  • 同时开始训练多个网络,算结果时取它们的平均值。
  • 对图像分类任务,可以把图像随机裁剪一部分并输入网络,多次执行这一步骤并取平均分类结果。

也就是说,只是为了提高精度的话,可以想办法对同一份输入执行多次条件不同的推理,并对结果求平均。当然,实际应用中是不可能用性能这么低的方法。

总结

这节课是CNN中最重要的一节课。通过学习一些经典的CNN架构,我们掌握了很多有关搭建CNN的知识。总结一下:

  • 早期CNN 
    • 卷积、池化、全连接
    • 边长减小,通道数增加

  • ResNet 
    • 为什么使用ResNet?
    • 梯度问题是怎么被解决的?
    • 残差块的一般结构
    • 输入输出通道数不同的残差块
    • 了解ResNet的结构(ResNet-18, ResNet-50)
  • Incpetion 网络 
    • 1x1卷积
    • 用1x1卷积减少计算量
    • Inception网络的基本模块

  • MobileNet 
    • 逐深度可分卷积
    • MobileNet v2中的瓶颈结构

3. 目标检测与语义分割简介 (YOLO, U-Net)

这节课中,我们要学习计算机视觉中最重要的任务之一——目标检测任务。我们会先认识目标定位和关键点检测这两个比较简单的任务,慢慢过度到目标检测任务。之后,我们会详细学习目标检测的经典算法YOLO。最后,我们会稍微认识一下语义分割任务及适用于此问题的U-Net架构。

3.1 目标定位

在图像分类问题中,给定一幅图片,我们只要是说出图片里的物体是什么就行了。这堂课要讨论的任务中,我们还要做一件事--定位。我们要先用边框圈出图中的物体,再说出框里的物体是什么。叫做带定位的分类问题。更进一步,如果我们不再是只讨论一个物体,而是要把图片中所有物体都框出来,并标出每一个物体的类别,这就是目标检测问题。

我们对分类任务的神经网络结构已经很熟悉了。那么,带定位的分类该使用怎样的网络呢?实际上,一个边框可以用边框中心和边框宽高这四个量表示。除了softmax出来的分类结果外,我们只要让网络再多输出四个数就行了。如下图所示:

这里,我们要统一下对边框的定义。用b_{x},b_{y}表示边框的中心坐标,b_{h},b_{w}表示边框的高、宽。

来看下标签y的具体写法。假设一共有四类物体:行人,汽车、摩托车、背景(无实物)。

那么标签y应该用y=\left [ p_{c} ,b_{x},b_{y},b_{h},b_{w},c_{1},c_{2},c_{3}\right ]^{T}表示。其中,p_{c}表示有没有物体,如

p_{c}=1,则  c_{1},c_{2},c_{3}表示物体属于除了背景的哪一类;p_{c}=0,则其他值无意义。

这样,在算误差时,也需要分类讨论,p_{c}=1,则算估计值与标签8个分量两两之间的均方误差;若p_{c}=0,只算p_{c}的均方误差,不用管其他量。

要更换一下神经网络的输出格式,我们就能得到一个完成目标定位问题的网络。

3.2 关键点检测

我们刚刚学了用2个点表示一个边框。其实,拓展一下边框检测,就是一个关键点(英文有时叫做"landmark",是“地标”的意思)检测问题。

比如,在人脸关键点检测中,我们可以定义一堆关键点,分别表示眼睛、鼻子、嘴巴……的位置。我们还是让网络先输出一个数,表示图中有没有人脸;再输出2n个数,表示n个人脸关键点。这样,网络就能学习怎么标出人脸关键点了。

很多应用都基于人脸关键点检测技术。比如我们检测到了眼睛周围的关键点后,就可以给人“戴上”墨镜。

总之,通过这一节的学习,我们要知道,目标定位中输出2个坐标只是关键点检测的一个特例。只要训练数据按照某种规律标出了关键点,不管这些关键点是表示一个框,还是人脸上各器官的位置,网络都能学习这种规律。

3.3 目标检测

 有了之前的知识储备,现在我们来正式学习目标检测。目标检测可以用一种叫做“滑动窗口”的技术实现。

假设我们要构建一个汽车的目标检测系统。我们可以先构造一个汽车分类数据集——数据集的x是一些等大的图片,y表示图片里是不是有汽车。如果图片里有汽车,汽车应该占据图片的大部分位置。

通过学习,网络就能够判断一个框里的物体是不是汽车了。这样,我们可以用一个边框框出图片的一部分,裁剪下来,让网络看看图片的这一部分是不是汽车。只要我们尝试的次数够多,总能找出图中的汽车。 

在遍历边框时,我们是通过“滑动”的方法:遍历边框的大小,选择好大小后把框放到左上角,先往右移,再往下移。所以这种方法叫做“滑动窗口”。

滑动窗口算法有一个缺点:如果我们移动窗口的步伐过小,则运行分类器的次数会很多;如果移动窗口的步伐过大,则算法的精度会受到影响。在深度学习时代之前,分类器都是一些简单的线性函数,能够快速算完,多遍历一些滑动窗口没有问题。而使用了深度CNN后,遍历滑动窗口的代价就很大了。

幸好,滑动窗口也可以通过卷积来生成,而不一定要遍历出来。让我们看下去。

3.4 基于卷积的滑动窗口

滑动窗口其实可以通过执行巧妙的卷积来生成。在那之前,我们先学一门前置技能:怎么把全连接层变成卷积层。

前两周学习CNN时,我们学过,卷积结束后,卷积的输出会被喂入全连接层中。实际上,我们可以用卷积来等价实现全连接层。比如下图中,一个5*5*16的体积块想变成一个长度为400的向量,可以通过执行400个的5*5卷积来实现。

知道了这一点后,我们就可以利用卷积来快速实现滑动窗口了。

假设我们按照上一节的算法,先实现了对的小图片进行分类的分类器。之后,我们输入了一张的大图片。我们遍历滑动窗口,令步幅为2。这样,理论上,有4个合法的滑动窗口,应该执行4次分类器的运算,如下图所示:

可是,仔细一想,在执行4次分类器的过程中,有很多重复的运算。比如,对于4个滑动窗口中间那共有的个像素,它们的卷积结果被算了4次。理想情况下,只需要对它们做一次卷积就行了。这该怎么优化呢

其实,很简单,我们可以利用卷积本身的特性来优化。卷积层只定义了卷积核,而没有规定输入图像的大小。我们可以拿出之前在14*14的图像上训练好的卷积层,把它们用在16*16的图片的卷积上。经过相同的网络,16*16的图片会生成一个2*2大小的分类结果,而不是1*1的。这个2*2分类结果,恰好就是那4个滑动窗口的分类结果。通过这样巧妙地利用卷积操作,我们规避了遍历滑动窗口带来的重复计算。

 不过,这个方法还是有一些缺陷的。在刚才那个例子中,16*16的图片其实可以放下9个14*14大小的边框。但是,由于分类网络中max pool的存在,我们只能生成4个分类结果,也就是步幅为2的滑动窗口的分类结果。同时,最准确的检测框也不一定是正方形的,而可能是长方形的。为了让生成的滑动窗口更准确一些,我们要用到其他方法。

3.5 预测边框

在这一节,我们要使用YOLO(You Only Look Once)算法解决上一节中碰到的问题。还记得这周课开头学的目标定位问题吗?我们可以把滑动窗口和目标定位结合一下。

给定一幅图像,我们可以把图像分成3*3个格子。训练模型前,我们要对训练数据做预处理。根据每个训练样本中物体的中心点所在的格子,我们把物体分配到每一个格子中。也就是说,不管一个物体的边框跨越了几个格子,它的中心点在哪,它就属于哪个格子。比如对于下图的训练样本,右边那辆车就属于橙色的格子。之后,我们给每个格子标上标签y。这个标签y就是目标定位中那个表示图片中是否有物体、物体的边框、物体的类别的标签向量。对于这个3*3的格子,有9个标签向量,整个标签张量的形状是3*3*8。

这样,每一幅图像的输出和标签一样,也是一个3*3*8的张量了。输入一幅图片后,我们利用上一节学的卷积滑动窗口,同时预测出每个格子里的物体边框。

另外,这里要详细讨论一下b_{x},b_{y},b_{h},b_{w}的表示方法。由于我们只关心框相对于格子的位置,因此我们可以把规定一个格子的边长为1。这样,z0\leq b_{x},b_{y}\leq 1就满足了。不过,由于物体的边框可以超出小框,b_{x},b_{y}>1是很有可能的。

 看到这,大家可能会有一些疑问:如果一个格子里有多个物体呢?的确,这个算法无法输出一个格子里的多个物体。一种解决方法是,我们可以把格子分得更细一点,比如19*19个格子。这样,可以被检测到物体会多一些。但是,增加格子数又会引入一个新的问题——多个格子检测到了同一个物体。下面的两节里我们会尝试解决这个新的问题。

3.5 IoU(交并比)

在目标检测中,有一个微妙的问题:框出一个物体的边框有无数个,想精确框出标签的边框是不可能的。怎么判定一个输出结果和标签里的边框“差不多”呢?这就要用到IoU(Intersection over Union,交并比) 这个概念。

IoU,顾名思义,二者的交集比上二者的并集,很好理解。比如下图中,网络的输出是紫框,真值是红框。二者的并集是绿色区域,交集是橙色区域。则IoU就是橙色比绿色。

依照惯例,如果IoU,我们就认为网络的输出是正确的。当然,想更严格一点,0.6,0.7也是可以的。

3.6 NMS(非极大值抑制) 

假设在YOLO中,我们用19*19个小格来检测物体。可是,由于小格子太多了,算法得到了多个重复的检测框(以及每个框中有物体的概率)。这该怎么办呢?

在学NMS之前,我们先动动脑,看看在去掉重复的框时,我们期望得到怎样的去重输出结果。

首先,既然是去重,那么就不能出现两个框过度重合的情况。其次,我们希望留下来的框的预测概率尽可能大。

在这两个要求下,我们来看看上面那幅图的输出应该是怎样的。 我们一眼就能看出,对于左边那辆车,我们应该保留0.8的框;对于右边那辆车,我们应该保留0.9的框。

为什么我们能“一眼看出”呢?这是因为左边两个框、右边三个框恰好都分别表示了一辆车。我们能够快速地把这些框分成两类。但是,在情况比较复杂时,我们就难以快速找出最好的框了。比如下面这种情况中,两辆车很近,有些框甚至同时标出了两辆车:

为了处理这种复杂的情况,我们必须想出一种万全的算法,以筛选出那些概率比较大的框。

稍微思考一下,其实这样的算法非常简单:找出最大的框,去掉所有和它过度重合的框;在剩下的框中,找出最大的框,去掉所有和它过度重合的框;……。一直重复直到没有未处理的框为止。这就是NMS算法。

还是让我们来看看刚刚那个例子。使用NMS时,我们会先找到0.9这个框,“抑制”掉右边0.6和0.7的框。在剩下的框中,最大的是0.8这个框,它会“抑制”掉左边那个0.7的框。

接下来,让我们来严格描述一下这个算法。假设我们有个输出结果,每个输出结果是一个长度为5的向量,分别表示有物体的概率、边框的中心和高宽(我们先不管检测多个物体的情况。事实上,当推广到多个物体时,只要往这个输出结果里多加几个概率就行了)。我们要用NMS输出应该保留的检测结果。“过度重合”,就是两个框的IoU大于0.5。

首先,先做一步初筛,扔掉概率小于0.6的结果。

之后,对于没有遍历的框,重复执行:找出概率最大的框,把它加入输出结果;去掉所有和它IoU大于0.5的框。

这个过程用伪代码表示如下:

# Input and preprocessing
input predicts of size [19, 19, 5]
resize predicts to [361, 5]

# Filter predicts with low probability
filtered_predicts = []
for predict in predicts:
    # drop p_c < 0.6
    if predict[0] >= 0.6:
        filtered_predicts.append(predict)

# NMS
n_remainder  = len(filtered_predicts)
vis = [False] * n_remainder # False for unvisited item
output_predicts = []
while n_remainder > 0:
    max_pro = -1
    max_index = 0
    # Find argmax
    for i, p in enumerate(filtered_predicts):
        if not vis[i]:
            if max_pro < p[0]:
                max_index = i
                max_pro = p[0]

    # Append output
    max_p = filtered_predicts[max_index]
    output_predicts.append(max_p)

    # Suppress
    for i, p in enumerate(filtered_predicts):
        if not vis[i] and i != max_index:
            if get_IoU(p[1:5], max_p[1:5]) > 0.5:
                vis[i] = True
                n_remainder -= 1
    vis[max_index] = True
    n_remainder -= 1

3.7 锚框 

 为了让一个格子能够检测到多个物体,YOLO论文还提出了一种叫做锚框(Anchor Boxes)的技术。

假设一个格子里同时包含了两个物体:一个“竖着”的人和一个“横着”的车。那么,我们可以以这个格子的中心点为“锚”,画一个竖的框和横的框,让每个格子可以检测到两个物体。这样,人和车都能被检测了。

严谨地描述,锚框技术是这样做改进的:

  • 之前,每一个格子只能包含一个样本。训练数据中每一个标签框会被分配到它中点所在格子
  • 现在,每一个格子能包含多个样本。每个格子都会预定义几个不同形状的锚框,有几个锚框,就最多能检测到几个物体。训练数据的每一个标签框会被分配到和它交并比最大的锚框

注意,之前的最小单元是格子,现在是锚框,所以说现在每个样本被分配到锚框上而不是格子上。可以看下面这两个样本的例子,第一个例子是两个物体都检测到了,第二个是只有锚框2里有物体。和之前一样,如果有某个锚框里没有物体,则除了外全填问号即可。

锚框技术实际上只是对训练数据做了一个约束,改变了训练数据的格式。检测算法本身没有什么改变。

3.8 YOLO 算法总结

让我们把前几节的内容总结一下,看一下YOLO算法的全貌。

在训练前,我们要对数据做预处理。首先,我们要指定以下超参数:图片切分成多大的格子、每个格子里有多少个锚框。之后,根据这些信息,我们可以得到每一个训练标签张量的形状。比如3*3*2*8的一个训练标签,表示图片被切成了3*3的格子,每个格子有两个锚框。这是一个三分类问题,对于每一个检测出来的物体,都可以用一个长度为8的向量表示。其中,p_{c}表示这个锚框里有没有物体, (b_{x},b_{y}),(b_{h},b_{w})分别表示中心点坐标、框的高宽,c_{1},c_{2},c_{3}分别表示是否该类物体。

有了预处理好的训练数据,就可以训练一个CNN了。

在网络给出了输出后,由于输出的框往往多于标签中的框,还要对输出结果进行筛选。筛选的过程如前文所述,先去掉概率过小的框,再分别对每一类物体的框做NMS。

 3.9 区域提案

YOLO算法是在一堆固定的框里找物体。实际上,我们还可以用神经网络来找出候选框,再在这些框里详细检测。这种技术就叫做区域提案(region proposal),相关的网络叫做R-CNN(Region with CNN)。

R-CNN 系列网络有多个改进版本:

  • R-CNN: 使用区域提案,但是每次只对一个区域里的物体做分类。
  • Fast R-CNN: 使用区域提案,并使用基于卷积的滑动窗口加速各区域里物体的分类。
  • Faster R-CNN: 前两个算法都是用传统方法提案区域,Faster R-CNN用CNN来提案区域,进一步令算法加速。

吴恩达老师认为,虽然区域提案的方法很酷,但把目标检测分两步来完成还是太麻烦了,一步到位的YOLO系列算法已经挺方便了。

3.10 基于U-Net的语义分割

 语义分割也是应用非常广泛的一项CV任务。相较于只把物体框出来的目标检测,语义分割会把每一类物体的每个像素都精确地标出来。如下图的示例所示,输入一张图片,语义分割会把每一类物体准确地用同一种颜色表示。

具体来说,语义分割的输出是一个单通道图片。图片的数字表示此处像素的类别。

 在分类模型中,图像会越卷越小,最后压平放进全连接层并输出多个类别的分类概率。而在语义分割模型中,由于模型的输出也是一幅图像,在输入图像被卷小了以后,应该还有一个放大的过程。

目前,我们还没有学过带学习参数的可以放大图像分辨率的结构。下一节介绍的反卷积能够完成这件事。

3.11 反卷积 

反卷积和卷积的输入输出大小彻底相反。让我们看看反卷积的形状是怎么计算的。

如上图所示,反卷积也有卷积核大小、步幅、填充这些参数。不过这些参数都是在输出图像上做的。也就是说,我们会在输出图像上做填充,并且每次在输出图像上一步一步移动。我们把正卷积的输出大小计算公式套到反卷积上的输出上,就能算出反卷积的输入的大小。

在卷积时,我们是把卷积核与图像对应位置的数字乘起来,再求和,算出一个输出值;反卷积则是反了过来,把一个输入值乘到卷积核的每个位置上,再把乘法结果放再输出的对应位置上。一趟反卷积计算如下图所示:

这里我们只需要知道反卷积可以做上采样就行了,不需要纠结底层实现细节。

3.12 U-Net 架构 

学完了反卷积,可以来看U-Net的结构了。

U-Net除了对图像使用了先缩小再放大的卷积外,还使用了一种跳连(不是ResNet中残差连接的跳连,而是把两份输入拼接在了一起)。这样,在反卷积层中,不仅有来自上一层的输入,还有来自前面相同大小的正卷积的结果。这样做的好处是,后半部分的网络既能获得前一个卷积的抽象、高级(比如类别)的输入,又能获得前半部分网络中具体,低级的特征(比如形状)。这样,后面的层能够更好地生成输出。

这幅图中,做运算的图像张量被表示成了一个二维矩形,矩形的高度是图像的宽高,矩形的宽度是通道数。U-Net的前半部分和常见的CNN一样,缩小图像大小,增大图像通道数。而在后半部分中,每次上采样时,一半的通道来自上一层的输出,另一半的通道来自于网络前半部分。

从图中能看出,U-Net的结构图是一个“U”型,因此它才被叫做U-Net。

总结

在这篇文章中,我们主要学习了以下内容:

  • 任务定义与输出格式 
    • 目标定位
    • 关键点检测
    • 目标检测
    • 语义分割

  • YOLO 
    • 用卷积实现全连接
    • 用卷积实现滑动窗口
    • 锚框
    • IoU
    • NMS
    • YOLO算法
  • U-Net 
    • 反卷积
    • U-Net架构

 4. 人脸识别与风格迁移

在这堂课里,我们要学习两个具体的应用:人脸识别、风格迁移。

相信大家已经在很多地方见识过人脸识别应用了:在火车站,要通过身份证和人脸核实身份;办理业务时,可以用手机完成人脸识别以核实身份;进办公楼时,员工只要刷脸通过就可以打开门禁。通过这节课的学习,我们能够学会如何用CNN完成人脸识别。

神经网络风格迁移是一项有趣的应用。它利用了CNN捕捉图像抽象信息的特点,能够把一幅图像的风格转移到另一幅图像上,从而生成一幅新的图像。这项技术不需要从头训练网络,学完这门课后,我们能快速地用代码实现神经网络风格迁移。

4.1 人脸识别

准确来说,在人脸识别(Face Recognition)任务中,会给定一个有K个人的数据库。之后,每一次识别都会输入一张图片,输出这张图片是K个人中的哪一个,或者没有检测到相关人士。

有一个与这个相关的任务叫做人脸验证(Face Verification)。这个任务稍微简单一些,输入是一张图片和一个标记身份的数据(比如身份证号),要求输出图片中的人是否符合该身份。

4.1.1 单样本学习

按我们之前学的方法,假如我们在K个人的数据库上做识别或者分类任务,应该套用一个CNN模型,并在模型最后接一个K+1类的softmax,表示输入图片是K个人中的哪一个,或者都不是。

但是,这样的架构不适合人脸识别任务。以公司的门禁识别为例,这种方法有如下的缺点:

  1. 每来一个新同事,模型就要重新训练一次。
  2. 每个人都得上传大量的个人照片供网络训练。

理想情况下,我们希望模型能从一张人脸照片中学会分辨这张人脸,这样每个新同事只需要上传一张照片即可。这叫做单样本学习(One-shot Learning)。

为了完成单样本学习,我们可以从另一个角度来建模人脸识别问题:如果输入的人脸和数据库里某张人脸极为相似,我们就说识别出了这张人脸;否则,就说没有识别到有效的人脸。

这样,人脸识别问题就被转换为了一个求两张图片相似度的问题。我们可以让网络学习一个输入是两张图片,输出是二者相似度的一个映射。

4.1.2 孪生网络

在完成和相似度有关的问题时,一种常见的做法是使用孪生网络(Siamese Network)。

假设网络的倒数第二层有128个神经元。在普通分类网络中,这128个神经元输出的长度为128的向量会被输入进最后的softmax层。而在孪生网络,我们要去掉softmax层,并用这个没有softmax的网络f 分别输出两张图片x^{(1)},x^{(2)} 对应的128维向量f(x^{(1)}),f(x^{(2)})。这样,每张图片有了唯一对应的一个128维向量,这个向量可以看成该图片的编码(encoding)。而我们又知道,对向量相似度是很方便的。我们可以利用两张图片的编码求出相似度。

 说起向量的相似度,最容易想到的是向量间距离\left \| v-u \right \|^{2} 因此,我们可以设法让网络学会这样的一种关系:

  • x^{(i)},x^{(j)}是同一个人,则\left \| f(x^{(i)})- f(x^{(j)})\right \|很小。
  • x^{(i)},x^{(j)}不是同一个人,则\left \| f(x^{(i)})- f(x^{(j)})\right \|很大。

为了达成这个目标,我们来看看应该用怎样的误差来指导网络的优化方向。

4.1.3 三元组误差 

在让网络学习基于距离的相似度时,一种常用的误差时三元组误差(Triplet Loss)

在一轮训练中,我们要用到三张图片:一张基准图(anchor)A和与其相对的正负样本P,N各一张,设d\left ( A,B \right )为图片A,B的编码的距离,则我们希望d\left ( A,P \right )\leq d(A,N)

所以:

                         ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        d(A,P)-d(A,N)\leq 0 

但这个条件太容易满足了,令f(x)=0恒成立的话无论怎样的输入都可以使上式左侧为0了。因此,我们要加一点小小的约束:

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        d(A,P)-d(A,N)+\alpha \leq 0

这个\alpha是一个较小的数,比如可以令\alpha=0.2。为了达成这个目标,我们可以设置以下的误差函数

        ​​​​​​​        ​​​​​​​        ​​​​​​​        L(A,P,N) = max((d(A,P)-d(A,N)+\alpha),0)

这里取max的直观解释是:只要满足开始那个不等式,让正样本和负样本能够分清楚就行了。至于两类样本要不要分辨得那么分明,我们并不关心。

为了利用这个误差训练网络,我们要在训练集里为同一个人准备多张照片。比如1000个人,每人100张照片,共100000张照片构成训练集。

在提出此设计的FaceNet中,还有一些小细节:为了加大训练难度,让模型效果更好,每次训练时应该选择较难的三元组。详情请阅读原论文。

4.1.4 人脸验证与二分类问题

其实,判断两张图片的编码是否相似的问题可以简单地转化成一个二分类问题:把两张图片的编码“拼起来”,输入进一个逻辑回归的单元,让网络输入两张图片是否相似。

这里把两张图片“拼起来”有很多的实现方式。一种简单的方式是求两个编码的绝对值(L1距离)。

另外,在部署时,由于比较的图像的编码是可以预处理的,只需要用神经网络跑一遍输入图片即可。

4.2 神经网络风格迁移

4.2.1 什么是神经网络风格迁移?

如上图所示,在神经网络风格迁移中,输入一张表示内容的图(C)和一张表示画家风格的图(S),我们可以借助神经网络生成一幅融合内容与风格的图片(G)。

接下来实现神经网络风格迁移时,我们会关注CNN浅层和深层提取出来的特征。因此,在具体介绍风格迁移的实现之前,我们先来看看CNN各层网络究竟学到了什么。

4.2.2 深度卷积网络学到了什么? 

为了设法可视化神经网络每一层的输出,我们可以考虑把整个训练集喂入网络,找出使某一层某一神经元激活输出最大的几个像素块。

以AlexNet为例,令第一层某几个神经元激活输出最大的像素块是

可以看出,浅层的神经网络更关注不同方向轮廓信息。

用同样的方式可视化更深的层,可以看出网络关注的信息越来越抽象。

 4.2.3 损失函数 

和优化一个网络的参数不同,在神经网络风格迁移中,我们要把一幅图像当成可以优化的参数,通过修改图像的像素值来最小化一个损失函数。让我们看看这个损失函数是怎么定义的。

损失函数由两部分构成:生成图像(G)和内容图像(C)的内容损失与和风格图像(S)的风格损失。二者之间的比例靠比例系数\alpha , \beta控制。

J(G) = \alpha J_{content}(C,G)+\beta J_{style}(S,G))

我们来看一个优化这个损失函数的步骤示例:

  1. 生成一个随机图像G:100*100*3
  2. 使用梯度下降更新G,最小化J(G)

4.2.3 内容损失 

1. 使用一个预训练网络(最常用的是VGG)。

2. 选择神经网络中的某一层l,这一层相关的数据会用来计算内容损失。l越浅,表示越具体的图像;l越深,表示越抽象的图像。一般选取始终的l;

3.令a^{[l](C)},a^{[l][G]}为图像C,G网络第l层的激活输出。我们认为,如果这两个值很相似,则两幅图像的内容很相似。

4.令J_{content}(C,G)=\left \| a^{[l](C)}-a^{[l][G]} \right \|^{2}

4.2.4  风格损失

看完了内容损失,现在来看风格损失的计算方式。我们刚刚一直在用名词“风格”。这个词放在美术里,可以指画家的绘画风格。而对于神经网络来说,“风格”又是什么意思呢?

和刚刚的内容损失类似,计算风格损失时,我们也要选择CNN的某层l,计算这一层卷积激活输出的通道相关量

这里“通道相关量”反映的是图像各个通道间两两的相关关系。这句话可能有点拗口,让我们看一个详细的通道相关量示例。

假设一个激活输出有5个通道,我们用颜色“红-黄-紫-绿-蓝”称呼它们。每个通道表示的是一类特征,比如红色的通道表示图像有没有竖着的条纹,黄色通道表示图像有没有橙色的色块。我们想计算红黄通道之间的相关量,其实就是计算图像每个像素处红色通道的值和绿色通道的值之间的相关关系,看看它们是会同时出现,还是一个出现了另一个就不出现。(两个数值的相关量就是它们的乘积,具体的数学表示会在后文中给出)

为什么这样的相关关系能够捕捉到风格信息呢?红色通道和黄色通道的相关关系,就是是不是有竖条纹的地方就有橙色色块。这样一种线条和颜色的关联,就可以代表图片的风格。(下图中红色的框和黄色的框分别圈出了两种特征)

从直觉上认识了风格,现在我们来看一看风格的具体计算方法。在计算两个向量的相关量时,可以直接求两个向量的积。因此,我们要用到一种叫做"风格矩阵“的中间量,它计算了所有通道向量的乘积:

                        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​       G_{kk^{'}}^{[l]}=\sum_{i}^{H}\sum_{i}^{W}a_{i,j,k}^{[l]}a_{i,j,k^{'}}^{[l]} 

其中,a_{i,j,k}^{[l]}是第i行j列第l个通道的激活输出,风格矩阵G的形状是n_{c}^{[l]}\times n_{c}^{[l]}G_{kk^{'}}^{[l]}描述的是第l层激活输出中,第k,k^{'}个通道间的相关量。

在数学中这个矩阵叫做gram矩阵。

有了风格矩阵,就可以算风格损失了。为了简化表示,我们用分别G^{[l]}(S),G^{[l]}(G)表示风格图像S和生成图像G的风格矩阵。和刚刚一样,我们选择一层l,并计算风格误差:

                                        J_{style}^{[l]}(S,G)=\left \| G^{[l]}(S)-G^{[l](G)}\right \|_{F}^{2}

这是一层上的风格误差。其实,我们还可以指定多个层上的风格误差,以使生成图像能够拟合风格图像在多个卷积层上(抽象程度不同)的风格。多个层的风格误差就是各层风格误差之和。

 5. 1D和3D的卷积

在结束这门课之前,我们来学习最后一项内容——1D和3D数据上的卷积。之前,我们只关注2D的图像数据,当维度不是2时,卷积有怎样的变化呢?

1D数据可以是心电图。和可以用2D卷积捕捉2D图像的边缘一样,我们可以用下图中那个尖尖的1D卷积来捕获高频的数据。1D卷积前后的形状变化规律和2D一样,比如用16个长度为5的卷积卷一个的1D图像,可以得到的1D图像(第一维是图像长度,第二维是通道数)。

3D数据也是类似的道理。用16个的卷积核卷一个单通道的单通道图像,可以得到一个的图像。 

总结

这节课主要介绍了人脸识别和神经网络风格迁移两项任务,最后顺便介绍了1D和3D上的卷积。

  • 人脸识别 
    • 人脸验证与人脸识别任务的定义
    • 如何对单样本学习建模
    • 孪生网络
    • 基于三元组误差和二分类误差的人脸识别网络

  • 神经网络风格迁移 
    • 神经网络风格迁移的输入输出
    • 利用小技巧可视化 CNN 学到的图像信息
    • 生成风格迁移图像时的内容误差与风格误差

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐