您的位置:首页 > Web前端

【Caffe细致入微】Solver_Step

2016-12-30 11:47 295 查看

void Solver::Step(int iters)

【Introduction】简单的说,这个函数就是核心的优化方法,不断通过前向和反向传播来更新参数的过程。

【Loss处理——均值滤波(广泛应用)】

template <typename Dtype>
void Solver<Dtype>::UpdateSmoothedLoss(Dtype loss, int start_iter, int average_loss)


每一次训练迭代都会进行前向传播,就会得到loss值。这个函数的作用就是把几次的loss进行平均化处理。

若average_loss为1:loss_容器里面只存当前获得的真实loss值,而smooth_loss_当然也是这个值

若average_loss为n:loss_容器里面就会存储前n个loss的值,而smooth_loss_相当于做了一个loss平均

【你可能会问】这么做好处是什么?

当然无论average_loss为多少,最终都是为了展示训练之后的loss,但是我们当然要从loss中得到一些信息和经验,比如,这一阶段的训练的效果等。平均值会比个体值更加客观的刻画整个集合。做这样的平均其实应用有很多,主要是去除掉一些噪音,让得到的值更加可信。caffe中默认average_loss为1。



【讲故事1】头部姿态估计

因为评估算法会产生一些误差,但是大部分估计的较准确,就可以利用中值滤波进行去除噪声。上图可以说明问题。Yaw和Pitch方向的Ground Truth都是0°,但是因为算法和环境的因素产生一些误差。

【讲故事2】 眼部疲劳检测

需要评估一段时间是睁眼还是闭眼这个事件,因为有眨眼这个动作,但是这个动作非常快,基本上不需要进行判断,属于噪声,假如每一帧对眼睛都会有个阈值来进行睁闭眼的判断,这个时候就需要用到上述方法了,如果是睁开眼睛的事件,一段时间会有相对很少的几个值是闭眼睛的阈值,做平均就会把这个值过滤掉,反之依然。这样就达到了滤波的作用。

【重要变量List】

// 进行平滑loss处理的loss数量
int average_loss;
// 存储average_loss个loss的容器,初始化的时候需要真实的loss一个一个添加
vector<Dtype> losses_;
// 平滑后的loss
Dtype smoothed_loss_;
// 一次迭代训练中进行几次前向和反向传播
param_.iter_size()
// TODO:还没搞懂。。。
vector<Callback*> callbacks_;
// True iff a request to stop early was received.
bool requested_early_exit_;


batch_size设多少合适?

一般的,batch_size增加,利用更多的数据一起进行梯度方向的判断,这样会更加接近整体样本,得到的梯度方向更加客观和相对准确,但是很多情况下,内存不足以一次加载batch_size个样本,所以需要设置param_.iter_size()。作用相当于进行一次训练迭代,需要进行param_.iter_size()次取batch_size个训练数据进行前向和反向传播。可以达到在有限的内存情况下增加batch_size效果。

当然,batch_size与训练集的总数,base_lr和任务本身的难度和样本的选取都有关系,是一个经验参数。

【测试阶段】

// 遍历所有的测试网络,当前只说明一个测试网络的情况
void Solver<Dtype>::TestAll();
// 对某个测试网络进行测试,获得相应任务的Loss
void Solver<Dtype>::Test(const int test_net_id)


首先需要的是几个Solver里面的变量。

test_iter:在一次测试中进行test_iter次迭代,每次迭代选取batch_size个测试集中的数据,最好是test_iter*batch_size = 测试集数据总数

test_interval:训练权值更新test_interval次后会进行一次测试,这个只是相当于测试频率,可大可小

CHECK_NOTNULL(test_nets_[test_net_id].get())->ShareTrainedLayersWith(net_.get());


测试时,训练网络的参数赋值给测试网络,例如:卷积层、ReLU的参数进行共享。

【你可能会问】为什么要训练网络参数赋值给测试网络那? 直接利用训练网络添加一些控制变量不行么?

【参数传播】

这部分主要涉及前向和反向传播操作,后续文章我会继续说明。

【权值更新——SGD说明】

template <typename Dtype>
void SGDSolver<Dtype>::ApplyUpdate()

template <typename Dtype>
void SGDSolver<Dtype>::ClipGradients()

template <typename Dtype>
void SGDSolver<Dtype>::Normalize(int param_id)

template <typename Dtype>
void SGDSolver<Dtype>::Regularize(int param_id)

template <typename Dtype>
void SGDSolver<Dtype>::ComputeUpdateValue(int param_id, Dtype rate)


程序会依次调用上述函数。咱们一个一个说明。

ClipGradients On the difficulty of training Recurrent Neural Networks

当在一次迭代中权重的更新过于迅猛的话,很容易导致loss divergence,出现gradient explosion的问题。clip_gradient 的直观作用就是让权重的更新限制在一个合适的范围。

具体的细节是:

1、在solver中先设置一个clip_gradient

2、在前向传播与反向传播之后,我们会得到每个权重的梯度diff,这时不像通常那样直接使用这些梯度进行权重更新,而是先求所有权重梯度的平方和 sumsq_diff,如果sumsq_diff > clip_gradient,则求缩放因子scale_factor = clip_gradient / sumsq_diff。这个scale_factor在(0,1)之间。如果权重梯度的平方和sumsq_diff越大,那缩放因子将越小。

3、最后将所有的权重梯度乘以这个缩放因子,这时得到的梯度才是最后的梯度信息。

这样就保证了在一次迭代更新中,所有权重的梯度的平方和在一个设定范围以内,这个范围就是clip_gradient。

Normalize 归一化

若param_.iter_size()!=1时,在一次训练迭代进行多次前向和反向传播,每一次都会产生梯度,要把他们一起看做一次前向和方向传播,所以需要把梯度归一化,就是都乘个系数(Dtype(1.) / this->param_.iter_size())。

Regularize 正则化 A Simple Weight Decay Can Improve Generalization

防止过拟合,利用参数和weight_decay作为惩罚项,caffe中分为L2和L1正则规则。

ComputeUpdateValue 利用梯度更新参数

参数都准备完成,利用momentum来更新参数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  caffe