让我们深入到vector中看一下softmax做了什么

Outline:softmax回归和caffe实现

  1. softmax的数学原理
  2. caffe的SoftmaxWithLoss层的实现

1.softmax的数学原理

与logistic regression相比,softmax的不同之处在于适用于多分类问题。而logistic regression只可以解决二分类。

我们来看softmax函数的定义:

J(θ)=1mi=1mj=1k1{y(i)=j}logeθTjx(i)kl=1eθTlx(i)

公式是logistic regression对于多类别的表达,首先说明几个参数:m是每一个batch的样本数量,k是特征的维度。其中i是对于m的遍历指针,j是对于k的遍历指针。
1{}={=1=010

例如:1{2==3}=0,1{3>2}=1

偏导计算:
在求解偏导之前,我们先进行一些notion的替换,目的是为了让公式简洁易懂:
eθTlx(i) = ez

原loss公式改写为:

J(z)=logezkl=1ez(l)=logl=1kez(l)z

则偏导数为:

J(z)z=ez(l)kl=1ez(l)αz

其中:
αz={10l=zl!=z

最终
J(θ)θ=J(z)zzθ
J(z)z 已经求得
zθ 可以在caffe中是softmax层中做的事情,它会成为SoftmaxWithloss层的bottom diff,因此也可以获得(具体的会在caffe实现中讲解)。

实际的离散数字来计算一次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,对特征向量中的每个值都需要进行 eθTjx(i)kl=1eθTlx(i) 运算,这里我们假设的v就是 θTlx(i) ,因此第二步的执行结果是vector=
[e0.1e0.1+e0,e0e0.1+e0] =[0.4750,0.5250]运算。

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,因此:

J(θ)θ=[(e0.1e0.1+e01)zθ,e0e0.1+e0zθ]=[0525zθ,0.5250zθ]

2.SoftmaxWithloss层的实现

在caffe中,我们通过softmaxwithloss层和softmax层共同实现softmax回归。

softmax layer实现 zθ 的计算,而softmaxwithloss实现最终的loss函数计算以及偏导计算。

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);
  }
}

猜你喜欢

转载自blog.csdn.net/zbzb1000/article/details/78170990