SGD简介

caffe中的SGDSolver类中实现了带动量的梯度下降法,其原理如下,\(lr\)为学习率,\(m\)为动量参数。

  1. 计算新的动量:history_data = local_rate * param_diff + momentum * history_data

    \(\nu_{t+1}=lr*\nabla_{\theta_{t}}+m*\nu_{t}\)
  2. 计算更新时使用的梯度:param_diff = history_data

    \(\Delta\theta_{t+1}=\nu_{t+1}\)
  3. 应用更新:param_data = param_data - param_diff

    \(\theta_{t+1}=\theta_{t}-\Delta\theta_{t+1}\)

步骤1和步骤2均在SGDSolver类的ComputeUpdateValue()函数中实现,步骤3对每个优化方法来说都是相同的,代码可参考之前的博客:Caffe源码-SGDSolver类

NAG(Nesterov Accelerated Gradient)简介

NAG算法在NesterovSolver类中实现,NAG与SGD相比唯一区别在于梯度的计算上。如上,SGD使用的梯度是参数\(\theta_{t}\)在当前位置的梯度\(\nabla_{\theta_{t}}\),而NAG中使用的是当前参数\(\theta_{t}\)在施加了动量之后的位置的梯度\(\nabla_{(\theta_{t}-m*\nu_{t})}\),其原理为:

  1. 应用临时更新:\(\tilde{\theta}_{t+1}=\theta_{t}-m*\nu_{t}\)
  2. 计算该位置的梯度:\(\nabla_{\tilde{\theta}_{t+1}}\)
  3. 计算新的动量:\(\nu_{t+1}=lr*\nabla_{\tilde{\theta}_{t+1}}+m*\nu_{t}\)
  4. 得到更新时使用的梯度:\(\Delta\theta_{t+1}=\nu_{t+1}\)
  5. 应用更新:\(\theta_{t+1}=\theta_{t}-\Delta\theta_{t+1}\)

网络上有一张常见的图用于表示SGD和NAG的过程。

对于SGD算法,蓝色向量\(p_{1}\)为当前参数\(\theta_{t}\)在该位置的梯度\(lr*\nabla_{\theta_{t}}\),蓝色向量\(p_{2}\)为动量\(m*\nu_{t}\),而\(p_{1}+p_{2}\)即为参数一次的更新量\(\Delta\theta_{t+1}\)。

对于NAG算法,\(O_{1}\)为参数\(\theta_{t}\)的初始位置,棕色向量\(p_{3}=p_{2}\),先计算运用动量后的参数\(\tilde{\theta}_{t+1}\)的位置\(O_{2}\),然后计算该位置梯度\(\nabla_{\tilde{\theta}_{t+1}}\),即为图中的红色向量\(p_{4}\),而\(p_{5}=p_{3}+p_{4}\)即为参数一次的更新量\(\Delta\theta_{t+1}=\nu_{t+1}\)。之后仿照该步骤计算下一次迭代的动量\(m*\nu_{t+1}\)(棕色向量\(p_{6}\))和梯度\(\nabla_{\tilde{\theta}_{t+2}}\)(红色向量\(p_{7}\)),得到更新量\(p_{8}\)。

NAG算法的原理还是很好理解的,但是实现起来却有一个非常难理解的地方,即如何计算参数临时更新位置的梯度\(\nabla_{\tilde{\theta}_{t+1}}\)?神经网络这种复杂的系统中想要根据当前位置的梯度\(\nabla_{\theta_{t}}\)来估算另一位置的梯度\(\nabla_{\tilde{\theta}_{t+1}}\)几乎是不可能的。网络上关于该算法的实现细节非常少,不过结合caffe代码和其他的开源代码等,可以判断出,NAG算法每次迭代时保存的参数是临时参数\(\tilde{\theta}_{t+1}\)(位置\(O_{2}\)),而非初始\(O_{1}\)位置处的参数\(\theta_{t}\),这样每次反向传播计算出的梯度实际上就是红色向量\(p_{4}\)。然后每次更新时,会根据动量\(p_{3}\)先将参数从位置\(O_{2}\)退回\(O_{1}\),然后计算得到一次迭代的更新量\(p_{5}\),使参数更新\(\theta_{t+1}\)(位置\(O_{3}\)),并保存下一次迭代时需要使用的临时参数\(\tilde{\theta}_{t+2}\)(位置\(O_{4}\))。

nesterov_solver.cpp源码

  1. //根据当前迭代次数对应的学习率rate,计算网络中第param_id个可学习参数在更新时使用的梯度
  2. template <typename Dtype>
  3. void NesterovSolver<Dtype>::ComputeUpdateValue(int param_id, Dtype rate) {
  4. const vector<Blob<Dtype>*>& net_params = this->net_->learnable_params(); //网络中的所有可学习参数
  5. const vector<float>& net_params_lr = this->net_->params_lr(); //网络中每个参数对应的学习率系数
  6. Dtype momentum = this->param_.momentum(); //求解器设置的动量
  7. Dtype local_rate = rate * net_params_lr[param_id]; //得到当前参数对应的学习率
  8. switch (Caffe::mode()) {
  9. case Caffe::CPU: { //CPU模式
  10. // save history momentum for stepping back
  11. caffe_copy(net_params[param_id]->count(), this->history_[param_id]->cpu_data(),
  12. this->update_[param_id]->mutable_cpu_data()); //将历史数据history_拷贝至update_中,update_data = history_data
  13. // update history //history_data = local_rate * net_params_diff + momentum * history_data
  14. caffe_cpu_axpby(net_params[param_id]->count(), local_rate, net_params[param_id]->cpu_diff(), momentum,
  15. this->history_[param_id]->mutable_cpu_data());
  16. // compute update: step back then over step //update_data = (1 + momentum) * history_data + (-momentum) * update_data
  17. caffe_cpu_axpby(net_params[param_id]->count(), Dtype(1) + momentum,
  18. this->history_[param_id]->cpu_data(), -momentum,
  19. this->update_[param_id]->mutable_cpu_data());
  20. // copy //net_params_diff = update_data
  21. caffe_copy(net_params[param_id]->count(), this->update_[param_id]->cpu_data(),
  22. net_params[param_id]->mutable_cpu_diff());
  23. break;
  24. }
  25. case Caffe::GPU: {
  26. #ifndef CPU_ONLY
  27. // gpu的操作同理
  28. // h_temp = history_data
  29. // history_data = momentum * h_temp + local_rate * net_params_diff
  30. // net_params_diff = (1+momentum) * history_data - momentum * h_temp
  31. nesterov_update_gpu(net_params[param_id]->count(), net_params[param_id]->mutable_gpu_diff(),
  32. this->history_[param_id]->mutable_gpu_data(), momentum, local_rate);
  33. #else
  34. NO_GPU;
  35. #endif
  36. break;
  37. }
  38. default:
  39. LOG(FATAL) << "Unknown caffe mode: " << Caffe::mode();
  40. }
  41. }

对应上述的说明,代码中的各步操作为:

  1. 当前迭代的动量\(\nu_{t}\):update_data = history_data
  2. net_params_diff临时位置的参数的梯度\(\nabla_{\tilde{\theta}_{t+1}}\),计算新的动量:history_data = local_rate * net_params_diff + momentum * history_data

    \(\nu_{t+1}=lr*\nabla_{\tilde{\theta}_{t+1}}+m*\nu_{t}\)
  3. 计算下一次迭代的临时参数相对于当前临时参数的更新量\(\Delta\tilde{\theta}_{t+2}\):update_data = (1 + momentum) * history_data + (-momentum) * update_data

    \(\Delta\tilde{\theta}_{t+2}=(1+m)*\nu_{t+1}-m*\nu_{t}\)

    注意,当前临时参数在位置\(O_{2}\),需要减去向量\(p_{3}\)(\(p_{3}=m*\nu_{t}\)),再加上向量\(p_{5}\)和\(p_{6}\)(\(p_{5}=\nu_{t+1},p_{6}=m*\nu_{t+1}\))才能得到新的临时位置\(O_{4}\)。
  4. 保存参数更新量:net_params_diff = update_data
  5. 应用更新:\(\tilde{\theta}_{t+2}=\tilde{\theta}_{t+1}-\Delta\tilde{\theta}_{t+2}\)

AdaGrad简介

AdaGrad算法通过缩放每个参数反比于其所有梯度历史平方值总和的平方跟,可使得具有较大梯度的参数能够快速下降,使具有小偏导的参数能够缓慢下降。

其原理如下,初始累积变量\(r=0\),\(\delta\)为较小常数,防止除法除数过小而不稳定。

  1. 累加平方梯度(\(\odot\)为逐元素点乘):\(r_{t+1}=r_{t}+\nabla_{\theta_{t}}\odot\nabla_{\theta_{t}}\)
  2. 计算梯度的更新量:\(\Delta\theta_{t+1}=\frac{lr}{\delta+\sqrt{r_{t+1}}}\odot\nabla_{\theta_{t}}\)
  3. 应用更新:\(\theta_{t+1}=\theta_{t}-\Delta\theta_{t+1}\)

adagrad_solver.cpp源码

  1. template <typename Dtype>
  2. void AdaGradSolver<Dtype>::ComputeUpdateValue(int param_id, Dtype rate) {
  3. const vector<Blob<Dtype>*>& net_params = this->net_->learnable_params();
  4. const vector<float>& net_params_lr = this->net_->params_lr();
  5. Dtype delta = this->param_.delta();
  6. Dtype local_rate = rate * net_params_lr[param_id];
  7. switch (Caffe::mode()) {
  8. case Caffe::CPU: {
  9. // compute square of gradient in update
  10. caffe_powx(net_params[param_id]->count(),
  11. net_params[param_id]->cpu_diff(), Dtype(2),
  12. this->update_[param_id]->mutable_cpu_data()); //update_data = net_params ^ 2
  13. // update history
  14. caffe_add(net_params[param_id]->count(),
  15. this->update_[param_id]->cpu_data(),
  16. this->history_[param_id]->cpu_data(),
  17. this->history_[param_id]->mutable_cpu_data()); //history_data = update_data + history_data
  18. // prepare update
  19. caffe_powx(net_params[param_id]->count(), this->history_[param_id]->cpu_data(), Dtype(0.5),
  20. this->update_[param_id]->mutable_cpu_data()); //update_data = history_data ^ 0.5
  21. caffe_add_scalar(net_params[param_id]->count(),
  22. delta, this->update_[param_id]->mutable_cpu_data()); //update_data += delta
  23. caffe_div(net_params[param_id]->count(),
  24. net_params[param_id]->cpu_diff(),
  25. this->update_[param_id]->cpu_data(),
  26. this->update_[param_id]->mutable_cpu_data()); //update_data = net_params_diff / update_data
  27. // scale and copy
  28. caffe_cpu_axpby(net_params[param_id]->count(), local_rate,
  29. this->update_[param_id]->cpu_data(), Dtype(0),
  30. net_params[param_id]->mutable_cpu_diff()); //net_params_diff = local_rate * update_data + 0 * net_params_diff
  31. break;
  32. }
  33. case Caffe::GPU: { //gpu操作同理
  34. #ifndef CPU_ONLY
  35. // gi = net_params_diff;
  36. // hi = history_data = history_data + gi*gi;
  37. // net_params_diff = local_rate * gi / (sqrt(hi) + delta);
  38. adagrad_update_gpu(net_params[param_id]->count(),
  39. net_params[param_id]->mutable_gpu_diff(),
  40. this->history_[param_id]->mutable_gpu_data(), delta, local_rate);
  41. #else
  42. NO_GPU;
  43. #endif
  44. break;
  45. }
  46. default:
  47. LOG(FATAL) << "Unknown caffe mode: " << Caffe::mode();
  48. }
  49. }

AdaGrad/RMSProp/AdaDelta/Adam算法的caffe代码很容易找到对应的公式,不再详细介绍。

RMSProp简介

RMSProp算法在AdaGrad基础上增加一个衰减系数\(\rho\),以便将很早之前的历史梯度数据丢弃。

其原理如下,初始累积变量\(r=0\),\(\delta\)同样为较小常数。

  1. 累加平方梯度:\(r_{t+1}=\rho*r_{t}+(1-\rho)*\nabla_{\theta_{t}}\odot\nabla_{\theta_{t}}\)
  2. 计算梯度的更新量:\(\Delta\theta_{t+1}=\frac{lr}{\delta+\sqrt{r_{t+1}}}\odot\nabla_{\theta_{t}}\)
  3. 应用更新:\(\theta_{t+1}=\theta_{t}-\Delta\theta_{t+1}\)

rmsprop_solver.cpp源码

  1. template <typename Dtype>
  2. void RMSPropSolver<Dtype>::ComputeUpdateValue(int param_id, Dtype rate) {
  3. const vector<Blob<Dtype>*>& net_params = this->net_->learnable_params(); //所有可学习参数
  4. const vector<float>& net_params_lr = this->net_->params_lr(); //参数对应的学习率系数
  5. // get the learning rate
  6. Dtype delta = this->param_.delta(); //常数delta
  7. Dtype rms_decay = this->param_.rms_decay(); //衰减速率
  8. Dtype local_rate = rate * net_params_lr[param_id]; //参数对应的学习率
  9. switch (Caffe::mode()) {
  10. case Caffe::CPU:
  11. // compute square of gradient in update
  12. caffe_powx(net_params[param_id]->count(), net_params[param_id]->cpu_diff(), Dtype(2),
  13. this->update_[param_id]->mutable_cpu_data()); //update_data = net_params_diff ^ 2
  14. // update history //history_data = (1-rms_decay) * update_data + rms_decay * history_data
  15. caffe_cpu_axpby(net_params[param_id] -> count(), Dtype(1-rms_decay), this->update_[param_id]->cpu_data(),
  16. rms_decay, this->history_[param_id]-> mutable_cpu_data());
  17. // prepare update
  18. caffe_powx(net_params[param_id]->count(), this->history_[param_id]->cpu_data(), Dtype(0.5),
  19. this->update_[param_id]->mutable_cpu_data()); //update_data = history_data ^ 0.5
  20. caffe_add_scalar(net_params[param_id]->count(),
  21. delta, this->update_[param_id]->mutable_cpu_data()); //update_data += delta
  22. //update_data = net_params_diff / update_data
  23. caffe_div(net_params[param_id]->count(), net_params[param_id]->cpu_diff(),
  24. this->update_[param_id]->cpu_data(), this->update_[param_id]->mutable_cpu_data());
  25. // scale and copy
  26. caffe_cpu_axpby(net_params[param_id]->count(), local_rate,
  27. this->update_[param_id]->cpu_data(), Dtype(0),
  28. net_params[param_id]->mutable_cpu_diff()); //net_params_diff = local_rate * update_data + 0 * net_params_diff
  29. break;
  30. case Caffe::GPU:
  31. #ifndef CPU_ONLY
  32. // g = net_params_diff
  33. // h = history_data
  34. // gi = g[i];
  35. // hi = h[i] = rms_decay*h[i] + (1-rms_decay)*gi*gi;
  36. // g[i] = local_rate * g[i] / (sqrt(hi) + delta);
  37. rmsprop_update_gpu(net_params[param_id]->count(),
  38. net_params[param_id]->mutable_gpu_diff(),
  39. this->history_[param_id]->mutable_gpu_data(),
  40. rms_decay, delta, local_rate);
  41. #else
  42. NO_GPU;
  43. #endif
  44. break;
  45. default:
  46. LOG(FATAL) << "Unknown caffe mode: " << Caffe::mode();
  47. }
  48. }

AdaDelta简介

AdaDelta也像RMSProp算法一样在AdaGrad基础上增加一个衰减系数\(\rho\),并且还额外维护一个状态量\(x\)。

其原理如下,初始累积变量\(x=0, r=0\),\(\delta\)同样为较小常数。

  1. 累加平方梯度:\(r_{t+1}=\rho*r_{t}+(1-\rho)*\nabla_{\theta_{t}}\odot\nabla_{\theta_{t}}\)
  2. 计算不带学习率的梯度的更新量:\(\Delta\tilde{\theta}_{t+1}=\sqrt{\frac{x_{t}+\delta}{r_{t+1}+\delta}}\odot\nabla_{\theta_{t}}\)
  3. 更新状态量:\(x_{t+1}=\rho*x_{t}+(1-\rho)*\Delta\tilde{\theta}_{t+1}\odot\Delta\tilde{\theta}_{t+1}\)
  4. 计算带学习率的梯度的更新量:\(\Delta\theta_{t+1}=lr*\Delta\tilde{\theta}_{t+1}\)

    参考 4中的说明不同,caffe代码中仍然有使用学习率\(lr\)。
  5. 应用更新:\(\theta_{t+1}=\theta_{t}-\Delta\theta_{t+1}\)

adadelta_solver.cpp源码

  1. template <typename Dtype>
  2. void AdaDeltaSolver<Dtype>::AdaDeltaPreSolve() { //AdaDeltaSolver类在构造时会调用该函数
  3. // Add the extra history entries for AdaDelta after those from SGDSolver::PreSolve
  4. const vector<Blob<Dtype>*>& net_params = this->net_->learnable_params(); //当前网络中的所有可学习参数
  5. for (int i = 0; i < net_params.size(); ++i) {
  6. const vector<int>& shape = net_params[i]->shape(); //第i个可学习参数的形状
  7. //在SGDSolver<Dtype>::PreSolve中history_已经存入一个与参数blob相同形状的空blob,此处再存入一个
  8. this->history_.push_back(shared_ptr<Blob<Dtype> >(new Blob<Dtype>(shape)));
  9. }
  10. }
  11. #ifndef CPU_ONLY
  12. template <typename Dtype>
  13. void adadelta_update_gpu(int N, Dtype* g, Dtype* h, Dtype* h2, Dtype momentum,
  14. Dtype delta, Dtype local_rate);
  15. #endif
  16. template <typename Dtype>
  17. void AdaDeltaSolver<Dtype>::ComputeUpdateValue(int param_id, Dtype rate) {
  18. const vector<Blob<Dtype>*>& net_params = this->net_->learnable_params(); //网络中的所有可学习参数
  19. const vector<float>& net_params_lr = this->net_->params_lr(); //每个参数对应的学习率系数
  20. Dtype delta = this->param_.delta(); //AdaDelta方法中的一个参数
  21. Dtype momentum = this->param_.momentum(); //动量系数
  22. Dtype local_rate = rate * net_params_lr[param_id]; //得到当前参数对应的学习率
  23. size_t update_history_offset = net_params.size(); //网络的参数个数
  24. //history_在AdaDeltaPreSolve()中又存入了一次与所有参数形状相同的空blob,下面将
  25. //history_[param_id]表示成 history_former, history_[update_history_offset + param_id]表示成 history_latter
  26. switch (Caffe::mode()) {
  27. case Caffe::CPU: {
  28. // compute square of gradient in update
  29. caffe_powx(net_params[param_id]->count(), net_params[param_id]->cpu_diff(), Dtype(2),
  30. this->update_[param_id]->mutable_cpu_data()); //update_data = net_params_diff ^ 2
  31. // update history of gradients //history_former_data = (1 - momentum) * update_data + momentum * history_former_data
  32. caffe_cpu_axpby(net_params[param_id]->count(), Dtype(1) - momentum, this->update_[param_id]->cpu_data(),
  33. momentum, this->history_[param_id]->mutable_cpu_data());
  34. // add delta to history to guard against dividing by zero later
  35. caffe_set(net_params[param_id]->count(), delta,
  36. this->temp_[param_id]->mutable_cpu_data()); //temp_中每个元素都置为delta, temp_data = delta
  37. caffe_add(net_params[param_id]->count(),
  38. this->temp_[param_id]->cpu_data(),
  39. this->history_[update_history_offset + param_id]->cpu_data(),
  40. this->update_[param_id]->mutable_cpu_data()); //update_data = temp_data + history_latter_data
  41. caffe_add(net_params[param_id]->count(),
  42. this->temp_[param_id]->cpu_data(),
  43. this->history_[param_id]->cpu_data(),
  44. this->temp_[param_id]->mutable_cpu_data()); //temp_data = temp_data + history_former_data
  45. // divide history of updates by history of gradients
  46. caffe_div(net_params[param_id]->count(),
  47. this->update_[param_id]->cpu_data(),
  48. this->temp_[param_id]->cpu_data(),
  49. this->update_[param_id]->mutable_cpu_data()); //update_data = update_data / temp_data
  50. // jointly compute the RMS of both for update and gradient history
  51. caffe_powx(net_params[param_id]->count(),
  52. this->update_[param_id]->cpu_data(), Dtype(0.5),
  53. this->update_[param_id]->mutable_cpu_data()); //update_data = update_data ^ 0.5
  54. // compute the update
  55. caffe_mul(net_params[param_id]->count(),
  56. net_params[param_id]->cpu_diff(),
  57. this->update_[param_id]->cpu_data(),
  58. net_params[param_id]->mutable_cpu_diff()); //net_params_diff = net_params_diff * update_data
  59. // compute square of update
  60. caffe_powx(net_params[param_id]->count(),
  61. net_params[param_id]->cpu_diff(), Dtype(2),
  62. this->update_[param_id]->mutable_cpu_data()); //update_data = net_params_diff ^ 2
  63. // update history of updates //history_latter_data = (1 - momentum) * update_data + momentum * history_latter_data
  64. caffe_cpu_axpby(net_params[param_id]->count(), Dtype(1) - momentum,
  65. this->update_[param_id]->cpu_data(), momentum,
  66. this->history_[update_history_offset + param_id]->mutable_cpu_data());
  67. // apply learning rate
  68. caffe_cpu_scale(net_params[param_id]->count(), local_rate,
  69. net_params[param_id]->cpu_diff(),
  70. net_params[param_id]->mutable_cpu_diff()); //net_params_diff = local_rate * net_params_diff
  71. break;
  72. }
  73. case Caffe::GPU: {
  74. #ifndef CPU_ONLY
  75. // g = net_params_diff;
  76. // h = history_former_data;
  77. // h2 = history_latter_data;
  78. // gi = g[i];
  79. // hi = h[i] = momentum * h[i] + (1-momentum) * gi * gi;
  80. // gi = gi * sqrt((h2[i] + delta) / (hi + delta));
  81. // h2[i] = momentum * h2[i] + (1-momentum) * gi * gi;
  82. // g[i] = local_rate * gi;
  83. adadelta_update_gpu(net_params[param_id]->count(),
  84. net_params[param_id]->mutable_gpu_diff(),
  85. this->history_[param_id]->mutable_gpu_data(),
  86. this->history_[update_history_offset + param_id]->mutable_gpu_data(),
  87. momentum, delta, local_rate);
  88. #else
  89. NO_GPU;
  90. #endif
  91. break;
  92. }
  93. default:
  94. LOG(FATAL) << "Unknown caffe mode: " << Caffe::mode();
  95. }
  96. }

Adam简介

Adam算法包含两个衰减参数\(\rho_{1}\)和\(\rho_{2}\),一般\(\rho_{1}=0.9, \rho_{2}=0.999\)。还包含一阶矩和二阶矩变量\(s, r\),时间步\(t\)。

初始时\(s=0, r=0, t=0\),\(\delta\)同样为较小常数。

  1. 更新一阶矩:\(s_{t+1}=\rho_{1}*s_{t}+(1-\rho_{1})*\nabla_{\theta_{t}}\)

  2. 更新二阶矩:\(r_{t+1}=\rho_{2}*r_{t}+(1-\rho_{2})*\nabla_{\theta_{t}}\odot\nabla_{\theta_{t}}\)

  3. 修正一阶矩的偏差:\(\tilde{s}_{t+1}=\frac{s_{t+1}}{1-\rho_{1}^{t+1}}\)

  4. 修正二阶矩的偏差:\(\tilde{r}_{t+1}=\frac{r_{t+1}}{1-\rho_{2}^{t+1}}\)

  5. 计算梯度的更新量:\(\Delta\theta_{t+1}=lr*\frac{\tilde{s}_{t+1}}{\sqrt{\tilde{r}_{t+1}}+\delta}\)

  6. 应用更新:\(\theta_{t+1}=\theta_{t}-\Delta\theta_{t+1}\)

adam_solver.cpp源码

  1. template <typename Dtype>
  2. void AdamSolver<Dtype>::AdamPreSolve() {
  3. // Add the extra history entries for Adam after those from SGDSolver::PreSolve
  4. const vector<Blob<Dtype>*>& net_params = this->net_->learnable_params(); //所有可学习参数
  5. for (int i = 0; i < net_params.size(); ++i) {
  6. const vector<int>& shape = net_params[i]->shape(); //第i个可学习参数对应的形状
  7. this->history_.push_back(shared_ptr<Blob<Dtype> >(new Blob<Dtype>(shape))); //history_再存入一个与参数大小相同的空blob
  8. }
  9. }
  10. #ifndef CPU_ONLY
  11. template <typename Dtype>
  12. void adam_update_gpu(int N, Dtype* g, Dtype* m, Dtype* v, Dtype beta1,
  13. Dtype beta2, Dtype eps_hat, Dtype corrected_local_rate);
  14. #endif
  15. template <typename Dtype>
  16. void AdamSolver<Dtype>::ComputeUpdateValue(int param_id, Dtype rate) {
  17. const vector<Blob<Dtype>*>& net_params = this->net_->learnable_params(); //所有可学习参数
  18. const vector<float>& net_params_lr = this->net_->params_lr(); //参数的学习率系数
  19. Dtype local_rate = rate * net_params_lr[param_id]; //当前参数的学习率
  20. const Dtype beta1 = this->param_.momentum(); //两个动量系数
  21. const Dtype beta2 = this->param_.momentum2();
  22. // we create aliases for convenience
  23. size_t update_history_offset = net_params.size(); //history_的大小为2 * update_history_offset
  24. Blob<Dtype>* val_m = this->history_[param_id].get();
  25. Blob<Dtype>* val_v = this->history_[param_id + update_history_offset].get();
  26. Blob<Dtype>* val_t = this->temp_[param_id].get();
  27. const int t = this->iter_ + 1; //步数
  28. const Dtype correction = std::sqrt(Dtype(1) - pow(beta2, t)) /
  29. (Dtype(1.) - pow(beta1, t)); //correction = sqrt(1 - beta2 ^ t) / (1 - beta1 ^ t)
  30. const int N = net_params[param_id]->count(); //参数的元素个数
  31. const Dtype eps_hat = this->param_.delta(); //微小值
  32. switch (Caffe::mode()) {
  33. case Caffe::CPU: {
  34. // update m <- \beta_1 m_{t-1} + (1-\beta_1)g_t
  35. caffe_cpu_axpby(N, Dtype(1)-beta1, net_params[param_id]->cpu_diff(), beta1,
  36. val_m->mutable_cpu_data()); //val_m = (1 - beta1) * net_params_diff + beta1 * val_m
  37. // update v <- \beta_2 m_{t-1} + (1-\beta_2)g_t^2
  38. caffe_mul(N, net_params[param_id]->cpu_diff(), net_params[param_id]->cpu_diff(),
  39. val_t->mutable_cpu_data()); //val_t = net_params_diff * net_params_diff
  40. caffe_cpu_axpby(N, Dtype(1)-beta2, val_t->cpu_data(), beta2,
  41. val_v->mutable_cpu_data()); //val_v = (1 - beta2) * val_t + beta2 * val_v
  42. // set update
  43. caffe_powx(N, val_v->cpu_data(), Dtype(0.5),
  44. val_t->mutable_cpu_data()); //val_t = val_v ^ 0.5
  45. caffe_add_scalar(N, eps_hat, val_t->mutable_cpu_data()); //val_t += eps_hat
  46. caffe_div(N, val_m->cpu_data(), val_t->cpu_data(),
  47. val_t->mutable_cpu_data()); //val_t = val_m / val_t
  48. caffe_cpu_scale(N, local_rate*correction, val_t->cpu_data(),
  49. net_params[param_id]->mutable_cpu_diff()); //net_params_diff = local_rate*correction * val_t
  50. break;
  51. }
  52. case Caffe::GPU: {
  53. #ifndef CPU_ONLY
  54. // g = net_params_diff
  55. // m = val_m
  56. // v = val_v
  57. // gi = g[i];
  58. // mi = m[i] = m[i]*beta1 + gi*(1-beta1);
  59. // vi = v[i] = v[i]*beta2 + gi*gi*(1-beta2);
  60. // g[i] = local_rate * correction * mi / (sqrt(vi) + eps_hat);
  61. adam_update_gpu(N, net_params[param_id]->mutable_gpu_diff(),
  62. val_m->mutable_gpu_data(), val_v->mutable_gpu_data(), beta1, beta2,
  63. eps_hat, local_rate*correction);
  64. #else
  65. NO_GPU;
  66. #endif
  67. break;
  68. }
  69. default:
  70. LOG(FATAL) << "Unknown caffe mode: " << Caffe::mode();
  71. }
  72. }

小结

  1. 很多地方的动量的符号与本文不用,是用\(\nu_{t+1}=-lr*\nabla_{\theta_{t}}+m*\nu_{t}\),然后\(\theta_{t+1}=\theta_{t}+\nu_{t+1}\),其实原理是一致的,本文只是为了保持与caffe的代码一致。

参考

  1. https://stats.stackexchange.com/questions/179915/whats-the-difference-between-momentum-based-gradient-descent-and-nesterovs-acc
  2. https://jlmelville.github.io/mize/nesterov.html
  3. https://zhuanlan.zhihu.com/p/22810533
  4. https://zh.d2l.ai/chapter_optimization/adadelta.html
  5. 《Deep Learning》-- Ian Goodfellow and Yoshua Bengio and Aaron Courville

Caffe的源码笔者是第一次阅读,一边阅读一边记录,对代码的理解和分析可能会存在错误或遗漏,希望各位读者批评指正,谢谢支持!

Caffe源码-几种优化算法的更多相关文章

  1. caffe源码阅读

    参考网址:https://www.cnblogs.com/louyihang-loves-baiyan/p/5149628.html 1.caffe代码层次熟悉blob,layer,net,solve ...

  2. caffe源码学习

    本文转载自:https://buptldy.github.io/2016/10/09/2016-10-09-Caffe_Code/ Caffe简介 Caffe作为一个优秀的深度学习框架网上已经有很多内 ...

  3. Caffe源码中math_functions文件分析

    Caffe源码(caffe version:09868ac , date: 2015.08.15)中有一些重要文件,这里介绍下math_functions文件. 1.      include文件: ...

  4. caffe源码学习之Proto数据格式【1】

    前言: 由于业务需要,接触caffe已经有接近半年,一直忙着阅读各种论文,重现大大小小的模型. 期间也总结过一些caffe源码学习笔记,断断续续,这次打算系统的记录一下caffe源码学习笔记,巩固一下 ...

  5. Caffe源码理解2:SyncedMemory CPU和GPU间的数据同步

    目录 写在前面 成员变量的含义及作用 构造与析构 内存同步管理 参考 博客:blog.shinelee.me | 博客园 | CSDN 写在前面 在Caffe源码理解1中介绍了Blob类,其中的数据成 ...

  6. Caffe源码中syncedmem文件分析

    Caffe源码(caffe version:09868ac , date: 2015.08.15)中有一些重要文件,这里介绍下syncedmem文件. 1.      include文件: (1).& ...

  7. Caffe源码中caffe.proto文件分析

    Caffe源码(caffe version:09868ac , date: 2015.08.15)中有一些重要文件,这里介绍下caffe.proto文件. 在src/caffe/proto目录下有一个 ...

  8. caffe源码整个训练过程

    Caffe源码 Blob protected: shared_ptr<SyncedMemory> data_; shared_ptr<SyncedMemory> diff_; ...

  9. Caffe源码-SyncedMemory类

    SyncedMemory类简介 最近在阅读caffe源码,代码来自BVLC/caffe,基本是参照网络上比较推荐的 Blob-->Layer-->Net-->Solver 的顺序来分 ...

随机推荐

  1. CNN对位移、尺度和旋转不变性的讨论

    CNN得益于全局共享权值和pool操作,具有平移不变性. 对于尺度不变性,是没有或者说具有一定的不变性(尺度变化不大),实验中小目标的检测是难点,需要采用FPN或者其他的方式单独处理. 对于旋转不变性 ...

  2. PHP程序连接Redis报read error on connection问题

    线上PHP程序动不动就报PHP Fatal error: Uncaught RedisException: read error on connection错误,就是连接Redis在那么1秒钟有问题, ...

  3. git操作——git pull 撤销误操作,恢复本地代码

    需求 开发的代码还未commit到git本地仓库,就从git远程仓库上pull了代码,导致开发的代码直接被冲掉,需要退回到上一个版本代码. 操作 进入到项目git本地仓库文件夹下 打开cmd窗口,执行 ...

  4. mysql 查询当天、昨天、本周、上周、本月、上月、今年、去年数据

    mysql查询今天.昨天.7天.近30天.本月.上一月 数据 今天 select * from 表名 where to_days(时间字段名) = to_days(now()); 昨天 SELECT ...

  5. python的if循环和嵌套

    1.    if 条件:    if语句块   执行流程:判断条件是否为真. 如果真. 执行if语句块 money = int(input('请输入你兜里的钱:')) if money >500 ...

  6. 一个项目看java TCP/IP Socket编程

    前一段时间刚做了个java程序和网络上多台机器的c程序通讯的项目,遵循的是TCP/IP协议,用到了java的Socket编程.网络通讯是java的强项,用TCP/IP协议可以方便的和网络上的其他程序互 ...

  7. @loj - 2674@ 「NOI2012」美食节

    目录 @description@ @solution@ @accepted code@ @details@ @description@ CZ 市为了欢迎全国各地的同学,特地举办了一场盛大的美食节. 作 ...

  8. @atcoder - Japanese Student Championship 2019 Qualification - F@ Candy Retribution

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 请找到满足以下条件的长度为 N 的非负整数序列 A1, A2, ...

  9. jqLite

    一.关于DOM导航的jqLite方法 children() 返回一组子元素.这个方法的jqLite实现不支持jQuery所提供的选择器特性 eq(index) 从一个元素集合中返回指定索引下的元素 f ...

  10. Keras框架下的保存模型和加载模型

    在Keras框架下训练深度学习模型时,一般思路是在训练环境下训练出模型,然后拿训练好的模型(即保存模型相应信息的文件)到生产环境下去部署.在训练过程中我们可能会遇到以下情况: 需要运行很长时间的程序在 ...