caffe学习-代码阅读Solver

本文摘录自《深度学习轻松学》,介绍了Caffe模型训练的核心代码。Solver的Step函数内有五个核心函数,涉及前向后向计算、Loss值平滑和参数更新等。ApplyUpdate函数完成参数更新,包含获取学习率、修剪梯度等步骤。还提及多卡训练的回调函数及GPU通信问题。

摘录自《深度学习轻松学》冯超
真正的训练在 Solver 的 Step 函数内 ,去掉一些相对不重要的内容,它的核心代码
如下所示 :
void Solver: :Step(int iters) {
while (iter_ < stop_iter)
{
net_->ClearParamDiffs ();
for (int i = 0; i < callbacks_ . size(); ++i)
{
callbacks_[i]->on_start();
}
Dtype loss = 0;
for (int i=0; i < param_.iter_size; ++i)
{
loss += net_ ->ForwardBackward();
}
loss /= param_.iter_size() ;
UpdateSmoothedLoss(loss, start_iter, average_loss) ;
for (int i = 0 ; i < callbacks_ . size() ; ++i)
{
callbacks_ [i] ->on_gradients_ready () ;
}
ApplyUpdate () ;
++iter_;
这段代码有五个核心函数 , 其中 on_start() 和 on_gradient_ready()是和多卡训练相关的回调函数 , 另外还有三个重要的过程: ForwardBackward、UpdateSmoothedLoss 和 ApplyUpdate。ForwardBackward 函数调用了 Net 中的代码,主要完成了模型的前向后向计算,前向用于计算模型的最终输出和 Loss , 后向用于计算每一层网络和参数的梯度 。UpdateSmoothedLoss 函数主要用于平滑模型产生的 Loss 值 。 Caffe 中参数的训练方式通常是基于批量数据的优化方法,由于批量数据的数量不够多,尤其在优化早期,每轮迭代产生的 Loss 差距可能很大,为了不让用户看到异常的 Loss 值而感到意外, Caffe对 Loss 值进行了平滑操作,这样如果有个别迭代轮数的 Loss 比较大, Caffe 就会将其平滑处理,这样用户看到的平滑后的 Loss 更能代表模型的整体表现。最复杂的就是 ApplyUpdate 函数,这个函数真正完成了参数更新的任务 。 下面就详细介绍这个函数的详细内容,它的核心代码如下所示:
void SGDSolver: :ApplyUpdate ()
{
Dtype rate = GetLearningRate ();
ClipGradients () ;
for (int param_id = 0; param_id < this->net_ ->learnable_params () .size() ;;++param_id)
{
Normalize(param_id);
Regularize(param_id);
ComputeUpdateValue(param_id, rate);
this- >net_->Update() ;
第一个函数是 GetLeamingRate , 关于 learning rate 更多的故事将在 8 .1 节详细介绍,训练过程中,选择合适的 learning rate 是个大问题。 一般来说,随着迭代轮数
不断增多, learning rate 应该逐渐变小。 为了让 learning rate 的配置更灵活, Caffe 提供了一系列选择 learning rate 的方案。
在这里插入图片描述
第二个函数是 ClipGradients ,这一步对梯度值的大小做限制 。 如果梯度值过大,函
数就会对梯度做一个修剪,对所有的参数乘以一个缩放因子,使所有参数的平方和不
超过某个设定的上限 。 这个功能像是对优化函数设置了一个 Trust Region ,可以防止参数更新量过大而导致无法预料的事情发生 。 这个函数主要用于防御性的修正梯度,并不能用于自适应地修正梯度大小。 因为在实际训练过程中,参数的数值大小很可能并
不平均,有些参数的梯度比较大,有些参数的梯度比较小,那么对所有的参数乘以相同
的因子会让一些本来比较小的参数变得更小,使得这些参数的训练变得更缓慢 。 所以
这个方案只是一个保守解决问题的方案,想要自适应地解决问题,最好采用其他方案 。
接下来就进入了每一个参数自己的训练过程。 首先是 Normalize 方法,这一步要完
成 Batch 级梯度数据平滑,它要让每一个参数的梯度除以 iter size 参数。 这主要考虑了单一Batch 数据量不足的场景,代码比较简单。
接下来是 Regularize 方法,到这一步要完成正则项梯度的计算 。 Caffe 提供了两种
正则方法-一-L2 和 Ll , 其中 L2 正则是可导的,所以就采用了标准的梯度下降方法进行优化;而 L1 在原点处不可导, Caffe 采用了 sub-gradient 的计算方法 。 这个函数将对应正则项的梯度加到参数的梯度数据中 。 L2 正则的优化计算比较简单,直接求导进行计算:
#L2 Norm gradient update
diff += local_decay * data
但是 L1 正则的计算还是有值得玩味的地方的:
#L1 Norm gradient update
diff += local_decay * sign(data)
最后是 ComputeUpdateValue 方法 。 这时梯度的计算已经完成,下面该考虑如何把
learning rate 和梯度结合起来计算最终的梯度优化值了 。 最经典的 SGDSolver 类采用基于动量的优化方法 。 除此之外, Caffe 还提供了一系列的梯度计算方法,这些优化方法各有特点,到此为止,每一个参数的更新量都已经计算好,最后一步就是要调用 Blob 中的Update 把每个参数的 data 和 diff 相加,将参数更新 。 这样,整个优化过程就完成了 。
I. 回调函数 on_start():将(更新后的)参数复制到每一个 GPU 中,使每个 GPU 拥
有最新的参数。
2. ForwardBackward():每个 GPU 各自完成不同数据前向后向的计算,得到每个参
数的梯度 。
3. on_gradient_ready():将每个 GPU 中参数的反向梯度汇总到一个 GPU 所在线程上 。
4. ApplyUpdate() : 在汇总的线程上进行参数的整体更新 。
其中第 2 步由每一个 CPU 线程和与自己绑定的 GPU 并行完成,第 4 步由汇总的
CPU 和与自己绑定的 GPU 完成,第 1 步和第 3 步主要是完成数据传输的任务,也是多
卡计算与单卡计算不同的地方。
有些 GPU 距离比较近,有些 GPU 距离比较远,因此不同 GPU 之间的数据传递速
度是不一样的 。 属于同一组的 GPU 之间传输速度非常快,而不与同一 CPU 相连的 GPU之间传输数据就会慢不少 。 在上面提到的第 1 步和第 3 步中,多个 GPU 之间的数据需要进行传递,那就要考虑 GPU 之间的通信问题。
后面介绍了GPU的通信和数据的算法,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值