Caffe框架原始碼剖析(2)—訓練網路
阿新 • • 發佈:2018-10-31
中間因為工程開發等雜七雜八原因暫停了Caffe原始碼分析,現在繼續補上。
上篇分析在函式 train() 中建立了網路,接下來就是進入訓練網路步驟了。
在函式train()中,使用前一步建立好的solver智慧指標物件呼叫函式Solve(),
int train() { ... // 建立solver shared_ptr<caffe::Solver<float> > solver(caffe::SolverRegistry<float>::CreateSolver(solver_param)); ... LOG(INFO) << "Starting Optimization"; // 開始訓練網路 solver->Solve(); LOG(INFO) << "Optimization Done."; }
在成員函式 Solve()內部,
下面我們看一下 Solver:: Step()函式內部實現情況,template <typename Dtype> void Solver<Dtype>::Solve(const char* resume_file) { LOG(INFO) << "Solving " << net_->name(); LOG(INFO) << "Learning Rate Policy: " << param_.lr_policy(); // 開始迭代 Step(param_.max_iter() - iter_); LOG(INFO) << "Optimization Done."; }
template <typename Dtype> void Solver<Dtype>::Step(int iters) { // 起始迭代步數 const int start_iter = iter_; // 終止迭代步數 const int stop_iter = iter_ + iters; // 判斷是否已經完成設定步數 while (iter_ < stop_iter) { // 將net_中的Blob梯度引數置為零 net_->ClearParamDiffs(); ... // accumulate the loss and gradient Dtype loss = 0; for (int i = 0; i < param_.iter_size(); ++i) { // 正向傳導和反向傳導,並計算loss loss += net_->ForwardBackward(); } loss /= param_.iter_size(); // 為了輸出結果平滑,將臨近的average_loss個loss數值進行平均,儲存在成員變數smoothed_loss_中 UpdateSmoothedLoss(loss, start_iter, average_loss); // BP演算法更新權重 ApplyUpdate(); // Increment the internal iter_ counter -- its value should always indicate // the number of times the weights have been updated. ++iter_; } }
while迴圈中先呼叫了網路類Net::ForwardBackward()成員函式進行正向傳導和反向傳導,並計算loss
template <typename Dtype>
class Net
{
Dtype ForwardBackward()
{
Dtype loss;
Forward(&loss); // 正向傳導
Backward(); // 反向傳導
return loss;
}
}
其中正向傳導函式Forward()呼叫了ForwardFromTo(int start, int end)函式
template <typename Dtype>
Dtype Net<Dtype>::ForwardFromTo(int start, int end)
{
CHECK_GE(start, 0);
CHECK_LT(end, layers_.size());
Dtype loss = 0;
// 逐層傳導
for (int i = start; i <= end; ++i)
{
// 雖然Forward()不是虛擬函式,但是包裝了虛擬函式Forward_cpu()和Forward_gpu(),不同層有不同的計算方法
Dtype layer_loss = layers_[i]->Forward(bottom_vecs_[i], top_vecs_[i]);
// 累加loss(非loss層都會返回0)
loss += layer_loss;
if (debug_info_)
{
ForwardDebugInfo(i);
}
}
return loss;
}
雖然Forward()不是虛擬函式,但是它包裝了虛擬函式Forward_cpu()和Forward_gpu(),分別對應CPU版本和GPU版本。其中Forward_cpu()為父類Layer的純虛擬函式,必須被子類過載。而Forward_gpu()在父類Layer中的實現為直接呼叫Forward_cpu(),於是該虛擬函式的實現為可選。總的來說,正因為這兩個虛擬函式,所以不同層有不同的正向傳導計算方法。
template <typename Dtype>
inline Dtype Layer<Dtype>::Forward(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top)
{
// 鎖住互斥量forward_mutex_
Lock();
Dtype loss = 0;
Reshape(bottom, top);
switch (Caffe::mode())
{
case Caffe::CPU:
// 呼叫虛擬函式Forward_cpu
Forward_cpu(bottom, top);
for (int top_id = 0; top_id < top.size(); ++top_id)
{
if (!this->loss(top_id)) { continue; }
const int count = top[top_id]->count();
const Dtype* data = top[top_id]->cpu_data();
const Dtype* loss_weights = top[top_id]->cpu_diff();
loss += caffe_cpu_dot(count, data, loss_weights);
}
break;
case Caffe::GPU:
// 呼叫虛擬函式Forward_gpu
Forward_gpu(bottom, top);
#ifndef CPU_ONLY
for (int top_id = 0; top_id < top.size(); ++top_id)
{
if (!this->loss(top_id)) { continue; }
const int count = top[top_id]->count();
const Dtype* data = top[top_id]->gpu_data();
const Dtype* loss_weights = top[top_id]->gpu_diff();
Dtype blob_loss = 0;
caffe_gpu_dot(count, data, loss_weights, &blob_loss);
loss += blob_loss;
}
#endif
break;
default:
LOG(FATAL) << "Unknown caffe mode.";
}
// 解鎖互斥量forward_mutex_
Unlock();
return loss;
}
反向傳導函式 Backward()呼叫了 BackwardFromTo(int start, int end)函式
template <typename Dtype>
void Net<Dtype>::Backward()
{
BackwardFromTo(layers_.size() - 1, 0);
}
template <typename Dtype>
void Net<Dtype>::BackwardFromTo(int start, int end)
{
CHECK_GE(end, 0);
CHECK_LT(start, layers_.size());
// 倒過來逐層傳導
for (int i = start; i >= end; --i)
{
if (layer_need_backward_[i])
{
// 與正向傳導函式類似,雖然Backward()不是虛擬函式,但是包裝了虛擬函式Backward_cpu()和Backward_gpu(),因此不同層有不同的計算方法
// 注意反向傳導比正向傳導多了一個引數bottom_need_backward_。在實現反向傳導時,首先判斷當前層是否需要反向傳導的層,不需要則直接返回
layers_[i]->Backward(top_vecs_[i], bottom_need_backward_[i], bottom_vecs_[i]);
if (debug_info_)
{
BackwardDebugInfo(i);
}
}
}
}
正向傳導和反向傳導結束後,再呼叫SGDSolver::ApplyUpdate()成員函式進行權重更新。
template <typename Dtype>
void SGDSolver<Dtype>::ApplyUpdate()
{
// 獲取當前學習速率
Dtype rate = GetLearningRate();
if (this->param_.display() && this->iter_ % this->param_.display() == 0)
{
LOG(INFO) << "Iteration " << this->iter_ << ", lr = " << rate;
}
// 在計算當前梯度的時候,如果該值超過了閾值clip_gradients,則將梯度直接設定為該閾值
// 此處閾值設為-1,即不起作用
ClipGradients();
// 逐層更新網路中的可學習層
for (int param_id = 0; param_id < this->net_->learnable_params().size();
++param_id)
{
// 歸一化
Normalize(param_id);
// L2範數正則化新增衰減權重
Regularize(param_id);
// 隨機梯度下降法計算更新值
ComputeUpdateValue(param_id, rate);
}
// 更新權重
this->net_->Update();
}
最後將迭代次數++iter_,繼續while迴圈,直到迭代次數完成。
接下來該分析逐層 Layer的虛擬函式 Forward_cpu()和 Backward_cpu()具體實現了(部分層還有對應的GPU版本: Forward_gpu()和 Backward_gpu())。