• caffe源码整个训练过程


    Caffe源码
    Blob
    protected:
      shared_ptr<SyncedMemory> data_;
      shared_ptr<SyncedMemory> diff_;
      shared_ptr<SyncedMemory> shape_data_;
      vector<int> shape_;
      int count_;
      int capacity_;
    
    Blob的构造函数
    Blob<Dtype>::Blob(const int num, const int channels, const int height,
        const int width)
      // capacity_ must be initialized before calling Reshape
      : capacity_(0) {
      Reshape(num, channels, height, width);
    }
    
    会调用reshape函数,为data_,diff_分配内存
    template <typename Dtype>
    void Blob<Dtype>::Reshape(const int num, const int channels, const int height,
        const int width) {
      vector<int> shape(4);
      shape[0] = num;
      shape[1] = channels;
      shape[2] = height;
      shape[3] = width;
      Reshape(shape);
    }
    
    template <typename Dtype>
    void Blob<Dtype>::Reshape(const vector<int>& shape) {
      CHECK_LE(shape.size(), kMaxBlobAxes);
      count_ = 1;
      shape_.resize(shape.size());
      if (!shape_data_ || shape_data_->size() < shape.size() * sizeof(int)) {
        shape_data_.reset(new SyncedMemory(shape.size() * sizeof(int)));
      }
      int* shape_data = static_cast<int*>(shape_data_->mutable_cpu_data());
      for (int i = 0; i < shape.size(); ++i) {
        CHECK_GE(shape[i], 0);
        CHECK_LE(shape[i], INT_MAX / count_) << "blob size exceeds INT_MAX";
        count_ *= shape[i];
        shape_[i] = shape[i];
        shape_data[i] = shape[i];
      }
      if (count_ > capacity_) {
        capacity_ = count_;
        data_.reset(new SyncedMemory(capacity_ * sizeof(Dtype)));
        diff_.reset(new SyncedMemory(capacity_ * sizeof(Dtype)));
      }
                 
    
    
    Blob的序列化函数:
    //in blob.hpp
    void FromProto(const BlobProto& proto, bool reshape = true);
    void ToProto(BlobProto* proto, bool write_diff = false) const;
    ToProto将Blob的shape_,data_,diff_分别copy到BlobProto的shape,data,diff,完成序列化。FromProto将BlobProto的shape,data,diff分别copy到Blob的shape_,data_,diff_,完成数据解析。最后数据持久化函数由Protocol Buffers的工具实现
    
    
    
    
     
    
    Blob中还有个更新参数的函数update(),data=data-diff
    void Blob<Dtype>::Update() {
      // We will perform update based on where the data is located.
      switch (data_->head()) {
      case SyncedMemory::HEAD_AT_CPU:
        // perform computation on CPU
        caffe_axpy<Dtype>(count_, Dtype(-1),
            static_cast<const Dtype*>(diff_->cpu_data()),
            static_cast<Dtype*>(data_->mutable_cpu_data()));
        break;
      case SyncedMemory::HEAD_AT_GPU:
      case SyncedMemory::SYNCED:
    #ifndef CPU_ONLY
        // perform computation on GPU
        caffe_gpu_axpy<Dtype>(count_, Dtype(-1),
            static_cast<const Dtype*>(diff_->gpu_data()),
            static_cast<Dtype*>(data_->mutable_gpu_data()));
    #else
        NO_GPU;
    #endif
        break;
      default:
        LOG(FATAL) << "Syncedmem not initialized.";
      }
    }
    
    
    
    Layer有5纯虚函数
    Reshape()
    Forward_cpu()
    Backword_cpu()
    Forward_gpu()
    Backword_gpu()
    
    Layer层:
    Loss_layer
    Common_layer没有了(softmax,innerproduct)
    Neuron_layer(tanh)
    Vision layer没有了(pooling,conv)
    Data_layer变成了BasePrefetchingDataLayer(hdf5 input)
    
    
    
    Net
    Solver
    整个过程
    solver变量的构造函数中有init(param)
    init中有initTrainNet()函数,initTrainNet()函数有net_.reset(new Net<Dtype>(net_param));
    然后调用net的构造函数
    template <typename Dtype>
    Net<Dtype>::Net(const NetParameter& param, const Net* root_net)
        : root_net_(root_net) {
      Init(param);
    }
    通过一个for循环将layer一个一个串起来,并且调用layer的setup函数
    // layer 初始化设置
    void SetUp(const vector<Blob<Dtype>*>& bottom,   
        const vector<Blob<Dtype>*>& top) {
      InitMutex();
      CheckBlobCounts(bottom, top);
      LayerSetUp(bottom, top);
      Reshape(bottom, top);
      SetLossWeights(top);
    }
    LayerSetUp(bottom, top):由Layer类派生出的特定类都需要重写这个函数,主要功能是设置权值参数(包括偏置)的空间以及对权值参数经行随机初始化。 
    Reshape(bottom, top):根据输出blob和权值参数计算输出blob的维数,并申请空间。
    
    经过上述过程基本上就完成了初始化的工作,总体的流程大概就是新建一个Solver对象,然后调用Solver类的构造函数,然后在Solver的构造函数中又会新建Net类实例,在Net类的构造函数中又会新建各个Layer的实例,一直具体到设置每个Blob,大概就介绍完了网络初始化的工作,当然里面还有很多具体的细节,但大概的流程就是这样。
    
    上面过程就是从shared_ptr<caffe::Solver<float> > //初始化
    solver(caffe::SolverRegistry<float>::CreateSolver(solver_param));
    这个solver开始的
    solver->Solve();
    template <typename Dtype>
    void Solver<Dtype>::Solve(const char* resume_file) {
      ...
      int start_iter = iter_;
      ...
      // 然后调用了'Step'函数,这个函数执行了实际的逐步的迭代过程
      Step(param_.max_iter() - iter_);
      ...
      LOG(INFO) << "Optimization Done.";
    }
    
    step函数如下
    template <typename Dtype>
    void Solver<Dtype>::Step(int iters) {
      ...
      //迭代
      while (iter_ < stop_iter) {
        ...
        // iter_size也是在solver.prototxt里设置,实际上的batch_size=iter_size*网络定义里的batch_size,
        // 因此每一次迭代的loss是iter_size次迭代的和,再除以iter_size,这个loss是通过调用`Net::ForwardBackward`函数得到的
        // accumulate gradients over `iter_size` x `batch_size` instances
        for (int i = 0; i < param_.iter_size(); ++i) {
        /*
         * 调用了Net中的代码,主要完成了前向后向的计算,
         * 前向用于计算模型的最终输出和Loss,后向用于
         * 计算每一层网络和参数的梯度。
         */
          loss += net_->ForwardBackward();
        }
    
        ...
    
        /*
         * 这个函数主要做Loss的平滑。由于Caffe的训练方式是SGD,我们无法把所有的数据同时
         * 放入模型进行训练,那么部分数据产生的Loss就可能会和全样本的平均Loss不同,在必要
         * 时候将Loss和历史过程中更新的Loss求平均就可以减少Loss的震荡问题。
         */
        UpdateSmoothedLoss(loss, start_iter, average_loss);
    
    
        ...
        // 执行梯度的更新,这个函数在基类`Solver`中没有实现,会调用每个子类自己的实现
        //,后面具体分析`SGDSolver`的实现
        ApplyUpdate();
    
        // 迭代次数加1
        ++iter_;
        ...
    
      }
    }
    
    // 进行一次正向传播,一次反向传播
    Dtype ForwardBackward() {
      Dtype loss;
      Forward(&loss);
      Backward();
      return loss;
    }
    
    for (int i = start; i <= end; ++i) {
    // 对每一层进行前向计算,返回每层的loss,其实只有最后一层loss不为0
      Dtype layer_loss = layers_[i]->Forward(bottom_vecs_[i], top_vecs_[i]);
      loss += layer_loss;
      if (debug_info_) { ForwardDebugInfo(i); }
    }
    
    ApplyUpdate();
    这个函数是Solver类的纯虚函数,需要派生类来实现,比如SGDSolver类实现的ApplyUpdate();函数如下,主要内容包括:设置参数的学习率;对梯度进行Normalize;对反向求导得到的梯度添加正则项的梯度;最后根据SGD算法计算最终的梯度;最后的最后把计算得到的最终梯度对权值进行更新。
    
    template <typename Dtype>
    void SGDSolver<Dtype>::ApplyUpdate() {
      CHECK(Caffe::root_solver());
    
      // GetLearningRate根据设置的lr_policy来计算当前迭代的learning rate的值
      Dtype rate = GetLearningRate();
    
      // 判断是否需要输出当前的learning rate
      if (this->param_.display() && this->iter_ % this->param_.display() == 0) {
        LOG(INFO) << "Iteration " << this->iter_ << ", lr = " << rate;
      }
    
      // 避免梯度爆炸,如果梯度的二范数超过了某个数值则进行scale操作,将梯度减小
      ClipGradients();
    
      // 对所有可更新的网络参数进行操作
      for (int param_id = 0; param_id < this->net_->learnable_params().size();
           ++param_id) {
        // 将第param_id个参数的梯度除以iter_size,
        // 这一步的作用是保证实际的batch_size=iter_size*设置的batch_size
        Normalize(param_id);
    
        // 将正则化部分的梯度降入到每个参数的梯度中
        Regularize(param_id);
    
        // 计算SGD算法的梯度(momentum等)
        ComputeUpdateValue(param_id, rate);
      }
      // 调用`Net::Update`更新所有的参数
      this->net_->Update();
    }
    

      

  • 相关阅读:
    正则表达式之re模块
    collections模块
    openpyxl模块
    hashlib模块
    random模块
    os模块
    sys模块
    nodeType
    数据类型转换
    添加删除
  • 原文地址:https://www.cnblogs.com/wuxiangli/p/6527460.html
Copyright © 2020-2023  润新知