编程指南

    阅读完本文档,您将了解在Paddle静态图编程方式中,如何表示和定义数据变量,以及如何完整的组建一个深度学习网络并进行训练。

    Paddle和其他主流框架一样,使用Tensor数据结构来承载数据,包括模型中的可学习参数(如网络权重、偏置等), 网络中每一层的输入输出数据,常量数据等。

    Tensor可以简单理解成一个多维数组,一般而言可以有任意多的维度。 不同的Tensor可以具有自己的数据类型和形状,同一Tensor中每个元素的数据类型是一样的, Tensor的形状就是Tensor的维度。关于Tensor的详细介绍请参阅:Tensor

    在Paddle中我们使用 来创建数据变量, fluid.data 需要指定Tensor的形状信息和数据类型, 当遇到无法确定的维度时,可以将相应维度指定为None,如下面的代码片段所示:

    fluid.data 之外,我们还可以使用 来创建常量, 如下代码将创建一个维度为[3, 4], 数据类型为int64的Tensor,其中所有元素均为16(value参数所指定的值)。

    1. import paddle.fluid as fluid
    2. data = fluid.layers.fill_constant(shape=[3, 4], value=16, dtype='int64')

    以上例子中,我们只使用了一种数据类型"int64",即有符号64位整数数据类型,更多Paddle目前支持的数据类型请查看:。

    需要注意的是,在静态图编程方式中,上述定义的Tensor并不具有值(即使创建常量的时候指定了value), 它们仅表示将要执行的操作,在网络执行时(训练或者预测)才会进行真正的赋值操作, 如您直接打印上例代码中的data将会得对其信息的描述:

    1. print data

    输出结果:

    1. name: "fill_constant_0.tmp_0"
    2. type {
    3. type: LOD_TENSOR
    4. lod_tensor {
    5. tensor {
    6. data_type: INT64
    7. dims: 3
    8. dims: 4
    9. }
    10. }
    11. }
    12. persistable: false

    在网络执行过程中,获取Tensor数值有两种方式:方式一是利用 paddle.fluid.layers.Print 创建一个打印操作, 打印正在访问的Tensor。方式二是将Variable添加在fetch_list中。

    方式一的代码实现如下所示:

    1. import paddle.fluid as fluid
    2.  
    3. data = fluid.layers.fill_constant(shape=[3, 4], value=16, dtype='int64')
    4. data = fluid.layers.Print(data, message="Print data:")
    5.  
    6. place = fluid.CPUPlace()
    7. exe = fluid.Executor(place)
    8. exe.run(fluid.default_startup_program())
    9.  
    10. ret = exe.run()

    运行时的输出结果:

    1. 1571742368 Print data: The place is:CPUPlace
    2. Tensor[fill_constant_0.tmp_0]
    3. shape: [3,4,]
    4. dtype: x
    5. data: 16,16,16,16,16,16,16,16,16,16,16,16,

    方式二Fetch_list的详细过程会在后文展开描述。

    数据读取

    使用 fluid.data 创建数据变量之后,我们需要把网络执行所需要的数据读取到对应变量中, 具体的数据准备过程,请阅读。

    在Paddle中,数据计算类API统一称为Operator(算子),简称OP,大多数OP在 模块中提供。

    输出结果:

    1. Please enter an integer: a=7
    2. Please enter an integer: b=3
    3. 7+3=10

    本次运行时,输入a=7,b=3,得到outs=10。

    您可以复制这段代码在本地执行,根据指示输入其他数值观察计算结果。

    如果想获取网络执行过程中的a,b的具体值,可以将希望查看的变量添加在fetch_list中。

    1. ...
    2. # 运行网络
    3. outs = exe.run(
    4. fetch_list=[a, b, result] # 通过fetch_list参数指定需要获取的变量结果
    5. )
    6.  
    7. # 输出计算结果
    8. print outs

    输出结果:

    1. [array([[7]]), array([[3]]), array([[10]])]

    组建更加复杂的网络

    某些场景下,用户需要根据当前网络中的某些状态,来具体决定后续使用哪一种操作,或者重复执行某些操作。在动态图中,可以方便的使用Python的控制流语句(如for,if-else等)来进行条件判断,但是在静态图中,由于组网阶段并没有实际执行操作,也没有产生中间计算结果,因此无法使用Python的控制流语句来进行条件判断,为此静态图提供了多个控制流API来实现条件判断。这里以为例来说明如何在静态图中实现条件循环的操作。

    while_loop API用于实现类似while/for的循环控制功能,使用一个callable的方法cond作为参数来表示循环的条件,只要cond的返回值为True,while_loop就会循环执行循环体body(也是一个callable的方法),直到 cond 的返回值为False。对于while_loop API的详细定义和具体说明请参考文档fluid.layers.while_loop

    下面的例子中,使用while_loop API进行条件循环操作,其实现的功能相当于在python中实现如下代码:

    1. i = 0
    2. ten = 10
    3. while i < ten:
    4. i = i + 1
    5. print('i =', i)

    在静态图中使用while_loop API实现以上代码的逻辑:

    1. # 该代码要求安装飞桨1.7+版本
    2.  
    3. # 该示例代码展示整数循环+1,循环10次,输出计数结果
    4. import paddle.fluid as fluid
    5. import paddle.fluid.layers as layers
    6.  
    7. # 定义cond方法,作为while_loop的判断条件
    8. def cond(i, ten):
    9. return i < ten
    10.  
    11. # 定义body方法,作为while_loop的执行体,只要cond返回值为True,while_loop就会一直调用该方法进行计算
    12. # 由于在使用while_loop OP时,cond和body的参数都是由while_loop的loop_vars参数指定的,所以cond和body必须有相同数量的参数列表,因此body中虽然只需要i这个参数,但是仍然要保持参数列表个数为2,此处添加了一个dummy参数来进行"占位"
    13. def body(i, dummy):
    14. # 计算过程是对输入参数i进行自增操作,即 i = i + 1
    15. i = i + 1
    16. return i, dummy
    17.  
    18. i = layers.fill_constant(shape=[1], dtype='int64', value=0) # 循环计数器
    19. ten = layers.fill_constant(shape=[1], dtype='int64', value=10) # 循环次数
    20. out, ten = layers.while_loop(cond=cond, body=body, loop_vars=[i, ten]) # while_loop的返回值是一个tensor列表,其长度,结构,类型与loop_vars相同
    21.  
    22. exe = fluid.Executor(fluid.CPUPlace())
    23. res = exe.run(fluid.default_main_program(), feed={}, fetch_list=out)
    24. print(res) #[array([10])]

    限于篇幅,上面仅仅用一个最简单的例子来说明如何在静态图中实现循环操作。循环操作在很多应用中都有着重要作用,比如NLP中常用的Transformer模型,在解码(生成)阶段的Beam Search算法中,需要使用循环操作来进行候选的选取与生成,可以参考模型的实现来进一步学习while_loop在复杂场景下的用法。

    除while_loop之外,飞桨还提供fluid.layers.cond API来实现条件分支的操作,以及fluid.layers.switch_case和fluid.layers.case API来实现分支控制功能,具体用法请参考文档:cond,和case

    一个典型的模型通常包含4个部分,分别是:输入数据定义,搭建网络(模型前向计算逻辑),定义损失函数,以及选择优化算法。

    下面我们通过一个非常简单的数据预测网络(线性回归),来完整的展示如何使用Paddle静态图方式完成一个深度学习模型的组建和训练。

    问题描述:给定一组数据

    • 定义数据

    假设输入数据X=[1 2 3 4],Y=[2 4 6 8],在网络中定义:

    • 搭建网络(定义前向计算逻辑)

    接下来需要定义预测值与输入的关系,本次使用一个简单的线性回归函数进行预测:

    1. # 定义输入数据类型
    2. x = fluid.data(name="x", shape=[None, 1], dtype='float32')
    3. y = fluid.data(name="y", shape=[None, 1], dtype='float32')
    4. # 搭建全连接网络
    5. y_predict = fluid.layers.fc(input=x, size=1, act=None)

    完成模型搭建后,如何评估预测结果的好坏呢?我们通常在设计的网络中添加损失函数,以计算真实值与预测值的差。

    在本例中,损失函数采用:

    1. cost = fluid.layers.square_error_cost(input=y_predict, label=y)
    2. avg_cost = fluid.layers.mean(cost)
    • 网络优化

    确定损失函数后,可以通过前向计算得到损失值,并根据损失值对网络参数进行更新,最简单的算法是随机梯度下降法:w=w−η⋅g,由 fluid.optimizer.SGD 实现:

    1. sgd_optimizer = fluid.optimizer.SGD(learning_rate=0.01)
    2. sgd_optimizer.minimize(avg_cost)

    让我们的网络训练100次,查看结果:

    1. # 加载库
    2. import paddle.fluid as fluid
    3. import numpy
    4.  
    5. # 定义输入数据
    6. train_data=numpy.array([[1.0],[2.0],[3.0],[4.0]]).astype('float32')
    7. y_true = numpy.array([[2.0],[4.0],[6.0],[8.0]]).astype('float32')
    8.  
    9. # 组建网络
    10. x = fluid.data(name="x",shape=[None, 1],dtype='float32')
    11. y = fluid.data(name="y",shape=[None, 1],dtype='float32')
    12. y_predict = fluid.layers.fc(input=x,size=1,act=None)
    13.  
    14. # 定义损失函数
    15. cost = fluid.layers.square_error_cost(input=y_predict,label=y)
    16. avg_cost = fluid.layers.mean(cost)
    17.  
    18. # 选择优化方法
    19. sgd_optimizer = fluid.optimizer.SGD(learning_rate=0.01)
    20. sgd_optimizer.minimize(avg_cost)
    21.  
    22. # 网络参数初始化
    23. cpu = fluid.CPUPlace()
    24. exe = fluid.Executor(cpu)
    25. exe.run(fluid.default_startup_program())
    26.  
    27. # 开始训练,迭代100次
    28. for i in range(100):
    29. outs = exe.run(
    30. feed={'x':train_data, 'y':y_true},
    31. fetch_list=[y_predict, avg_cost])
    32.  
    33. # 输出训练结果
    34. print outs

    输出结果:

    1. [array([[2.2075021],
    2. [4.1005487],
    3. [5.9935956],

    可以看到100次迭代后,预测值已经非常接近真实值了,损失值也下降到了0.0165。

    恭喜您!已经成功完成了第一个简单网络的搭建,想尝试线性回归的进阶版——房价预测模型,请阅读:线性回归。更多丰富的模型实例可以在中找到。

    进一步学习

    如果您已经掌握了基本操作,可以进行下一阶段的学习了:

    跟随这一教程将学习到如何对实际问题建模并使用Paddle构建模型:配置简单的网络

    完成网络搭建后,可以开始在单机上训练您的网络了,详细步骤请参考。

    除此之外,使用文档模块根据开发者的不同背景划分了三个学习阶段:快速上手、和进阶指南