Caffe2 手册

core

from caffe2.python import core :

Operators

Caffe2中的Operators有点像函数, 从C++的角度来说, 它们都是从一个通用接口派生而来的, 并且通过类型注册(什么意思?), 因此我们可以在runtime下调用不同的operators. 关于operators的接口定义在 caffe2/proto/caffe2.proto 中. 通常情况下, 它会接受一大批输入, 同时会返回一大批输出.

记住, 当我们说在 Caffe2 Python 中创建一个operator时, 还没有任何东西开始运行. 这仅仅是创建了一个protocal buffer(协议缓冲区), 用于指定具体的operator, 在稍后的一段时间内, 它将会被送入到C++后台去执行.(protobuf仅仅是一个针对结构化数据的json-like的序列化工具).

Relu

下面的代码创建了一个operator:

1
2
3
4
5
6
7
8
9
10
11
12
op = core.CreateOperator(
"Relu", # 我希望运行的operator的类型
["X"], # 一个列表, 里面存放的是输入的blobs的名字
["Y"] # 仍然是一个列表, 里面存放的是输出的blobs的名字
)

print(type(op)) # <class 'caffe2.proto.caffe2_pb2.OperatorDef'>
print(op)
# input: "X"
# output: "Y"
# name: ""
# type: "Relu"

接下来, 尝试使用operator, 首先将输入blobs添加到workspace中, 然后使用最简单的运行operator的方法, 即调用workspace.RunOperatorOnce(operator):

1
2
workspace.FeedBlob("X", np.random.randn(2,3).astype(np.float32))
workspace.RunOperatorOnce(op) # 指定需要运行的operator变量

执行了RunOperatorOnce以后, workspace中会多出一个名字为name="Y"的blobs, 用FetchBlobs()获取该blobs以后, 可以看到Y的值实际上就是对X执行Relu函数以后的结果(对应位置大于0的元素保留, 其他位置归0). 注意, 只有在执行了RunOperatorOnce以后, 名字为Y的blobs才会存在于workspace中, 此时使用FetchBlobs()才能够获取到对应值, 否则, Y是不存在的

CreateOperator

operators同时也可以接受一些可选参数, 这些参数可以通过键值的方式来指定:

1
2
3
4
5
6
7
8
op = core.CreateOperator(
"GaussianFill",
[], # 填充时不需要输入任何数据
["Z"], # 指定输出的名字
shape=[100,100], # 指定shape, 这里是一个100×100的二维列表
mean=1.0, # 指定平均值
std=1.0 # 指定标准差
)

Nets

Nets本质上就是计算图. 一个Net由多个operators组成, 当我们谈到Nets的时候, 我们同时也会谈到 BlobReference, 这是一个对象, 通过它我们可以轻易的将各种operators连接起来.

在创建网络时, 暗示着protocal buffer除了name以外其余都是空的

1
2
3
my_net = core.Net("my_first_net")  # 重复调用时, 会不断创建net, 每次都会在后面加上一个后缀以区别不同的net, 如my_first_net, my_first_net_1, my_first_net_2等等
print(my_net.Proto()) # name: "my_first_net"
print(type(my_net)) # <class 'caffe2.python.core.Net'>

接下来, 向net中创建一些blobs

1
X = my_net.GaussianFill([], ["X"], mean=0.0, shape=[2,3], std=1.0, run_once=0)

此时, my_net中就创建了一个operator, 其类型为”GaussianFill”. 在这里, 对象X的类型为: <class 'caffe2.python.core.BlobReference'>, 它会记录两个值, 一个是blob的名字, 另一个记录该对象是从哪个net中来的(_from_net).

上面没有使用core, 而是直接利用my_net进行operator的创建, 同样也可以使用core中的CreateOperator来创建operator, 并将其添加到对应的net中, 如下所示:

1
2
op = core.CreateOperator("op_name", ... )
net.Proto().op.append(op)

继续创建W和b:

1
2
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对象知道自己是从哪个net中来的, 因此, 可以直接利用BlobReference对象来创建operators, 下面显示了如何创建FC operator:

1
Y = X.FC([W,b], ["Y"])

如果利用net创建则是下面的形式:

1
Y = my_net.FC([X,W,b], ["Y"])

此时, 如果利用my_net.Proto()来查看net信息的话, 由于参数变多, 难以观察, 因此 Caffe2 提供了一个精简的minimal graph视图来帮助观察net:

1
2
3
4
from caffe2.python import net_drawer
from IPython import display
graph = net_drawer.GetPydotGraph(net, rankdir="LR")
display.Image(graph.create_pgn(), width=800)

通过上面的代码, 我们创建一个Net, 但是还没有任何东西被运行, 当我们运行network的时候, 会发生下面两件事情:

  • 一个C++的net对象会从protobuf中实例化
  • 实例化的网络的Run()函数会被调用

再做其他事情之前, 我们需要先用ResetWorkspace()清空早前的workspace, 然后有两种方式可以用来run net, 首先看看第一种:

1
2
3
4
5
6
workspace.ResetWorkspace()
print(workspace.Blobs()) # [] 可以看到, 列表为空, 因为X,Y,W,b均是operators
workspace.RunNetOnce(net) # 只执行一次, 无需调用CreateNet
print(workspace.Blobs()) # ['W','X','Y','b'], 在执行了RunNetOnce以后, workspace内被添加了有关operators的blobs.
for name in workspace.Blobs():
print(name, workspace.FetchBlobs(name))

下面是第二种创建并执行net的方式, 首先, 清空workspace里面的blobs, 然后创建net对象, 最后, 执行net

1
2
3
4
5
6
7
workspace.ResetWorkspace()
print(workspace.Blobs()) #[] 之前的blobs又被清空了
workspace.CreateNet(net) # 先创建net
workspace.RunNet(net.Proto().name) # 执行net, 传入net.Proto().name参数
print(workspace.Blobs()) # ['W','X','Y','b']
for name in workspace.Blobs():
print(name, workspace.FetchBlob(name))

RunNetOnceRunNet 之间有一点小区别, 最重要的区别就是计算开销的不同. 因为RunNetOnce包含了序列化protobuf, 同时还要将其在Python和C中传递, 并对network进行初始化, 因此它需要更长的执行时间.

net.FC()

1
model.net.FC()

net.Softmax()

net.SoftmaxWithLoss()

参数:
logits: Unscaled log probabilities
labels:
weight_tensor:

返回值:
softmax: Tensor with softmax cross entropy loss (也可以认为是概率)
loss: Average loss

示例:

1
model.net.SoftmaxWithLoss()

net.LabelCrossEntropy()

示例:

1
xent = model.LabelCrossEntropy([softmax, label], 'xent')

net.AveragedLoss(xent, “loss”)

示例:

1
loss = model.AveragedLoss(xent, "loss")

net.Cast()

示例:

1
data = model.net.Cast(data_uint8, "data", to=core.DataType.FLOAT)

net.Scale()

示例:

1
2
# 将 data 从 [0, 255] 放缩到 [0,1]
data = model.Scale(data, data, scale=float(1./256))

net.StopGradient()

示例:

1
2
# 避免计算数据的梯度
data = model.StopGradient(data, data)

net.Checkpoint()

workspace

from caffe2.python import workspace

1
2
import cv2 # NOQA(Must import before importing caffe2 due to bug in cv2)
from caffe2.python import workspace

GlobalInit()

1
2
3
workspace.GlobalInit(
['caffe2', '--caffe2_log_level=0', '--caffe2_gpu_memory_tracking=1']
)

初始化caffe2的全局环境, 如果希望看到更加详细的初始化信息, 可以将--caffe2_log_level设置为1. 当初始化成功时, 返回 True, 否则, 返回 False.

caffe2的工作空间由你创建的blobs组成, 并且将其存储在内存当中. 将blob当做是numpy中的一个N维数组, 可以从Basics.ipynb中看出, blob实际上是一个指针, 它指向可以存储任意类型的一个C++对象, 但是最常存储的类型仍然是 Tensor 类型.

Blobs()

workspace.Blobs()
方法可以打印出workspace中的所有存在的blobs (只会显示出blobs的名称).

HasBlob()

workspace.HasBlob("blob_name")
用于查询workspace中是否存在名为blob_name的blob, 返回一个布尔值

workspace.FeedBlob()

用于向workspace中添加blobs

1
2
3
4
X = np.random.randn(2,3).astype(np.float32)
print("Generated X from numpy:\n{}".format(X))
workspace.FeedBlob("X", X) # 若添加成功, 则返回True, 否则, 返回False
# 如果调用该函数时, 名字已经在workspace中存在, 则后添加的值会覆盖之前添加的值

workspace.FetchBlob("X")

用来获取对应名字的blob的值, 如果获取的名字不在workspace中, 那么就会抛出错误, 我们可以用利用try except语句来输出错误信息:

1
2
3
4
try:
workspace.FetchBlob("not_exists_name")
except RuntimeError as err:
print(err) # Can't find blob:...

workspace.ResetWorkspace()

说明:
重置工作空间, 如果root_folder为空, 则会保持当前的目录设置.

参数:
root_folder(str): 路径

返回:

定义:

备注:
返回

workspace.SwitchWorkspace()

同时, 你可以拥有多个不同的workspaces, 每一个workspace都有不同的名字, 你可以调用workspace.SwitchWorkspace("name")来在这些不同的名字之间交换. 不同workspace中的blobs是相互独立的, 你可以使用workspace.CurrentWorkspace来查询当前所有的workspace:

1
2
3
4
5
6
# Switch the workspace. The second argument "True" means creating
# the workspace if it is not exists
workpsace.SwitchWorkspace("new_workspace_name", True) # 如果名为"new_workspace_name"的workspace存在, 则切换当前workspace到该workspace, 如果不存在, 那么就创建它.

print("Current workspace:{}".format(workspace.CurrentWorkspace())) # 默认的workspace的名称为: default
print("Current blobs in the workspace:{}".format(workspace.Blobs()))

利用workspace.ResetWorkspace()可以清空当前workspace中的所有东西, 谨慎使用

1
workspace.ResetWorkspace()

ModelHelper

源码: model_helper.py

1
2
from caffe2.python import model_helper
model = model_helper.ModelHelper(...)

param_init()

XavierFill()

1
2
m = model_helper.ModelHelper(name="my model")
weight = m.param_init_net.XavierFill([], "fc_w", shape=[10, 100])

ConstantFill()

1
bias = m.param_init_net.ConstantFill([], "fc_b", shape=[10, ])

net

net.Accuracy()

net.FC()

1
fc_1 = m.net.FC(["data", "fc_w", "fc_b"], "fc1") # "data", "fc_w", "fc_b" 都是workspace中的blobs

net.Sigmoid()

1
pred = m.net.Sigmoid(fc_1, "pred")

net.SoftmaxWithLoss()

1
softmax, loss = m.net.SoftmaxWithLoss([pred, "label"], ["softmax", "loss"])

brew

源码: brew.py, caffe2/python/helpers/

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
from caffe2.python.helpers.algebra import *
from caffe2.python.helpers.arg_scope import *
from caffe2.python.helpers.array_helpers import *
from caffe2.python.helpers.control_ops import *
from caffe2.python.helpers.conv import *
from caffe2.python.helpers.db_input import *
from caffe2.python.helpers.dropout import *
from caffe2.python.helpers.elementwise_linear import *
from caffe2.python.helpers.fc import *
from caffe2.python.helpers.nonlinearity import *
from caffe2.python.helpers.normalization import *
from caffe2.python.helpers.pooling import *
from caffe2.python.helpers.tools import *
from caffe2.python.helpers.train import *

class HelperWrapper(object):
_registry = {
'arg_scope': arg_scope,
'fc': fc,
'packed_fc': packed_fc,
'fc_decomp': fc_decomp,
'fc_sparse': fc_sparse,
'fc_prune': fc_prune,
'dropout': dropout,
'max_pool': max_pool,
'average_pool': average_pool,
'max_pool_with_index' : max_pool_with_index,
'lrn': lrn,
'softmax': softmax,
'instance_norm': instance_norm,
'spatial_bn': spatial_bn,
'relu': relu,
'prelu': prelu,
'tanh': tanh,
'concat': concat,
'depth_concat': depth_concat,
'sum': sum,
'transpose': transpose,
'iter': iter,
'accuracy': accuracy,
'conv': conv,
'conv_nd': conv_nd,
'conv_transpose': conv_transpose,
'group_conv': group_conv,
'group_conv_deprecated': group_conv_deprecated,
'image_input': image_input,
'video_input': video_input,
'add_weight_decay': add_weight_decay,
'elementwise_linear': elementwise_linear,
'layer_norm': layer_norm,
'batch_mat_mul' : batch_mat_mul,
'cond' : cond,
'loop' : loop,
'db_input' : db_input,
}
# ...

示例:

1
2
3
4
5
6
from caffe2.python import brew

def AddLeNetModel(model, data):
conv1 = brew.conv(model, data, 'conv1', 1, 20, 5)
pool1 = brew.max_pool(model, conv1, 'pool1', kernel=2, stride=2)
# ...

brew.conv()

参数:
model:
blobs_in:
blobs_out:
dim_in:
dim_out:
kernel:
stride:
pad:

返回值:
blobs_out 的引用

示例:

1
2
3
4
conv1 = brew.conv(
model, data, 'conv1', dim_in=image_channels, dim_out=32,
kernel=5, stride=1, pad=2
)

brew.max_pool()

参数:
model:
blobs_in:
blobs_out:
kernel:
stride:

返回值:
blobs_out 的引用

池化层:

1
pool1 = brew.max_pool(model, conv1, "pool1", kernel=2, stride=2)

brew.average_pool()

示例:

1
pool2 = brew.average_pool(model, relu2, "pool2", kernel=3, stride=2)

brew.relu()

示例:

1
relu1 = brew.relu(model, pool1, "relu1")

brew.accuracy()

参数:
model: 模型对象
blob_in: 输入的blob
blob_out: 输出的blob
**kwargs:
kwargs['device_option']:
kwargs['top_k']:
kwargs['accuracy']:

返回值:
model.net.Accuracy(...,blob_out,...)

源码: caffe2/python/helpers/train.py

1
2
3
4
5
6
7
8
9
10
11
12
# brew.py
# rows 55
class HelperWrapper(object):
_registry = {
'accuracy': accuracy
}

# caffe2/python/helpers/train.py
# rows 37
def accuracy(model, blob_in, blob_out, **kwargs):
# ...
model.net.Accuracy(...)

示例:

1
2
3
4
# blobs_in 为 [softmax, label]
# blobs_out 为 "accuracy"
def AddAccuracy(model, softmax, label):
accuracy = brew.accuracy(model, [softmax, label], "accuracy")

brew.fc()

参数:
model
blob_in
blob_out
dim_in
dim_out
weight_init=None
bias_init=None
WeightInitializer=None
BiasInitializer=None
enable_tensor_core=False
float16_compute=False
**kwargs

返回值:
_: blob_out 的引用

源码: caffe2/python/helpers/fc.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# brew.py
# rows 34
from caffe2.python.helpers.fc import *

class HelperWrapper(object):
_registry={
'fc':fc
}

# caffe2/python/helpers/fc.py
# rows 57
def fc(model, *args, **kwargs):
return _FC_or_packed_FC(model, model.net.FC, *args, **kwargs)

# rows 13
def _FC_or_packed_FC(
model, op_call, blob_in, blob_out, dim_in, dim_out,
weight_init=None, bias_init=None, WeightInitializer=None, BiasInitializer=None,
enable_tensor_core=False, float16_compute=False, **kwargs
):
# ...
return op_call([blob_in, weight, bias], blob_out, **kwargs)

brew.softmax()

参数:
model
blob_in
blob_out=None
use_cudnn=False
**kwargs:
kwargs['engine']: 'CUDNN'

返回值:

源码: caffe2/python/helpers/normalization.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# brew.py
# rows 44

_registry={
'softmax':softmax
}

# caffe2/python/helpers/normalization.py
# rows 37
def softmax(model, blob_in, blob_out=None, use_cudnn=False, **kwargs):
if use_cudnn:
kwargs['engine'] = 'CUDNN'
if blob_out is not None:
return model.net.Softmax(blob_in, blob_out, **kwargs)
else:
return model.net.Softmax(blob_in, **kwargs)

brew.db_input()

数据集输入层处理函数

参数:
model(ModelHelper):
blobs_out:
batch_size(int): eg: 64
db(str): 数据文件夹的路径, eg: /home/zerozone/caffe2_notebooks/tutorial_data/cifar10/training_lmdb
db_type(str): eg: 'lmdb'

返回值:

源码: caffe2/python/helpers/db_input.py

1
2
3
4
5
6
7
8
9
10
def db_input(model, blobs_out, batch_size, db, db_type):
dbreader_name = "dbreader_" + db
dbreader = model.param_init_net.CreateDB(
[],
dbreader_name,
db=db,
db_type=db_type,
)
return model.net.TensorProtosDBInput(
dbreader, blobs_out, batch_size=batch_size)

示例:

1
2
3
4
5
6
7
data_uint8, label = brew.db_input(
model, # ModelHelper 实例
blobs_out=["data_uint8", "label"],
batch_size=batch_size,
db=db,
db_type=db_type,
)

optimizer

1
from caffe2.python import optimizer

源码: caffe2/python/optimizer.py

optimizer.build_sgd()

参数:
model:
base_learning_rate(float):
max_gradient_norm=None:
allow_lr_injection=False:
**kwargs:
kwargs['policy'](str): eg "fixed"
kwargs['momentum'](float): eg 0.9
kwargs['weight_decay'](float): eg 0.004

返回值:
优化器实例

源码:

1
2
3
4
5
6
7
8
9
10
11
# caffe2/python/optimizer.py
# rows 1410
def build_sgd(
model,
base_learning_rate,
max_gradient_norm=None,
allow_lr_injection=False,
**kwargs
):
sgd_optimizer = SgdOptimizer(base_learning_rate, **kwargs)
return _build(...)

CNNModelHelper

源码: cnn.py

1
2
3
from caffe2.python import cnn

m = cnn.CNNModelHelper(name="my cnn helper")

CNNModelHelper.FC()

1
2
3
4
# cnn.py
# rows 133
def FC(self, *args, **kwargs):
return brew.fc(self, *args, **kwargs)

CNNModelHelper.Softmax()

1
2
3
4
# cnn.py
# rows 158
def Softmax(self, *args, **kwargs):
return brew.softmax(self, *args, use_cudnn=self.use_cudnn, **kwargs)

caffe2_pb2

1
from caffe2.proto import caffe2_pb2

源码: caffe2.proto

caffe2_pb2.NetDef()

示例:

1
2
3
4
5
6
7
8
# 这里的 init_net_proto 也可以替换成 predict_net_proto
# 相应的应该将 .param_init_net 替换成 .net
init_net_proto = caffe2_pb2.NetDef()
with open(init_net_path, "rb") as f:
init_net_proto.ParseFromString(f.read())
test_model.param_init_net = test_model.param_init_net.AppendNet(
core.Net(init_net_proto)
)

caffe2_pb2.TensorProtos()

定义:

1
2
3
4
5
// caffe2/proto/caffe2.proto
// rows 134
message TensorProtos{
repeated TensorProto protos = 1;
}

备注:

1
2
3
tensor_protos = caffe2_pb2.TensorProtos()

img_tensor = tensor_protos.protos.add()

caffe2_pb2.TensorProto()

TensorProto.dims

定义:

1
2
3
// caffe2/proto/caffe2.proto
// rows 44
repeated int64 dims = 1;

备注:

1
2
3
4
5
print(type(img_tensor.dims))
# <class 'google.protobuf.pyext._message.RepeatedScalarContainer'>

img_tensor.dims.extend((32,32,3))
print(img_tensor.dims) # [32, 32,3]

TensorProto.data_type

定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// caffe2/proto/caffe2.proto
// rows 47
enum DataType {
UNDEFINED = 0;

// Basic types
FLOAT = 1; // float
INT32 = 2; // int
BYTE = 3; // byte, when deserialized, is going to be restored as uint8
STRING = 4; // string

// Less-commonly used data types
BOOL = 5; // bool
UINT8 = 6; // uint8_t
INT8 = 7; // int8_t
UINT16 = 8; // uint16_t
INT16 = 9; // int16_t
INT64 = 10; // int64_t
FLOAT16 = 12; // at::Half
DOUBLE = 13; // double
}
optional DataType data_type = 2 [default = FLOAT];

备注:

1
2
3
4
5
print(type(img_tensor.data_type))
# <class 'int'>
img_tensor.data_type = 1 # 默认值为2
print(img_tensor.data_type)
# 1

TensorProto.float_data

定义:

1
2
3
// caffe2/proto/caffe2.proto
// rows 85
repeated float float_data = 3 [packed = true];

备注:

1
img_tensor.float_data.extend(flatten_img) # flatten_img 是 3072 长的一维向量(32×32×3)

TensorProto.int32_data

定义:

1
2
3
// caffe2/proto/caffe2.proto
// rows 89
repeated int32 int32_data = 4 [packed = true];

备注:

1
label_tensor.int32_data.append(5)

Caffe2 模型结果可视化

下面的网站可以对 .Prototxt 格式的文件或者代码可视化显示

http://ethereon.github.io/netscope/quickstart.html

http://ethereon.github.io/netscope/#/editor

mobile_exporter

mobile_exporter.Export()