1. Blobs and Workspace, Tensors
Caffe2 的 Data 是以 blobs 的形式组织的。一个blob就是内存中被命名的数据块。blobs 一般包含一个 tenso,在 Python 中的存在形式是 numpy arrays。
Workspace 存储所有的 blobs. 下例展示了将 blobs Feed到 workspace 以及从 workspace 中Fetch blobs的方法。
from caffe2.python import workspace, model_helper
import numpy as np
# Create random tensor of three dimensions
x = np.random.rand(4, 3, 2)
print(x)
print(x.shape)
workspace.FeedBlob("my_x", x)
x2 = workspace.FetchBlob("my_x")
print(x2)
2. Nets,Operators
Caffe2 基础的模型抽象是 net. 而net又是由 operators 组成的 graph,每一个 operator 根据输入 input blobs 集,而输出一个或多个 output blobs.
- 1 个全连接层(FC);
- 1 个采用 softmax 的 Sigmoid 激活层;
- 1 个 CrossEntropy 层
这里不直接构建 nets,而是借助 model_helpers 来进行 nets 构建; model_helpers 是创建 nets 的 Python 类.
将待创建的网络记为 “my first net”,ModelHelper 会另外创建两个相互关联的 nets:
- 参数初始化网络(记为 init_net)
- 真实训练网络(记为 exec_net)
首先,随机生成 data 和label,并作为 blobs 输入到 workspace:
# Create the input data
data = np.random.rand(16, 100).astype(np.float32)
# Create labels for the data as integers [0, 9].
label = (np.random.rand(16) * 10).astype(np.int32)
workspace.FeedBlob("data", data)
workspace.FeedBlob("label", label)
然后,采用 model_helper 来创建 init_net 和 exec_net;
# Create model using a model helper
m = model_helper.ModelHelper(name="my first net")
接下来对 model 用 FC operator 来添加全连接层. 这里 FC op 需要先生成一些随机 fills.Weights 和 bias 可以使用 XavierFill 或 ConstantFill初始化,参数包括三部分:空数组([]);名称(‘fc_w’,’fc_b’);shape (shape=[output, input])
weight = m.param_init_net.XavierFill([], 'fc_w', shape=[10, 100])
bias = m.param_init_net.ConstantFill([], 'fc_b', shape=[10, ])
生成随机 fills 后,即可将 fc ops 添加到模型,并使用创建的 weights 和 bias blobs,可以根据其名字进行调用.
Caffe2 中,FC op 包括三部分: input blob,weights 和 bias.
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"])
回顾一下代码构建过程:
- 首先,创建内存里的 input data 和 label blobs;实际应用中,从相应的 database 来加载读取. data 和 label blobs 的 first dim=16,(first dim 即 batchsize=16). 基于
ModelHelper可以进行处理许多 Caffe2 operators,更多细节参考 ModelHelper’s Operator List. - 然后,通过定义多个 operators 来创建模型:FC, Sigmoid 和 SoftmaxWithLoss. 此时,只是定义了operators 和 model.
ModelHelper 创建了两个 nets:
m.param_init_net —– 只运行一次;初始化参数 blobs;
m.net —– 训练网络
网络定义保存为 protobuf 结构,可以通过调用 net.Proto() 来查看网络结构.
print(m.net.Proto())
The output should look like:
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"
You also should have a look at the param initialization net:
print(m.param_init_net.Proto())
输出应该是这样:
name: "my first net_init_2"
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
}
}
Caffe2 API 的基本思想:
采用 Python 方便快捷的组织网络来训练模型;
将网络以序列化 serialized protobuffers 传递到 C++ code;
然后,利用 C++ code 运行网络.
3.Executing
定义好模型训练 operators 后,可以开始训练模型.
首先,运行一次参数初始化网络:
workspace.RunNetOnce(m.param_init_net)
然后,创建训练网络:
workspace.CreateNet(m.net)
创造一次,可以有效的运行多次:
# Run 100 x 10 iterations
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) # run for 10 times
执行之后,可以查看结果
print(workspace.FetchBlob("softmax"))
print(workspace.FetchBlob("loss"))
Backward pass
以上网络训练只有 forward pass,没有学习到任何东西。
通过对 forward pass 中的每个 operator 创建其梯度 operators,即可 backward pass.
If you want to try this, add the following steps and examine the results!
Insert before you call RunNetOnce():
m.AddGradientOperators([loss])
Examine the protobuf output:
print(m.net.Proto())
输出应该是:
name: "my first net_5"
op {
input: "data"
input: "fc_w"
input: "fc_b"
output: "fc_1"
name: ""
type: "FC"
}
op {
input: "fc_1"
output: "pred"
name: ""
type: "Sigmoid"
}
op {
input: "pred"
input: "label"
output: "softmax"
output: "loss"
name: ""
type: "SoftmaxWithLoss"
}
op {
input: "loss"
output: "loss_autogen_grad"
name: ""
type: "ConstantFill"
arg {
name: "value"
f: 1.0
}
}
op {
input: "pred"
input: "label"
input: "softmax"
input: "loss_autogen_grad"
output: "pred_grad"
name: ""
type: "SoftmaxWithLossGradient"
is_gradient_op: true
}
op {
input: "pred"
input: "pred_grad"
output: "fc_1_grad"
name: ""
type: "SigmoidGradient"
is_gradient_op: true
}
op {
input: "data"
input: "fc_w"
input: "fc_1_grad"
output: "fc_w_grad"
output: "fc_b_grad"
output: "data_grad"
name: ""
type: "FCGradient"
is_gradient_op: true
}
external_input: "data"
external_input: "fc_w"
external_input: "fc_b"
external_input: "label"