(1) softmax函数

            caffe中softmax源码阅读-LMLPHP                                     (1)

其中,z是softmax层的bottom输入, f(z)是softmax层的top输出,C为该层的channel数。

(2) softmax_layer.cpp中的Reshape函数:

 template <typename Dtype>
void SoftmaxLayer<Dtype>::Reshape(const vector<Blob<Dtype>*>& bottom, //bottom blob为softmax层的输入,top blob为该层输出。
const vector<Blob<Dtype>*>& top) {
softmax_axis_ = //softmax_axis_为1
bottom[]->CanonicalAxisIndex(this->layer_param_.softmax_param().axis());
top[]->ReshapeLike(*bottom[]); //使用bttom[0]的shape和值去初始化top[0],后面所有的操作基于top[0]
   //bottom[0]的shape为[N, C, H, W], bottom[0]->shape(softmax_axis_)的值为C
vector<int> mult_dims(, bottom[]->shape(softmax_axis_));
//Blob<Dtype> sum_multipiler、Blob<Dtype> scale_、int outer_num_、int inner_num_变量定义在softmax_layer.hpp中
//初始化sum_multiplier, mult_dims的值为C
sum_multiplier_.Reshape(mult_dims);
Dtype* multiplier_data = sum_multiplier_.mutable_cpu_data();
//设置sum_multiplier的所有元素值为1
caffe_set(sum_multiplier_.count(), Dtype(), multiplier_data);
   //blob的shape为[N, C, H, W], 形象点说就是blob->shape[0] = N, blob->shape[1] = C
   //blob的count为N*C*H*W,形象点说就是blob->count() = N*C*H*W
//blob->count(0, 2)中的(0, 2)是左闭右开区间,返回的是N*C
//所以就有outer_num_ = bottom[0]->count(0, softmax_axis_) = N
// inner_num_ = bottom[0]->count(softmax_axis_) = H*W
outer_num_ = bottom[]->count(, softmax_axis_);
inner_num_ = bottom[]->count(softmax_axis_ + );
//下面两行scale_dims的shape为[N, 1, H, W]
vector<int> scale_dims = bottom[]->shape();
scale_dims[softmax_axis_] = ;
   //scale_ blob的shape为[N, 1, H, W]
scale_.Reshape(scale_dims);
}

(3) softmax_layer.cpp中的Forward_cpu函数:

 template <typename Dtype>
void SoftmaxLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
const Dtype* bottom_data = bottom[]->cpu_data();
Dtype* top_data = top[]->mutable_cpu_data();
Dtype* scale_data = scale_.mutable_cpu_data();
int channels = bottom[]->shape(softmax_axis_); //channels = C
int dim = bottom[]->count() / outer_num_; //dim = N*C*H*W / N = C*H*W
caffe_copy(bottom[]->count(), bottom_data, top_data); //将bottom_data的blob数据复制给top_data的blob.
// We need to subtract the max to avoid numerical issues, compute the exp,
// and then normalize.
//求channel最大值,存放在scale_ blob中。
for (int i = ; i < outer_num_; ++i) {
// initialize scale_data to the first plane
caffe_copy(inner_num_, bottom_data + i * dim, scale_data);
for (int j = ; j < channels; j++) {
for (int k = ; k < inner_num_; k++) {
scale_data[k] = std::max(scale_data[k],
bottom_data[i * dim + j * inner_num_ + k]);
}
}
// subtraction
//bottom blob数据减去对应channel的最大值
caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, channels, inner_num_,
, -., sum_multiplier_.cpu_data(), scale_data, ., top_data);
// exponentiation
//对每个样本的每个channel数据取e.
caffe_exp<Dtype>(dim, top_data, top_data);
// sum after exp
// 下面的代码实现的是公式(1)
caffe_cpu_gemv<Dtype>(CblasTrans, channels, inner_num_, .,
top_data, sum_multiplier_.cpu_data(), ., scale_data);
// division
for (int j = ; j < channels; j++) {
caffe_div(inner_num_, top_data, scale_data, top_data);
//指针指向下一个数据
top_data += inner_num_;
}
}
}

该函数分为下面几个步骤:

<1> 求每个样本channel的最大值;

<2> softmax的每个输入减去其所在channel的最大值,即caffe_cpu_gemm函数的功能,该函数的原型为:

 void caffe_cpu_gemm<float>(const CBLAS_TRANSPOSE TransA, const CBLAS_TRANSPOSE TransB, const int M, const int N, const int K, const float alpha, const float *A, const float *B, const float beta, float *C){
int lda = (TransA == CblasNoTrans) ? K : M;
int ldb = (TransB == CblasNoTrans) ? N : K;
cblas_sgemm(CblasRowMajor, TransA, TransB, M, N, K, alpha, A, lda, B, ldb, beta, C, N);
}

cblas_sgemm函数作用为实现矩阵间的乘法,原型为:

//该函数实现的运算为:C = alpha*A*B + beta*C
//cblasTrans/cblasNoTrans表示对输入矩阵是否转置
//M为矩阵A,C的行数,若转置,则表示转置后的行数
//N为矩阵B、C的列数,若转置,则表示转置后的列数
//K为矩阵A的列数,或B的行数,若转置,则为转置后的列数和行数
//alpha, beta为系数
//A'cols为矩阵A的列数,与是否转置无关
//B'cols为矩阵B的列数,与是否转置无关
1 cblas_sgemm(cblasRowMajor, cblasNoTrans cblasNoTrans, M, N, K, alpha, A, A'cols, B, B'cols, beta, C, C'cols)

形象点说,caffe_cpu_gemm实现的功能为:

caffe中softmax源码阅读-LMLPHP

<3> 对top blob中的每个数据取e.

<4> 对每个样本的channel求和,与caffe_cpu_gemm不同的是,caffe_cpu_gemv实现的是矩阵与向量的乘法,具体的相乘过程和上面<2>中一样;

<5> 对每个样本而言,其channel的每个值除以该channel的和,也就是caffe_div完成的功能。

(4) softmax_layer.cpp中的Backward_cpu函数:

因为该函数在实际中没有用到,所以没有作过多阅读。

 template <typename Dtype>
void SoftmaxLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down,
const vector<Blob<Dtype>*>& bottom) {
const Dtype* top_diff = top[]->cpu_diff();
const Dtype* top_data = top[]->cpu_data();
Dtype* bottom_diff = bottom[]->mutable_cpu_diff();
Dtype* scale_data = scale_.mutable_cpu_data();
int channels = top[]->shape(softmax_axis_);
int dim = top[]->count() / outer_num_;
caffe_copy(top[]->count(), top_diff, bottom_diff);
for (int i = ; i < outer_num_; ++i) {
// compute dot(top_diff, top_data) and subtract them from the bottom diff
for (int k = ; k < inner_num_; ++k) {
scale_data[k] = caffe_cpu_strided_dot<Dtype>(channels,
bottom_diff + i * dim + k, inner_num_,
top_data + i * dim + k, inner_num_);
}
// subtraction
caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, channels, inner_num_, ,
-., sum_multiplier_.cpu_data(), scale_data, ., bottom_diff + i * dim);
}
// elementwise multiplication
caffe_mul(top[]->count(), bottom_diff, top_data, bottom_diff);
}
05-29 00:03