一.softmax简介
softmax主要应用的地方是logistic模型对于多分类模型的推广,将多个神经元的输出问题映射到(0,1)区间中,它的公式为
当某个类的预测值很大时该类的分量流趋向于1,其他类的分量值就变得很小。二.前向传播
在caffe中的softmax layer中它的前向传播过程如下所示
输入的向量经过指数化和归一化后进行输出,这就是softmax layer的前向计算过程,主要代码为
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_); //channnel的深度 int dim = bottom[0]->count() / outer_num_; //总的类别的数目,也就是长高channnel caffe_copy(bottom[0]->count(), bottom_data, top_data); //将底层的数据拷贝到顶层缓冲中 // 我们需要先抽取最大值以防止在指数运算时超过了计算机能表达的最大值 for (int i = 0; i < outer_num_; ++i) {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]);//找出了指定num的channnel通道的最大值 }}// subtractioncaffe_cpu_gemm(CblasNoTrans, CblasNoTrans, channels, inner_num_,//将输出缓存区的值减去均值 1, -1., sum_multiplier_.cpu_data(), scale_data, 1., top_data);// exponentiation 求指数caffe_exp (dim, top_data, top_data);// sum after exp 求1+expcaffe_cpu_gemv (CblasTrans, channels, inner_num_, 1., top_data, sum_multiplier_.cpu_data(), 0., scale_data);// division 求softmax的值 即exp/(1+exp)for (int j = 0; j < channels; j++) { caffe_div(inner_num_, top_data, scale_data, top_data); //最后的结果存储在top中的 top_data += inner_num_;}
}
}上述函数的作用就是计算softmax函数的结果并且保存在起初的位置,至于为什么要减去均值视为了放置经过指数化后的值过大无法在计算机中表示而出现上溢的问题,至于下溢的问题对结果无影响所有就不予考虑了。三.反向梯度计算
如上图所示,我们设节点4的输入Z4,输出为a4同理适用于节点5,6则z4=w41a1+w42a2+w43*a3 z5=w51a1+w52a2+w53*a3 z6=w61a1+w62a2+w63*a3a4=exp(z4)/(exp(z4)+exp(z5)+exp(z6))同理可得a5,a6,我们假设更新的是权值w41(对于位于前面的层的权值,我们可以通过梯度传递来进行计算)那么要使用梯度降就必须要有一个损失函数,我们定义损失函数
至于为什么采用该函数为损失函数可以观看cs229课程中有讲解,主要是由极大似然估计得到的,其中batch是批数据中样本的数量。
那么我们要计算的是loss对于w41的导数
由之前的数据我们可以看到a4=exp(z4)/(exp(z4)+exp(z5)+exp(z6))就可以就出a4对于z4的导数。更一般的当我们更新的权值不是直接与softmax layer相连的怎么办?
我们只需要计算loss对于输入Zi的的偏导再进行链式法则传播就可以则一般而言由于其中f(Zk)就是ak,因此loss对于a的偏导数很好计算,其中f就是softmax函数那么对于反向传播的路径本文中就有三条为a4->节点4,a4->节点5,a4->节点6,也许你会疑惑为什么a4的值会和5节点有关系,原因在于softmax函数的分母中包含了exp(z4),exp(z5)和exp(z6)即每个节点的输出值都与所有节点的输入值存在关系,那么
则
推导过程为
最后一部分有书写错误其中ak和f(zk)是一样的,依次累加ai即可得到loss关于zk的偏导数,这就是他的基本原理代码为
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对于a的导数 const Dtype* top_data = top[0]->cpu_data(); //存储的是a(即softmax后的结果) Dtype* bottom_diff = bottom[0]->mutable_cpu_diff(); 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);//将loss关于a的导数拷贝到底层diff for (int i = 0; i < outer_num_; ++i) {// compute dot(top_diff, top_data) and subtract them from the bottom difffor (int k = 0; k < inner_num_; ++k) { scale_data[k] = caffe_cpu_strided_dot(channels, bottom_diff + i * dim + k, inner_num_, top_data + i * dim + k, inner_num_);}//点积即top_diff*top_data// subtraction 减值 即top_diff-top_diff*top_datacaffe_cpu_gemm (CblasNoTrans, CblasNoTrans, channels, inner_num_, 1, -1., sum_multiplier_.cpu_data(), scale_data, 1., bottom_diff + i * dim);
}
// elementwise multiplication 乘法也就是最后一步将结果存储在bottom_diff中 caffe_mul(top[0]->count(), bottom_diff, top_data, bottom_diff);}本文的主要内容来自于感谢博主,本人能力有限,多多见谅