Caffe2 入门教程

Caffe2 概念


Caffe2已经发布几个月了,但目前的使用率并不高,相关文档并不完善,与Caffe(1)相比入门较难。本文主要讲解Caffe2的一些概念,由于本人也是新手,仅做参考,欢迎交流。

Tutorials: Intro Tutorial

Blobs and Workspace, Tensors


和Caffe1一样,Caffe2也有Blobs,并也代表着内存中的数据块。大多数的blobs中都含有tensor,就像Tensorflow一样,可以简单视为一种多维数组,具体在python中被视为numpy arrays。

除了tensor还有workspace这一全新概念。下面举个例子,演示一下如何把blobs 添加到 workspace,并取出。Workspace会在你开始使用时初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
from caffe2.python import workspace, model_helper
import numpy as np
# 创建三维随机张量
x = np.random.rand(4, 3, 2)
print(x)
print(x.shape)
# 将创建的tensor输入到Workspace中,命名为"my_x"
workspace.FeedBlob("my_x", x)
# 从Workspace中取出数据
x2 = workspace.FetchBlob("my_x")
print(x2)

对比一下xx2,发现是一样的。

网络与操作符


Caffe2的基本对象是网络(net)。Net 是一种操作符(operators)的图,并每个操作符取输入blobs数据并输出一或多个blobs。
下面的程序块会创建一个简单的模型,包含这些组成:

  • 一个全连接层
    • Sigmoid activation with a Softmax
    • a CrossEntropy loss

直接组合网络是否乏味,因而最好使用一些有助于创建网络的模型助手。python中使用ModelHelper来帮助我们建立”my first net”网络。ModelHelper将创建两个相互关联的网络:

  1. 一个初始化参数 (ref. init_net)
  2. 一个运行实际的训练(ref. exec_net)
1
2
3
4
5
6
7
8
# 创建随机输入数据
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中。

1
2
# 使用模型助手建立模型
m = model_helper.ModelHelper(name="my first net")

现在使用model_helper创建了我们之前提到的两个网络(init_netexec_net)。 在下面的模型中使用FC操作符添加一个全连接层,但首先我们需要做一些准备工作:创建FC操作符所期望的一些随机数作为权重。 接下来,我们可以添加操作,并使用我们创建的权重和偏差blobs。

1
2
weight = m.param_init_net.XavierFill([], 'fc_w', shape=[10, 100])
bias = m.param_init_net.ConstantFill([], 'fc_b', shape=[10, ])

在Caffe2中,FC操作符的输入为:数据、权重、偏置。权重和偏置分别使用XavierFillConstantFill初始化,输入为空数组、名称、形状。

1
2
3
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"])

首先创建了内存中的数据和标签的blob(但没有加载数据)。数据和标签第一个维度为16,是一个mini-batch。 许多Caffe2的操作符可以通过ModelHelper直接访问,并且可以处理一个mini-batch的输入。
其次,我们通过定义一组操作符来创建一个模型:FC,Sigmoid和SoftmaxWithLoss。 注意:在这一点上,运算符没有被执行,你只是创建模型的定义。
Model helper将创建两个网络:m.param_init_net只运行一次。它会初始化所有blob参数,如FC的权重。实际的训练是通过m.net的。 这对你来说是透明的,并且会自动运行。

网络定义储存在protobuf中,可以通过net.Proto()查看:

1
print(str(m.net.Proto()))

输出为:

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
name: "my first net"
op {
input: "data"
input: "fc_w"
input: "fc_b"
output: "fc1"
name: ""
type: "FC"
}
op {
input: "fc1"
output: "pred"
name: ""
type: "Sigmoid"
}
op {
input: "pred"
input: "label"
output: "softmax"
output: "loss"
name: ""
type: "SoftmaxWithLoss"
}
external_input: "data"
external_input: "fc_w"
external_input: "fc_b"
external_input: "label"

你也可以查看参数初始网络:

1
print(str(m.param_init_net.Proto()))

输出为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
name: "my first net_init"
op {
output: "fc_w"
name: ""
type: "XavierFill"
arg {
name: "shape"
ints: 10
ints: 100
}
}
op {
output: "fc_b"
name: ""
type: "ConstantFill"
arg {
name: "shape"
ints: 10
}
}

可以看到有两个操作符可以为FC操作符的权重和偏置点创建随机初始化。
这是Caffe2 API的主要思想:使用Python方便地编写网络来训练模型,将网络传递给C ++代码作为序列化的原始缓冲区,然后让C ++代码运行网络,可以达到最好的性能。

执行


现在,当我们定义了模型训练运算符时,我们可以开始运行它来训练我们的模型。
首先,我们只运行一次param初始化:

1
workspace.RunNetOnce(m.param_init_net)

注意,像往常一样,这将实际上将param_init_net的protobuf下载到C ++运行。
然后我们创建实际的训练网络:

1
workspace.CreateNet(m.net)

我们创建一次,然后我们可以有效地运行它多次:

1
2
3
4
5
6
7
8
9
# 运行 100 x 10 次迭代
for j in range(0, 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) # 运行10

注意我们如何引用RunNet()中的网络名称。 由于网络是在workspace内创建的,所以我们不需要再次通过网络定义。
执行后,可以检查存储在输出blob中的结果(包含张量,即numpy数组):

反向传播


这个网只包含正向传播,所以没有学习任何东西。反向传播是通过在正向传播中为每个运算符创建梯度运算符而创建的。

在调用RunNetOnce()之前插入以下内容:

1
m.AddGradientOperators([loss])

检查protobuf的输出:

1
print(str(m.net.Proto()))


本文只是简单翻译一下官方教程,涉及内容不多,以后会尝试更多的功能。
原文的jupyter notebook 版本上传到git中。