Caffe框架源码剖析(2)—训练网络

来源:互联网 发布:淘宝优惠券图标 编辑:程序博客网 时间:2024/05/17 05:14

中间因为工程开发等杂七杂八原因暂停了Caffe源码分析,现在继续补上。


上篇分析在函数 train() 中建立了网络,接下来就是进入训练网络步骤了。

在函数train()中,使用前一步创建好的solver智能指针对象调用函数Solve(),

[cpp] view plain copy
  1. int train()  
  2. {  
  3.     ...  
  4.     // 创建solver  
  5.     shared_ptr<caffe::Solver<float> >  
  6.       solver(caffe::SolverRegistry<float>::CreateSolver(solver_param));  
  7.   
  8.     ...  
  9.   
  10.     LOG(INFO) << "Starting Optimization";  
  11.     // 开始训练网络  
  12.     solver->Solve();  
  13.     LOG(INFO) << "Optimization Done.";  
  14. }  

在成员函数Solve()内部,
[cpp] view plain copy
  1. template <typename Dtype>  
  2. void Solver<Dtype>::Solve(const char* resume_file)  
  3. {  
  4.     LOG(INFO) << "Solving " << net_->name();  
  5.     LOG(INFO) << "Learning Rate Policy: " << param_.lr_policy();  
  6.   
  7.     // 开始迭代  
  8.     Step(param_.max_iter() - iter_);  
  9.   
  10.     LOG(INFO) << "Optimization Done.";  
  11. }  
下面我们看一下Solver::Step()函数内部实现情况,
[cpp] view plain copy
  1. template <typename Dtype>  
  2. void Solver<Dtype>::Step(int iters)  
  3. {  
  4.     // 起始迭代步数  
  5.     const int start_iter = iter_;  
  6.     // 终止迭代步数  
  7.     const int stop_iter = iter_ + iters;  
  8.   
  9.     // 判断是否已经完成设定步数  
  10.     while (iter_ < stop_iter)  
  11.     {  
  12.         // 将net_中的Blob梯度参数置为零  
  13.         net_->ClearParamDiffs();  
  14.   
  15.         ...  
  16.   
  17.         // accumulate the loss and gradient  
  18.         Dtype loss = 0;  
  19.         for (int i = 0; i < param_.iter_size(); ++i)  
  20.         {  
  21.             // 正向传导和反向传导,并计算loss  
  22.             loss += net_->ForwardBackward();  
  23.         }  
  24.         loss /= param_.iter_size();  
  25.   
  26.         // 为了输出结果平滑,将临近的average_loss个loss数值进行平均,存储在成员变量smoothed_loss_中  
  27.         UpdateSmoothedLoss(loss, start_iter, average_loss);  
  28.   
  29.         // BP算法更新权重  
  30.         ApplyUpdate();  
  31.   
  32.         // Increment the internal iter_ counter -- its value should always indicate  
  33.         // the number of times the weights have been updated.  
  34.         ++iter_;  
  35.     }  
  36. }  


while循环中先调用了网络类Net::ForwardBackward()成员函数进行正向传导和反向传导,并计算loss

[cpp] view plain copy
  1. template <typename Dtype>  
  2. class Net  
  3. {  
  4.     Dtype ForwardBackward()  
  5.     {  
  6.         Dtype loss;  
  7.         Forward(&loss);  // 正向传导  
  8.         Backward();      // 反向传导  
  9.         return loss;  
  10.     }  
  11. }  

其中正向传导函数Forward()调用了ForwardFromTo(int start, int end)函数

[cpp] view plain copy
  1. template <typename Dtype>  
  2. Dtype Net<Dtype>::ForwardFromTo(int start, int end)  
  3. {  
  4.     CHECK_GE(start, 0);  
  5.     CHECK_LT(end, layers_.size());  
  6.     Dtype loss = 0;  
  7.     // 逐层传导  
  8.     for (int i = start; i <= end; ++i)  
  9.     {  
  10.         // 虽然Forward()不是虚函数,但是包装了虚函数Forward_cpu()和Forward_gpu(),不同层有不同的计算方法  
  11.         Dtype layer_loss = layers_[i]->Forward(bottom_vecs_[i], top_vecs_[i]);  
  12.         // 累加loss(非loss层都会返回0)  
  13.         loss += layer_loss;  
  14.         if (debug_info_)  
  15.         {  
  16.             ForwardDebugInfo(i);  
  17.         }  
  18.     }  
  19.   return loss;  
  20. }  


虽然Forward()不是虚函数,但是它包装了虚函数Forward_cpu()和Forward_gpu(),分别对应CPU版本和GPU版本。其中Forward_cpu()为父类Layer的纯虚函数,必须被子类重载。而Forward_gpu()在父类Layer中的实现为直接调用Forward_cpu(),于是该虚函数的实现为可选。总的来说,正因为这两个虚函数,所以不同层有不同的正向传导计算方法。


[cpp] view plain copy
  1. template <typename Dtype>  
  2. inline Dtype Layer<Dtype>::Forward(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top)  
  3. {  
  4.     // 锁住互斥量forward_mutex_  
  5.     Lock();  
  6.     Dtype loss = 0;  
  7.     Reshape(bottom, top);  
  8.     switch (Caffe::mode())  
  9.     {  
  10.     case Caffe::CPU:  
  11.         // 调用虚函数Forward_cpu  
  12.         Forward_cpu(bottom, top);  
  13.         for (int top_id = 0; top_id < top.size(); ++top_id)  
  14.         {  
  15.             if (!this->loss(top_id)) { continue; }  
  16.             const int count = top[top_id]->count();  
  17.             const Dtype* data = top[top_id]->cpu_data();  
  18.             const Dtype* loss_weights = top[top_id]->cpu_diff();  
  19.             loss += caffe_cpu_dot(count, data, loss_weights);  
  20.         }  
  21.     break;  
  22.     case Caffe::GPU:  
  23.         // 调用虚函数Forward_gpu  
  24.         Forward_gpu(bottom, top);  
  25. #ifndef CPU_ONLY  
  26.         for (int top_id = 0; top_id < top.size(); ++top_id)  
  27.         {  
  28.             if (!this->loss(top_id)) { continue; }  
  29.             const int count = top[top_id]->count();  
  30.             const Dtype* data = top[top_id]->gpu_data();  
  31.             const Dtype* loss_weights = top[top_id]->gpu_diff();  
  32.             Dtype blob_loss = 0;  
  33.             caffe_gpu_dot(count, data, loss_weights, &blob_loss);  
  34.             loss += blob_loss;  
  35.         }  
  36. #endif  
  37.     break;  
  38.     default:  
  39.         LOG(FATAL) << "Unknown caffe mode.";  
  40.     }  
  41.     // 解锁互斥量forward_mutex_  
  42.     Unlock();  
  43.     return loss;  
  44. }  


反向传导函数Backward()调用了BackwardFromTo(int start, int end)函数
[cpp] view plain copy
  1. template <typename Dtype>  
  2. void Net<Dtype>::Backward()  
  3. {  
  4.     BackwardFromTo(layers_.size() - 1, 0);  
  5. }  

[cpp] view plain copy
  1. template <typename Dtype>  
  2. void Net<Dtype>::BackwardFromTo(int start, int end)  
  3. {  
  4.   CHECK_GE(end, 0);  
  5.   CHECK_LT(start, layers_.size());  
  6.   // 倒过来逐层传导  
  7.   for (int i = start; i >= end; --i)  
  8.   {  
  9.     if (layer_need_backward_[i])  
  10.     {  
  11.       // 与正向传导函数类似,虽然Backward()不是虚函数,但是包装了虚函数Backward_cpu()和Backward_gpu(),因此不同层有不同的计算方法  
  12.       // 注意反向传导比正向传导多了一个参数bottom_need_backward_。在实现反向传导时,首先判断当前层是否需要反向传导的层,不需要则直接返回  
  13.       layers_[i]->Backward(top_vecs_[i], bottom_need_backward_[i], bottom_vecs_[i]);  
  14.       if (debug_info_)  
  15.       {  
  16.         BackwardDebugInfo(i);  
  17.       }  
  18.     }  
  19.   }  
  20. }  


正向传导和反向传导结束后,再调用SGDSolver::ApplyUpdate()成员函数进行权重更新。

[cpp] view plain copy
  1. template <typename Dtype>  
  2. void SGDSolver<Dtype>::ApplyUpdate()  
  3. {  
  4.     // 获取当前学习速率  
  5.     Dtype rate = GetLearningRate();  
  6.     if (this->param_.display() && this->iter_ % this->param_.display() == 0)  
  7.     {  
  8.         LOG(INFO) << "Iteration " << this->iter_ << ", lr = " << rate;  
  9.     }  
  10.   
  11.     // 在计算当前梯度的时候,如果该值超过了阈值clip_gradients,则将梯度直接设置为该阈值  
  12.     // 此处阈值设为-1,即不起作用  
  13.     ClipGradients();  
  14.   
  15.     // 逐层更新网络中的可学习层  
  16.     for (int param_id = 0; param_id < this->net_->learnable_params().size();  
  17.        ++param_id)  
  18.     {  
  19.         // 归一化  
  20.         Normalize(param_id);  
  21.         // L2范数正则化添加衰减权重  
  22.         Regularize(param_id);  
  23.         // 随机梯度下降法计算更新值  
  24.         ComputeUpdateValue(param_id, rate);  
  25.     }  
  26.     // 更新权重  
  27.     this->net_->Update();  
  28. }  

最后将迭代次数++iter_,继续while循环,直到迭代次数完成。



接下来该分析逐层Layer的虚函数Forward_cpu()和Backward_cpu()具体实现了(部分层还有对应的GPU版本:Forward_gpu()和Backward_gpu())。
阅读全文
0 0