Caffe2 教程-Brewing Models

Brew 简介

brew 是 Caffe2 里面的一个新的 API, 该 API 和过去的 CNNModelHelper 的作用是相似的, 但是由于 Caffe2 现在不仅仅支持 CNN, 因此将 CNNModelHelper 替换为 ModelHelper 显得更加合理. brew 封装了 ModelHelper , 是的搭建模型更加简单.

Model Building and Brew’s Helper Functions

在本小节中, 我们将会介绍一个由 helper functions 组成的轻量级的集合, 这些函数可以帮助我们更轻易的搭建模型. 下面我们会先介绍关于 Ops 和 Helper Functions 的相关概念. 接下来我们将会展示如何使用 brew 以及引入 brew 的动机.

Concepts: Ops vs Helper Functions

在介绍 brew 之前, 我们首先应该回顾一下 Caffe2 中的一些常规概念, 以及在一个神经网络中, 一个网络层是如何表示的. 在 Caffe2 中, 深度学习框架是建立在 operators 之上的. 通常这些 operators 使用 C++ 实现, 同时 Caffe2 会提供封装了 C++ 代码的 Python API. 在 Caffe2 中, operators 的命名方式采用驼峰风格, 而对应的 helper functions 则采用相似的小写名称.

Ops

通常我们将 operators 缩写为 Op, 将一系列 operators 的集合缩写为 Ops. 例如, 一个 FC Op 代表了一个 Fully-Connected operator. 下面的代码创建了一个 FC 网络层.

1
model.net.FC([blob_in, weights, bias], blob_out)

下面的代码创建了一个 Copy Op:

1
model.net.Copy(blob_in, blob_out)

也可以省略.net, 含义等价, 如下所示:

1
model.Copy(blob_in, blob_out)

ModelHelper 包含了29个最常用的 Ops. 其持有的 Ops 是 Caffe2 中400+ Ops 的一个子集

Helper Functions

如果使用单一的 operators 来创建网络或模型, 则在参数初始化的时候会非常麻烦, 并且还需要自己选择 device/engine (这也是为什么 Caffe2 很快!). 利用, 创建一个 FC 层的时候, 我们需要自己写代码来准备 weightsbias, 然后将其送入到 Op 中, 如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# This is the longer, manual way:

model = model_helper.ModelHelper(name="train")
# 初始化权重
weight = model.param_init_net.XavierFill(
[],
blob_out + '_w',
shape=[dim_out, dim_in],
**kwargs, # 指定 weights 的GPU位置
)
# 初始化偏置
bias = model.param_init_net.ConstantFill(
[],
blob_out + '_b',
shape=[dim_out, ],
**kwargs,
)
# 接着创建 FC
model.net.FC([blob_in, weights, bias], blob_out, **kwargs)

可以看出, 上面的代码十分麻烦, 因此, 我们可以利用 Caffe2 helper functions 来帮助我们. helper function 会自动处理参数的初始化, operator 的定义, 以及 engine 的选择. Caffe2 中默认的 helper functions 采用的是 Python PEP8 的命名规范. 例如, 它会使用 fc 来实现 FC Op. 下面的代码和上面的代码相同, 可以明显看出更加简洁:

1
fcLayer = fc(model, blob_in, blob_out, **kwargs)

brew

既然我们已经了解的 Ops 和 Helper Functions, 下面就来介绍一下如何利用 brew 更简单的搭建模型. brew 更像是一个智能的集合, 你可以利用它调用所有的 Caffe2 helper functions, 如下所示, 调用了一个 fc 辅助函数.

1
2
from caffe2.python import brew
brew.fc(model, blob_in, blob_out, ...)

当模型变的复杂度, brew 就会显得十分有用, 下面的代码定义了一个经典的 LeNet.

1
2
3
4
5
6
7
8
9
def AddLeNetModel(model, data):
conv1 = brew.conv(model, data, "conv1", 1, 20, 5)
pool1 = brew.max_pool(model, conv1, "pool1", kernel=2, stride=2)
conv2 = brew.conv(model, pool1, "conv2", 20, 50, 5)
pool2 = brew.max_pool(model, conv2, "pool2", kernel=2, stride=2)
fc3 = brew.fc(model, pool2, "fc3", 50*4*4, 500)
fc3 = brew.relu(model, fc3, fc3)
pred = brew.fc(model, fc3, "pred", 500, 10)
softmax = brew.softmax(model, pred, "softmax")

上面的代码使用 brew 来定义网络, 代码变的十分整洁.

arg_scope

arg_scope 更像是一种语法糖, 它可以让你在当前上下文环境中设置默认的 helper function 的参数. 举个例子, 如果你想要在 ResNet-150 的训练脚本中尝试使用不同的权重初始化, 你首先会想到这么做:

1
2
3
4
5
6
# 改变所有的 weight_init 参数
brew.conv(model, ..., weight_init=("XavierFill", {}), ...)
...
# 重复150次
...
brew.conv(model, ..., weight_init=("XavierFill", {}), ...)

而如果使用 arg_scope, 我们只需要更改一处地方即可:

1
2
3
4
with brew.arg_scope([brew.conv], weight_init=("XavierFill", {})):
brew.conv(model, ...) # 这里不用指定 weight_init 参数
brew.conv(model, ...)
...

自定义 Helper Function

如果你经常使用 brew, 而某个你希望的 Op 没有默认的实现, 那么你可以自定义使用一个 helper function.
创建自定义的 new_helper_function, 首先利用 brew.Register 进行注册, 然后就可以使用 brew.new_helper_function 进行调用, 如下所示:

1
2
3
4
5
def my_super_layer(model, blob_in, blob_out, **kwargs):
#...balabala

brew.Register(my_super_layer)
brew.my_super_layer(model, blob_in, blob_out)

Caffe2 Default Helper Functions

下面是 Caffe2 中默认的29个最常用的 Ops, 点击这里可以查看详细信息.

引入 brew 的动机.

长话短说: Caffe2 的开发者希望将模型的创建过程和模型的存储分离开. 在开发者的想法中, ModelHelper 类应该只包含网络的定义和网络参数信息. 而 brew 会包含用于创建网络的初始化参数的函数.
旧版的 CNNModelHelper 同时包含模型存储和模型搭建, 与之相比, ModelHelper + brew 的模型创建方式更加模块化且易于扩展. 并且避免了命名方面造成的疑惑(因为 Caffe2 不仅仅有CNN, 还有 RNN 和 MLP 等).

关于 brew 的一个详细示例

https://github.com/pytorch/pytorch/blob/master/caffe2/python/brew_test.py