基于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中的若干个值,而大部分的值都是没有修改过的。
const Dtype* bottom_data = bottom[]->gpu_data();
const Dtype* label_data = bottom[]->gpu_data();
Dtype* top_data = top[]->mutable_gpu_data();
const Dtype* weight = this->blobs_[]->gpu_data();
// 普通fc层的计算
if (M_ == ) {
caffe_gpu_gemv<Dtype>(CblasNoTrans, N_, K_, (Dtype).,
weight, bottom_data, (Dtype)., top_data);
} else {
caffe_gpu_gemm<Dtype>(CblasNoTrans,
transpose_ ? CblasNoTrans : CblasTrans,
M_, N_, K_, (Dtype).,
bottom_data, weight, (Dtype)., top_data);
}
这样就计算完了一个普通的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
// w * x
// 直接从前馈的结果中复制
Dtype *wx_data = this->wx_.mutable_gpu_data();
copy_label_score<Dtype><<<CAFFE_GET_BLOCKS(M_), CAFFE_CUDA_NUM_THREADS>>>(M_, N_, label_data, top_data, wx_data); // w * w
Dtype *abs_w_data = this->abs_w_.mutable_cpu_data();
for (int m = ; m < M_; ++ m) {
abs_w_data[m] = caffe_cpu_dot<Dtype>(
K_,
this->blobs_[]->cpu_data() + static_cast<int>(label_cpu_data[m]) * K_,
this->blobs_[]->cpu_data() + static_cast<int>(label_cpu_data[m]) * K_
);
} // x * x
Dtype *abs_x_data = this->abs_x_.mutable_cpu_data();
for (int m = ; m < M_; ++ m) {
abs_x_data[m] = caffe_cpu_dot<Dtype>(
K_,
bottom[]->cpu_data() + m * K_,
bottom[]->cpu_data() + m * K_
);
}
// abs_w, abs_x
caffe_gpu_powx<Dtype>(M_, this->abs_w_.mutable_gpu_data(), 0.5, this->abs_w_.mutable_gpu_data());
caffe_gpu_powx<Dtype>(M_, this->abs_x_.mutable_gpu_data(), 0.5, this->abs_x_.mutable_gpu_data()); // cos_t = wx / (|x| * |w|)
Dtype *cos_t_data = this->cos_t_.mutable_gpu_data();
caffe_gpu_div<Dtype>(M_, wx_data, this->abs_x_.gpu_data(), cos_t_data);
caffe_gpu_div<Dtype>(M_, cos_t_data, this->abs_w_.gpu_data(), cos_t_data);
其中copy_label_score是我们自己编写的用来复制结果的核函数(如何编写Cuda程序就是另一门学科了):
template <typename Dtype>
__global__ void copy_label_score(const int M, const int N, const Dtype *label_data, const Dtype *top_data, Dtype *wx_data) {
CUDA_KERNEL_LOOP(index, M) {
wx_data[index] = top_data[index * N + static_cast<int>(label_data[index])];
}
}
相信机智如你的喵粉,看到这几行代码,一定可以轻松理解。
我们知道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)$的值了。
template <typename Dtype>
__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) {
CUDA_KERNEL_LOOP(index, count) {
Dtype cos_t = cos_t_data[index];
Dtype sin_t_2 = - cos_t * cos_t;
Dtype cos_mt = .;
int flag = -;
for (int n = ; n <= (margin / ); ++ n) {
flag *= -;
cos_mt += flag * C_M_N[ * n] * powf(cos_t, (margin - * n)) * powf(sin_t_2, n);
}
cos_mt_data[index] = cos_mt;
}
}
上面是用来计算$\cos(m\theta)$的cuda函数,调用也十分的简单:
// cos(mt)
cal_cos_mt<Dtype><<<CAFFE_GET_BLOCKS(M_), CAFFE_CUDA_NUM_THREADS>>>(
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)$与各个区间进行比较就可以得到。
// k
int *k_cpu_data = this->k_.mutable_cpu_data();
const Dtype *cos_t_cpu_data = this->cos_t_.cpu_data();
for (int m = ; m < M_; ++ m) {
for (int _k = ; _k < this->cos_theta_bound_.count(); ++ _k) {
if (this->cos_theta_bound_.cpu_data()[_k] < cos_t_cpu_data[m]) {
k_cpu_data[m] = _k - ;
break;
}
}
}
最后一步就是计算出真正的前馈值了!按照公式容易编写程序:
template <typename Dtype>
__global__ void LMForward(
const int M, const int N, const float lambda,
const Dtype *label_data, const Dtype *cos_mt_data, const int *k_data,
const Dtype *abs_w_data, const Dtype *abs_x_data, Dtype *top_data) { CUDA_KERNEL_LOOP(index, M) {
Dtype cos_mt = cos_mt_data[index];
int k = k_data[index];
int label = static_cast<int>(label_data[index]);
Dtype abs_w = abs_w_data[index];
Dtype abs_x = abs_x_data[index];
top_data[N * index + label] = (lambda * top_data[N * index + label] + abs_w * abs_x * ( powf(-, k) * cos_mt - * k )) / ( + lambda);
}
}
调用也十分简单:
// y
LMForward<Dtype><<<CAFFE_GET_BLOCKS(M_), CAFFE_CUDA_NUM_THREADS>>>(
M_, N_, this->lambda,
label_data, this->cos_mt_->gpu_data(), this->k_.gpu_data(),
this->abs_w_.gpu_data(), this->abs_x_.gpu_data(), top[]->mutable_gpu_data());
最后附上,完整的前馈代码(省略头文件和caffe的名字空间):
template <typename Dtype>
__global__ void copy_label_score(const int M, const int N, const Dtype *label_data, const Dtype *top_data, Dtype *wx_data) {
CUDA_KERNEL_LOOP(index, M) {
wx_data[index] = top_data[index * N + static_cast<int>(label_data[index])];
}
} template <typename Dtype>
__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) {
CUDA_KERNEL_LOOP(index, count) {
Dtype cos_t = cos_t_data[index];
Dtype sin_t_2 = - cos_t * cos_t;
Dtype cos_mt = .;
int flag = -;
for (int n = ; n <= (margin / ); ++ n) {
flag *= -;
cos_mt += flag * C_M_N[ * n] * powf(cos_t, (margin - * n)) * powf(sin_t_2, n);
}
cos_mt_data[index] = cos_mt;
}
} template <typename Dtype>
__global__ void LMForward(
const int M, const int N, const float lambda,
const Dtype *label_data, const Dtype *cos_mt_data, const int *k_data,
const Dtype *abs_w_data, const Dtype *abs_x_data, Dtype *top_data) { CUDA_KERNEL_LOOP(index, M) {
Dtype cos_mt = cos_mt_data[index];
int k = k_data[index];
int label = static_cast<int>(label_data[index]);
Dtype abs_w = abs_w_data[index];
Dtype abs_x = abs_x_data[index];
top_data[N * index + label] = (lambda * top_data[N * index + label] + abs_w * abs_x * ( powf(-, k) * cos_mt - * k )) / ( + lambda);
}
} template <typename Dtype>
void LargeMarginInnerProductLayer<Dtype>::Forward_gpu(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
const Dtype* bottom_data = bottom[]->gpu_data();
const Dtype* label_data = bottom[]->gpu_data();
Dtype* top_data = top[]->mutable_gpu_data();
const Dtype* weight = this->blobs_[]->gpu_data(); // 普通fc层的计算
if (M_ == ) {
caffe_gpu_gemv<Dtype>(CblasNoTrans, N_, K_, (Dtype).,
weight, bottom_data, (Dtype)., top_data);
} else {
caffe_gpu_gemm<Dtype>(CblasNoTrans,
transpose_ ? CblasNoTrans : CblasTrans,
M_, N_, K_, (Dtype).,
bottom_data, weight, (Dtype)., top_data);
} const Dtype* label_cpu_data = bottom[]->cpu_data(); // w * x
// 直接从前馈的结果中复制
Dtype *wx_data = this->wx_.mutable_gpu_data();
copy_label_score<Dtype><<<CAFFE_GET_BLOCKS(M_), CAFFE_CUDA_NUM_THREADS>>>(M_, N_, label_data, top_data, wx_data); // w * w
Dtype *abs_w_data = this->abs_w_.mutable_cpu_data();
for (int m = ; m < M_; ++ m) {
abs_w_data[m] = caffe_cpu_dot<Dtype>(
K_,
this->blobs_[]->cpu_data() + static_cast<int>(label_cpu_data[m]) * K_,
this->blobs_[]->cpu_data() + static_cast<int>(label_cpu_data[m]) * K_
);
} // x * x
Dtype *abs_x_data = this->abs_x_.mutable_cpu_data();
for (int m = ; m < M_; ++ m) {
abs_x_data[m] = caffe_cpu_dot<Dtype>(
K_,
bottom[]->cpu_data() + m * K_,
bottom[]->cpu_data() + m * K_
);
} // abs_w, abs_x
caffe_gpu_powx<Dtype>(M_, this->abs_w_.mutable_gpu_data(), 0.5, this->abs_w_.mutable_gpu_data());
caffe_gpu_powx<Dtype>(M_, this->abs_x_.mutable_gpu_data(), 0.5, this->abs_x_.mutable_gpu_data()); // cos_t = wx / (|x| * |w|)
Dtype *cos_t_data = this->cos_t_.mutable_gpu_data();
caffe_gpu_div<Dtype>(M_, wx_data, this->abs_x_.gpu_data(), cos_t_data);
caffe_gpu_div<Dtype>(M_, cos_t_data, this->abs_w_.gpu_data(), cos_t_data); // cos(mt)
cal_cos_mt<Dtype><<<CAFFE_GET_BLOCKS(M_), CAFFE_CUDA_NUM_THREADS>>>(
M_, this->margin,
this->C_M_N_.gpu_data(),
this->cos_t_.gpu_data(),
this->cos_mt_.mutable_gpu_data()
); // k
int *k_cpu_data = this->k_.mutable_cpu_data();
const Dtype *cos_t_cpu_data = this->cos_t_.cpu_data();
for (int m = ; m < M_; ++ m) {
for (int _k = ; _k < this->cos_theta_bound_.count(); ++ _k) {
if (this->cos_theta_bound_.cpu_data()[_k] < cos_t_cpu_data[m]) {
k_cpu_data[m] = _k - ;
break;
}
}
} // y
LMForward<Dtype><<<CAFFE_GET_BLOCKS(M_), CAFFE_CUDA_NUM_THREADS>>>(
M_, N_, this->lambda,
label_data, this->cos_mt_.gpu_data(), this->k_.gpu_data(),
this->abs_w_.gpu_data(), this->abs_x_.gpu_data(), top[]->mutable_gpu_data());
}
那么,这样关于large margin softmax loss的前馈我们就轻松的实现了。下一篇,我们要讲最复杂的后馈的实现了。
如果您觉得本文对您有帮助,那请小喵喝杯茶吧~~O(∩_∩)O~~ 再次感慨 $\LaTeX$ 大法好。

转载请注明出处~
基于Caffe的Large Margin Softmax Loss的实现(中)的更多相关文章
- 基于Caffe的Large Margin Softmax Loss的实现(上)
小喵的唠叨话:在写完上一次的博客之后,已经过去了2个月的时间,小喵在此期间,做了大量的实验工作,最终在使用的DeepID2的方法之后,取得了很不错的结果.这次呢,主要讲述一个比较新的论文中的方法,L- ...
- 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 ...
随机推荐
- MySQL高级知识- MySQL的架构介绍
[TOC] 1.MySQL 简介 概述 MySQL是一个关系型数据库管理系统,由瑞典MySQL AB公司开发,目前属于Oracle公司. MySQL是一种关联数据库管理系统,将数据保存在不同的表中,而 ...
- 从I/O复用谈epoll为什么高效
上一篇文章中,谈了一些网络编程的基本概念.在现实使用中,用的最多的就是I/O复用了,无非就是select,poll,epoll 很多人提到网络就说epoll,认为epoll效率是最高的.单纯的这么认为 ...
- ASP.NET Core 1.1.0 Release Notes
ASP.NET Core 1.1.0 Release Notes We are pleased to announce the release of ASP.NET Core 1.1.0! Antif ...
- 戏说HTML5
如果有非技术人员问你,HTML5是什么,你会怎么回答? 新的HTML规范... 给浏览器提供了牛逼能力,干以前不能干的事...(确切地说应该是给浏览器规定了许多新的接口标准,要求浏览器实现牛逼的功能. ...
- 【.net 深呼吸】启动一个进程并实时获取状态信息
地球人和火星人都知道,Process类既可以获取正在运行的进程,也可以启动一个新的进程.在79.77%应用场合,我们只需要让目标进程顺利启动就完事了,至于它执行了啥,有没有出错,啥时候退出就不管了. ...
- .NET平台开源项目速览(13)机器学习组件Accord.NET框架功能介绍
Accord.NET Framework是在AForge.NET项目的基础上封装和进一步开发而来.因为AForge.NET更注重与一些底层和广度,而Accord.NET Framework更注重与机器 ...
- 关于.NET参数传递方式的思考
年关将近,整个人已经没有了工作和写作的激情,估计这个时候很多人跟我差不多,该相亲的相亲,该聚会喝酒的聚会喝酒,总之就是没有了干活的心思(我有很多想法,但就是叫不动我的手脚,所以我只能看着别人在做我想做 ...
- [原] 利用 OVS 建立 VxLAN 虚拟网络实验
OVS 配置 VxLAN HOST A ------------------------------------------ | zh-veth0(10.1.1.1) VM A | | ---|--- ...
- 【原】无脑操作:express + MySQL 实现CRUD
基于node.js的web开发框架express简单方便,很多项目中都在使用.这里结合MySQL数据库,实现最简单的CRUD操作. 开发环境: IDE:WebStorm DB:MySQL ------ ...
- ABAP单元测试最佳实践
本文包含了我在开发项目中经历过的实用的ABAP单元测试指导方针.我把它们安排成为问答的风格,欢迎任何人添加更多的Q&A's,以完成这个列表. 在我的项目中,只使用传统的ABAP report. ...