基于Caffe的Large Margin Softmax Loss的实现(中)

小喵的唠叨话:前一篇博客,我们做完了L-Softmax的准备工作。而这一章,我们开始进行前馈的研究。

小喵博客: http://miaoerduo.com

博客原文:  http://www.miaoerduo.com/deep-learning/基于caffe的large-margin-softmax-loss的实现(中).html

四、前馈

还记得上一篇博客,小喵给出的三个公式吗?不记得也没关系。

这次,我们要一点一点的通过代码来实现这些公式。小喵主要是GPU上实现前后馈的代码,因为这个层只是用来训练,GPU速度应该会快一点。

我们首先要进行一般的FC层的前馈,因为LM_FC的前馈只是修改了一般的FC中的若干个值,而大部分的值都是没有修改过的。

 1 const Dtype* bottom_data = bottom[0]->gpu_data();
 2 const Dtype* label_data = bottom[1]->gpu_data();
 3 Dtype* top_data = top[0]->mutable_gpu_data();
 4 const Dtype* weight = this->blobs_[0]->gpu_data();
 5 // 普通fc层的计算
 6 if (M_ == 1) {
 7   caffe_gpu_gemv<Dtype>(CblasNoTrans, N_, K_, (Dtype)1.,
 8                        weight, bottom_data, (Dtype)0., top_data);
 9 } else {
10   caffe_gpu_gemm<Dtype>(CblasNoTrans,
11                         transpose_ ? CblasNoTrans : CblasTrans,
12                         M_, N_, K_, (Dtype)1.,
13                         bottom_data, weight, (Dtype)0., top_data);
14 }

这样就计算完了一个普通的FC的前馈。

之后是一些具体的实现。

1,$\cos(\theta_j)=\frac{W_j^Tx_i}{\|W_j\|\|x_i\|}$

这是要求出label为$j$的weight的权值和feature之间的余弦值。公式大家在高中应该就学过了。这样需要出三部分:$W_j^Tx_i$,$\|W_j\|$和$\|x_i\|$。这里$i$表示feature的序号,因为一个mini batch中有很多张图片。$j$表示正确的label值。

$W_j^Tx_i$的计算非常简单,因为FC层的前馈计算出来的就是这个值。因此我们可以直接从FC的前馈结果中直接复制对应位置的结果。

$\|W_j\|$和$\|x_i\|$是比较简单的模值的计算,使用caffe_cpu_dot很容易就可以求得(为什么不使用caffe_gpu_dot呢?因为小喵在使用caffe_gpu_dot的时候,caffe会报一个奇怪的错误,不知道是不是因为GPU的显存不能随意访问的)。

最后的余弦值带入到上面的式子,就一下子搞定~

这里用到了几个变量:

M_: batch size

N_: class num

K_: feature length

 1 // w * x
 2 // 直接从前馈的结果中复制
 3 Dtype *wx_data = this->wx_.mutable_gpu_data();
 4 copy_label_score<Dtype><<<CAFFE_GET_BLOCKS(M_), CAFFE_CUDA_NUM_THREADS>>>(M_, N_, label_data, top_data, wx_data);
 5
 6 // w * w
 7 Dtype *abs_w_data = this->abs_w_.mutable_cpu_data();
 8 for (int m = 0; m < M_; ++ m) {
 9   abs_w_data[m] = caffe_cpu_dot<Dtype>(
10     K_,
11     this->blobs_[0]->cpu_data() + static_cast<int>(label_cpu_data[m]) * K_,
12     this->blobs_[0]->cpu_data() + static_cast<int>(label_cpu_data[m]) * K_
13     );
14 }
15
16 // x * x
17 Dtype *abs_x_data = this->abs_x_.mutable_cpu_data();
18 for (int m = 0; m < M_; ++ m) {
19   abs_x_data[m] = caffe_cpu_dot<Dtype>(
20     K_,
21     bottom[0]->cpu_data() + m * K_,
22     bottom[0]->cpu_data() + m * K_
23     );
24 }
25 // abs_w, abs_x
26 caffe_gpu_powx<Dtype>(M_, this->abs_w_.mutable_gpu_data(), 0.5, this->abs_w_.mutable_gpu_data());
27 caffe_gpu_powx<Dtype>(M_, this->abs_x_.mutable_gpu_data(), 0.5, this->abs_x_.mutable_gpu_data());
28
29 // cos_t = wx / (|x| * |w|)
30 Dtype *cos_t_data = this->cos_t_.mutable_gpu_data();
31 caffe_gpu_div<Dtype>(M_, wx_data, this->abs_x_.gpu_data(), cos_t_data);
32 caffe_gpu_div<Dtype>(M_, cos_t_data, this->abs_w_.gpu_data(), cos_t_data);

其中copy_label_score是我们自己编写的用来复制结果的核函数(如何编写Cuda程序就是另一门学科了):

1 template <typename Dtype>
2 __global__ void copy_label_score(const int M, const int N, const Dtype *label_data, const Dtype *top_data, Dtype *wx_data) {
3   CUDA_KERNEL_LOOP(index, M) {
4     wx_data[index] = top_data[index * N + static_cast<int>(label_data[index])];
5   }
6 }

相信机智如你的喵粉,看到这几行代码,一定可以轻松理解。

这里,小喵想多介绍一点东西。
我们知道Caffe里面的数据都是通过Blob结构来存储的,比如这里的bottom_data,其实就是一个blob,默认形状是(n, c, h, w),n表示的就是batch size,c是channel数,h,w分贝表示高和宽。而且blob中的内存的存储顺序,也和一般的C语言中的数组一样。因此我们这里计算feature的模的时候,是直接每K_个数值计算一次点乘。
同理,weight是存储在this->blobs[0]中的,那么weight的形状又是什么样子的呢?这里非常碰巧的是,如果我们在prototxt中设置的transpose为false的话,weight的形状是N_*K_,也就是说,我们可以将weight看成一个矩阵,它的每一行都与feature直接点乘,得到输出,也就是说weight的每一行都是我们需要计算模值的$W_j$,所以我们计算weight的模的时候,用的计算方法和计算feature模时很相似。我们这里强制设置transpose为false,因为这样计算会比较简单。如果你设成了true,那就必须自己写个求模的函数了。

2,$\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)$

我们在(1)中求出了$\cos(\theta)$,对于给定的margin,只需要代入公式就可以求出$\cos(m\theta)$的值了。

 1 template <typename Dtype>
 2 __global__ void cal_cos_mt(const int count, const unsigned int margin, const int *C_M_N, const Dtype *cos_t_data, Dtype *cos_mt_data) {
 3   CUDA_KERNEL_LOOP(index, count) {
 4     Dtype cos_t = cos_t_data[index];
 5     Dtype sin_t_2 = 1 - cos_t * cos_t;
 6     Dtype cos_mt = 0.;
 7     int flag = -1;
 8     for (int n = 0; n <= (margin / 2); ++ n) {
 9       flag *= -1;
10       cos_mt += flag * C_M_N[2 * n] * powf(cos_t, (margin - 2 * n)) * powf(sin_t_2, n);
11     }
12     cos_mt_data[index] = cos_mt;
13   }
14 }

上面是用来计算$\cos(m\theta)$的cuda函数,调用也十分的简单:

1 // cos(mt)
2 cal_cos_mt<Dtype><<<CAFFE_GET_BLOCKS(M_), CAFFE_CUDA_NUM_THREADS>>>(
3   M_, this->margin, this->C_M_N_.gpu_data(), this->cos_t_.mutable_gpu_data(), this->cos_mt_->mutable_gpu_data());

3,$f_{y_{i}}=(-1)^k\cdot\|W_{y_{i}}\|\|x_{i}\|\cos(m\theta_i)-2k\cdot\|W_{y_i}\|\|x_i\|$

严格上来说,我们需要求的并不是这个式子,而是:

\[f_{y_i}=\frac{\lambda\|W_{y_i}\|\|x_i\|\cos(\theta_{y_i})+\|W_{y_i}\|\|x_i\|\varphi(\theta_{y_i})}{1+\lambda}\]

\[\varphi(\theta)=(-1)^k\cos(m\theta)-2k, \theta\in[\frac{k\pi}{m}, \frac{(k+1)\pi}{m}]\]

可以看出,当$\lambda$为0的时候,这两个式子就退化成前面的一个式子了。

k的求法十分简单,只需要将$\cos(\theta)$与各个区间进行比较就可以得到。

 1 // k
 2 int *k_cpu_data = this->k_.mutable_cpu_data();
 3 const Dtype *cos_t_cpu_data = this->cos_t_.cpu_data();
 4 for (int m = 0; m < M_; ++ m) {
 5   for (int _k = 0; _k < this->cos_theta_bound_.count(); ++ _k) {
 6     if (this->cos_theta_bound_.cpu_data()[_k] < cos_t_cpu_data[m]) {
 7       k_cpu_data[m] = _k - 1;
 8       break;
 9     }
10   }
11 }

最后一步就是计算出真正的前馈值了!按照公式容易编写程序:

 1 template <typename Dtype>
 2 __global__ void LMForward(
 3   const int M, const int N, const float lambda,
 4   const Dtype *label_data, const Dtype *cos_mt_data, const int *k_data,
 5   const Dtype *abs_w_data, const Dtype *abs_x_data, Dtype *top_data) {
 6
 7   CUDA_KERNEL_LOOP(index, M) {
 8     Dtype cos_mt = cos_mt_data[index];
 9     int k = k_data[index];
10     int label = static_cast<int>(label_data[index]);
11     Dtype abs_w = abs_w_data[index];
12     Dtype abs_x = abs_x_data[index];
13     top_data[N * index + label] =  (lambda * top_data[N * index + label] + abs_w * abs_x * ( powf(-1, k) * cos_mt - 2 * k )) / (1 + lambda);
14   }
15 }

调用也十分简单:

1 // y
2 LMForward<Dtype><<<CAFFE_GET_BLOCKS(M_), CAFFE_CUDA_NUM_THREADS>>>(
3   M_, N_, this->lambda,
4   label_data, this->cos_mt_->gpu_data(), this->k_.gpu_data(),
5   this->abs_w_.gpu_data(), this->abs_x_.gpu_data(), top[0]->mutable_gpu_data());

最后附上,完整的前馈代码(省略头文件和caffe的名字空间):

  1 template <typename Dtype>
  2 __global__ void copy_label_score(const int M, const int N, const Dtype *label_data, const Dtype *top_data, Dtype *wx_data) {
  3   CUDA_KERNEL_LOOP(index, M) {
  4     wx_data[index] = top_data[index * N + static_cast<int>(label_data[index])];
  5   }
  6 }
  7
  8 template <typename Dtype>
  9 __global__ void cal_cos_mt(const int count, const unsigned int margin, const int *C_M_N, const Dtype *cos_t_data, Dtype *cos_mt_data) {
 10   CUDA_KERNEL_LOOP(index, count) {
 11     Dtype cos_t = cos_t_data[index];
 12     Dtype sin_t_2 = 1 - cos_t * cos_t;
 13     Dtype cos_mt = 0.;
 14     int flag = -1;
 15     for (int n = 0; n <= (margin / 2); ++ n) {
 16       flag *= -1;
 17       cos_mt += flag * C_M_N[2 * n] * powf(cos_t, (margin - 2 * n)) * powf(sin_t_2, n);
 18     }
 19     cos_mt_data[index] = cos_mt;
 20   }
 21 }
 22
 23 template <typename Dtype>
 24 __global__ void LMForward(
 25   const int M, const int N, const float lambda,
 26   const Dtype *label_data, const Dtype *cos_mt_data, const int *k_data,
 27   const Dtype *abs_w_data, const Dtype *abs_x_data, Dtype *top_data) {
 28
 29   CUDA_KERNEL_LOOP(index, M) {
 30     Dtype cos_mt = cos_mt_data[index];
 31     int k = k_data[index];
 32     int label = static_cast<int>(label_data[index]);
 33     Dtype abs_w = abs_w_data[index];
 34     Dtype abs_x = abs_x_data[index];
 35     top_data[N * index + label] =  (lambda * top_data[N * index + label] + abs_w * abs_x * ( powf(-1, k) * cos_mt - 2 * k )) / (1 + lambda);
 36   }
 37 }
 38
 39 template <typename Dtype>
 40 void LargeMarginInnerProductLayer<Dtype>::Forward_gpu(const vector<Blob<Dtype>*>& bottom,
 41     const vector<Blob<Dtype>*>& top) {
 42   const Dtype* bottom_data = bottom[0]->gpu_data();
 43   const Dtype* label_data = bottom[1]->gpu_data();
 44   Dtype* top_data = top[0]->mutable_gpu_data();
 45   const Dtype* weight = this->blobs_[0]->gpu_data();
 46
 47   // 普通fc层的计算
 48   if (M_ == 1) {
 49     caffe_gpu_gemv<Dtype>(CblasNoTrans, N_, K_, (Dtype)1.,
 50                          weight, bottom_data, (Dtype)0., top_data);
 51   } else {
 52     caffe_gpu_gemm<Dtype>(CblasNoTrans,
 53                           transpose_ ? CblasNoTrans : CblasTrans,
 54                           M_, N_, K_, (Dtype)1.,
 55                           bottom_data, weight, (Dtype)0., top_data);
 56   }
 57
 58   const Dtype* label_cpu_data = bottom[1]->cpu_data();
 59
 60   // w * x
 61   // 直接从前馈的结果中复制
 62   Dtype *wx_data = this->wx_.mutable_gpu_data();
 63   copy_label_score<Dtype><<<CAFFE_GET_BLOCKS(M_), CAFFE_CUDA_NUM_THREADS>>>(M_, N_, label_data, top_data, wx_data);
 64
 65   // w * w
 66   Dtype *abs_w_data = this->abs_w_.mutable_cpu_data();
 67   for (int m = 0; m < M_; ++ m) {
 68     abs_w_data[m] = caffe_cpu_dot<Dtype>(
 69       K_,
 70       this->blobs_[0]->cpu_data() + static_cast<int>(label_cpu_data[m]) * K_,
 71       this->blobs_[0]->cpu_data() + static_cast<int>(label_cpu_data[m]) * K_
 72       );
 73   }
 74
 75   // x * x
 76   Dtype *abs_x_data = this->abs_x_.mutable_cpu_data();
 77   for (int m = 0; m < M_; ++ m) {
 78     abs_x_data[m] = caffe_cpu_dot<Dtype>(
 79       K_,
 80       bottom[0]->cpu_data() + m * K_,
 81       bottom[0]->cpu_data() + m * K_
 82       );
 83   }
 84
 85   // abs_w, abs_x
 86   caffe_gpu_powx<Dtype>(M_, this->abs_w_.mutable_gpu_data(), 0.5, this->abs_w_.mutable_gpu_data());
 87   caffe_gpu_powx<Dtype>(M_, this->abs_x_.mutable_gpu_data(), 0.5, this->abs_x_.mutable_gpu_data());
 88
 89   // cos_t = wx / (|x| * |w|)
 90   Dtype *cos_t_data = this->cos_t_.mutable_gpu_data();
 91   caffe_gpu_div<Dtype>(M_, wx_data, this->abs_x_.gpu_data(), cos_t_data);
 92   caffe_gpu_div<Dtype>(M_, cos_t_data, this->abs_w_.gpu_data(), cos_t_data);
 93
 94   // cos(mt)
 95   cal_cos_mt<Dtype><<<CAFFE_GET_BLOCKS(M_), CAFFE_CUDA_NUM_THREADS>>>(
 96     M_, this->margin,
 97     this->C_M_N_.gpu_data(),
 98     this->cos_t_.gpu_data(),
 99     this->cos_mt_.mutable_gpu_data()
100     );
101
102   // k
103   int *k_cpu_data = this->k_.mutable_cpu_data();
104   const Dtype *cos_t_cpu_data = this->cos_t_.cpu_data();
105   for (int m = 0; m < M_; ++ m) {
106     for (int _k = 0; _k < this->cos_theta_bound_.count(); ++ _k) {
107       if (this->cos_theta_bound_.cpu_data()[_k] < cos_t_cpu_data[m]) {
108         k_cpu_data[m] = _k - 1;
109         break;
110       }
111     }
112   }
113
114   // y
115   LMForward<Dtype><<<CAFFE_GET_BLOCKS(M_), CAFFE_CUDA_NUM_THREADS>>>(
116     M_, N_, this->lambda,
117     label_data, this->cos_mt_.gpu_data(), this->k_.gpu_data(),
118     this->abs_w_.gpu_data(), this->abs_x_.gpu_data(), top[0]->mutable_gpu_data());
119 }

那么,这样关于large margin softmax loss的前馈我们就轻松的实现了。下一篇,我们要讲最复杂的后馈的实现了。

如果您觉得本文对您有帮助,那请小喵喝杯茶吧~~O(∩_∩)O~~ 再次感慨 $\LaTeX$ 大法好。

转载请注明出处~

时间: 2024-08-08 13:56:01

基于Caffe的Large Margin Softmax Loss的实现(中)的相关文章

基于Caffe的Large Margin Softmax Loss的实现(上)

小喵的唠叨话:在写完上一次的博客之后,已经过去了2个月的时间,小喵在此期间,做了大量的实验工作,最终在使用的DeepID2的方法之后,取得了很不错的结果.这次呢,主要讲述一个比较新的论文中的方法,L-Softmax,据说单model在LFW上能达到98.71%的等错误率.更重要的是,小喵觉得这个方法和DeepID2并不冲突,如果二者可以互补,或许单model达到99%+将不是梦想. 再次推销一下~ 小喵的博客网址是: http://www.miaoerduo.com 博客原文:  http://

Large-Margin Softmax Loss for Convolutional Neural Networks

paper url: https://arxiv.org/pdf/1612.02295 year:2017 Introduction 交叉熵损失与softmax一起使用可以说是CNN中最常用的监督组件之一. 尽管该组件简单而且性能出色, 但是它只要求特征的可分性, 没有明确鼓励网络学习到的特征具有类内方差小, 类间方差大的特性. 该文中,作者提出了一个广义的 large margin softmax loss(L-Softmax),是large margin系列的开篇之作. 它明确地鼓励了学习特

基于caffe与MATLAB接口的回归分析与可视化

如果遇到一些问题,可以在这里找下是否有解决方案.本文内容主要分为两部分,第一部分介绍基于caffe的回归分析,包括了数据准备.配置文件等:第二部分介绍了在MATLAB上进行的可视化.(话说本人最近有个课题需要做场景分类,有兴趣可以共同探讨一下). Preparation 预装好caffe on windows,并编译成功MATLAB接口. 通过caffe进行回归分析 通过caffe进行回归分析,在实验上主要分成HDF5数据准备.网络设计.训练.测试.该实验已经有网友做过,可以参考:http://

卷积神经网络系列之softmax,softmax loss和cross entropy的讲解

我们知道卷积神经网络(CNN)在图像领域的应用已经非常广泛了,一般一个CNN网络主要包含卷积层,池化层(pooling),全连接层,损失层等.虽然现在已经开源了很多深度学习框架(比如MxNet,Caffe等),训练一个模型变得非常简单,但是你对这些层具体是怎么实现的了解吗?你对softmax,softmax loss,cross entropy了解吗?相信很多人不一定清楚.虽然网上的资料很多,但是质量参差不齐,常常看得眼花缭乱.为了让大家少走弯路,特地整理了下这些知识点的来龙去脉,希望不仅帮助自

损失函数 hinge loss vs softmax loss

1. 损失函数 损失函数(Loss function)是用来估量你模型的预测值 f(x) 与真实值 Y 的不一致程度,它是一个非负实值函数,通常用 L(Y,f(x)) 来表示. 损失函数越小,模型的鲁棒性就越好. 损失函数是经验风险函数的核心部分,也是结构风险函数的重要组成部分.模型的风险结构包括了风险项和正则项,通常如下所示: 其中,前面的均值函数表示的是经验风险函数,L代表的是损失函数,后面的 Φ 是正则化项(regularizer)或者叫惩罚项(penalty term), 它可以是L1,

归一化指数函数:softmax loss function

1. softmax 损失函数:归一化指数函数,可以将一个K维向量z“压缩”到另一个K维实向量σ(z)中,使每一个元素的范围在(0,1)之间,并且所有元素的和为1. softmax loss包含三个部分:指数化.归一化.取-log(x) ①指数化:是指将一个样本中各个分类的得分指数化,使得各分类的得分都大于等于0,也就是将每个分数x变为e^x,而e^x函数大于0,即保证了非负性 ②归一化:计算指数化后的各个分类的得分在所有分类的得分总和中所占的比例,所以最后得到的是一个分类的分数在总的得分中的比

Large Margin DAGs for Multiclass Classification

Abstract We present a new learning architecture: the Decision Directed Acyclic Graph (DDAG), which is used to combine many two-class classifiers into a multiclass classifiers. For an -class problem, the DDAG contains classifiers, one for each pair of

基于caffe的艺术迁移学习 style-transfer

这个是去年微博里面非常流行的,在git_hub上的代码是https://github.com/fzliu/style-transfer 比如这是梵高的画 这是你自己的画 然后你想生成这样 怎么实现呢在caffe上,很简单. 1 首先在 https://github.com/fzliu/style-transfer 把代码下载下来,另外主要这个代码基于pycaffe的,需要将pycaffe编译好. 最好是在电脑上装一个python progressbar包 在windows cmd下输入 pip

DeepLearning (四) 基于自编码算法与softmax回归的手写数字识别

[原创]Liu_LongPo 转载请注明出处 [CSDN]http://blog.csdn.net/llp1992 softmax 回归模型,是logistic 回归模型在多分类问题上的推广.关于logistic回归算法的介绍,前面博客已经讲得很清楚,详情可以参考博客 机器学习实战ByMatlab(五)Logistic Regression 在logistic回归模型中,我们的激励函数sigmoid的输入为: z=θ0x0+θ1x1+θ2x2+...+θnxn 则可以得到假设函数为: hθ(x)