↓ Skip to main content
  1. Posts/

caffe 源码学习笔记(7) slice layer

·1121 words·3 mins
Note: This article is available in Chinese only. 本文暂无英文版本。 View original

背景 
#

OCR 组那边有个 shufflenet 的网络,里面有个 pytorch op 叫 chunk,转成的 onnx 对应的 op 是 split。

作用是:

Split a tensor into a list of tensors, along the specified ‘axis’. Lengths of the parts can be specified using argument ‘split’. Otherwise, the tensor is split to equal sized parts.

然后发现这个 op 在模型转换里不支持转到 caffe 的 layer,于是想办法支持了一下,发现是要转到 caffe 的 slice layer。(caffe 也有一个 split layer,但那个 split layer 是用于一个输出 blob 作为多个 layer 的输入的情况)

proto
#

 1
 2message SliceParameter {
 3  // The axis along which to slice -- may be negative to index from the end
 4  // (e.g., -1 for the last axis).
 5  // By default, SliceLayer concatenates blobs along the "channels" axis (1).
 6  optional int32 axis = 3 [default = 1];
 7  repeated uint32 slice_point = 2;
 8
 9  // DEPRECATED: alias for "axis" -- does not support negative indexing.
10  optional uint32 slice_dim = 1 [default = 1];
11}

看起来 slice_dim 和 axis 是新旧两种写法,slice_point 应该就是切割点。这个 layer 的文档仍然是 “to do” 状态,因此只能看代码了。

c++实现
#

 1
 2template <typename Dtype>
 3void SliceLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom,
 4      const vector<Blob<Dtype>*>& top) {
 5  const SliceParameter& slice_param = this->layer_param_.slice_param();
 6  CHECK(!(slice_param.has_axis() && slice_param.has_slice_dim()))
 7      << "Either axis or slice_dim should be specified; not both.";
 8  slice_point_.clear();
 9  std::copy(slice_param.slice_point().begin(),
10      slice_param.slice_point().end(),
11      std::back_inserter(slice_point_));
12}

LayerSetUp 就是单纯把 slice_point 存到成员变量里。

Reshape 其实是比较重点的部分,注意不提供 slice_point 时的默认行为。其他部分很好懂,就是把切割点换算成每一段切割的长度。

详细代码
 1template <typename Dtype>
 2void SliceLayer<Dtype>::Reshape(const vector<Blob<Dtype>*>& bottom,
 3      const vector<Blob<Dtype>*>& top) {
 4  const int num_axes = bottom[0]->num_axes();
 5  const SliceParameter& slice_param = this->layer_param_.slice_param();
 6  if (slice_param.has_slice_dim()) {
 7    slice_axis_ = static_cast<int>(slice_param.slice_dim());
 8    // Don't allow negative indexing for slice_dim, a uint32 -- almost
 9    // certainly unintended.
10    CHECK_GE(slice_axis_, 0) << "casting slice_dim from uint32 to int32 "
11        << "produced negative result; slice_dim must satisfy "
12        << "0 <= slice_dim < " << kMaxBlobAxes;
13    CHECK_LT(slice_axis_, num_axes) << "slice_dim out of range.";
14  } else {
15    slice_axis_ = bottom[0]->CanonicalAxisIndex(slice_param.axis());
16  }
17  vector<int> top_shape = bottom[0]->shape();
18  const int bottom_slice_axis = bottom[0]->shape(slice_axis_);
19  // bottom_slice_axis为切割的那个维度的总数
20  num_slices_ = bottom[0]->count(0, slice_axis_);
21  //  计算[0,slice_axis_)的count
22  slice_size_ = bottom[0]->count(slice_axis_ + 1);
23  //  计算[slice_axis_+1,num_axes()]的体积
24  int count = 0;
25  if (slice_point_.size() != 0) {
26    CHECK_EQ(slice_point_.size(), top.size() - 1);
27    //  n个点把一条线段切割成n+1(top.size())份
28    CHECK_LE(top.size(), bottom_slice_axis);
29    //  抽屉原理,保证每一个输出blob至少有一份.
30    int prev = 0;
31    vector<int> slices;
32    //  slices保存每一个切割的长度
33    for (int i = 0; i < slice_point_.size(); ++i) {
34      CHECK_GT(slice_point_[i], prev);
35      slices.push_back(slice_point_[i] - prev);
36      prev = slice_point_[i];
37    }
38    slices.push_back(bottom_slice_axis - prev);
39    for (int i = 0; i < top.size(); ++i) {
40      top_shape[slice_axis_] = slices[i];
41      top[i]->Reshape(top_shape);
42      count += top[i]->count();
43    }
44  } else {
45    // 如果不填写 slice_point,默认是把slice维度平均分给所有输出的blob
46    // 比如输入为[1,3,M,224],在M所在的维度所slice,输出为3个blob
47    //  那么就会得到三个shape为 [1,3,M/3,224]的blob,并且保证M%3==0;
48    CHECK_EQ(bottom_slice_axis % top.size(), 0)
49        << "Number of top blobs (" << top.size() << ") should evenly "
50        << "divide input slice axis (" << bottom_slice_axis << ")";
51    top_shape[slice_axis_] = bottom_slice_axis / top.size();
52    for (int i = 0; i < top.size(); ++i) {
53      top[i]->Reshape(top_shape);
54      count += top[i]->count();
55    }
56  }
57  CHECK_EQ(count, bottom[0]->count());
58  if (top.size() == 1) {
59    top[0]->ShareData(*bottom[0]);
60    top[0]->ShareDiff(*bottom[0]);
61  }
62}

然后是forward,没什么好说的.

 1
 2template <typename Dtype>
 3void SliceLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,
 4      const vector<Blob<Dtype>*>& top) {
 5  if (top.size() == 1) { return; }
 6  int offset_slice_axis = 0;
 7  const Dtype* bottom_data = bottom[0]->cpu_data();
 8  const int bottom_slice_axis = bottom[0]->shape(slice_axis_);
 9  for (int i = 0; i < top.size(); ++i) {
10    Dtype* top_data = top[i]->mutable_cpu_data();
11    const int top_slice_axis = top[i]->shape(slice_axis_);
12    for (int n = 0; n < num_slices_; ++n) {
13      const int top_offset = n * top_slice_axis * slice_size_;
14      const int bottom_offset =
15          (n * bottom_slice_axis + offset_slice_axis) * slice_size_;
16      caffe_copy(top_slice_axis * slice_size_,
17          bottom_data + bottom_offset, top_data + top_offset);
18    }
19    offset_slice_axis += top_slice_axis;
20  }
21}

Related

caffe 源码学习笔记(6) reshape layer

·1851 words·4 mins
背景 # 最近在魔改 TensorRT 的 caffe parser。 之前 caffe 模型转到 trt 模型时,有一个修改需要将 reshape layer 的 param 末尾补 1,比较繁琐,于是看了下 caffe 的 reshape layer 的实现。

caffe 源码学习笔记(5) 卷积

·2756 words·6 mins
caffe中卷积运算的实现 # 暴力实现的卷积大概是这样子的 1 2for w in 1..W 3 for h in 1..H 4 for x in 1..K 5 for y in 1..K 6 for m in 1..M 7 for d in 1..D 8 output(w, h, m) += input(w+x, h+y, d) * filter(m, x, y, d) 9 end 10 end 11 end 12 end 13 end 14end 这种方式的效率显然很低,不意外地,caffe中并不是这样实现的.

caffe 源码学习笔记(4) 激活函数

·2604 words·6 mins
在看过 caffe 代码的三个核心部分 blob、layer、net 之后,陷入了不知道以什么顺序继续看的困境。 blob、layer、net 只是三个最基本的概念,关键还是在于各个 layer。但是 layer 这么多,要怎么看呢?想了一下,决定把相同作用的 layer 放在一起分析。今天先分析一下激活函数。

caffe 源码学习笔记(3) Net

·1790 words·4 mins
Net 基本介绍 # 网络通过组合与自动微分,共同定义一个函数及其梯度。 网络是由一些 Layer 组成的 DAG,也就是有向无环图,在 caffe 中通常由 prototxt 定义。 比如 1name: "LogReg" 2layer { 3 name: "mnist" 4 type: "Data" 5 top: "data" 6 top: "label" 7 data_param { 8 source: "input_leveldb" 9 batch_size: 64 10 } 11} 12layer { 13 name: "ip" 14 type: "InnerProduct" 15 bottom: "data" 16 top: "ip" 17 inner_product_param { 18 num_output: 2 19 } 20} 21layer { 22 name: "loss" 23 type: "SoftmaxWithLoss" 24 bottom: "ip" 25 bottom: "label" 26 top: "loss" 27} 定义了

caffe 源码学习笔记(2) Layer

·2896 words·6 mins
layer 整体介绍 # layer 是模型计算的基本单元, 类似于 pytorch 或者其他深度学习框架的 op。 layer 中的数据流向为:输入若干个 blob,称之为 “bottom blob”,经过 layer 的计算后,输出若干个 blob,称之为 “top blob”。