目录
- 第四门课 卷积神经网络(Convolutional Neural Networks)
- 第四周 特殊应用:人脸识别和神经风格转换(Special applications: Face recognition &Neural style transfer)
- 4.3 Siamese 网络(Siamese network)
- 4.4 Triplet 损失(Triplet 损失)
第四门课 卷积神经网络(Convolutional Neural Networks)
第四周 特殊应用:人脸识别和神经风格转换(Special applications: Face recognition &Neural style transfer)
4.3 Siamese 网络(Siamese network)
上个视频中你学到的函数𝑑的作用就是输入两张人脸,然后告诉你它们的相似度。实现这个功能的一个方式就是用 Siamese 网络,我们看一下。
你经常看到这样的卷积网络,输入图片 x ( 1 ) x^{(1)} x(1),然后通过一些列卷积,池化和全连接层,最终得到这样的特征向量(编号 1)。有时这个会被送进 softmax 单元来做分类,但在这个视频里我们不会这么做。我们关注的重点是这个向量(编号 1),加如它有 128 个数,它是由网络深层的全连接层计算出来的,我要给这 128 个数命个名字,把它叫做 f ( x ( 1 ) ) f(x^{(1)}) f(x(1))。你可以把 f ( x ( 1 ) ) f(x^{(1)}) f(x(1))看作是输入图像 x ( 1 ) x^{(1)} x(1)的编码,取这个输入图像(编号 2),在这里是 Kian 的图片,然后表示成 128 维的向量。
建立一个人脸识别系统的方法就是,如果你要比较两个图片的话,例如这里的第一张(编号 1)和第二张图片(编号 2),你要做的就是把第二张图片喂给有同样参数的同样的神经网络,然后得到一个不同的 128 维的向量(编号 3),这个向量代表或者编码第二个图片,我要把第二张图片的编码叫做 f ( x ( 2 ) ) f(x^{(2)}) f(x(2))。这里我用 x ( 1 ) x^{(1)} x(1)和 x ( 2 ) x^{(2)} x(2)仅仅代表两个输入图片,他们没必要非是第一个和第二个训练样本,可以是任意两个图片。
最后如果你相信这些编码很好地代表了这两个图片,你要做的就是定义𝑑,将 x ( 1 ) x^{(1)} x(1)和 x ( 2 ) x^{(2)} x(2)的距离定义为这两幅图片的编码之差的范数, d ( x ( 1 ) , x ( 2 ) ) = ∣ ∣ f ( x ( 1 ) ) − f ( x ( 2 ) ) ∣ ∣ 2 2 d(x^{(1)},x^{(2)}) = ||f(x^{(1)}) −f(x^{(2)})||_2^2 d(x(1),x(2))=∣∣f(x(1))−f(x(2))∣∣22。对于两个不同的输入,运行相同的卷积神经网络,然后比较它们,这一般叫做 Siamese网络架构。这里提到的很多观点,都来自于 Yaniv Taigman,Ming Yang,Marc’ Aurelio Ranzato,Lior Wolf 的这篇论文,他们开发的系统叫做 DeepFace。
怎么训练这个 Siamese 神经网络呢?不要忘了这两个网络有相同的参数,所以你实际要做的就是训练一个网络,它计算得到的编码可以用于函数𝑑,它可以告诉你两张图片是否是同一个人。更准确地说,神经网络的参数定义了一个编码函数 f ( x ( i ) ) f(x^{(i)}) f(x(i)),如果给定输入图像 x ( i ) x^{(i)} x(i),这个网络会输出 x ( i ) x^{(i)} x(i)的 128 维的编码。你要做的就是学习参数,使得如果两个图片 x ( i ) x^{(i)} x(i)和 x ( j ) x^{(j)} x(j)是同一个人,那么你得到的两个编码的距离就小。前面几个幻灯片我都用的是 x ( 1 ) x^{(1)} x(1)和 x ( 2 ) x^{(2)} x(2),其实训练集里任意一对 x ( i ) x^{(i)} x(i)和 x ( j ) x^{(j)} x(j)都可以。相反,如果 x ( i ) x^{(i)} x(i)和 x ( j ) x^{(j)} x(j)是不同的人,那么你会想让它们之间的编码距离大一点。
如果你改变这个网络所有层的参数,你会得到不同的编码结果,你要做的就是用反向传播来改变这些所有的参数,以确保满足这些条件。
你已经了解了 Siamese 网络架构,并且知道你想要网络输出什么,即什么是好的编码。但是如何定义实际的目标函数,能够让你的神经网络学习并做到我们刚才讨论的内容呢?在下一个视频里,我们会看到如何用三元组损失函数达到这个目的。
4.4 Triplet 损失(Triplet 损失)
要想通过学习神经网络的参数来得到优质的人脸图片编码,方法之一就是定义三元组损失函数然后应用梯度下降。
我们看下这是什么意思,为了应用三元组损失函数,你需要比较成对的图像,比如这个图片,为了学习网络的参数,你需要同时看几幅图片,比如这对图片(编号 1 和编号 2),你想要它们的编码相似,因为这是同一个人。然而假如是这对图片(编号 3 和编号 4),你会想要它们的编码差异大一些,因为这是不同的人。
用三元组损失的术语来说,你要做的通常是看一个 Anchor 图片,你想让 Anchor 图片和 Positive 图片(Positive 意味着是同一个人)的距离很接近。然而,当 Anchor 图片与 Negative图片(Negative 意味着是非同一个人)对比时,你会想让他们的距离离得更远一点。
这就是为什么叫做三元组损失,它代表你通常会同时看三张图片,你需要看 Anchor 图片、Postive 图片,还有 Negative 图片,我要把 Anchor 图片、Positive 图片和 Negative 图片简写成𝐴、𝑃、𝑁。把这些写成公式的话,你想要的是网络的参数或者编码能够满足以下特性,也就是说你想要||𝑓(𝐴) − 𝑓(𝑃)||2,你希望这个数值很小,准确地说,你想让它小于等𝑓(𝐴)和𝑓(𝑁)之间的距离,或者说是它们的范数的平方(即:||𝑓(𝐴) − 𝑓(𝑃)||2 ≤ ||𝑓(𝐴) − 𝑓(𝑁)||2)。(||𝑓(𝐴) −𝑓(𝑃)||2)当然这就是𝑑(𝐴, 𝑃),(||𝑓(𝐴) − 𝑓(𝑁)||2)这是𝑑(𝐴, 𝑁),你可以把𝑑看作是距离(distance)函数,这也是为什么我们把它命名为𝑑。
现在如果我把方程右边项移到左边,最终就得到:||𝑓(𝐴) − 𝑓(𝑃)||2 ≤ ||𝑓(𝐴) − 𝑓(𝑁)||2
现在我要对这个表达式做一些小的改变,有一种情况满足这个表达式,但是没有用处,就是把所有的东西都学成 0,如果𝑓总是输出 0,即 0-0≤0,这就是 0 减去 0 还等于 0,如果所有图像的𝑓都是一个零向量,那么总能满足这个方程。所以为了确保网络对于所有的编码不会总是输出 0,也为了确保它不会把所有的编码都设成互相相等的。另一种方法能让网络得到这种没用的输出,就是如果每个图片的编码和其他图片一样,这种情况,你还是得到 0-0。
为了阻止网络出现这种情况,我们需要修改这个目标,也就是,这个不能是刚好小于等于 0,应该是比 0 还要小,所以这个应该小于一个−𝑎值(即||𝑓(𝐴) − 𝑓(𝑃)||2 − ||𝑓(𝐴) −𝑓(𝑁)||2 ≤ −𝑎),这里的𝑎是另一个超参数,这个就可以阻止网络输出无用的结果。按照惯例,我们习惯写+𝑎(即||𝑓(𝐴) − 𝑓(𝑃)||2 − ||𝑓(𝐴) − 𝑓(𝑁)||2 + 𝑎 ≤ 0),而不是把−𝑎写在后面,它也叫做间隔(margin),这个术语你会很熟悉,如果你看过关于支持向量机 (SVM) 的文献,没看过也不用担心。我们可以把上面这个方程(||𝑓(𝐴) − 𝑓(𝑃)||2 − ||𝑓(𝐴) − 𝑓(𝑁)||2)也修改一下,加上这个间隔参数。
举个例子,假如间隔设置成 0.2,如果在这个例子中,𝑑(𝐴, 𝑃) = 0.5,如果 Anchor 和Negative 图片的𝑑,即𝑑(𝐴, 𝑁)只大一点,比如说 0.51,条件就不能满足。虽然 0.51 也是大于0.5 的,但还是不够好,我们想要𝑑(𝐴, 𝑁)比𝑑(𝐴, 𝑃)大很多,你会想让这个值(𝑑(𝐴, 𝑁))至少是 0.7 或者更高,或者为了使这个间隔,或者间距至少达到 0.2,你可以把这项调大或者这个调小,这样这个间隔𝑎,超参数𝑎 至少是 0.2,在𝑑(𝐴, 𝑃)和𝑑(𝐴, 𝑁)之间至少相差 0.2,这就是间隔参数𝑎的作用。它拉大了 Anchor 和 Positive 图片对和 Anchor 与 Negative 图片对之间的差距。取下面的这个方框圈起来的方程式,在下个幻灯片里,我们会更公式化表示,然后定义三元组损失函数。
三元组损失函数的定义基于三张图片,假如三张图片𝐴、𝑃、𝑁,即 anchor 样本、positive样本和 negative 样本,其中 positive 图片和 anchor 图片是同一个人,但是 negative 图片和anchor 不是同一个人。
接下来我们定义损失函数,这个例子的损失函数,它的定义基于三元图片组,我先从前一张幻灯片复制过来一些式子,就是||𝑓(𝐴) − 𝑓(𝑃)||
2 − ||𝑓(𝐴) − 𝑓(𝑁)||2 + 𝑎 ≤ 0。所以为了定义这个损失函数,我们取这个和 0 的最大值:𝐿(𝐴, 𝑃, 𝑁) = 𝑚𝑎𝑥(||𝑓(𝐴) − 𝑓(𝑃)||2 − ||𝑓(𝐴) − 𝑓(𝑁)||
2 + 𝑎, 0)这个𝑚𝑎𝑥函数的作用就是,只要这个||𝑓(𝐴) − 𝑓(𝑃)||2 − ||𝑓(𝐴) − 𝑓(𝑁)||2 + 𝑎 ≤ 0,那么损失函数就是 0。只要你能使画绿色下划线部分小于等于 0,只要你能达到这个目标,那么这个例子的损失就是 0。
另一方面如果这个||𝑓(𝐴) − 𝑓(𝑃)||2 − ||𝑓(𝐴) − 𝑓(𝑁)||2 + 𝑎 ≤ 0,然后你取它们的最大值,最终你会得到绿色下划线部分(即||𝑓(𝐴) − 𝑓(𝑃)||
2 − ||𝑓(𝐴) − 𝑓(𝑁)||2 + 𝑎)是最大值,这样你会得到一个正的损失值。通过最小化这个损失函数达到的效果就是使这部分||𝑓(𝐴) −𝑓(𝑃)||2 − ||𝑓(𝐴) − 𝑓(𝑁)||2 + 𝑎成为 0,或者小于等于 0。只要这个损失函数小于等于 0,网络不会关心它负值有多大。
这是一个三元组定义的损失,整个网络的代价函数应该是训练集中这些单个三元组损失的总和。假如你有一个 10000 个图片的训练集,里面是 1000 个不同的人的照片,你要做的就是取这 10000 个图片,然后生成这样的三元组,然后训练你的学习算法,对这种代价函数用梯度下降,这个代价函数就是定义在你数据集里的这样的三元组图片上。
注意,为了定义三元组的数据集你需要成对的𝐴和𝑃,即同一个人的成对的图片,为了训练你的系统你确实需要一个数据集,里面有同一个人的多个照片。这是为什么在这个例子中,我说假设你有 1000 个不同的人的 10000 张照片,也许是这 1000 个人平均每个人 10 张照片,组成了你整个数据集。如果你只有每个人一张照片,那么根本没法训练这个系统。当然,训练完这个系统之后,你可以应用到你的一次学习问题上,对于你的人脸识别系统,可能你只有想要识别的某个人的一张照片。但对于训练集,你需要确保有同一个人的多个图片,至少是你训练集里的一部分人,这样就有成对的 Anchor 和 Positive 图片了。
现在我们来看,你如何选择这些三元组来形成训练集。一个问题是如果你从训练集中,随机地选择𝐴、𝑃和𝑁,遵守𝐴和𝑃是同一个人,而𝐴和𝑁是不同的人这一原则。有个问题就是,如果随机的选择它们,那么这个约束条件(𝑑(𝐴, 𝑃) + 𝑎 ≤ 𝑑(𝐴, 𝑁))很容易达到,因为随机选择的图片,𝐴和𝑁比𝐴和𝑃差别很大的概率很大。我希望你还记得这个符号𝑑(𝐴, 𝑃)就是前几个幻灯片里写的||𝑓(𝐴) − 𝑓(𝑃)||2,𝑑(𝐴, 𝑁)就是||𝑓(𝐴) − 𝑓(𝑁)||
2,𝑑(𝐴, 𝑃) + 𝑎 ≤ 𝑑(𝐴, 𝑁)即||𝑓(𝐴) − 𝑓(𝑃)||2 + 𝑎 ≤ ||𝑓(𝐴) − 𝑓(𝑁)||2。但是如果𝐴和𝑁是随机选择的不同的人,有很大的可能性||𝑓(𝐴) − 𝑓(𝑁)||2会比左边这项||𝑓(𝐴) − 𝑓(𝑃)||2大,而且差距远大于𝑎,这样网络并不能从中学到什么。
所以为了构建一个数据集,你要做的就是尽可能选择难训练的三元组𝐴、𝑃和𝑁。具体而言,你想要所有的三元组都满足这个条件(𝑑(𝐴, 𝑃) + 𝑎 ≤ 𝑑(𝐴, 𝑁)),难训练的三元组就是,你的𝐴、𝑃和𝑁的选择使得𝑑(𝐴, 𝑃)很接近𝑑(𝐴, 𝑁),即𝑑(𝐴, 𝑃) ≈ 𝑑(𝐴, 𝑁),这样 你的学习算法会竭尽全力使右边这个式子变大(𝑑(𝐴, 𝑁)),或者使左边这个式子(𝑑(𝐴, 𝑃))变小,这样左右两边至少有一个𝑎的间隔。并且选择这样的三元组还可以增加你的学习算法的计算效率,如果随机的选择这些三元组,其中有太多会很简单,梯度算法不会有什么效果,因为网络总是很轻松就能得到正确的结果,只有选择难的三元组梯度下降法才能发挥作用,使得这两边离得尽可能远。
如果你对此感兴趣的话,这篇论文中有更多细节,作者是 Florian Schroff, Dmitry Kalenichenko, James Philbin,他们建立了这个叫做 FaceNet 的系统,我视频里许多的观点都是来自于他们的工作。
顺便说一下,这有一个有趣的事实,关于在深度学习领域,算法是如何命名的。如果你研究一个特定的领域,假如说“某某”领域,通常会将系统命名为“某某”网络或者深度“某某”,我们一直讨论人脸识别,所以这篇论文叫做 FaceNet(人脸网络),上个视频里你看到过DeepFace(深度人脸)。“某某”网络或者深度“某某”,是深度学习领域流行的命名算法的方式,你可以看一下这篇论文,如果你想要了解更多的关于通过选择最有用的三元组训练来加速算法的细节,这是一个很棒的论文。
总结一下,训练这个三元组损失你需要取你的训练集,然后把它做成很多三元组,这就是一个三元组(编号 1),有一个 Anchor 图片和 Positive 图片,这两个(Anchor 和 Positive)是同一个人,还有一张另一个人的 Negative 图片。这是另一组(编号 2),其中 Anchor 和
Positive 图片是同一个人,但是 Anchor 和 Negative 不是同一个人,等等。
定义了这些包括𝐴、𝑃和𝑁图片的数据集之后,你还需要做的就是用梯度下降最小化我们之前定义的代价函数𝐽,这样做的效果就是反向传播到网络中的所有参数来学习到一种编码,使得如果两个图片是同一个人,那么它们的𝑑就会很小,如果两个图片不是同一个人,它们的𝑑就会很大。
这就是三元组损失,并且如何用它来训练网络输出一个好的编码用于人脸识别。现在的人脸识别系统,尤其是大规模的商业人脸识别系统都是在很大的数据集上训练,超过百万图片的数据集并不罕见,一些公司用千万级的图片,还有一些用上亿的图片来训练这些系统。这些是很大的数据集,即使按照现在的标准,这些数据集并不容易获得。幸运的是,一些公司已经训练了这些大型的网络并且上传了模型参数。所以相比于从头训练这些网络,在这一领域,由于这些数据集太大,这一领域的一个实用操作就是下载别人的预训练模型,而不是一切都要从头开始。但是即使你下载了别人的预训练模型,我认为了解怎么训练这些算法也是有用的,以防针对一些应用你需要从头实现这些想法。
这就是三元组损失,下个视频中,我会给你展示 Siamese 网络的一些其他变体,以及如何训练这些网络。