提交 187ccebb 编写于 作者: P phlrain

update doc; test=develop

上级 a0329a8f
# 动态图使用教程 # 命令式编程模式使用教程
从编程范式上说,飞桨兼容支持声明式编程和命令式编程,通俗地讲即动态图和静态图。其实飞桨本没有图的概念,在飞桨的设计中,把一个神经网络定义成一段类似程序的描述,也就是用户在写程序的过程中,就定义了模型表达及计算。在声明式编程模式的控制流实现方面,飞桨借助自己实现的控制流OP而不是python原生的if else和for循环,这使得在飞桨中的定义的program即一个网络模型,可以有一个内部的表达,是可以全局优化编译执行的。考虑对开发者来讲,更愿意使用python原生控制流,飞桨也做了支持,并通过解释方式执行,这就是命令式编程模式。但整体上,这两种编程范式是相对兼容统一的。飞桨将持续发布更完善的命令式编程模式功能,同时保持更强劲的性能。 从编程范式上说,飞桨兼容支持声明式编程和命令式编程,通俗地讲即静态图和动态图。其实飞桨本没有图的概念,在飞桨的设计中,把一个神经网络定义成一段类似程序的描述,也就是用户在写程序的过程中,就定义了模型表达及计算。在声明式编程模式的控制流实现方面,飞桨借助自己实现的控制流OP而不是python原生的if else和for循环,这使得在飞桨中的定义的program即一个网络模型,可以有一个内部的表达,是可以全局优化编译执行的。考虑对开发者来讲,更愿意使用python原生控制流,飞桨也做了支持,并通过解释方式执行,这就是命令式编程模式。但整体上,这两种编程范式是相对兼容统一的。飞桨将持续发布更完善的命令式编程模式功能,同时保持更强劲的性能。
飞桨平台中,将神经网络抽象为计算表示**Operator**(算子,常简称OP)和数据表示**Variable**(变量),如 图1 所示。神经网络的每层操作均由一个或若干**Operator**组成,每个**Operator**接受一系列的**Variable**作为输入,经计算后输出一系列的**Variable** 飞桨平台中,将神经网络抽象为计算表示**Operator**(算子,常简称OP)和数据表示**Variable**(变量),如 图1 所示。神经网络的每层操作均由一个或若干**Operator**组成,每个**Operator**接受一系列的**Variable**作为输入,经计算后输出一系列的**Variable**
<center><img src="https://ai-studio-static-online.cdn.bcebos.com/15197499f49840fcb43a38d19d9c729e19f3a7bf5ae5432a8eeca083ac4e02b7" width="600" ></center> <center><img src="https://ai-studio-static-online.cdn.bcebos.com/15197499f49840fcb43a38d19d9c729e19f3a7bf5ae5432a8eeca083ac4e02b7" width="600" ></center>
...@@ -8,14 +8,14 @@ ...@@ -8,14 +8,14 @@
<center>图1 Operator和Variable关系示意图</center> <center>图1 Operator和Variable关系示意图</center>
根据**Operator**解析执行方式不同,飞桨支持如下两种编程范式: 根据**Operator**解析执行方式不同,飞桨支持如下两种编程范式:
* **声明式编程模式模式(动态图)**:先编译后执行的方式。用户需预先定义完整的网络结构,再对网络结构进行编译优化后,才能执行获得计算结果。 * **声明式编程模式(静态图)**:先编译后执行的方式。用户需预先定义完整的网络结构,再对网络结构进行编译优化后,才能执行获得计算结果。
* **命令式编程模式模式(静态图)**:解析式的执行方式。用户无需预先定义完整的网络结构,每写一行网络代码,即可同时获得计算结果。 * **命令式编程模式(动态图)**:解析式的执行方式。用户无需预先定义完整的网络结构,每写一行网络代码,即可同时获得计算结果。
举例来说,假设用户写了一行代码:y=x+1,在声明式编程模式模式下,运行此代码只会往计算图中插入一个Tensor加1的**Operator**,此时**Operator**并未真正执行,无法获得y的计算结果。但在命令式编程模式模式下,所有**Operator**均是即时执行的,运行完此代码后**Operator**已经执行完毕,用户可直接获得y的计算结果。 举例来说,假设用户写了一行代码:y=x+1,在声明式编程模式下,运行此代码只会往计算图中插入一个Tensor加1的**Operator**,此时**Operator**并未真正执行,无法获得y的计算结果。但在命令式编程模式下,所有**Operator**均是即时执行的,运行完此代码后**Operator**已经执行完毕,用户可直接获得y的计算结果。
## 为什么命令式编程模式模式越来越流行? ## 为什么命令式编程模式越来越流行?
声明式编程模式模式作为较早提出的一种编程范式,提供丰富的 API ,能够快速的实现各种模型;并且可以利用全局的信息进行图优化,优化性能和显存占用;在预测部署方面也可以实现无缝衔接。 但具体实践中声明式编程模式模式存在如下问题: 声明式编程模式作为较早提出的一种编程范式,提供丰富的 API ,能够快速的实现各种模型;并且可以利用全局的信息进行图优化,优化性能和显存占用;在预测部署方面也可以实现无缝衔接。 但具体实践中声明式编程模式存在如下问题:
1. 采用先编译后执行的方式,组网阶段和执行阶段割裂,导致调试不方便。 1. 采用先编译后执行的方式,组网阶段和执行阶段割裂,导致调试不方便。
2. 属于一种符号化的编程方式,要学习新的编程方式,有一定的入门门槛。 2. 属于一种符号化的编程方式,要学习新的编程方式,有一定的入门门槛。
3. 网络结构固定,对于一些树结构的任务支持的不够好。 3. 网络结构固定,对于一些树结构的任务支持的不够好。
...@@ -26,22 +26,22 @@ ...@@ -26,22 +26,22 @@
3. 网络的结构在不同的层次中可以变化,使用更灵活。 3. 网络的结构在不同的层次中可以变化,使用更灵活。
综合以上优势,使得命令式编程模式模式越来越受开发者的青睐,本章侧重介绍在飞桨中命令式编程模式的编程方法,包括如下几部分: 综合以上优势,使得命令式编程模式越来越受开发者的青睐,本章侧重介绍在飞桨中命令式编程模式的编程方法,包括如下几部分:
1. 如何开启命令式编程模式模式 1. 如何开启命令式编程模式
2. 如何使用命令式编程模式进行模型训练 2. 如何使用命令式编程模式进行模型训练
3. 如何基于命令式编程模式进行多卡训练 3. 如何基于命令式编程模式进行多卡训练
4. 如何部署命令式编程模式模型 4. 如何部署命令式编程模式模型
5. 命令式编程模式模式常见的使用技巧,如中间变量值/梯度打印、断点调试、阻断反向传递,以及某些场景下如何改写为声明式编程模式模式运行。 5. 命令式编程模式常见的使用技巧,如中间变量值/梯度打印、断点调试、阻断反向传递,以及某些场景下如何改写为声明式编程模式运行。
## 1. 开启命令式编程模式模式 ## 1. 开启命令式编程模式
目前飞桨默认的模式是声明式编程模式,采用基于 context (上下文)的管理方式开启命令式编程模式模式 目前飞桨默认的模式是声明式编程模式,采用基于 context (上下文)的管理方式开启命令式编程模式:
``` ```
with fluid.dygraph.guard() with fluid.dygraph.guard()
``` ```
我们先通过一个实例,观察一下命令式编程模式模式开启前后执行方式的差别: 我们先通过一个实例,观察一下命令式编程模式开启前后执行方式的差别:
```python ```python
...@@ -55,12 +55,12 @@ with fluid.program_guard(main_program=main_program, startup_program=startup_prog ...@@ -55,12 +55,12 @@ with fluid.program_guard(main_program=main_program, startup_program=startup_prog
# 利用np.ones函数构造出[2*2]的二维数组,值为1 # 利用np.ones函数构造出[2*2]的二维数组,值为1
data = np.ones([2, 2], np.float32) data = np.ones([2, 2], np.float32)
# 声明式编程模式模式下,使用layers.data构建占位符用于数据输入 # 声明式编程模式下,使用layers.data构建占位符用于数据输入
x = fluid.layers.data(name='x', shape=[2], dtype='float32') x = fluid.layers.data(name='x', shape=[2], dtype='float32')
print('In static mode, after calling layers.data, x = ', x) print('In static mode, after calling layers.data, x = ', x)
# 声明式编程模式模式下,对Variable类型的数据执行x=x+10操作 # 声明式编程模式下,对Variable类型的数据执行x=x+10操作
x += 10 x += 10
# 在声明式编程模式模式下,需要用户显示指定运行设备 # 在声明式编程模式下,需要用户显示指定运行设备
# 此处调用fluid.CPUPlace() API来指定在CPU设备上运行程序 # 此处调用fluid.CPUPlace() API来指定在CPU设备上运行程序
place = fluid.CPUPlace() place = fluid.CPUPlace()
# 创建“执行器”,并用place参数指明需要在何种设备上运行 # 创建“执行器”,并用place参数指明需要在何种设备上运行
...@@ -74,14 +74,14 @@ with fluid.program_guard(main_program=main_program, startup_program=startup_prog ...@@ -74,14 +74,14 @@ with fluid.program_guard(main_program=main_program, startup_program=startup_prog
# 此时我们打印执行器返回的结果,可以看到“执行”后,Tensor中的数据已经被赋值并进行了运算,每个元素的值都是11 # 此时我们打印执行器返回的结果,可以看到“执行”后,Tensor中的数据已经被赋值并进行了运算,每个元素的值都是11
print('In static mode, data after run:', data_after_run) print('In static mode, data after run:', data_after_run)
# 开启命令式编程模式模式 # 开启命令式编程模式
with fluid.dygraph.guard(): with fluid.dygraph.guard():
# 命令式编程模式模式下,将numpy的ndarray类型的数据转换为Variable类型 # 命令式编程模式下,将numpy的ndarray类型的数据转换为Variable类型
x = fluid.dygraph.to_variable(data) x = fluid.dygraph.to_variable(data)
print('In DyGraph mode, after calling dygraph.to_variable, x = ', x) print('In DyGraph mode, after calling dygraph.to_variable, x = ', x)
# 命令式编程模式模式下,对Variable类型的数据执行x=x+10操作 # 命令式编程模式下,对Variable类型的数据执行x=x+10操作
x += 10 x += 10
# 命令式编程模式模式下,调用Variable的numpy函数将Variable类型的数据转换为numpy的ndarray类型的数据 # 命令式编程模式下,调用Variable的numpy函数将Variable类型的数据转换为numpy的ndarray类型的数据
print('In DyGraph mode, data after run:', x.numpy()) print('In DyGraph mode, data after run:', x.numpy())
``` ```
...@@ -117,8 +117,8 @@ with fluid.dygraph.guard(): ...@@ -117,8 +117,8 @@ with fluid.dygraph.guard():
从以上输出结果可以看出: 从以上输出结果可以看出:
* 命令式编程模式模式下,所有操作在运行时就已经完成,更接近我们平时的编程方式,可以随时获取每一个操作的执行结果。 * 命令式编程模式下,所有操作在运行时就已经完成,更接近我们平时的编程方式,可以随时获取每一个操作的执行结果。
* 声明式编程模式模式下,过程中并没有实际执行操作,上述例子中可以看到只能打印声明的类型,最后需要调用执行器来统一执行所有操作,计算结果需要通过执行器统一返回。 * 声明式编程模式下,过程中并没有实际执行操作,上述例子中可以看到只能打印声明的类型,最后需要调用执行器来统一执行所有操作,计算结果需要通过执行器统一返回。
## 2. 使用命令式编程模式进行模型训练 ## 2. 使用命令式编程模式进行模型训练
接下来我们以一个简单的手写体识别任务为例,说明如何使用飞桨的命令式编程模式来进行模型的训练。包括如下步骤: 接下来我们以一个简单的手写体识别任务为例,说明如何使用飞桨的命令式编程模式来进行模型的训练。包括如下步骤:
...@@ -181,9 +181,9 @@ test_reader = paddle.batch( ...@@ -181,9 +181,9 @@ test_reader = paddle.batch(
在开始构建网络模型前,需要了解如下信息: 在开始构建网络模型前,需要了解如下信息:
> <font size=2>在命令式编程模式模式中,参数和变量的存储管理方式与声明式编程模式不同。命令式编程模式模式下,网络中学习的参数和中间变量,生命周期和 Python 对象的生命周期是一致的。简单来说,一个 Python 对象的生命周期结束,相应的存储空间就会释放。</font> > <font size=2>在命令式编程模式中,参数和变量的存储管理方式与声明式编程模式不同。命令式编程模式下,网络中学习的参数和中间变量,生命周期和 Python 对象的生命周期是一致的。简单来说,一个 Python 对象的生命周期结束,相应的存储空间就会释放。</font>
对于一个网络模型,在模型学习的过程中参数会不断更新,所以参数需要在整个学习周期内一直保持存在,因此需要一个机制来保持网络的所有的参数不被释放,飞桨的命令式编程模式模式采用了继承自 [fluid.dygraph.Layer](https://www.paddlepaddle.org.cn/documentation/docs/zh/api_cn/dygraph_cn/Layer_cn.html#layer) 的面向对象设计的方法来管理所有的参数,该方法也更容易模块化组织代码。 对于一个网络模型,在模型学习的过程中参数会不断更新,所以参数需要在整个学习周期内一直保持存在,因此需要一个机制来保持网络的所有的参数不被释放,飞桨的命令式编程模式采用了继承自 [fluid.dygraph.Layer](https://www.paddlepaddle.org.cn/documentation/docs/zh/api_cn/dygraph_cn/Layer_cn.html#layer) 的面向对象设计的方法来管理所有的参数,该方法也更容易模块化组织代码。
下面介绍如何通过继承 fluid.dygraph.Layers 实现一个简单的ConvPool层;该层由一个 [卷积层](https://www.paddlepaddle.org.cn/documentation/docs/zh/api_cn/dygraph_cn/Conv2D_cn.html#conv2d) 和一个 [池化层](https://www.paddlepaddle.org.cn/documentation/docs/zh/api_cn/dygraph_cn/Pool2D_cn.html#pool2d) 组成。 下面介绍如何通过继承 fluid.dygraph.Layers 实现一个简单的ConvPool层;该层由一个 [卷积层](https://www.paddlepaddle.org.cn/documentation/docs/zh/api_cn/dygraph_cn/Conv2D_cn.html#conv2d) 和一个 [池化层](https://www.paddlepaddle.org.cn/documentation/docs/zh/api_cn/dygraph_cn/Pool2D_cn.html#pool2d) 组成。
...@@ -314,7 +314,7 @@ with fluid.dygraph.guard(): ...@@ -314,7 +314,7 @@ with fluid.dygraph.guard():
# 定义MNIST类的对象 # 定义MNIST类的对象
mnist = MNIST() mnist = MNIST()
# 定义优化器为AdamOptimizer,学习旅learning_rate为0.001 # 定义优化器为AdamOptimizer,学习旅learning_rate为0.001
# 注意命令式编程模式模式下必须传入parameter_list参数,该参数为需要优化的网络参数,本例需要优化mnist网络中的所有参数 # 注意命令式编程模式下必须传入parameter_list参数,该参数为需要优化的网络参数,本例需要优化mnist网络中的所有参数
adam = AdamOptimizer(learning_rate=0.001, parameter_list=mnist.parameters()) adam = AdamOptimizer(learning_rate=0.001, parameter_list=mnist.parameters())
``` ```
...@@ -341,7 +341,7 @@ with fluid.dygraph.guard(): ...@@ -341,7 +341,7 @@ with fluid.dygraph.guard():
# 定义MNIST类的对象 # 定义MNIST类的对象
mnist = MNIST() mnist = MNIST()
# 定义优化器为AdamOptimizer,学习旅learning_rate为0.001 # 定义优化器为AdamOptimizer,学习旅learning_rate为0.001
# 注意命令式编程模式模式下必须传入parameter_list参数,该参数为需要优化的网络参数,本例需要优化mnist网络中的所有参数 # 注意命令式编程模式下必须传入parameter_list参数,该参数为需要优化的网络参数,本例需要优化mnist网络中的所有参数
adam = AdamOptimizer(learning_rate=0.001, parameter_list=mnist.parameters()) adam = AdamOptimizer(learning_rate=0.001, parameter_list=mnist.parameters())
# 设置全部样本的训练次数 # 设置全部样本的训练次数
...@@ -466,7 +466,7 @@ with fluid.dygraph.guard(): ...@@ -466,7 +466,7 @@ with fluid.dygraph.guard():
### 2.5 模型参数的保存和加载 ### 2.5 模型参数的保存和加载
在命令式编程模式模式下,模型和优化器在不同的模块中,所以模型和优化器分别在不同的对象中存储,使得模型参数和优化器信息需分别存储。 在命令式编程模式下,模型和优化器在不同的模块中,所以模型和优化器分别在不同的对象中存储,使得模型参数和优化器信息需分别存储。
因此模型的保存需要单独调用模型和优化器中的 state_dict() 接口,同样模型的加载也需要单独进行处理。 因此模型的保存需要单独调用模型和优化器中的 state_dict() 接口,同样模型的加载也需要单独进行处理。
保存模型 : 保存模型 :
...@@ -500,7 +500,7 @@ with fluid.dygraph.guard(): ...@@ -500,7 +500,7 @@ with fluid.dygraph.guard():
## 3. 多卡训练 ## 3. 多卡训练
针对数据量、计算量较大的任务,我们需要多卡并行训练,以提高训练效率。目前命令式编程模式模式可支持GPU的单机多卡训练方式,在命令式编程模式中多卡的启动和单卡略有不同,命令式编程模式多卡通过 Python 基础库 subprocess.Popen 在每一张 GPU 上启动单独的 Python 程序的方式,每张卡的程序独立运行,只是在每一轮梯度计算完成之后,所有的程序进行梯度的同步,然后更新训练的参数。 针对数据量、计算量较大的任务,我们需要多卡并行训练,以提高训练效率。目前命令式编程模式可支持GPU的单机多卡训练方式,在命令式编程模式中多卡的启动和单卡略有不同,命令式编程模式多卡通过 Python 基础库 subprocess.Popen 在每一张 GPU 上启动单独的 Python 程序的方式,每张卡的程序独立运行,只是在每一轮梯度计算完成之后,所有的程序进行梯度的同步,然后更新训练的参数。
我们通过一个实例了解如何进行多卡训练: 我们通过一个实例了解如何进行多卡训练:
><font size=2>由于AI Studio上未配置多卡环境,所以本实例需在本地构建多卡环境后运行。</font> ><font size=2>由于AI Studio上未配置多卡环境,所以本实例需在本地构建多卡环境后运行。</font>
...@@ -818,16 +818,16 @@ print(y.gradient()) ...@@ -818,16 +818,16 @@ print(y.gradient())
### 5.3 使用声明式编程模式模式运行 ### 5.3 使用声明式编程模式运行
命令式编程模式虽然有友好编写、易于调试等功能,但是命令式编程模式中需要频繁进行 Python 与 C++ 交互,会导致一些任务在命令式编程模式中运行比声明式编程模式慢,根据经验,这类任务中包含了很多小粒度的 OP(指运算量相对比较小的 OP,如加减乘除、sigmoid 等,像 conv、matmul 等属于大粒度的 OP不在此列 )。 命令式编程模式虽然有友好编写、易于调试等功能,但是命令式编程模式中需要频繁进行 Python 与 C++ 交互,会导致一些任务在命令式编程模式中运行比声明式编程模式慢,根据经验,这类任务中包含了很多小粒度的 OP(指运算量相对比较小的 OP,如加减乘除、sigmoid 等,像 conv、matmul 等属于大粒度的 OP不在此列 )。
在实际任务中,如果发现这类任务运行较慢,有以下两种处理方式: 在实际任务中,如果发现这类任务运行较慢,有以下两种处理方式:
* 1. 当用户使用的 if/else、switch、for/while 与输入(包括输入的值和 shape )无关时,可以在不改动模型定义的情况下使用声明式编程模式的模式运行。该方法将模型训练改为了声明式编程模式模式,区别于第4小节仅预测部署改为了声明式编程模式模式。 * 1. 当用户使用的 if/else、switch、for/while 与输入(包括输入的值和 shape )无关时,可以在不改动模型定义的情况下使用声明式编程模式的模式运行。该方法将模型训练改为了声明式编程模式,区别于第4小节仅预测部署改为了声明式编程模式。
* 2. 如果使用了与输入相关的控制流,请参照[如何把命令式编程模式转写成声明式编程模式](https://www.paddlepaddle.org.cn/tutorials/projectdetail/360460#anchor-3)章节,将命令式编程模式代码进行转写。 * 2. 如果使用了与输入相关的控制流,请参照[如何把命令式编程模式转写成声明式编程模式](https://www.paddlepaddle.org.cn/tutorials/projectdetail/360460#anchor-3)章节,将命令式编程模式代码进行转写。
下面我们介绍上面的第一种方案,仍然以手写字体识别任务为例,在声明式编程模式模式下的实现如下: 下面我们介绍上面的第一种方案,仍然以手写字体识别任务为例,在声明式编程模式下的实现如下:
```python ```python
...@@ -844,7 +844,7 @@ with fluid.program_guard(main_program=main_program, startup_program=startup_prog ...@@ -844,7 +844,7 @@ with fluid.program_guard(main_program=main_program, startup_program=startup_prog
# 定义MNIST类的对象,可以使用命令式编程模式定义好的网络结构 # 定义MNIST类的对象,可以使用命令式编程模式定义好的网络结构
mnist_static = MNIST() mnist_static = MNIST()
# 定义优化器对象,声明式编程模式模式下不需要传入parameter_list参数 # 定义优化器对象,声明式编程模式下不需要传入parameter_list参数
sgd_static = fluid.optimizer.SGDOptimizer(learning_rate=1e-3) sgd_static = fluid.optimizer.SGDOptimizer(learning_rate=1e-3)
# 通过调用paddle.dataset.mnist的train函数,直接获取处理好的MNIST训练集 # 通过调用paddle.dataset.mnist的train函数,直接获取处理好的MNIST训练集
...@@ -897,7 +897,7 @@ with fluid.program_guard(main_program=main_program, startup_program=startup_prog ...@@ -897,7 +897,7 @@ with fluid.program_guard(main_program=main_program, startup_program=startup_prog
2. 组网 2. 组网
* 优化器对象在声明式编程模式模式下不需要传入parameter_list参数。 * 优化器对象在声明式编程模式下不需要传入parameter_list参数。
* 将定义的占位符,输入给模型执行正向,然后计算损失值,最后利用优化器将损失值做最小化优化,得到要训练的网络。 * 将定义的占位符,输入给模型执行正向,然后计算损失值,最后利用优化器将损失值做最小化优化,得到要训练的网络。
3. 执行 3. 执行
...@@ -959,7 +959,7 @@ with fluid.dygraph.guard(): ...@@ -959,7 +959,7 @@ with fluid.dygraph.guard():
如果OP只要有一个输入需要梯度,那么该OP的输出也需要梯度。相反,只有当OP的所有输入都不需要梯度时,该OP的输出也不需要梯度。在所有的 Variable 都不需要梯度的子图中,反向计算就不会进行计算了。 如果OP只要有一个输入需要梯度,那么该OP的输出也需要梯度。相反,只有当OP的所有输入都不需要梯度时,该OP的输出也不需要梯度。在所有的 Variable 都不需要梯度的子图中,反向计算就不会进行计算了。
在命令式编程模式模式下,除参数以外的所有 Variable 的 stop_gradient 属性默认值都为 True,而参数的 stop_gradient 属性默认值为 False。 该属性用于自动剪枝,避免不必要的反向运算。 在命令式编程模式下,除参数以外的所有 Variable 的 stop_gradient 属性默认值都为 True,而参数的 stop_gradient 属性默认值为 False。 该属性用于自动剪枝,避免不必要的反向运算。
使用方式如下: 使用方式如下:
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册