1.前向传播部分
这部分直接参照softmax公式:
template <typename Dtype> void SoftmaxLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) { const Dtype* bottom_data = bottom[0]->cpu_data(); //输入数据 Dtype* top_data = top[0]->mutable_cpu_data(); //输出数据 Dtype* scale_data = scale_.mutable_cpu_data(); //保存中间结果数据 int channels = bottom[0]->shape(softmax_axis_); //通道数 int dim = bottom[0]->count() / outer_num_; //类别数目 caffe_copy(bottom[0]->count(), bottom_data, top_data); // We need to subtract the max to avoid numerical issues, compute the exp,指数不能太大,避免数值问题。 // and then normalize. for (int i = 0; i < outer_num_; ++i) { //i表示第i个数据,每个数据有dim维 // initialize scale_data to the first plane caffe_copy(inner_num_, bottom_data + i * dim, scale_data); for (int j = 0; j < channels; j++) { for (int k = 0; k < inner_num_; k++) { scale_data[k] = std::max(scale_data[k], //获得每一参数维的最大值 bottom_data[i * dim + j * inner_num_ + k]); } } // subtraction 通过矩阵相乘的方式来计算,有outer_num个top_data,每层元素减去该层的最大值。太巧妙了 caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, channels, inner_num_, 1, -1., sum_multiplier_.cpu_data(), scale_data, 1., top_data); //C = alpha*op( A )*op( B ) + beta*C // exponentiation 计算自然对数 caffe_exp<Dtype>(dim, top_data, top_data); // sum after exp 每一层各自求和放到scale_data中 caffe_cpu_gemv<Dtype>(CblasTrans, channels, inner_num_, 1., top_data, sum_multiplier_.cpu_data(), 0., scale_data); // division 每一层各自除以该层的和 for (int j = 0; j < channels; j++) { caffe_div(inner_num_, top_data, scale_data, top_data); top_data += inner_num_; } } }
2.后向传播部分
原理分析:
backward过程的输入:1,softmax正向传播的输出a,也就是top_data。
2,loss对a的梯度,也就是top_diff。
backward过程的输出:loss对softmax正向传播的输入z的梯度,保存在bottom_diff。
公式推导过程:
其中 在caffe中是top_diff,a为caffe中得top_data,需要计算的是 if i!=k if i==k 于是 整理一下得到 其中表示将标量扩展为n维向量,表示向量按元素相乘
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[0]->cpu_diff(); //表示loss对softmax层输出a的梯度 const Dtype* top_data = top[0]->cpu_data(); //表示softmax的输出a Dtype* bottom_diff = bottom[0]->mutable_cpu_diff(); //表示loss对softmax层输入z的梯度 Dtype* scale_data = scale_.mutable_cpu_data(); int channels = top[0]->shape(softmax_axis_); int dim = top[0]->count() / outer_num_; caffe_copy(top[0]->count(), top_diff, bottom_diff); //把top_diff复制到了bottom_diff for (int i = 0; i < outer_num_; ++i) { // 此处计算点积,注意到top_diff已经拷贝到bottom_diff for (int k = 0; 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_); } // 此处计算大括号内的减法 caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, channels, inner_num_, 1, -1., sum_multiplier_.cpu_data(), scale_data, 1., bottom_diff + i * dim); } // 此处计算大括号外的元素相乘 caffe_mul(top[0]->count(), bottom_diff, top_data, bottom_diff); }