Caffe2基础概念

下面记录一下caffe2的一些基本概念。

Caffe2框架是一个静态的图框架。同大多静态图框架一样,它的应用程序一般会干两件事情,先是建立模型(即建图,主要使用Python等较友好的语言脚本或像caffe那样直接使用prototxt文件),然后则是迭代执行模型。

核心概念

Operator

可以看成是一个函数,输入一些数据,输出一些数据,如 layers、数值操作等。感觉与TensorFlow中的op是一个概念。

  • 基本结构参考这里,创建Operator的本质就是创建了一个protobuf对象。
  • C++: they all derive from a common interface, and are registered by type, so that we can call different operators during runtime。

Nets

Operator组成的计算图。

  • Python(源码): Caffe2’s core.Net is a wrapper class around a NetDef protocol buffer。
  • C++(源码)。

Blob

operator之间通过相互依赖的输入、输出发生关联关系。而这些operator的输入、输出则被称为Blob。Blob是深度学习模型当中数据元素的抽象,它可以指的是具体的输入、输出数据,也可以指的是operator计算当中用当到的可训练参数等。

  • Python:可以看成一个numpy’s ndarray对象。
  • C++(源码):a typed pointer that can store any type of C++ objects。

Workspace

当具体执行某个深度学习模型训练或推理任务时,需要考虑对全局所用到的变量及计算单元命名空间进行管理。它应当作为深度学习任务的发起者及终结者(当然还是收拾者)。这么一个角色在Tensorflow里面是Session,而caffe2中则是Workspace。它包含了一切运行时创建的对象,包括所有的Blob,以及Net等。

操作:

  • Blob的操作(如获取、判断是否存在、保存等)。
  • 对当前Workspace的一些操作(如切换、重置等)。
  • 运行计算图。
  • Python(源码):从这里这里中可以看出,几乎所有操作都通过pybind11调用了底层C++代码。
  • C++(源码)。

Tensor

显然反映high level数据抽象的Blob都应有相应的底层内存单元在背后。而Tensor则是这些底层内存单元的进一步封装。每个tensor都可以直接访问对应的内存单元地址,它之上的数据元素类型,它的大小,内存初始化及析构函数等则被封装到另外一个单元当中。这个单元即为TypeMeta,它具体反映tensor里面的数据元素的类型及相应的底层实现所需细节。

Context

Tensor与Operator分别对应相应的内存单元抽象及计算单元抽象。众所周知,当下可用于深度学习任务的计算单元有许多像CPU/GPU/FPGA/ASIC芯片等。它们在执行具体的计算任务时,分别有着不同的实现(分别包装着其各自特定的计算指令集及内存分配与读写单元)。caffe2中将各类不同的device封装为一个个不同的context对象。然后Tensor/Operator则以模板的形式分别在实例化时支持不同的底层计算设备context。

Registry

像Windows中使用注册表来管理其系统上的配置等信息一样,caffe2中也使用了全局Registry的方式对各种不同类型Op/OpSchema/Net等具体实现都进行了注册。这样当具体某个operator运行时,都会去全局Registry table里面查找此operator在相应设备上对应的注册实现并调用执行。一旦它发现此种类型实现不存在,那么就会报错退出。Net的不同类型执行也与此机理大致相似。

Python绑定

Caffe2中上当只支持C++与Python两种语言。在它的哲学当中,一切计算复杂的单元都应由C++实现的函数中来完成,而Python只是因其用户友好性被用来进行建图,规划整个图执行,并不真正参与核心的计算。这一点保证了它的高效率,但同时也意味着它对其它语言的支持不够好,同时也无法很灵活地添加一些由Python等高级语言写的Operators

一般由Python脚本所描绘的图会会序列化为protobuf文件,然后传入C++端,开始执行网络的计算过程。

在使用Python构建一个完整的用来training工作的图时,只需要显示写出做前向计算的系列operators,然后通过使用AddGradientOperators方法(以最终的loss作为其输入参数)可自动完成反向梯度计算的operators的添加。

总之Caffe2中的一切真正计算像先向/反向计算/Checkpoints保存/参数更新乃至参数初始化等等无一例外均是通过某一实现好的C++ operator函数来执行。

Blob & Workspace 操作

Python

  • 导入库
1
2
3
from caffe2.python import core, workspace, model_helper
from caffe2.proto import caffe2_pb2
import numpy as np
  • Blob 与 Workspace 操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# blob的底层实现可以是numpy中ndarray对象
X = np.random.randn(2, 3).astype(np.float32)

# 查看当前workspace拥有的blob
print("Current blobs in the workspace: {}".format(workspace.Blobs()))

# workspace是否有当前指定的blob
print("Workspace has blob 'X'? {}".format(workspace.HasBlob("X")))
workspace.FeedBlob("X", X) # 添加blob到workspace

# 从workspace中获取blob
# 如果要获取不存在的blob,会报错
print("Fetched X:\n{}".format(workspace.FetchBlob("X")))
try:
workspace.FetchBlob("invincible_pink_unicorn")
except RuntimeError as err:
print(err)
# 查看当前workspace拥有的blob
print("Current blobs in the workspace: {}".format(workspace.Blobs()))
# workspace是否有当前指定的blob
print("Workspace has blob 'X'? {}".format(workspace.HasBlob("X")))
  • workspace 相关操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 查看当前workspace
print("Current workspace: {}".format(workspace.CurrentWorkspace()))

# 查看当前workspace中的blobs
print("Current blobs in the workspace: {}".format(workspace.Blobs()))

# 切换workspace
workspace.SwitchWorkspace("gutentag", True)

# 查看当前workspace
print("Current workspace: {}".format(workspace.CurrentWorkspace()))

# 查看当前workspace中的blobs
print("Current blobs in the workspace: {}".format(workspace.Blobs()))

# 切换workspace
workspace.SwitchWorkspace("default")

# 查看当前workspace
print("Current workspace: {}".format(workspace.CurrentWorkspace()))

# 查看当前workspace中的blobs
print("Current blobs in the workspace: {}".format(workspace.Blobs()))

# 清楚当前workspace中的所有内容
workspace.ResetWorkspace()

# 查看当前workspace中的blobs
print("Current blobs in the workspace after reset: {}".format(workspace.Blobs()))

C++

  • blob与workspace操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <caffe2/core/init.h>
#include <caffe2/core/operator.h>
#include <caffe2/core/operator_gradient.h>
#include <caffe2/proto/caffe2.pb.h>
namespace caffe2 {
void print(const Blob* blob, const std::string& name) {
auto tensor = blob->Get<TensorCPU>();
const auto& data = tensor.data<float>();
std::cout << name << "(" << tensor.dims() << "): "
<< std::vector<float>(data, data+tensor.size())
<< std::endl;
}

void run() {
//define blobs with std::vector
std::vector<float> x(4*3*2);
for(float &v : x ) {
v = (float)rand() / RAND_MAX - 0.5;
}

// define workspace
Workspace workspace;

// print all blobls
std::cout << "current workspace has blobs" << std::endl;
std::vector<std::string> blobs = workspace.Blobs();
for(std::string &s:blobs){
std::cout << s << std::endl;
}

//feed blob
auto tensor = workspace.CreateBlob("X")->GetMutable<TensorCPU>();
TensorCPU value = TensorCPU({4, 3, 2}, x, NULL);
tensor->ResizeLike(value);
tensor->ShareData(value);

// print all blobs
std::cout << "current workspace has blobs" << std::endl;
blobs = workspace.Blobs();
for(std::string &s:blobs){
std::cout << s << std::endl;
}

// fetch blob
auto tensor2 = workspace.GetBlob("X");
print(tensor2, "X");

// has blob
std::cout << "current workspace has blob \"X\"?" << workspace.HasBlob("X") << std::endl;
}
}
int main(int argc, char** argv) {
caffe2::GlobalInit(&argc, &argv);
caffe2::run();
google::protobuf::ShutdownProtobufLibrary();
return 0;
}

workspace 本身操作:

  • 切换 workspace 的本质就是创建新的Workspace对象。
  • 重置 workspace 的本质就是调用 std::unique_ptr.reset(),使用新的对象取代旧对象,并删除旧对象。
  • C++源码中有一个 map对象 (std::string -> std::unique_ptr) 存储所有workspace 以及他们的名称。

Operator操作

Python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from caffe2.python import core, workspace, model_helper
from caffe2.proto import caffe2_pb2
import numpy as np

op = core.CreateOperator(
"Relu", # The type of operator that we want to run
["X"], # A list of input blobs by their names
["Y"], # A list of output blobs by their names
)

# op的本质就是protobuf object
print("Type of the created op is: {}".format(type(op)))
print("Content:\n")
print(str(op))

# 定义输入数据
workspace.FeedBlob("X", np.random.randn(2, 3).astype(np.float32))

# 运行op
workspace.RunOperatorOnce(op)

# 查看运行结果
print("Current blobs in the workspace: {}\n".format(workspace.Blobs()))
print("X:\n{}\n".format(workspace.FetchBlob("X")))
print("Y:\n{}\n".format(workspace.FetchBlob("Y")))
print("Expected:\n{}\n".format(np.maximum(workspace.FetchBlob("X"), 0)))

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <caffe2/core/init.h>
#include <caffe2/core/operator.h>
#include <caffe2/core/operator_gradient.h>
#include <caffe2/proto/caffe2.pb.h>
namespace caffe2 {
void print(const Blob* blob, const std::string& name) {
auto tensor = blob->Get<TensorCPU>();
const auto& data = tensor.data<float>();
std::cout << name << "(" << tensor.dims() << "): "
<< std::vector<float>(data, data+tensor.size())
<< std::endl;
}
void run() {
//define blobs with std::vector
std::vector<float> x(4*3*2);
for(float &v : x ) {
v = (float)rand() / RAND_MAX - 0.5;
}
// define workspace
Workspace workspace;
//feed blob
auto tensor = workspace.CreateBlob("X")->GetMutable<TensorCPU>();
TensorCPU value = TensorCPU({4, 3, 2}, x, NULL);
tensor->ResizeLike(value);
tensor->ShareData(value);
// create a OperatorDef and run it with workspace
caffe2::OperatorDef* op_def = new OperatorDef();
op_def->set_type("Relu");
op_def->add_input("X");
op_def->add_output("Y");
// run op
workspace.RunOperatorOnce(*op_def);
// print op output
print(workspace.GetBlob("Y"), "Y");
}
}

int main(int argc, char** argv) {
caffe2::GlobalInit(&argc, &argv);
caffe2::run();
google::protobuf::ShutdownProtobufLibrary();
return 0;
}

Net

Python

  • core.Net实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
from caffe2.python import core, workspace, model_helper
from caffe2.proto import caffe2_pb2
import numpy as np

# 新建Net
net = core.Net("my_first_net")

# 查看net的protoc
print("Current network proto:\n\n{}".format(net.Proto()))

# 通过net创建Operator,这种创建方式等价于:
# op = core.CreateOperator("SomeOp", ...)
# net.Proto().op.append(op)
X = net.GaussianFill([], ["X"], mean=0.0, std=1.0, shape=[2, 3], run_once=0)

# 查看net的protoc
print("New network proto:\n\n{}".format(net.Proto()))

# net创建Operator的,返回值是BlobReference
# BlobReference中保存了blob name以及创建该BlobReference的net
print("Type of X is: {}".format(type(X)))
print("The blob name is: {}".format(str(X)))
W = net.GaussianFill([], ["W"], mean=0.0, std=1.0, shape=[5, 3], run_once=0)
b = net.ConstantFill([], ["b"], shape=[5,], value=1.0, run_once=0)

# 也可以通过BlobReference直接创建Operator,其中Blob为输入
# 以下操作等价于 Y = net.FC([X, W, b], ["Y"])
Y = X.FC([W, b], ["Y"])

# 要运行Net可以通过workspace
# 方法一:workspace.RunNetOnce()
workspace.ResetWorkspace()
print("Current blobs in the workspace: {}".format(workspace.Blobs()))
workspace.RunNetOnce(net)
print("Blobs in the workspace after execution: {}".format(workspace.Blobs()))

# Let's dump the contents of the blobs
for name in workspace.Blobs():
print("{}:\n{}".format(name, workspace.FetchBlob(name)))

# 方法二:先workspace.CreateNet() 再workspace.RunNet()
workspace.ResetWorkspace()
print("Current blobs in the workspace: {}".format(workspace.Blobs()))
workspace.CreateNet(net)
workspace.RunNet(net.Proto().name)
print("Blobs in the workspace after execution: {}".format(workspace.Blobs()))
for name in workspace.Blobs():
print("{}:\n{}".format(name, workspace.FetchBlob(name)))
  • ModelHelper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from caffe2.python import core, workspace, model_helper
from caffe2.proto import caffe2_pb2
import numpy as np

# 搭建模型前,随机生成一些数据
data = np.random.rand(16, 100).astype(np.float32)
label = (np.random.rand(16) * 10).astype(np.int32)
workspace.FeedBlob("data", data)
workspace.FeedBlob("label", label)

# 使用ModelHelper搭建模型
m = model_helper.ModelHelper(name="my first net")
weight = m.param_init_net.XavierFill([], 'fc_w', shape=[10, 100])
bias = m.param_init_net.ConstantFill([], 'fc_b', shape=[10, ])
fc_1 = m.net.FC(["data", "fc_w", "fc_b"], "fc1")
pred = m.net.Sigmoid(fc_1, "pred")
softmax, loss = m.net.SoftmaxWithLoss([pred, "label"], ["softmax", "loss"])
m.AddGradientOperators([loss])
print(m.net.Proto())

# ModelHelper创建了两个对象
# m.param_init_net,只需要运行一次,用于初始化
# m.net 用于真正训练/预测模型
# 因为没有 反向传播过程,感觉下面这段代码有点呆
workspace.RunNetOnce(m.param_init_net)
workspace.CreateNet(m.net)
for _ in range(100):
data = np.random.rand(16, 100).astype(np.float32)
label = (np.random.rand(16) * 10).astype(np.int32)
workspace.FeedBlob("data", data)
workspace.FeedBlob("label", label)
workspace.RunNet(m.name, 10)
print(workspace.FetchBlob("softmax"))
print(workspace.FetchBlob("loss"))

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#include <caffe2/core/init.h>
#include <caffe2/core/operator.h>
#include <caffe2/core/operator_gradient.h>
#include <caffe2/proto/caffe2.pb.h>

namespace caffe2 {
void print(const Blob* blob, const std::string& name) {
auto tensor = blob->Get<TensorCPU>();
const auto& data = tensor.data<float>();
std::cout << name << "(" << tensor.dims() << "): "
<< std::vector<float>(data, data+tensor.size())
<< std::endl;
}

void run() {
// >>> data = np.random.rand(16, 100).astype(np.float32)
std::vector<float> data(16 * 100);
for (auto& v : data) {
v = (float)rand() / RAND_MAX;
}

// >>> label = (np.random.rand(16) * 10).astype(np.int32)
std::vector<int> label(16);
for (auto& v : label) {
v = 10 * rand() / RAND_MAX;
}

// >>> workspace.FeedBlob("data", data)
{
auto tensor = workspace.CreateBlob("data")->GetMutable<TensorCPU>();
auto value = TensorCPU({16, 100}, data, NULL);
tensor->ResizeLike(value);
tensor->ShareData(value);
}

// >>> workspace.FeedBlob("label", label)
{
auto tensor = workspace.CreateBlob("label")->GetMutable<TensorCPU>();
auto value = TensorCPU({16}, label, NULL);
tensor->ResizeLike(value);
tensor->ShareData(value);
}

// >>> m = model_helper.ModelHelper(name="my first net")
NetDef initModel;
initModel.set_name("my first net_init");
NetDef predictModel;
predictModel.set_name("my first net");

// >>> weight = m.param_initModel.XavierFill([], 'fc_w', shape=[10, 100])
{
auto op = initModel.add_op();
op->set_type("XavierFill");
auto arg = op->add_arg();
arg->set_name("shape");
arg->add_ints(10);
arg->add_ints(100);
op->add_output("fc_w");
}

// >>> bias = m.param_initModel.ConstantFill([], 'fc_b', shape=[10, ])
{
auto op = initModel.add_op();
op->set_type("ConstantFill");
auto arg = op->add_arg();
arg->set_name("shape");
arg->add_ints(10);
op->add_output("fc_b");
}
std::vector<OperatorDef*> gradient_ops;

// >>> fc_1 = m.net.FC(["data", "fc_w", "fc_b"], "fc1")
{
auto op = predictModel.add_op();
op->set_type("FC");
op->add_input("data");
op->add_input("fc_w");
op->add_input("fc_b");
op->add_output("fc1");
gradient_ops.push_back(op);
}

// >>> pred = m.net.Sigmoid(fc_1, "pred")
{
auto op = predictModel.add_op();
op->set_type("Sigmoid");
op->add_input("fc1");
op->add_output("pred");
gradient_ops.push_back(op);
}

// >>> [softmax, loss] = m.net.SoftmaxWithLoss([pred, "label"], ["softmax",
// "loss"])
{
auto op = predictModel.add_op();
op->set_type("SoftmaxWithLoss");
op->add_input("pred");
op->add_input("label");
op->add_output("softmax");
op->add_output("loss");
gradient_ops.push_back(op);
}

// >>> m.AddGradientOperators([loss])
{
auto op = predictModel.add_op();
op->set_type("ConstantFill");
auto arg = op->add_arg();
arg->set_name("value");
arg->set_f(1.0);
op->add_input("loss");
op->add_output("loss_grad");
op->set_is_gradient_op(true);
}
std::reverse(gradient_ops.begin(), gradient_ops.end());
for (auto op : gradient_ops) {
vector<GradientWrapper> output(op->output_size());
for (auto i = 0; i < output.size(); i++) {
output[i].dense_ = op->output(i) + "_grad";
}
GradientOpsMeta meta = GetGradientForOp(*op, output);
auto grad = predictModel.add_op();
grad->CopyFrom(meta.ops_[0]);
grad->set_is_gradient_op(true);
}

// >>> print(str(m.net.Proto()))
std::cout << std::endl;
print(predictModel);

// >>> print(str(m.param_init_net.Proto()))
std::cout << std::endl;
print(initModel);

// >>> workspace.RunNetOnce(m.param_init_net)
CAFFE_ENFORCE(workspace.RunNetOnce(initModel));

// >>> workspace.CreateNet(m.net)
CAFFE_ENFORCE(workspace.CreateNet(predictModel));

// >>> for j in range(0, 100):
for (auto i = 0; i < 100; i++) {
// >>> data = np.random.rand(16, 100).astype(np.float32)
std::vector<float> data(16 * 100);
for (auto& v : data) {
v = (float)rand() / RAND_MAX;
}
// >>> label = (np.random.rand(16) * 10).astype(np.int32)
std::vector<int> label(16);
for (auto& v : label) {
v = 10 * rand() / RAND_MAX;
}
// >>> workspace.FeedBlob("data", data)
{
auto tensor = workspace.GetBlob("data")->GetMutable<TensorCPU>();
auto value = TensorCPU({16, 100}, data, NULL);
tensor->ShareData(value);
}
// >>> workspace.FeedBlob("label", label)
{
auto tensor = workspace.GetBlob("label")->GetMutable<TensorCPU>();
auto value = TensorCPU({16}, label, NULL);
tensor->ShareData(value);
}
// >>> workspace.RunNet(m.name, 10) # run for 10 times
for (auto j = 0; j < 10; j++) {
CAFFE_ENFORCE(workspace.RunNet(predictModel.name()));
// std::cout << "step: " << i << " loss: ";
// print(*(workspace.GetBlob("loss")));
// std::cout << std::endl;
}
}
std::cout << std::endl;
// >>> print(workspace.FetchBlob("softmax"))
print(workspace.GetBlob("softmax"), "softmax");
std::cout << std::endl;
// >>> print(workspace.FetchBlob("loss"))
print(workspace.GetBlob("loss"), "loss");
}
}

int main(int argc, char** argv) {
caffe2::GlobalInit(&argc, &argv);
caffe2::run();
google::protobuf::ShutdownProtobufLibrary();
return 0;
}

参考

Caffe2核心代码解析系列之一:综述
Caffe2学习笔记(1) 一些基本概念

------ 本文结束------
坚持原创技术分享,您的支持将鼓励我继续创作!

欢迎关注我的其它发布渠道