一. 静态图基础概念
Paddle静态图中有以下几个基本的概念,包括:
-
Variable变量:包括网络中可学习参数、占位符和常量Variable等。Variable的值可以是
int32
、float64
等多种任何的类型。 -
Tensor数据和LoDTensor数据:Tensor和LoDTensor是paddle中的数据形式,和其他框架一样,Tensor可以简单理解成一个多维数组;而LoDTensor数据主要用于变长序列的数据表达。LoDTensor将长度不一致的维度拼接为一个维度,并引入了一个索引数据结构(LoD)来将张量进行辨识。
-
Operator操作(简称OP):在paddle中,所有对数据的操作都由Operator表示。例如,加减乘除的操作在Paddle中称为一个OP。
-
Program 程序: Program描述了整个网络的计算过程。一个Program的集合通常包含初始化程序(startup_program)与主程序(main_program) 。初始化程序用于初始化,一般只运行一次来初始化参数,主程序将会包含用来训练的网络结构和变量。在每个mini batch中运行并更新权重。
-
Executor 执行器:用户完成对 Program 的定义后,需要使用Executor 执行器来执行Program,同时配置好feed输入数据(对应fulid.data占位符)和fecth_list输出列表。
Executor 实际上将 Program 转化为C++后端可以执行的代码,以提高运行效率。 -
Name参数:一般用来作为网络层输出、权重的前缀标识。在 ParamAttr 中,可通过指定 name 参数实现多个网络层的权重共享
在paddle的静态图编程模式中,其基本的思路如下:
-
首先,用户需要定义一些Operator,定义好后,系统会默认将这些Operator添加到fluid.default_main_program这个Program中;
-
然后,初始化一个Executor 执行器,同时配置好Executor 执行器调用的硬件资源(CPU或GPU);
-
最后,利用Executor 执行器运行Program的初始化程序(startup_program)与主程序(main_program)。
以下代码实现了这个基本思想:
import paddle.fluid as fluid
import numpy
# 1.定义占位符Variable变量,类似tf的placeholder
a = fluid.data(name="a",shape=[1],dtype='float32')
b = fluid.data(name="b",shape=[1],dtype='float32')
# 2.定义Operator,用户没有自定义Program时,系统自动添加到default_main_program()中
result = fluid.layers.elementwise_add(a,b)
# 3.定义执行器并指定硬件设备
cpu = fluid.core.CPUPlace() # 指定执行的设备为CPU
exe = fluid.Executor(cpu)
# 4.执行器运行默认的初始化startup_program()
exe.run(fluid.default_startup_program())
# 5. 执行器运行系统默认的main_program()
x = numpy.array([5]).astype("float32")
y = numpy.array([7]).astype("float32")
outs = exe.run(program=fluid.default_main_program(),
feed={'a':x,'b':y}, # feed: 输入数据,用一个map输入,与占位符数据对应
fetch_list=[result]) # fetch_list: 输出数据列表
# 6. 打印输出结果,[array([12.], dtype=float32)]
print( outs )
二. 静态图基本概念详解
1. Variable变量
- 创建可学习参数
包括网络权重W、偏置b等可学习参数可以用一下方法创建:
w = fluid.layers.create_parameter(name="w",shape=[1],dtype='float32')
- 创建占位 Variable
静态图模式下,通常不知道实际输入的信息,因此需要一个类似于tf.palceholder
的占位变量,来接收输入数据,如下:
x = fluid.data(name="x", shape=[None,3,None], dtype="int64")
当遇到类似batch size这种无法确定的维度时,可以先指定为 None,程序执行过程中再确定。
- 常量 Variable
Fluid 通过 fluid.layers.fill_constant 来实现常量Variable,
data = fluid.layers.fill_constant(shape=[1], value=0, dtype='int64')
注:Paddle高度集成化,一般用不到这级别的方法。例如可以使用构造一个全连接层。
2. Tensor 和 LodTensor
Tensor就是多维数组,而 LodTensor是Paddle中对Tensor的扩充。
在nlp的任务中,一个batch中包含多个句子,句子的长度可能会不一致,不能直接输入网络中。为了解决这种长度不一致问题,Paddle提供了两种解决方案:
- padding,即在句子的结尾(或开头)添加padding;
- LoDTensor,tensor中同时保存序列的长度信息;
LoDTensor的思想是将一个多维tensor进行flatten铺平,然后用一个额外LoD索引数组来记录结构信息。
例如,在NLP中,假设batch_size是2,在一个batch内,第一个序列包含一个单词,第二个序列话包含三个单词,则可以表示如下Tensor:
"""
# Tensor:
text = [
array([[1]], dtype=int32),
array([[2],
[3],
[4]], dtype=int32)
]
"""
这个Tensor如果不作padding,则不能直接输入到模型中,
而如果将其转化为如下格式,便可以解决这个问题。
"""
# text对应的LoDTensor:
lod: {{0, 1, 4}}
dim: 4, 1
layout: NCHW
dtype: int
data: [1 2 3 4]
"""
LoDTensor中的data是text进行flatten的结果,lod索引列表记录
原来的序列信息,其中lod={{0, 1, 4}}表示data中[0,1)是第一个序列,[1,4)表示第二个序列
- 创建 LoD-Tensor
# 1. 创建 LoD-Tensor
import paddle.fluid as fluid
import numpy as np
a = fluid.create_lod_tensor(np.array([[1], [2],[3],[4]]).astype('int32'), [[1,3]], fluid.CPUPlace())
#查看lod-tensor
print(a)
#查看lod索引
print(a.lod())
#查看lod-tensor嵌套层数
print (len(a.recursive_sequence_lengths()))
#查看最基础元素个数
print (sum(a.recursive_sequence_lengths()[-1]))
"""
output:
lod: {{0, 1, 4}}
dim: 4, 1
layout: NCHW
dtype: int
data: [1 2 3 4]
[[0, 1, 4]]
1
4
"""
3. Program
paddle.fluid.Program的方法用于创建Program。在深度学习中,一般包含训练和测试两个阶段,在训练阶段包含需要进行反向传播更新参数,而测试阶段则不需要,因此通常训练和测试阶段的网络模型会被拆分成两个不同的Program进行。
对于一个Program,会包含startup_program
来执行一些参数初始化工作
以及一个main_program
来容纳网络的OP。由于网络的训练和测试需要参数共享,因此当train阶段和test阶段使用同一个startup_program
时,训练和测试程序的所有参数将会拥有同样的名字。在Paddle Fluid中同样的变量名便可以实现共享权重。因而实现了使训练和测试程序的参数共享,如以下代码所示:
import paddle.fluid as fluid
import six
def print_prog(prog):
for name, value in sorted(six.iteritems(prog.block(0).vars)):
print(value)
for op in prog.block(0).ops:
print("op type is {}".format(op.type))
print("op inputs are {}".format(op.input_arg_names))
print("op outputs are {}".format(op.output_arg_names))
for key, value in sorted(six.iteritems(op.all_attrs())):
if key not in ['op_callstack', 'op_role_var']:
print(" [ attrs: {}: {} ]".format(key, value))
def network():
img = fluid.layers.data(name='image', shape=[784])
hidden = fluid.layers.fc(input=img, size=200, act='relu')
hidden = fluid.layers.dropout(hidden, dropout_prob=0.5)
loss = fluid.layers.cross_entropy(
input=fluid.layers.fc(hidden, size=10, act='softmax'),
label=fluid.layers.data(name='label', shape=[1], dtype='int64'))
avg_loss = fluid.layers.mean(loss)
return avg_loss
train_program_2 = fluid.Program()
startup_program_2 = fluid.Program()
test_program_2 = fluid.Program()
with fluid.program_guard(train_program_2, startup_program_2):
with fluid.unique_name.guard():
avg_loss = network()
sgd = fluid.optimizer.SGD(learning_rate=1e-3)
sgd.minimize(avg_loss)
# 使用训练阶段的启动程序
with fluid.program_guard(test_program_2, startup_program_2):
with fluid.unique_name.guard():
avg_loss = network()
print_prog(test_program_2)
以test_program_2的定义可以使用Program.clone()
进行代码简化,得到同样的结果,详见官网教程的Program API。
其他:
-
Program中包括至少一个 Block ,Block 可以理解为一个由OP组成的代码块,当用户不指定Program式,系统将使用默认的
default_startup_program
和default_main_program
将OP自动填入default_main_program 。 -
fluid.program_guard:可以使用
with fluid.program_guard(main_program,, startup_program)
将with 下的OP和Variable添加进指定的全局主程序(main program)和启动程序(startup program)。而当定义的网络不需要startup_program初始化各变量时,可以传入一个临时的program。 -
Program接口还存在查看num_blocks、list_vars()变量和all_parameters() 参数等多个属性。
4. Executor运行器
1. 初始化
paddle.fluid.Executor(place=None)
中place为 fluid.CPUPlace()或fluid.CUDAPlace(N)|或None。
place该参数表示Executor执行所在的设备,这里的N为GPU对应的ID。
当该参数为 None 时:
- 当安装的Paddle为CPU版时,默认运行设置会设置成 CPUPlace() ,
- 而当Paddle为GPU版时,默认运行设备会设置成 CUDAPlace(0) 。默认值为None。
2. 方法
- close()方法: 关闭执行器。
- run()方法: 执行指定的Program或者CompiledProgram
- train_from_datase()方法: 从预定义的数据集中训练。 数据集在paddle.fluid.dataset中定义。
- infer_from_dataset()方法: infer_from_dataset的文档与train_from_dataset几乎完全相同,只是在分布式训练中,推进梯度将在infer_from_dataset中禁用。