10

基于Caffe的DeepID2实现(中)

  小喵的唠叨话:我们在上一篇博客里面,介绍了Caffe的Data层的编写。有了Data层,下一步则是如何去使用生成好的训练数据。也就是这一篇的内容。

 

二、精髓,DeepID2 Loss层

DeepID2这篇论文关于verification signal的部分,给出了一个用于监督verification的loss。

verification_loss

其中,fi和fj是归一化之后的特征。

当fi和fj属于同一个identity的时候,也就是yij=1时,loss是二者的L2距离,约束使得特征更为相近。

当fi和fj不属于同一个identity的时候,即yij=-1,这时的loss表示什么呢?参数m又表示什么?

m在这里是margin的意思,是一个可以自行设置的参数,表示期望的不同identity的feature之间的距离。当两个feature的大于margin时,说明网络已经可以很好的区分这两个特征,因此这是loss为0,当feature间的距离小于margin时,loss则为(m-|fi – fj|)^2,表示还需要两个特征能够更好的区分。因此这个loss函数比较好的反应了我们的需求,也就是DeepID2的算法思想。

这个Loss层实现起来似乎并不麻烦,前馈十分的简单。至于后馈,求导也非常简单。但是Caffe加入新层,需要在caffe.proto文件中,做一些修改,这也是最困扰小喵的地方。

不过有个好消息就是:Caffe官网增加了ContrastiveLossLayer这个层!

官网的文件描述如下:

Computes the contrastive loss contrastive_loss where l2. This can be used to train siamese networks.

和我们的需要是一样的。因此我们不需要自己实现这个层。

喜大普奔之余,小喵也专门看了Caffe的文档,以及这里提到了siamese network,发现这个网络使用ContrastiveLossLayer的方式比较独特,Caffe项目中的examples中有例子,感兴趣可以看看。

ContrastiveLossLayer的输入,也就是bottom有三部分,feature1、feature2、label,feature1和feature2是分别对应的两组feature,而label则表示该对feature是否是属于同一个identity,是的话,则为1,不是则为0。而且该层还提供一个参数margin,也就是论文的公式里面的m。

最终的结论就是,虽然我们不需要自己写Loss层,但是还是必须增加一些额外的层。

主要有2个,用于将特征归一化的NormalizationLayer以及用于将feature层转换成ContrastiveLossLayer的输入的层,不妨命名为ID2SliceLayer。

三、小问题,大智慧之Normalization Layer

这个归一化的层用于将输入的feature map进行归一化。Caffe官网并没有提供相关的层,因此我们必须自己实现(或者从网上找),这里我们还是选择自己来实现,顺便学习一下Caffe加层的技巧。

Normalization层的前馈非常的简单,输入为一个向量x,输出为归一化之后的向量:

    \[ f(\vec x) = \frac{{\vec x}}{{\left\| {\vec x} \right\|}} \]

至于后馈,需要求导,计算稍微有点复杂,小喵在推导4遍之后才给出如下表达式:

    \[ \frac{{\partial \vec f}}{{\partial \vec x}} = - \frac{1}{{{{\left\| {\vec x} \right\|}^3}}}*\vec x*{\vec x^T} + \frac{1}{{\left\| {\vec x} \right\|}} \]

其中x为输入的特征向量,为列向量。这里是将整个feature map看做一个列向量。

知道了前馈后馈的计算规则,那么很容易编写自己的层了,这里小喵建议大家找个Caffe已经有了的内容相近的层,照着改写。比如这个Normalization层,没有任何层的参数,所以照着ReLU类似的层就很好编写。

之后就祭出我们的code:

这个层的头文件异常的简单,和ReLU的仅有的区别就是类的名字不一样,而且多了个成员变量norm_val_,用来记录每个feature的模值。

 最后就是GPU部分的代码,如果不在乎性能的话,直接在CUDA的前后馈里面调用CPU版的前后馈就行。当然如果了解CUDA的话,完全可以写一份GPU版的代码。小喵这里就偷懒了一下。。。

这样,我们就写完了Normalization层的所有代码。

对于比较老版本的Caffe,还需要修改/caffe_root/src/caffe/caffe.proto文件。而新版的Caffe只要在新增参数的情况下才需要修改。我们的这个Normalization层并没有用到新的参数,因此并不需要修改caffe.proto文件。

至于新版的Caffe为什么这么智能,原因其实就在这两行代码:

宏INSTANTIATE_CLASS在/caffe_root/include/caffe/common.hpp中定义。

宏REGISTER_LAYER_CLASS在/caffe_root/include/caffe/layer_factory.hpp中定义。

感兴趣可以自行查阅。

重要更新:
1,小喵最近训练的时候实际上已经不使用Normalization层了,而是将Contrastive Loss直接接在feature层的后面,同时由于训练数据都是正样本对,那么margin就没有意义了。不过比较麻烦的是loss weight的选取。

 

如果您觉得本文对您有帮助,那请小喵喝杯茶吧~~O(∩_∩)O~~

%e6%89%93%e8%b5%8f

 

转载请注明出处~

miao

miao

10 Comments

  1. 博主你好,我又来了。

    最近我也需要用到DeepID2的结构,所以尝试实现出它,所以来请教一下。

    你在ContrastiveLoss layer之前加入Normalization Layer是为了什么?我看siamese network和DeepID2都没有这样操作,这样做有什么好处吗?

    谢谢。

    • 因为deepid2有一部分说id2的loss的两个feature是归一化之后的。如果不先归一化的话,距离可能会有几万这么大,而且margin不好设置。我也有同学实现的时候没有先归一化,说也有效果。

      • 好的,距离会极端的问题我也是想到了。只是我不太确定应该怎么能有效的度量这个距离,比如margin的设置,比如归一化特征等等。博主是通过实验得到margin的吗?参考博主第三篇博文的margin设置为1,这是最优结果吗?而且请问一下博主用哪个数据集做训练和测试的,我目前手头只有一个CACD。

        问题有点多,打扰了。谢谢

        • 我实验的结果大致是负样本对的距离一般都比较大,可能就是因为identity的监督的原因,之前设置为1是因为两个单位向量的距离最大为2。为了让id2监督更好,我现在的策略就是全部使用正样本对,也就和论文说的一样,然后margin就没有意义了。
          训练数据的话,我是用的内部的数据,不能公开。我有同学做识别用的是imdb的一个数据,名字我忘记了。
          不过目前正在训练的google net出现了前面的loss比后面的loss还要小的情况,头大中。。。

  2. 楼主你好呀,谢谢你的文章。想请教一下,按楼主的程序,每次得到的batch不是有可能和以前的batch中的样本又重复么?或者有些样本一直都不会被选中使用?

    • 是呀,但是也没什么关系吧。策略可以自己随便改的。这里只是一个思路。

  3. 你好,请问你的实验中Contrastive loss的提升有多少? 我实验时加了L2Norm很难收敛 你之前是否有类似情况?

    • 我没有使用norm。直接用原始特征做的(loss weight根据feature的模值来设定)。使用contrastive loss,大概提高2个点。

  4. 博主你好,我训练deepid2网络的过程中,发现softmaxloss可以收敛,而contrastiveloss不能收敛是怎么回事?我设置的margin为1,softmaxloss的loss_weight为1,contrastiveloss的loss_weight为0.1

    • 你好,你这个现象我也碰到过。我最终的训练方法是这样的:
      1,特征不要使用归一化(讲道理虽然可以归一化)
      2,不归一化的特征会比较大,loss weight设小,让两种loss相差不要太大。
      3,只用正样本对(我是这样的),margin设不设都没关系(只有负样本对才用margin)

发表评论

电子邮件地址不会被公开。