模型与层
模型是深度学习中的重要概念之一。模型的核心功能是将一组输入变量经过一系列计算,映射到另一组输出变量,该映射函数即代表一种深度学习算法。在Paddle
框架中,模型包括以下两方面内容:
- 一系列层的组合用于进行映射(前向执行)
- 一些参数变量在训练过程中实时更新
1.1 在Paddle中定义模型与层
在Paddle
中,大多数模型由一系列层组成,层是模型的基础逻辑执行单元。层中持有两方面内容:
- 一方面是计算所需的变量,以临时变量或参数的形式作为层的成员持有
- 另一方面则持有一个或多个具体的
Operator
来完成相应的计算。
1.1.1 模型与层
从零开始构建变量、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 层展平。
1.1.2 测试用例
x = paddle.to_tensor([[1,2,3],[4,5,6]])
print(x)
model = Model()
y = model(x)
print(y)
测试结果 如下
Tensor(shape=[2, 3], dtype=int64, place=Place(gpu:0), stop_gradient=True,
[[1, 2, 3],
[4, 5, 6]])
W1007 17:07:41.602807 22256 gpu_context.cc:278] Please NOTE: device: 0, GPU Compute Capability: 8.6, Driver API Version: 11.6, Runtime API Version: 11.2
W1007 17:07:41.699020 22256 gpu_context.cc:306] device: 0, cuDNN Version: 8.2.
Tensor(shape=[2, 3], dtype=int64, place=Place(gpu:0), stop_gradient=True,
[[1, 2, 3],
[4, 5, 6]])
从输出的结果可以看出,1.print(x),print(y)都调用0号GPU。
注意:电脑上安装的paddle是GPU版本的
2. 对比两个结果,Flatten的展平功能呢?结果怎么没有被层展平呢?
修改一下 上面的类中的Flatten()
class Model(paddle.nn.Layer):
def __init__(self):
super(Model, self).__init__()
def forward(self, inputs):
y = self.Flatten(inputs)
return y
def Flatten(self, x):
xx = x.numpy().flatten()
return paddle.to_tensor(xx)
x = paddle.to_tensor([[1,2,3],[4,5,6]])
print(x)
model = Model()
y = model(x)
print(y)
重写了Flatten方法,用numpy中的Flatten功能函数实现
“xx = x.numpy().flatten()”
然后 再转tensor, 因为paddle只能运行tensor,所有数据都要是tensor的格式。
看一下运行后的结果
Tensor(shape=[2, 3], dtype=int64, place=Place(gpu:0), stop_gradient=True,
[[1, 2, 3],
[4, 5, 6]])
Tensor(shape=[6], dtype=int64, place=Place(gpu:0), stop_gradient=True,
[1, 2, 3, 4, 5, 6])
从tensor的输出信息可以看错,第一个是print(x),第二个是print(y),明显实现了矩阵展平的功能。
他们都调用了0号gpu
1.2 子层接口
如果想要访问或修改一个模型中定义的层,则可以调用SubLayer相关的接口
1.2.1 继承子层
以上文创建的简单模型为例,
如果想要查看模型中定义的所有子层:
1.可以通过调用 model.sublayers()接口,打印出了前述模型中持有的全部子层(这时模型中只有一个 paddle.nn.Flatten子层)。
2.而遍历 model.named_sublayers() 时,每一轮循环会拿到一组 ( 子层名称('flatten'),子层对象(paddle.nn.Flatten))的元组。
完整代码如下:
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
model = Model()
print(model.sublayers())
print("***************************")
for item in model.named_sublayers():
print(item)
输出如下,用上面的两种方法实现
[Flatten()]
***************************
('flatten', Flatten())
如果增加self中的一个层次,可以看到多出了更多的层。
完整代码如下
class Model(paddle.nn.Layer):
def __init__(self):
super(Model, self).__init__()
self.flatten = paddle.nn.Flatten()
self.f1 = paddle.nn.Flatten()
def forward(self, inputs):
y = self.Flatten(inputs)
return y
model = Model()
print(model.sublayers())
print("*********************")
for item in model.named_sublayers():
print(item)
输出结果如下
[Flatten(), Flatten()]
*********************
('flatten', Flatten())
('f1', Flatten())
1.2.2 增加子层
fc = paddle.nn.Linear(10, 3)
model.add_sublayer("fc", fc)
print(model.sublayers())
通过add_sublayer()函数实现增加一个子层
完整代码
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
model = Model()
fc = paddle.nn.Linear(10, 3)
model.add_sublayer("fc", fc)
print(model.sublayers())
print("***************************")
for item in model.named_sublayers():
print(item)
输出结果:
[Flatten(), Linear(in_features=10, out_features=3, dtype=float32)]
***************************
('flatten', Flatten())
('fc', Linear(in_features=10, out_features=3, dtype=float32))
Process finished with exit code 0
大家看 “***”行上面的内容就可以了。*行下面的是迭代输出层的内容。结果都是一样的
可以看到 model.add_sublayer()
向模型中添加了一个 paddle.nn.Linear
子层,这样模型中总共有 paddle.nn.Flatten
和 paddle.nn.Linear
两个子层了。
1.2.3 修改子层
通过上述方法可以往模型中添加成千上万个子层,当模型中子层数量较多时,如何高效地对所有子层进行统一修改呢?
Paddle
提供了 apply()
接口。通过这个接口,可以自定义一个函数,然后将该函数批量作用在所有子层上:
def function(layer):
print(layer)
model.apply(function)
完整的代码如下
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
model = Model()
fc = paddle.nn.Linear(10, 3)
model.add_sublayer("fc", fc)
print(model.sublayers())
print("***************************")
def function(layer):
print(layer)
model.apply(function)
大家重点关注print("***************************")这一行下面的输出
输出结果如下
[Flatten(), Linear(in_features=10, out_features=3, dtype=float32)]
***************************
Flatten()
Linear(in_features=10, out_features=3, dtype=float32)
Model(
(flatten): Flatten()
(fc): Linear(in_features=10, out_features=3, dtype=float32)
)
大家会发现,这个function的作用是输出层的信息。它并不属于层结构。当然你也可以根据自己的需求添加自定义函数。
当前例子中,定义了一个以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)
1.3 层中的变量成员
1.3.1 参数变量添加与修改
有的时候希望向网络中添加一个参数作为输入。比如在使用图像风格转换模型时,会使用参数作为输入图像,在训练过程中不断更新该图像参数,最终拿到风格转换后的图像。
这时可以通过 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()
print('*********************************')
for item in model.named_parameters():
print(item)
完整的代码
import os
import random
from PIL import Image
import paddle
import numpy as np
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
model = Model()
model.parameters()
print('*********************************')
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.00325643, 0.00255461, -0.00272588, ..., 0.00072279,
0.00351666, 0.00107995],
[ 0.00228817, -0.00450356, 0.00099415, ..., -0.00419101,
-0.00114995, -0.00345563],
[-0.00230776, -0.00335513, -0.00203175, ..., 0.00088496,
-0.00449636, 0.00399134],
...,
[ 0.00094661, -0.00471443, 0.00290659, ..., 0.00265058,
-0.00124940, -0.00047589],
[ 0.00168675, 0.00419965, -0.00275908, ..., 0.00343265,
0.00299130, -0.00223703],
[ 0.00415531, 0.00045260, 0.00318608, ..., 0.00276867,
-0.00381224, -0.00249666]],
[[-0.00344608, -0.00063777, -0.00464929, ..., 0.00418700,
-0.00114783, 0.00303779],
[ 0.00281780, 0.00415517, 0.00325592, ..., -0.00453324,
0.00362573, 0.00342112],
[ 0.00107392, -0.00197935, 0.00418595, ..., -0.00245356,
0.00081301, -0.00452506],
...,
[ 0.00380220, -0.00328189, 0.00261540, ..., 0.00317596,
0.00307307, 0.00269659],
[-0.00017473, 0.00471288, -0.00106649, ..., 0.00014306,
-0.00251754, 0.00312610],
[-0.00216883, -0.00461749, 0.00325944, ..., 0.00402439,
-0.00412884, 0.00464935]],
[[ 0.00392813, -0.00054541, 0.00432546, ..., 0.00195517,
-0.00387182, 0.00159391],
[ 0.00079864, 0.00041935, -0.00388202, ..., 0.00288824,
-0.00195494, -0.00401914],
[-0.00071680, -0.00152895, -0.00349227, ..., 0.00245612,
-0.00156318, -0.00027233],
...,
[ 0.00423688, -0.00445199, 0.00230713, ..., -0.00377530,
0.00056901, -0.00395238],
[-0.00262016, -0.00386400, -0.00408922, ..., 0.00300010,
0.00113233, 0.00426760],
[-0.00451327, 0.00095532, 0.00452803, ..., 0.00038162,
0.00199159, 0.00332551]]]]))
Process finished with exit code 0
可以看到,model.parameters() 将模型中所有参数以数组的方式返回。
在实际的模型训练过程中,当调用反向图执行方法后,Paddle会计算出模型中每个参数的梯度并将其保存在相应的参数对象中。如果已经对该参数进行了梯度更新,或者出于一些原因不希望该梯度累加到下一轮训练,则可以调用 clear_gradients() 来清除这些梯度值。
model = Model()
out = model()
out.backward()
model.clear_gradients()
1.3.2 非参数变量的添加
参数变量往往需要参与梯度更新,但很多情况下只是需要一个临时变量甚至一个常量。比如在模型执行过程中想将一个中间变量保存下来,这时需要调用 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, input):
y = self.flatten(input)
# Save intermediate tensor
paddle.assign(y, self.saved_tensor)
y = self.fc(y)
return y
model = Model()
print(model.sublayers())
运行结果
[Flatten(), Linear(in_features=10, out_features=100, dtype=float32)]
Process finished with exit code 0
这里调用 self.create_tensor()
创造了一个临时变量并将其记录在模型的 self.saved_tensor
中。在模型执行时调用 paddle.assign
用该临时变量记录变量y
的数值。
1.3.3 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(Not initialized)]
('saved_tensor', Tensor(Not initialized))
可以看到 model.buffers()
以数组形式返回了模型中注册的所有Buffer
1.4 执行层的功能
经过一系列对模型的配置,假如已经准备好了一个Paddle
模型如下:
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
想要执行该模型,首先需要对执行模式进行设置
1.4.1 执行模式设置
模型的执行模式有两种,如果需要训练的话调用 train()
,如果只进行前向执行则调用 eval()
:
x = paddle.randn([10, 1], 'float32')
model = Model()
model.eval() # set model to eval mode
out = model(x)
model.train() # set model to train mode
out = model(x)
1.4.2 执行函数
模式设置完成后可以直接调用执行函数。可以直接调用forward()
方法进行前向执行,也可以调用 __call__()
,从而执行在 forward()
当中定义的前向计算逻辑。
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
def Flatten(self, inputs):
return paddle.to_tensor(inputs.numpy().flatten())
model = Model()
x = paddle.randn([10, 1], 'float32')
out = model(x)
print(out)
输出:
Tensor(shape=[10], dtype=float32, place=Place(gpu:0), stop_gradient=True,
[ 0.23584343, -0.09136827, -0.16628900, 1.35541523, -0.24900350,
-0.12770079, 2.31148815, 0.99044156, -0.31726629, 0.71666038])
Process finished with exit code 0
这里直接调用 __call__()
方法调用模型的前向执行逻辑。
1.4.3 添加hook函数
有时希望某些变量在进入层前首先进行一些预处理,这个功能可以通过注册hook来实现。hook是一个作用于变量的自定义函数,在模型执行时调用。对于注册在层上的hook函数,可以分为pre_hook和post_hook两种。pre_hook可以对层的输入变量进行处理,用函数的返回值作为新的变量参与层的计算。post_hook则可以对层的输出变量进行处理,将层的输出进行进一步处理后,用函数的返回值作为层计算的输出。
通过 register_forward_post_hook()
接口,我们可以注册一个post_hook
:
def forward_post_hook(layer, input, output):
return 2*output
x = paddle.ones([10, 1], 'float32')
model = Model()
forward_post_hook_handle = model.flatten.register_forward_post_hook(forward_post_hook)
out = model(x)
print(out)
完整代码
import os
import random
import paddle
import numpy as np
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
def Flatten(self, inputs):
return paddle.to_tensor(inputs.numpy().flatten())
def forward_post_hook(layer, input, output):
return 2*output
x = paddle.ones([10, 1], 'float32')
model = Model()
forward_post_hook_handle = model.flatten.register_forward_post_hook(forward_post_hook)
out = model(x)
print(out)
输出结果
Tensor(shape=[10], dtype=float32, place=Place(gpu:0), stop_gradient=True,
[1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])
Process finished with exit code 0
1.5 保存模型参数
如果想要保存模型中参数而不存储模型本身,则可以首先调用 state_dict()
接口将模型中的参数以及永久变量存储到一个Python
字典中,随后保存该字典。
model = Model()
state_dict = model.state_dict()
paddle.save( state_dict, "./paddle_dy.pdparams")
警告
D:\ProgramData\Anaconda3\envs\paddlede\lib\site-packages\paddle\framework\io.py:748: UserWarning: The input state dict is empty, no need to save.
warnings.warn("The input state dict is empty, no need to save.")
虽然出现警告,但是模型还是保存了。第二次运行,就没有这个警告了。
欢迎 点赞 收藏 加 关注
参考: