基于Caffe的Large Margin Softmax Loss的实现(上)
小喵的唠叨话:在写完上一次的博客之后,已经过去了2个月的时间,小喵在此期间,做了大量的实验工作,最终在使用的DeepID2的方法之后,取得了很不错的结果。这次呢,主要讲述一个比较新的论文中的方法,L-Softmax,据说单model在LFW上能达到98.71%的等错误率。更重要的是,小喵觉得这个方法和DeepID2并不冲突,如果二者可以互补,或许单model达到99%+将不是梦想。
再次推销一下~
小喵的博客网址是: http://www.miaoerduo.com
博客原文: http://www.miaoerduo.com/deep-learning/基于caffe的large-ma…ftmax-loss的实现(上).html
和上一篇博客一样,小喵对读者做了如下的假定:
- 了解Deep Learning的基本知识。
- 仔细阅读过L-Softmax的论文,了解其中的数学推导。
- 使用Caffe作为训练框架。
- 即使不满足上述3条,也能持之以恒的学习。
L-Softmax的论文:Large-Margin Softmax Loss for Convolutional Neutral Networks
Google一下,第一条应该就是论文的地址,鉴于大家时间有限,小喵把原文地址也贴出来了,但不保证长期有效。http://jmlr.org/proceedings/papers/v48/liud16.pdf 这里我们也将整个系列分几部分来讲。
一、margin与lambda
margin和lambda这两个参数是我们这篇博客的重点。也是整篇论文的重点。对于分类的任务,每个样本都会有N的输出的分数(N的类别),如果在训练中,人为的使正确类别的得分变小,也就是说加大了区分正确类别的难度,那么网络就会学习出更有区分能力的特征,并且加大类间的距离。作者选用的加大难度的方式就是改变最后一个FC层中的weight和特征之间的角度值,角度增大的倍数就是margin,从而使特定类别的得分变小。而第二个参数lambda是为了避免网络不收敛而设定的,我们之后会讲到。
为了实现这个效果,我们需要设计一个新的层,large_margin_inner_product_layer。这个层和一般的inner_product_layer很相似,但是多了特定类别削弱的功能。 考虑到这个层是有参数的,我们需要在caffe.proto(caffe_home/src/caffe/proto/caffe.proto)中做一些修改。这里的定义是按照protobuf的语法写的,简单的修改只要照着其他的参数来改写就好。 首先定义我们的这个层的参数。
message LargeMarginInnerProductParameter {
optional uint32 num_output = ; // The number of outputs for the layer
optional bool bias_term = [default = true]; // whether to have bias terms
optional FillerParameter weight_filler = ; // The filler for the weight
optional FillerParameter bias_filler = ; // The filler for the bias
// The first axis to be lumped into a single inner product computation;
// all preceding axes are retained in the output.
// May be negative to index from the end (e.g., -1 for the last axis).
optional int32 axis = [default = ];
// Specify whether to transpose the weight matrix or not.
// If transpose == true, any operations will be performed on the transpose
// of the weight matrix. The weight matrix itself is not going to be transposed
// but rather the transfer flag of operations will be toggled accordingly.
optional bool transpose = [default = false];
optional uint32 margin = [default = ];
optional float lambda = [default = ];
}
参数的定义和InnerProductParameter非常相似,只是多了两个参数margin和lambda。 之后在LayerParameter添加一个可选参数(照着InnerProductParameter写就好)。
optional LargeMarginInnerProductParameter large_margin_inner_product_param = 147;
这时,喵粉可能很在意这个147是怎么回事。其实呢,在protobuf中,每个结构中的变量都需要一个id,只要保证不重复即可。我们在LayerParameter的最开始可以看到这么一行注释: 
说明下一个有效的id是147。这里我们新加的参数就果断占用了这个id。
修改之后,建议把注释改一下(不要人为的挖坑): LayerParameter next available layer-specific ID: 148 (last added: large_margin_inner_product_param)
避免之后再新加层的时候出问题。
工作完毕,我们就可以在train_val.prototxt中用这种方式使用这个新层了(具体的使用,后面再说):
layer {
name: "fc2"
type: "LargeMarginInnerProduct"
bottom: "fc1"
bottom: "label"
top: "fc2"
param {
lr_mult:
decay_mult:
}
param {
lr_mult:
decay_mult:
}
large_margin_inner_product_param {
num_output:
margin:
lambda:
weight_filler {
type: "xavier"
}
}
}
二,运筹帷幄之成员变量
我们刚刚在caffe.proto中,添加了新参数的定义。而事实上,我们还没有这个层的具体实现。这部分,主要介绍我们需要的临时变量。 首先,我们要理清整个计算的流程。
先看前馈。
第一步,需要求出W和x的夹角的余弦值:
\[\cos(\theta_j)=\frac{W_j^Tx_i}{\|W_j\|\|x_i\|}\]
第二步,计算m倍角度的余弦值:
\[\cos(m\theta_i)=\sum_n(-1)^n{C_m^{2n}\cos^{m-2n}(\theta_i)\cdot(1-\cos(\theta_i)^2)^n}, (2n\leq m)\]
第三步,计算前馈:
\[f_{y_{i}}=(-1)^k\cdot\|W_{y_{i}}\|\|x_{i}\|\cos(m\theta_i)-2k\cdot\|W_{y_i}\|\|x_i\|\]
k是根据$\cos(\theta)$的取值决定的。
后馈比前馈要复杂一些,不过使用的变量也是一样的。 因此我们可以编写自己的头文件了。
#ifndef CAFFE_LARGE_MARGIN_INNER_PRODUCT_LAYER_HPP_
#define CAFFE_LARGE_MARGIN_INNER_PRODUCT_LAYER_HPP_ #include <vector> #include "caffe/blob.hpp"
#include "caffe/layer.hpp"
#include "caffe/proto/caffe.pb.h" namespace caffe { template <typename Dtype>
class LargeMarginInnerProductLayer : public Layer<Dtype> {
public:
explicit LargeMarginInnerProductLayer(const LayerParameter& param)
: Layer<Dtype>(param) {}
virtual void LayerSetUp(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top);
virtual void Reshape(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top); virtual inline const char* type() const { return "LargeMarginInnerProduct"; }
// edited by miao
// LM_FC层有两个bottom
virtual inline int ExactNumBottomBlobs() const { return ; }
// end edited
virtual inline int ExactNumTopBlobs() const { return ; } protected:
virtual void Forward_cpu(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top);
virtual void Forward_gpu(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top);
virtual void Backward_cpu(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);
virtual void Backward_gpu(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom); int M_;
int K_;
int N_;
bool bias_term_;
Blob<Dtype> bias_multiplier_;
bool transpose_; ///< if true, assume transposed weights // added by miao // 一些常数
Blob<Dtype> cos_theta_bound_; // 区间边界的cos值
Blob<int> k_; // 当前角度theta所在的区间的位置
Blob<int> C_M_N_; // 组合数
unsigned int margin; // margin
float lambda; // lambda Blob<Dtype> wx_; // wjT * xi
Blob<Dtype> abs_w_; // ||wj||
Blob<Dtype> abs_x_; // ||xi||
Blob<Dtype> cos_t_; // cos(theta)
Blob<Dtype> cos_mt_; // cos(margin * theta) Blob<Dtype> dydw_; // 输出对w的导数
Blob<Dtype> dydx_; // 输出对x的导数
// end added
}; } // namespace caffe #endif // CAFFE_LARGE_MARGIN_INNER_PRODUCT_LAYER_HPP_
这里主要是复制了inner_product_layer.hpp,然后做了一点修改。具体是增加了几个成员变量,同时改了ExactNumBottomBlobs的返回值,因为我们的这个层磁带bottom需要两个,前一层的feature和样本的label。
三、内存和常量的初始化
这部分,主要给我们的各个成员变量分配内存,同时给几个常量进行初始化。这里也是照着inner_product_layer.cpp来写的,在setup的时候,增加了一些用于初始化的代码,并删除了forward_cpu和backwark_cpu的具体实现。
修改之后的代码如下:
#include <vector>
#include <cmath> #include "caffe/filler.hpp"
#include "caffe/layers/large_margin_inner_product_layer.hpp"
#include "caffe/util/math_functions.hpp" #define PI 3.14159265 namespace caffe { int factorial(int n) {
if ( == n) return ;
int f = ;
while (n) {
f *= n;
-- n;
}
return f;
} template <typename Dtype>
void LargeMarginInnerProductLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) { const int axis = bottom[]->CanonicalAxisIndex(
this->layer_param_.large_margin_inner_product_param().axis());
// added by miao
std::vector<int> wx_shape();
wx_shape[] = bottom[]->shape();
this->wx_.Reshape(wx_shape);
this->abs_w_.Reshape(wx_shape);
this->abs_x_.Reshape(wx_shape);
this->k_.Reshape(wx_shape);
this->cos_t_.Reshape(wx_shape);
this->cos_mt_.Reshape(wx_shape); std::vector<int> cos_theta_bound_shape();
this->margin = static_cast<unsigned int>(this->layer_param_.large_margin_inner_product_param().margin());
cos_theta_bound_shape[] = this->margin + ;
this->cos_theta_bound_.Reshape(cos_theta_bound_shape);
for (int k = ; k <= this->margin; ++ k) {
this->cos_theta_bound_.mutable_cpu_data()[k] = std::cos(PI * k / this->margin);
}
this->C_M_N_.Reshape(cos_theta_bound_shape);
for (int n = ; n <= this->margin; ++ n) {
this->C_M_N_.mutable_cpu_data()[n] = factorial(this->margin) / factorial(this->margin - n) / factorial(n);
} // d size
std::vector<int> d_shape();
d_shape[] = bottom[]->shape();
d_shape[] = bottom[]->count(axis);
this->dydw_.Reshape(d_shape);
this->dydx_.Reshape(d_shape); this->lambda = this->layer_param_.large_margin_inner_product_param().lambda();
// end added transpose_ = false; // 坚决不转置! const int num_output = this->layer_param_.large_margin_inner_product_param().num_output();
bias_term_ = this->layer_param_.large_marin_inner_product_param().bias_term();
N_ = num_output; // Dimensions starting from "axis" are "flattened" into a single
// length K_ vector. For example, if bottom[0]'s shape is (N, C, H, W),
// and axis == 1, N inner products with dimension CHW are performed.
K_ = bottom[]->count(axis);
// Check if we need to set up the weights
if (this->blobs_.size() > ) {
LOG(INFO) << "Skipping parameter initialization";
} else {
if (bias_term_) {
this->blobs_.resize();
} else {
this->blobs_.resize();
}
// Initialize the weights
vector<int> weight_shape();
if (transpose_) {
weight_shape[] = K_;
weight_shape[] = N_;
} else {
weight_shape[] = N_;
weight_shape[] = K_;
}
this->blobs_[].reset(new Blob<Dtype>(weight_shape));
// fill the weights
shared_ptr<Filler<Dtype> > weight_filler(GetFiller<Dtype>(
this->layer_param_.large_margin_inner_product_param().weight_filler()));
weight_filler->Fill(this->blobs_[].get());
// If necessary, intiialize and fill the bias term
if (bias_term_) {
vector<int> bias_shape(, N_);
this->blobs_[].reset(new Blob<Dtype>(bias_shape));
shared_ptr<Filler<Dtype> > bias_filler(GetFiller<Dtype>(
this->layer_param_.inner_product_param().bias_filler()));
bias_filler->Fill(this->blobs_[].get());
} } // parameter initialization
this->param_propagate_down_.resize(this->blobs_.size(), true);
} template <typename Dtype>
void LargeMarginInnerProductLayer<Dtype>::Reshape(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
// Figure out the dimensions
const int axis = bottom[]->CanonicalAxisIndex(
this->layer_param_.large_margin_inner_product_param().axis());
const int new_K = bottom[]->count(axis);
CHECK_EQ(K_, new_K)
<< "Input size incompatible with large margin inner product parameters.";
// The first "axis" dimensions are independent inner products; the total
// number of these is M_, the product over these dimensions.
M_ = bottom[]->count(, axis);
// The top shape will be the bottom shape with the flattened axes dropped,
// and replaced by a single axis with dimension num_output (N_).
vector<int> top_shape = bottom[]->shape();
top_shape.resize(axis + );
top_shape[axis] = N_;
top[]->Reshape(top_shape);
} template <typename Dtype>
void LargeMarginInnerProductLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
// not implement
} template <typename Dtype>
void LargeMarginInnerProductLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down,
const vector<Blob<Dtype>*>& bottom) {
// not implement
} #ifdef CPU_ONLY
STUB_GPU(LargeMarginInnerProductLayer);
#endif INSTANTIATE_CLASS(LargeMarginInnerProductLayer);
REGISTER_LAYER_CLASS(LargeMarginInnerProduct); } // namespace caffe
至此,large_margin_inner_product_layer的准备工作就做完了。下一篇博客,我们来详细的讨论前馈的具体实现。
如果您觉得本文对您有帮助,那请小喵喝杯茶吧~~O(∩_∩)O~~ 小喵为了写公式,还专门学习了$\LaTeX$。

转载请注明出处~
基于Caffe的Large Margin Softmax Loss的实现(上)的更多相关文章
- 基于Caffe的Large Margin Softmax Loss的实现(中)
小喵的唠叨话:前一篇博客,我们做完了L-Softmax的准备工作.而这一章,我们开始进行前馈的研究. 小喵博客: http://miaoerduo.com 博客原文: http://www.miao ...
- Large Margin Softmax Loss for Speaker Verification
[INTERSPEECH 2019接收] 链接:https://arxiv.org/pdf/1904.03479.pdf 这篇文章在会议的speaker session中.本文主要讨论了说话人验证中的 ...
- cosface: large margin cosine loss for deep face recognition
目录 概 主要内容 Wang H, Wang Y, Zhou Z, et al. CosFace: Large Margin Cosine Loss for Deep Face Recognition ...
- Large-Margin Softmax Loss for Convolutional Neural Networks
paper url: https://arxiv.org/pdf/1612.02295 year:2017 Introduction 交叉熵损失与softmax一起使用可以说是CNN中最常用的监督组件 ...
- caffe中softmax loss源码阅读
(1) softmax loss <1> softmax loss的函数形式为: (1) zi为softmax的输入,f(zi)为softmax的输出. <2> sof ...
- 基于Caffe的DeepID2实现(下)
小喵的唠叨话:这次的博客,真心累伤了小喵的心.但考虑到知识需要巩固和分享,小喵决定这次把剩下的内容都写完. 小喵的博客:http://www.miaoerduo.com 博客原文: http://ww ...
- 基于Caffe的DeepID2实现(中)
小喵的唠叨话:我们在上一篇博客里面,介绍了Caffe的Data层的编写.有了Data层,下一步则是如何去使用生成好的训练数据.也就是这一篇的内容. 小喵的博客:http://www.miaoerduo ...
- 基于Caffe的DeepID2实现(上)
小喵的唠叨话:小喵最近在做人脸识别的工作,打算将汤晓鸥前辈的DeepID,DeepID2等算法进行实验和复现.DeepID的方法最简单,而DeepID2的实现却略微复杂,并且互联网上也没有比较好的资源 ...
- 卷积神经网络系列之softmax,softmax loss和cross entropy的讲解
我们知道卷积神经网络(CNN)在图像领域的应用已经非常广泛了,一般一个CNN网络主要包含卷积层,池化层(pooling),全连接层,损失层等.虽然现在已经开源了很多深度学习框架(比如MxNet,Caf ...
随机推荐
- mapreduce多文件输出的两方法
mapreduce多文件输出的两方法 package duogemap; import java.io.IOException; import org.apache.hadoop.conf ...
- react-router 组件式配置与对象式配置小区别
1. react-router 对象式配置 和 组件式配置 组件式配置(Redirect) ----对应---- 对象式配置(onEnter钩子) IndexRedirect -----对应-- ...
- Vagrant 基础全面解析
这篇 Vagrant 入门文章将带你创建一个 Vagrant 项目,这个过程将会用到 Vagrant 所提供的主要基本特性.如果想了解 Vagrant 能为你带来哪些好处,可以阅读 Vagrant 官 ...
- [APUE]UNIX进程的环境(下)
一.共享库 共享库使得可执行文件中不再需要包含常用的库函数,而只需在所有进程都可存取的存储区中保存这种库例程的一个副本.程序第一次执行的时候或第一次调用某个库函数的时候,用动态链接方法将程序与共享库函 ...
- 开源免费且稳定实用的.NET PDF打印组件itextSharp(.NET组件介绍之八)
在这个.NET组件的介绍系列中,受到了很多园友的支持,一些园友(如:数据之巅. [秦时明月]等等这些大神 )也给我提出了对应的建议,我正在努力去改正,有不足之处还望大家多多包涵.在传播一些简单的知识的 ...
- fir.im Weekly - 关于 iOS10 适配、开发、推送的一切
"小程序"来了,微信变成名副其实的 Web OS,新一轮的Web App 与Native App争论四起.程序员对新技术永远保持灵敏的嗅觉和旺盛的好奇心,@李锦发整理了微信小程序资 ...
- JAVA面试题
在这里我将收录我面试过程中遇到的一些好玩的面试题目 第一个面试题:ABC问题,有三个线程,工作的内容分别是打印出"A""B""C",需要做的 ...
- DirectX Graphics Infrastructure(DXGI):最佳范例 学习笔记
今天要学习的这篇文章写的算是比较早的了,大概在DX11时代就写好了,当时龙书11版看得很潦草,并没有注意这篇文章,现在看12,觉得是跳不过去的一篇文章,地址如下: https://msdn.micro ...
- Log4Net应用问题
问题 一.日志存储方式 1.txt 2.SQLServer数据库 3.log文件 二.项目类型不同 1winFrom 2webFrom 3MVC 4WPF 5控制台 三.切分依据不同 1.空间大小 2 ...
- hbase集群安装与部署
1.相关环境 centos7 hadoop2.6.5 zookeeper3.4.9 jdk1.8 hbase1.2.4 本篇文章仅涉及hbase集群的搭建,关于hadoop与zookeeper的相关部 ...