Caffe源码分析--Blob代码分析

Blob代码分析

本文主要介绍代码相关内容,应用可参考caffe源码分析–Blob应用

1.原理分析

Caffe使用Blob结构在CNN网络中存储、传递、修改数据。

​ 对于批量2D图像数据,Blob的维度为: 图像数量N × 通道数C × 图像高度H × 图像宽度W

​ 在此种场景下,Blob使用4维坐标定位数据,如(n, c, h, w),其中n为图像序号(0到N-1),c为通道序号(0到C-1),h为图像行序(0到H-1),w为图像列序(0到W-1)。那么我们如何根据这个坐标找到对应的数据呢?要想得到这个问题的答案,就得弄清楚Blob在内存中的数据组织形式,也就是这批量的2D图像在内存中是如何存储的。其实它的存储方式很简单,见下图:

在这里插入图片描述

​ 图像数据依序存储,单张图像数据按通道序依次存储,组织形式简单明了。上图给了三张图像的存储例子,每张图像的通道数C为3,H为8,W为16。坐标(2, 1, 3, 9),代表这是第3张图像、第2个通道、第4行、第10列的像素值,实际存储位置为:

(((2 × C) + 1) × H + 3) × W + 9 = (((2 × 3) + 1) × 8 + 3) × 16 + 9 = 953

​ 通俗地讲,在内存中,第一张图像的第0个像素值存储在内存的第0个位置,Blob按照从左到右,从上到下的顺序,逐列、逐行、逐通道、逐张图像,将每个像素值存入内存:

​ b. blob中数据的dimentions为Num Nchannel K * Height H * Width W.内存是行优先的(row-major)。访问数据的时候按照如下的规则来访问index(n,k,h,w) 在物理上位于index((nK + k) *H + h)*W + w. 这里要注意,index(n,k,h,w)实际上访问的是内存中(n+1,k+1,h+1,w+1)位置的数据,这是因为索引是从0开始的。

在这里插入图片描述

​ 也就是说Blob的组织格式并无特别之处,顺序存储而已。坐标位置(n, c, h, w)与具体内存读取位置M的换算公式如下:

M = (((n × C) + c) × H + h) × W + w

​ blob数据结构,首先找到存储在内存中的数据,然后对数据进行修改操作,现在开始分析blob源码。

整理了一个关于Blob函数图,方便查看。
在这里插入图片描述

成员变量

​ 在blob.hpp代码中定义

protected:
  shared_ptr<SyncedMemory> data_; //存储前向传递权重数据
  shared_ptr<SyncedMemory> diff_; //存储反向传播的梯度数据
  shared_ptr<SyncedMemory> shape_data_;//shape_data_存储Blob的形状 
  vector<int> shape_;  //shape_存储Blob的形状 
  int count_; //count_表示Blob中的元素个数,也就是个数*通道数*高度*宽度
  int capacity_;//capacity表示当前的元素个数,因为Blob可能会reshape 

​ shared_ptr为C++的智能指针,是C++11提供的一种智能指针类,使用智能指针,可以在任何地方都不使用时自动删除相关指针,解决内存泄漏和悬空指针的问题。

​ SyncedMemory是caffe中用来管理内存分配和CPU、GPU数据及同步的类,只服务于Blob类 ,可参考caffe源码分析–SyncedMemory 内存管理机制

​ *shape_data_*是利用智能指针的存储blob形状的数据结构,*shape_*是一种vector类的数据结构,用来存储blob的数据结构,blob数据结构为【图像数量N × 通道数C × 图像高度H × 图像宽度W】。

构造函数和Reshape函数

初始化shape_ 和shape_data_里的cpu_ptr

Blob() //构造函数:初始化列表 {空函数体}
       : data_(), diff_(), count_(0), capacity_(0) {}
  //当构造函数被声明 explicit 时,编译器将不使用它作为转换操作符。
  explicit Blob(const int num, const int channels, const int height, const int width);   
//可以通过设置数据维度(N,C,H,W)初始化
  //const 传递过来的参数在函数内不可以改变(无意义,因为本身就是形参)
  explicit Blob(const vector<int>& shape); //也可以通过传入vector<int>直接传入维数

根据shape来初始化shape_shape_data_,以及为data_ diff_ 分配空间。

void Reshape(const int num, const int channels, const int height,
      const int width);
void Reshape(const vector<int>& shape);
void Reshape(const BlobShape& shape);
void ReshapeLike(const Blob& other);

具体实现:

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>

// 完成blob形状shape_的记录,大小count_的计算,合适大小capacity_存储的申请
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)));  
    // 只是构造了SyncedMemory对象,并未真正分配内存和显存
    diff_.reset(new SyncedMemory(capacity_ * sizeof(Dtype)));   
    // 真正分配是在第一次访问数据时
  }
}

template <typename Dtype>
void Blob<Dtype>::Reshape(const BlobShape& shape) {
  CHECK_LE(shape.dim_size(), kMaxBlobAxes);
  vector<int> shape_vec(shape.dim_size());
  for (int i = 0; i < shape.dim_size(); ++i) {
    shape_vec[i] = shape.dim(i);
  }
  Reshape(shape_vec);
}

template <typename Dtype>
void Blob<Dtype>::ReshapeLike(const Blob<Dtype>& other) {
  Reshape(other.shape());
}

template <typename Dtype>
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);
}

template <typename Dtype>
Blob<Dtype>::Blob(const vector<int>& shape)
  // capacity_ must be initialized before calling Reshape
  : capacity_(0) {
  Reshape(shape);
}

​ 构造函数就是先初始化capacity_,然后再调用Reshape函数,该函数的功能是改变blob的维,若新的存储空间变大,则重新分配存储空间。

成员变量设置函数

//打印数据维度,即blob的形状
inline string shape_string() const { //
    ostringstream stream;
    for (int i = 0; i < shape_.size(); ++i) {
      stream << shape_[i] << " ";
    }
    stream << "(" << count_ << ")";
    return stream.str();
  }
inline const vector<int>& shape() const { return shape_; }//获取shape_
inline int shape(int index) const;//获取index维的大小
inline int num_axes() const { return shape_.size(); }//返回Blob维度数,对于维数(N,C,H,W),返回4 
inline int count() const { return count_; }//返回Blob维度数,对于维数(N,C,H,W),返回N×C×H×W
inline int count(int start_axis, int end_axis) const;//获取某几维数据的大小
inline int count(int start_axis) const;//获取某一维到结束数据的大小
inline int CanonicalAxisIndex(int axis_index) const;//标准化索引,主要是对参数索引进行标准化,以满足要求
inline int LegacyShape(int index) const;//data_维数不大于4时才能使用,功能同shape()类似。
inline int offset(const int n, const int c = 0, \
            const int h = 0,const int w = 0) const;
            //计算物理偏移量,(n,c,h,w)的偏移量为((n∗C+c)∗H+h)∗W+w
inline int offset(const vector<int>& indices) const;//同上,只是参数不同
inline Dtype data_at(const int n, const int c, const int h,
      const int w) const;//获取某位置的data_数据
inline Dtype data_at(const vector<int>& index);//同上
inline Dtype diff_at(const int n, const int c, const int h,
      const int w) const;//获取某位置的diff_数据
inline Dtype diff_at(const vector<int>& index) const;//同上
inline const shared_ptr<SyncedMemory>& data() const;//获取data_
inline const shared_ptr<SyncedMemory>& diff() const;//获取diff_
const Dtype* cpu_data() const;//获取data_ cpu指针
const int* gpu_shape() const;//获取shape_data_的gpu指针
const Dtype* gpu_data() const;//获取data_的gpu指针
const Dtype* cpu_diff() const;//获取diff_的cpu指针
const Dtype* gpu_diff() const;//获取diff_的gpu指针

成员变量处理函数

void CopyFrom(const Blob<Dtype>& source, bool copy_diff =false,bool reshape = false);
/**功能:由source Blob拷贝到本Blob。
  参数:source 源Blob
       copy_diff 如果是false,拷贝data_,如果是true,拷贝diff_
       reshape 如果是false,则需要源Blob与本Blob形状相同,如果是
**/
void set_cpu_data(Dtype* data);//设置data_的cpu指针,只是修改了指针
Dtype* mutable_cpu_data();//见SyncedMemory的mutable_cpu_data();
Dtype* mutable_gpu_data();//见SyncedMemory的mutable_gpu_data();
Dtype* mutable_cpu_diff();//见SyncedMemory的mutable_cpu_data();
Dtype* mutable_gpu_diff();//见SyncedMemory的mutable_gpu_data();
void Update();
/**
其中用到math_functions.hpp中的函数caffe_axpy(),该函数封装了cblas_saxpy,实现的是Y=alpha*X+Y。由此,知该函数的功能是data_=(data_-diff_)。
另外,该函数只实现了对double和float型数据,对于unsigned int和int由于该函数主要是在Net中被调用,只有Blob<float>和Blob<double>型式,因此没有定义unsigned int和int。
**/
void FromProto(const BlobProto& proto, bool reshape = true);
//由BlobProto对Blob进行赋值操作。reshape代表是否允许修改shape_的大小。需要注意的是再这里有double和float两种类型的数据 ,在代码中可以看到具体的体现
void ToProto(BlobProto* proto, bool write_diff = false) const;//针对double和float有两个实现的函数,这里只举例说明其中的一个。将Blob中的数据存入BlobProto中,write_diff表示是否存diff_。
Dtype asum_data() const;
/**
功能:计算L1范数
说明:其中用到了math_function.hpp中的函数caffe_cpu_asum()和caffe_gpu_asum,实现的功能是对向量X求其每个元素绝对值的和,不同的是X分别在cpu和gpu中。
**/
Dtype asum_diff() const;//同上,不同的是针对的是diff_
Dtype sumsq_data() const;
/**
功能:计算L2范数。
说明:用到了math_function.hpp中的caffe_cpu_dot(),caffe_cpu_strided_dot(),caffe_gpu_dot(), caffe_gpu_strided_dot()。具体就是就向量X的平方和。
**/
Dtype sumsq_diff() const;//同上,不同的是针对的是diff_。
void scale_data(Dtype scale_factor);
/**
功能:正规化data_。
说明:用到math_function.hpp中的caffe_scal()和caffe_gpu_scal()函数,我的理解就是对向量X乘上一个因子。
**/
void scale_diff(Dtype scale_factor);//同上,不同在于针对diff_。
void ShareData(const Blob& other);//本Blob共享other的data_
void ShareDiff(const Blob& other);//本Blob共享other的diff_
bool ShapeEquals(const BlobProto& other);//判断other与本Blob形状是否相同。

几个重要的函数实现如下:

Update()函数:更新单个参数的权值

template <typename Dtype>
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
	// 参数更新,新参数(data_) = 原参数(data_) - 梯度(diff_)
    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.";
  }
}

asum_data() :计算data_的L1范数

template <typename Dtype>
Dtype Blob<Dtype>::asum_data() const { 
  if (!data_) { return 0; }
  switch (data_->head()) {
  case SyncedMemory::HEAD_AT_CPU:
    return caffe_cpu_asum(count_, cpu_data());
  case SyncedMemory::HEAD_AT_GPU:
  case SyncedMemory::SYNCED:
#ifndef CPU_ONLY
  {
    Dtype asum;
    caffe_gpu_asum(count_, gpu_data(), &asum);
    return asum;
  }
#else
    NO_GPU;
#endif
  case SyncedMemory::UNINITIALIZED:
    return 0;
  default:
    LOG(FATAL) << "Unknown SyncedMemory head state: " << data_->head();
  }
  return 0;
}

sumsq_data():计算data_ L2范式

template <typename Dtype>
Dtype Blob<Dtype>::sumsq_data() const {   
  Dtype sumsq;
  const Dtype* data;
  if (!data_) { return 0; }
  switch (data_->head()) {
  case SyncedMemory::HEAD_AT_CPU:
    data = cpu_data();
    sumsq = caffe_cpu_dot(count_, data, data);
    break;
  case SyncedMemory::HEAD_AT_GPU:
  case SyncedMemory::SYNCED:
#ifndef CPU_ONLY
    data = gpu_data();
    caffe_gpu_dot(count_, data, data, &sumsq);
#else
    NO_GPU;
#endif
    break;
  case SyncedMemory::UNINITIALIZED:
    return 0;
  default:
    LOG(FATAL) << "Unknown SyncedMemory head state: " << data_->head();
  }
  return sumsq;
}

scale_data(Dtype scale_factor): 对data_乘上某个因子

template <typename Dtype>
void Blob<Dtype>::scale_data(Dtype scale_factor) {   
  Dtype* data;
  if (!data_) { return; }
  switch (data_->head()) {
  case SyncedMemory::HEAD_AT_CPU:
    data = mutable_cpu_data();
    caffe_scal(count_, scale_factor, data);
    return;
  case SyncedMemory::HEAD_AT_GPU:
  case SyncedMemory::SYNCED:
#ifndef CPU_ONLY
    data = mutable_gpu_data();
    caffe_gpu_scal(count_, scale_factor, data);
    return;
#else
    NO_GPU;
#endif
  case SyncedMemory::UNINITIALIZED:
    return;
  default:
    LOG(FATAL) << "Unknown SyncedMemory head state: " << data_->head();
  }
}

参考:

【1】 Caffe中的Blob数据结构

【2】caffe源码阅读——Blob类

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值