我是靠谱客的博主 生动棒棒糖,最近开发中收集的这篇文章主要介绍【caffe】梯度更新,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

最近用caffe做yolo,看到其中loss层中,和ground的匹配的anchor处的loss梯度计算和源码相反:

// https://github.com/pjreddie/darknet/blob/f6d861736038da22c9eb0739dca84003c5a5e275/src/yolo_layer.c#L93-L108
178                    l.delta[obj_index] = 0 - l.output[obj_index];
183                    l.delta[obj_index] = 1 - l.output[obj_index];
223                    l.delta[obj_index] = 1 - l.output[obj_index];

而caffe中都是用output - 0/1作为梯度传入diff。这个区别一度让强迫症如我打算改掉代码(还好大哥及时拉住了我)。查了一些资料也终于明白了其中的道理,这里且做记录。

YOLOv3的loss

yolo用的是平方误差损失函数。具体操作上,yolo有三个金字塔层,每个金字塔的最后一层是卷积层,卷积层的输出经过sigmoid函数之后和ground truth做平方损失。即:
J ( w , b ) = ∑ ( σ ( c o n v ( x ) ) − g t ) 2 J(w,b)=sum(sigma(conv(x))-gt)^2 J(w,b)=(σ(conv(x))gt)2
这个是不是有点眼熟?对咯,这就是逻辑回归的代价函数啊。所以我们按照逻辑回归的算法来推导梯度及权重更新。

逻辑回归

首先,对于代价函数为 J = ∑ i N ( h w ( x i ) − y i ) 2 J=sum_i^N(h_w(x_i)-y_i)^2 J=iN(hw(xi)yi)2的逻辑回归问题,梯度下降过程表示为:
w : = w − l r ∂ J ( w , b ) ∂ w w:=w-lrdfrac{partial J(w,b)}{partial w} w:=wlrwJ(w,b)
b : = b − l r ∂ J ( w , b ) ∂ b b:=b-lrdfrac{partial J(w,b)}{partial b} b:=blrbJ(w,b)
上式的梯度可以表示为
∂ J ( w , b ) ∂ w = − 1 N ∑ i N ( y i − o u t p u t ( x i ) ) x i dfrac{partial J(w,b)}{partial w}=-dfrac{1}{N}sum_i^N(y_i-output(x_i))x_i wJ(w,b)=N1iN(yioutput(xi))xi
∂ J ( w , b ) ∂ b = − 1 N ∑ i N ( y i − o u t p u t ( x i ) ) dfrac{partial J(w,b)}{partial b}=-dfrac{1}{N}sum_i^N(y_i-output(x_i)) bJ(w,b)=N1iN(yioutput(xi))

参考:https://blog.csdn.net/u014258807/article/details/80616647

caffe的权重更新机制

首先我们知道caffe中blob由数据data和梯度diff两部分组成,而二者的更新是在
caffe的权重更新是通过net.cpp中的Net::Update():

// https://github.com/BVLC/caffe/blob/master/src/caffe/net.cpp

template <typename Dtype>
void Net<Dtype>::Update() {
  for (int i = 0; i < learnable_params_.size(); ++i) {
    learnable_params_[i]->Update();
  }
}

而Update()函数的实现为:

// https://github.com/BVLC/caffe/blob/master/src/caffe/blob.cpp

template <typename Dtype>
void Blob<Dtype>::Update() {
  // We will perform update based on where the data is located.
  switch (data_->head()) {
  case SyncedMemory::HEAD_AT_CPU:
    // perform computation on CPU
    caffe_axpy<Dtype>(count_, Dtype(-1),
        static_cast<const Dtype*>(diff_->cpu_data()),
        static_cast<Dtype*>(data_->mutable_cpu_data()));
    break;
  case SyncedMemory::HEAD_AT_GPU:
  case SyncedMemory::SYNCED:
#ifndef CPU_ONLY
    // perform computation on GPU
    caffe_gpu_axpy<Dtype>(count_, Dtype(-1),
        static_cast<const Dtype*>(diff_->gpu_data()),
        static_cast<Dtype*>(data_->mutable_gpu_data()));
#else
    NO_GPU;
#endif
    break;
  default:
    LOG(FATAL) << "Syncedmem not initialized.";
  }
}

其中caffe_axpy()函数的实现为:

// https://github.com/BVLC/caffe/blob/master/src/caffe/util/math_functions.cpp

template <>
void caffe_axpy<float>(const int N, const float alpha, const float* X,
    float* Y) { cblas_saxpy(N, alpha, X, 1, Y, 1); }

template <>
void caffe_axpy<double>(const int N, const double alpha, const double* X,
    double* Y) { cblas_daxpy(N, alpha, X, 1, Y, 1); }

其中cblas_saxpy()的用法:

https://developer.apple.com/documentation/accelerate/1513188-cblas_saxpy?language=objc

void cblas_saxpy(const int __N, const float __alpha, const float *__X, const int __incX, float *__Y, const int __incY);

On return, the contents of vector Y are replaced with the result. The value computed is (alpha * X[i]) + Y[i].

所以更新之后的权重应该是Dtype(-1) * diff_ + data_。

darknet和caffe不同实现的原因

因为caffe按照Dtype(-1) * diff_ + data_更新data_,所以diff_应当传 ∂ J ( w , b ) ∂ σ = ( σ i − g t i ) dfrac{partial J(w,b)}{partial sigma}=(sigma_i-gt_i) σJ(w,b)=(σigti)(此处系数忽略)。那么问题来了,为什么darknet传递的是负梯度呢?其更新源码为:

// https://github.com/pjreddie/darknet/blob/f6d861736038da22c9eb0739dca84003c5a5e275/src/convolutional_layer.c

void update_convolutional_layer(convolutional_layer l, update_args a)
{
    float learning_rate = a.learning_rate*l.learning_rate_scale;
    float momentum = a.momentum;
    float decay = a.decay;
    int batch = a.batch;

    axpy_cpu(l.n, learning_rate/batch, l.bias_updates, 1, l.biases, 1);
    scal_cpu(l.n, momentum, l.bias_updates, 1);

    if(l.scales){
        axpy_cpu(l.n, learning_rate/batch, l.scale_updates, 1, l.scales, 1);
        scal_cpu(l.n, momentum, l.scale_updates, 1);
    }

    axpy_cpu(l.nweights, -decay*batch, l.weights, 1, l.weight_updates, 1);
    axpy_cpu(l.nweights, learning_rate/batch, l.weight_updates, 1, l.weights, 1);
    scal_cpu(l.nweights, momentum, l.weight_updates, 1);
}

可知,其用的是 l r b a t c h dfrac{lr}{batch} batchlr更新的权重,所以当然要传负梯度进去了。

最后

以上就是生动棒棒糖为你收集整理的【caffe】梯度更新的全部内容,希望文章能够帮你解决【caffe】梯度更新所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(46)

评论列表共有 0 条评论

立即
投稿
返回
顶部