小喵的唠叨话:前一篇博客,我们做完了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的实现(中)的更多相关文章

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

    小喵的唠叨话:在写完上一次的博客之后,已经过去了2个月的时间,小喵在此期间,做了大量的实验工作,最终在使用的DeepID2的方法之后,取得了很不错的结果.这次呢,主要讲述一个比较新的论文中的方法,L- ...

  2. Large Margin Softmax Loss for Speaker Verification

    [INTERSPEECH 2019接收] 链接:https://arxiv.org/pdf/1904.03479.pdf 这篇文章在会议的speaker session中.本文主要讨论了说话人验证中的 ...

  3. 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 ...

  4. Large-Margin Softmax Loss for Convolutional Neural Networks

    paper url: https://arxiv.org/pdf/1612.02295 year:2017 Introduction 交叉熵损失与softmax一起使用可以说是CNN中最常用的监督组件 ...

  5. caffe中softmax loss源码阅读

    (1) softmax loss <1> softmax loss的函数形式为:     (1) zi为softmax的输入,f(zi)为softmax的输出. <2> sof ...

  6. 基于Caffe的DeepID2实现(下)

    小喵的唠叨话:这次的博客,真心累伤了小喵的心.但考虑到知识需要巩固和分享,小喵决定这次把剩下的内容都写完. 小喵的博客:http://www.miaoerduo.com 博客原文: http://ww ...

  7. 基于Caffe的DeepID2实现(中)

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

  8. 基于Caffe的DeepID2实现(上)

    小喵的唠叨话:小喵最近在做人脸识别的工作,打算将汤晓鸥前辈的DeepID,DeepID2等算法进行实验和复现.DeepID的方法最简单,而DeepID2的实现却略微复杂,并且互联网上也没有比较好的资源 ...

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

    我们知道卷积神经网络(CNN)在图像领域的应用已经非常广泛了,一般一个CNN网络主要包含卷积层,池化层(pooling),全连接层,损失层等.虽然现在已经开源了很多深度学习框架(比如MxNet,Caf ...

随机推荐

  1. MySQL高级知识- MySQL的架构介绍

    [TOC] 1.MySQL 简介 概述 MySQL是一个关系型数据库管理系统,由瑞典MySQL AB公司开发,目前属于Oracle公司. MySQL是一种关联数据库管理系统,将数据保存在不同的表中,而 ...

  2. 从I/O复用谈epoll为什么高效

    上一篇文章中,谈了一些网络编程的基本概念.在现实使用中,用的最多的就是I/O复用了,无非就是select,poll,epoll 很多人提到网络就说epoll,认为epoll效率是最高的.单纯的这么认为 ...

  3. 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 ...

  4. 戏说HTML5

    如果有非技术人员问你,HTML5是什么,你会怎么回答? 新的HTML规范... 给浏览器提供了牛逼能力,干以前不能干的事...(确切地说应该是给浏览器规定了许多新的接口标准,要求浏览器实现牛逼的功能. ...

  5. 【.net 深呼吸】启动一个进程并实时获取状态信息

    地球人和火星人都知道,Process类既可以获取正在运行的进程,也可以启动一个新的进程.在79.77%应用场合,我们只需要让目标进程顺利启动就完事了,至于它执行了啥,有没有出错,啥时候退出就不管了. ...

  6. .NET平台开源项目速览(13)机器学习组件Accord.NET框架功能介绍

    Accord.NET Framework是在AForge.NET项目的基础上封装和进一步开发而来.因为AForge.NET更注重与一些底层和广度,而Accord.NET Framework更注重与机器 ...

  7. 关于.NET参数传递方式的思考

    年关将近,整个人已经没有了工作和写作的激情,估计这个时候很多人跟我差不多,该相亲的相亲,该聚会喝酒的聚会喝酒,总之就是没有了干活的心思(我有很多想法,但就是叫不动我的手脚,所以我只能看着别人在做我想做 ...

  8. [原] 利用 OVS 建立 VxLAN 虚拟网络实验

    OVS 配置 VxLAN HOST A ------------------------------------------ | zh-veth0(10.1.1.1) VM A | | ---|--- ...

  9. 【原】无脑操作:express + MySQL 实现CRUD

    基于node.js的web开发框架express简单方便,很多项目中都在使用.这里结合MySQL数据库,实现最简单的CRUD操作. 开发环境: IDE:WebStorm DB:MySQL ------ ...

  10. ABAP单元测试最佳实践

    本文包含了我在开发项目中经历过的实用的ABAP单元测试指导方针.我把它们安排成为问答的风格,欢迎任何人添加更多的Q&A's,以完成这个列表. 在我的项目中,只使用传统的ABAP report. ...