Outline:softmax回归和caffe实现
- softmax的数学原理
- caffe的SoftmaxWithLoss层的实现
1.softmax的数学原理
与logistic regression相比,softmax的不同之处在于适用于多分类问题。而logistic regression只可以解决二分类。
我们来看softmax函数的定义:
公式是logistic regression对于多类别的表达,首先说明几个参数:m是每一个batch的样本数量,k是特征的维度。其中i是对于m的遍历指针,j是对于k的遍历指针。
例如:1{2==3}=0,1{3>2}=1
偏导计算:
在求解偏导之前,我们先进行一些notion的替换,目的是为了让公式简洁易懂:
原loss公式改写为:
则偏导数为:
其中:
最终
实际的离散数字来计算一次softmax的过程:
假设m=1,也就是batch为1,k=2,也就是最后一层的输出特征维度是2;最后一层的特征向量
vector=[1.3,1.4]
softmax的执行流程为:
1,vector :=vector-max(vector)
vector=[1.3-1.4,1.3-1.3]=[-0.1,0],这一步在上述的公式中没有涉及,仅仅是工程上的实现。原因是为了尽可能的将vector中的值映射到0附近,如果一个特别大的vector进入到softmax进行分类,假设vector=[2000,2002],那么经过指数运算之后的值往往会造成浮点数上溢。
2,对特征向量中的每个值都需要进行
3,第三步标签就要登场啦。不论是在segmentation task还是在classification task,label都是在loss层发挥其作用。
这里我们假设这个识别过程是第0类,对应的标签
label=0,j的取值有两种可能j=0和j=1
因此y=0, 1{y=j} 就是 1{0=1}=0, 1{0=0}=1,因此计算log的时候只对j=0维度进行计算,j=1的维度无论是多少,最终的值都是0
4,计算loss
loss=-log0.4750.
5,计算梯度,反向传播:
由于label=0,因此:
2.SoftmaxWithloss层的实现
在caffe中,我们通过softmaxwithloss层和softmax层共同实现softmax回归。
softmax layer实现
softmax的实现大家可以看caffe的源码,这里我们讲解softmaxwithloss层的实现:
softmax_loss_layer.cpp如下:
template <typename Dtype>
void SoftmaxWithLossLayer<Dtype>::LayerSetUp(
const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) {
LossLayer<Dtype>::LayerSetUp(bottom, top);
LayerParameter softmax_param(this->layer_param_);
softmax_param.set_type("Softmax");
// 创建SoftmaxLayer层
softmax_layer_ = LayerRegistry<Dtype>::CreateLayer(softmax_param);
softmax_bottom_vec_.clear();
// bottom[0]作为SoftmaxLayer层输入
softmax_bottom_vec_.push_back(bottom[0]);
softmax_top_vec_.clear();
// prob_作为SoftmaxLayer层输出
softmax_top_vec_.push_back(&prob_);
// 建立SoftmaxLayer层
softmax_layer_->SetUp(softmax_bottom_vec_, softmax_top_vec_);
has_ignore_label_ =
this->layer_param_.loss_param().has_ignore_label();
if (has_ignore_label_) {
// 如果支持忽略标签,则读取被忽略标签的值
ignore_label_ = this->layer_param_.loss_param().ignore_label();
}
// 确定归一化计数方式
if (!this->layer_param_.loss_param().has_normalization() &&
this->layer_param_.loss_param().has_normalize()) {
normalization_ = this->layer_param_.loss_param().normalize() ?
LossParameter_NormalizationMode_VALID :
LossParameter_NormalizationMode_BATCH_SIZE;
} else {
normalization_ = this->layer_param_.loss_param().normalization();
}
}
template <typename Dtype>
void SoftmaxWithLossLayer<Dtype>::Reshape(
const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) {
LossLayer<Dtype>::Reshape(bottom, top);
softmax_layer_->Reshape(softmax_bottom_vec_, softmax_top_vec_);
softmax_axis_ =
bottom[0]->CanonicalAxisIndex(this->layer_param_.softmax_param().axis());
outer_num_ = bottom[0]->count(0, softmax_axis_);
inner_num_ = bottom[0]->count(softmax_axis_ + 1);
CHECK_EQ(outer_num_ * inner_num_, bottom[1]->count())
<< "Number of labels must match number of predictions; "
<< "e.g., if softmax axis == 1 and prediction shape is (N, C, H, W), "
<< "label count (number of labels) must be N*H*W, "
<< "with integer values in {0, 1, ..., C-1}.";
if (top.size() >= 2) {
// softmax output
top[1]->ReshapeLike(*bottom[0]);
}
}
// 返回归一化计数
template <typename Dtype>
Dtype SoftmaxWithLossLayer<Dtype>::get_normalizer(
LossParameter_NormalizationMode normalization_mode, int valid_count) {
Dtype normalizer;
switch (normalization_mode) {
case LossParameter_NormalizationMode_FULL:
// 返回全部输出样本数
normalizer = Dtype(outer_num_ * inner_num_);
break;
case LossParameter_NormalizationMode_VALID:
if (valid_count == -1) {
normalizer = Dtype(outer_num_ * inner_num_);
} else {
// 只返回有效统计的样本数
normalizer = Dtype(valid_count);
}
break;
case LossParameter_NormalizationMode_BATCH_SIZE:
normalizer = Dtype(outer_num_);
break;
case LossParameter_NormalizationMode_NONE:
normalizer = Dtype(1);
break;
default:
LOG(FATAL) << "Unknown normalization mode: "
<< LossParameter_NormalizationMode_Name(normalization_mode);
}
// 防止使用不带标签的数据出现归一化计数为0,从而导致分母为零
return std::max(Dtype(1.0), normalizer);
}
// CPU正向传导
template <typename Dtype>
void SoftmaxWithLossLayer<Dtype>::Forward_cpu(
const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) {
// 先对SoftmaxLayer层正向传导
softmax_layer_->Forward(softmax_bottom_vec_, softmax_top_vec_);
const Dtype* prob_data = prob_.cpu_data();
const Dtype* label = bottom[1]->cpu_data();
int dim = prob_.count() / outer_num_;
int count = 0;
Dtype loss = 0;
for (int i = 0; i < outer_num_; ++i) {
for (int j = 0; j < inner_num_; j++) {
// 取出真实标签值
const int label_value = static_cast<int>(label[i * inner_num_ + j]);
if (has_ignore_label_ && label_value == ignore_label_) {
continue;
}
DCHECK_GE(label_value, 0);
DCHECK_LT(label_value, prob_.shape(softmax_axis_));
// 从SoftmaxLayer层输出(prob_data)中,找到与标签值对应位的预测概率,对其取-log,并对batch_size个值累加
loss -= log(std::max(prob_data[i * dim + label_value * inner_num_ + j],
Dtype(FLT_MIN)));
++count;
}
}
// loss除以样本总数batch,得到平均单个样本的loss
top[0]->mutable_cpu_data()[0] = loss / get_normalizer(normalization_, count);
if (top.size() == 2) {
top[1]->ShareData(prob_);
}
}
// CPU反向传导
template <typename Dtype>
void SoftmaxWithLossLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) {
if (propagate_down[1]) {
LOG(FATAL) << this->type()
<< " Layer cannot backpropagate to label inputs.";
}
if (propagate_down[0]) {
Dtype* bottom_diff = bottom[0]->mutable_cpu_diff();
const Dtype* prob_data = prob_.cpu_data();
// 先将正向传导时计算的prob_数据(f(y_k))拷贝至偏导
caffe_copy(prob_.count(), prob_data, bottom_diff);
const Dtype* label = bottom[1]->cpu_data();
int dim = prob_.count() / outer_num_;
int count = 0;
for (int i = 0; i < outer_num_; ++i) {
for (int j = 0; j < inner_num_; ++j) {
const int label_value = static_cast<int>(label[i * inner_num_ + j]);
// 如果为忽略标签,则偏导为0
if (has_ignore_label_ && label_value == ignore_label_) {
for (int c = 0; c < bottom[0]->shape(softmax_axis_); ++c) {
bottom_diff[i * dim + c * inner_num_ + j] = 0;
}
} else {
// 计算偏导,预测正确的bottom_diff = f(y_k) - 1,其它不变
bottom_diff[i * dim + label_value * inner_num_ + j] -= 1;
++count;
}
}
}
// top[0]->cpu_diff()[0] = 1.0,已在SetLossWeights()函数中初始化u_diff()[0] /
get_normalizer(normalization_, count);
// 将bottom_diff归一化,bottom_diff就是刚才我们讲解的$\frac{\partial z}{\partial \theta}$
caffe_scal(prob_.count(), loss_weight, bottom_diff);
}
}