编程指南

目前飞桨(PaddlePaddle,以下简称Paddle)已经同时支持动态图和静态图两种编程方式, 本文主要侧重于介绍静态图的编程方法,关于动态图编程方法,请参考动态图机制-DyGraph

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

数据的表示和定义

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

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

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

  1. import paddle.fluid as fluid
  2.  
  3. # 定义一个数据类型为int64的二维数据变量x,x第一维的维度为3,第二个维度未知,要在程序执行过程中才能确定,因此x的形状可以指定为[3, None]
  4. x = fluid.data(name="x", shape=[3, None], dtype="int64")
  5.  
  6. # 大多数网络都会采用batch方式进行数据组织,batch大小在定义时不确定,因此batch所在维度(通常是第一维)可以指定为None
  7. batched_x = fluid.data(name="batched_x", shape=[None, 3, None], dtype='int64')

fluid.data 之外,我们还可以使用 fluid.layers.fill_constant 来创建常量, 如下代码将创建一个维度为[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在 paddle.fluid.layers 模块中提供。

例如用户可以利用 paddle.fluid.layers.elementwise_add() 实现两个输入Tensor的加法运算:

  1. # 定义变量
  2. import paddle.fluid as fluid
  3. a = fluid.data(name="a", shape=[None, 1], dtype='int64')
  4. b = fluid.data(name="b", shape=[None, 1], dtype='int64')
  5.  
  6. # 组建网络(此处网络仅由一个操作构成,即elementwise_add)
  7. result = fluid.layers.elementwise_add(a,b)
  8.  
  9. # 准备运行网络
  10. cpu = fluid.CPUPlace() # 定义运算设备,这里选择在CPU下训练
  11. exe = fluid.Executor(cpu) # 创建执行器
  12. exe.run(fluid.default_startup_program()) # 网络参数初始化
  13.  
  14. # 读取输入数据
  15. import numpy
  16. data_1 = int(input("Please enter an integer: a="))
  17. data_2 = int(input("Please enter an integer: b="))
  18. x = numpy.array([[data_1]])
  19. y = numpy.array([[data_2]])
  20.  
  21. # 运行网络
  22. outs = exe.run(
  23. feed={'a':x, 'b':y}, # 将输入数据x, y分别赋值给变量a,b
  24. fetch_list=[result] # 通过fetch_list参数指定需要获取的变量结果
  25. )
  26.  
  27. # 输出计算结果
  28. print "%d+%d=%d" % (data_1,data_2,outs[0][0])

输出结果:

  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. feed={'a':x, 'b':y}, # 将输入数据x, y分别赋值给变量a,b
  5. fetch_list=[a, b, result] # 通过fetch_list参数指定需要获取的变量结果
  6. )
  7.  
  8. # 输出计算结果
  9. print outs

输出结果:

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

组建更加复杂的网络

Paddle提供顺序、分支和循环三种执行逻辑,用户可以通过组合描述任意复杂的模型。

顺序执行:

用户可以使用顺序执行的方式搭建网络:

  1. x = fluid.data(name='x', shape=[None, 13], dtype='float32')
  2. y_predict = fluid.layers.fc(input=x, size=1, act=None)
  3. y = fluid.data(name='y', shape=[None, 1], dtype='float32')
  4. cost = fluid.layers.square_error_cost(input=y_predict, label=y)

条件分支和循环:

某些场景下,用户需要根据当前网络中的某些状态,来具体决定后续使用哪一种操作, 或者需要根据某些网络状态来重复执行某些操作,针对这类需求, Paddle提供了 fluid.layers.Switchfluid.layers.IfElse 两个API来实现条件分支的操作,以及 fluid.layers.While 来实现循环操作。

下面的代码展示了如何使用 fluid.layers.Switch API来根据当前训练的step数进行动态学习率的调整:

  1. lr = fluid.layers.tensor.create_global_var(
  2. shape=[1],
  3. value=0.0,
  4. dtype='float32',
  5. persistable=True,
  6. name="learning_rate")
  7.  
  8. one_var = fluid.layers.fill_constant(
  9. shape=[1], dtype='float32', value=1.0)
  10. two_var = fluid.layers.fill_constant(
  11. shape=[1], dtype='float32', value=2.0)
  12.  
  13. with fluid.layers.control_flow.Switch() as switch:
  14. with switch.case(global_step == zero_var):
  15. fluid.layers.tensor.assign(input=one_var, output=lr)
  16. with switch.default():
  17. fluid.layers.tensor.assign(input=two_var, output=lr)

其他控制流API的使用方法,请参见文档:IfElseWhile

一个完整的网络示例

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

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

问题描述:给定一组数据

编程指南 - 图1 ,求解出函数 编程指南 - 图2 ,使得 编程指南 - 图3 ,其中 编程指南 - 图4 , 编程指南 - 图5 均为一维张量。最终网络可以依据输入 编程指南 - 图6 ,准确预测出 编程指南 - 图7

  • 定义数据

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

  1. # 定义X数值
  2. train_data=numpy.array([[1.0], [2.0], [3.0], [4.0]]).astype('float32')
  3. # 定义期望预测的真实值y_true
  4. y_true = numpy.array([[2.0], [4.0], [6.0], [8.0]]).astype('float32')
  • 搭建网络(定义前向计算逻辑)

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

  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],
  4. [7.8866425]], dtype=float32), array([0.01651453], dtype=float32)]

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

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

进一步学习

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

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

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

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

如果您希望阅读更多场景下的应用案例,可以参考深度学习基础教程。已经具备深度学习基础知识的用户,可以从使用指南开始阅读。