深度学习:Paddle 中的模型与层
引用自海龙王精神病院院长
模型是深度学习中的重要概念之一。
模型的核心功能是将一组输入变量经过一系列计算,映射到另一组输出变量,该映射函数即代表一种深度学习算法。
在Paddle框架中,模型包括以下两方面内容:
- 一系列层的组合用于进行映射(前向执行)
- 一些参数变量在训练过程中实时更新
在Paddle中定义模型与层
在Paddle中,大多数模型由一系列层组成,层是模型的基础逻辑执行单元。层中持有两方面内容:
- 计算所需的变量以临时变量或参数的形式作为层的成员持有
- 持有一个或多个具体的Operator来完成相应的计算
从零开始构建变量、Operator,从而组建层、模型是一个很复杂的过程。并且当中难以避免的会出现很多冗余代码,因此Paddle提供了基础数据类型paddle.nn.Layer,来方便你快速的实现自己的层和模型。
模型和层都可以基于 paddle.nn.Layer扩充实现,因此也可以说模型只是一种特殊的层。下面将演示如何利用 paddle.nn.Layer 建立自己的模型:
class Model(paddle.nn.Layer):
def __init__(self):
super(Model, self).__init__()
self.flatten = paddle.nn.Flatten()
def forward(self, inputs):
y = self.flatten(inputs)
return y
当前示例中,通过继承 paddle.nn.Layer 的方式构建了一个模型类型 Model ,模型中仅包含一个 paddle.nn.Flatten 层。模型执行时,输入变量inputs会被 paddle.nn.Flatten 层展平。
子层接口
如果想要访问或修改一个模型中定义的层,则可以调用SubLayer相关的接口。
以上文创建的简单模型为例:
如果想要查看模型中定义的所有子层:
model = Model()
print(model.sublayers())
print("----------------------")
for item in model.named_sublayers():
print(item)
[Flatten()]
----------------------
('flatten', Flatten())
可以看到,通过调用model.sublayers()
接口,打印出了前述模型中持有的全部子层。这时模型中只有一个 paddle.nn.Flatten 子层。
而遍历 model.named_sublayers()
时,每一轮循环会拿到一组 ( 子层名称,子层对象 )的元组。
接下来如果想要进一步添加一个子层,则可以调用 add_sublayer() 接口:
fc=paddle.nn.Linear(10,3)
model.add_sublayer("fc",fc)
print(model.sublayers())
[Flatten(), Linear(in_features=10, out_features=3, dtype=float32)]
可以看到 model.add_sublayer()
向模型中添加了一个 paddle.nn.Linear 子层。这样模型中总共有 paddle.nn.Flatten 和 paddle.nn.Linear 两个子层了。
通过上述方法可以往模型中添加成千上万个子层,当模型中子层数量较多时,如何高效地对所有子层进行统一修改呢?Paddle 提供了 apply()
接口。通过这个接口,可以自定义一个函数,然后将该函数批量作用在所有子层上:
def function(layer):
print(layer)
model.apply(function)
Flatten()
Linear(in_features=10, out_features=3, dtype=float32)
Model(
(flatten): Flatten()
(fc): Linear(in_features=10, out_features=3, dtype=float32)
)
当前例子中,定义了一个以layer作为参数的函数function,用来打印传入的layer信息。通过调用 model.apply()
接口,将function作用在模型的所有子层中,也因此输出信息中打印了model中所有子层的信息。
另外一个批量访问子层的接口是 children() 或者 named_children()
。这两个接口通过Iterator的方式访问每个子层:
sublayer_iter = model.children()
for sublayer in sublayer_iter:
print(sublayer)
Flatten()
Linear(in_features=10, out_features=3, dtype=float32)
可以看到,遍历 model.children() 时,每一轮循环都可以按照子层注册顺序拿到对应 paddle.nn.Layer 的对象
层的变量成员
参数变量的添加与修改
有的时候希望向网络中添加一个参数作为输入。比如在使用图像风格转换模型时,会使用参数作为输入图像,在训练过程中不断更新该图像参数,最终拿到风格转换后的图像。
这时可以通过 create_parameter() 与 add_parameter() 组合
,来创建并记录参数:
class Model(paddle.nn.Layer):
def __init__(self):
super(Model, self).__init__()
img = self.create_parameter([1, 3, 256, 256])
self.add_parameter("img", img)
self.flatten = paddle.nn.Flatten()
def forward(self):
y = self.flatten(self.img)
return y
上述例子创建并向模型中添加了一个名字为"img"的参数。随后可以直接通过调用model.img来访问该参数。
对于已经添加的参数,可以通过 parameters() 或者 named_parameters()
来访问
model = Model()
model.parameters()
for item in model.named_parameters():
print(item)
('img', Parameter containing:
Tensor(shape=[1, 3, 256, 256], dtype=float32, place=Place(gpu:0), stop_gradient=False,
[[[[-0.00461889, 0.00225172, 0.00227235, ..., 0.00409489,
-0.00349021, -0.00373849],
[ 0.00279323, -0.00248744, -0.00465616, ..., -0.00066955,
-0.00466067, -0.00228231],
[-0.00084478, 0.00203255, -0.00409841, ..., -0.00176809,
0.00013303, 0.00047466],
...,
[ 0.00093416, -0.00027048, -0.00409319, ..., -0.00175130,
0.00439515, -0.00183520],
[-0.00405663, 0.00018820, -0.00104859, ..., -0.00048076,
0.00368483, -0.00131203],
[-0.00044382, 0.00167044, 0.00211861, ..., -0.00148156,
-0.00383941, -0.00109923]],
[[ 0.00254006, -0.00426744, 0.00272125, ..., 0.00124850,
-0.00204948, -0.00197827],
[ 0.00374253, -0.00021346, -0.00320000, ..., 0.00321487,
0.00302210, 0.00304987],
[ 0.00470376, -0.00453460, 0.00273284, ..., -0.00005697,
0.00361763, 0.00246640],
...,
[-0.00381022, 0.00026991, -0.00180812, ..., 0.00352815,
0.00204997, 0.00112459],
[ 0.00237271, -0.00361106, -0.00392339, ..., -0.00071314,
0.00152636, 0.00107991],
[-0.00200252, -0.00033821, 0.00304269, ..., 0.00284959,
-0.00270319, 0.00203611]],
[[-0.00261145, 0.00322097, 0.00389150, ..., 0.00332576,
0.00277365, 0.00038895],
[ 0.00173984, -0.00130420, 0.00288810, ..., 0.00168986,
0.00324349, 0.00177176],
[ 0.00383024, 0.00344940, -0.00105563, ..., 0.00310889,
-0.00215480, 0.00346017],
...,
[-0.00052241, 0.00286528, -0.00172010, ..., -0.00159214,
-0.00426167, 0.00257842],
[-0.00297369, -0.00181348, 0.00409362, ..., -0.00355920,
0.00022863, -0.00101265],
[ 0.00168806, -0.00168727, 0.00346997, ..., -0.00183322,
0.00240351, 0.00192561]]]]))
可以看到,model.parameters()
将模型中所有参数以数组的方式返回。
在实际的模型训练过程中,当调用反向图执行方法后,Paddle会计算出模型中每个参数的梯度并将其保存在相应的参数对象中。
如果已经对该参数进行了梯度更新,或者出于一些原因不希望该梯度累加到下一轮训练,则可以调用 clear_gradients()
来清除这些梯度值。
model = Model()
out = model()
out.backward()
model.clear_gradients()
非参数变量的添加
参数变量往往需要参与梯度更新,但很多情况下只是需要一个临时变量甚至一个常量。比如在模型执行过程中想将一个中间变量保存下来,这时需要调用 create_tensor()
接口:
class Model(paddle.nn.Layer):
def __init__(self):
super(Model, self).__init__()
self.saved_tensor = self.create_tensor(name="saved_tensor0")
self.flatten = paddle.nn.Flatten()
self.fc = paddle.nn.Linear(10, 100)
def forward(self, x):
y = self.flatten(x)
# Save intermediate tensor
paddle.assign(y,self.saved_tensor)
y = self.fc(y)
return y
这里调用 self.create_tensor()
创造了一个临时变量并将其记录在模型的 self.saved_tensor 中。在模型执行时调用 paddle.assign 用该临时变量记录变量y的数值。
Buffer 变量的添加
Buffer的概念仅仅影响动态图向静态图的转换过程。
在上一节中创建了一个临时变量用来临时存储中间变量的值。但这个临时变量在动态图向静态图转换的过程中并不会被记录在静态的计算图当中。如果希望该变量成为静态图的一部分,就需要进一步调用 register_buffers()
接口:
class Model(paddle.nn.Layer):
def __init__(self):
super(Model, self).__init__()
saved_tensor = self.create_tensor(name="saved_tensor0")
self.register_buffer("saved_tensor", saved_tensor, persistable=True)
self.flatten = paddle.nn.Flatten()
self.fc = paddle.nn.Linear(10, 100)
def forward(self, input):
y = self.flatten(input)
# Save intermediate tensor
paddle.assign(y, self.saved_tensor)
y = self.fc(y)
return y
这样在动态图转静态图时saved_tensor就会被记录到静态图中。
对于模型中已经注册的Buffer,可以通过 buffers() 或者 named_buffers()
进行访问:
model=Model()
print(model.buffers())
for item in model.named_buffers():
print(item)
[Tensor(shape=[], dtype=float32, place=Place(undefined:0), stop_gradient=True, [])]
('saved_tensor', Tensor(shape=[], dtype=float32, place=Place(undefined:0), stop_gradient=True, []))
可以看到 model.buffers()
以数组形式返回了模型中注册的所有Buffer