百度PaddlePaddle框架简易使用教程

本文详细介绍了百度的深度学习框架PaddlePaddle的使用,包括快速安装、基本操作,如线性回归模型的运行,以及LoD-Tensor的概念和应用。还探讨了数据准备、模型训练、分布式训练策略,如同步和异步训练,并提供了保存和加载模型的指导。此外,文章解释了如何创建和操作LoD-Tensor,解决变长序列的挑战,并展示了数据预处理和数据读取的方法。
摘要由CSDN通过智能技术生成

本文介绍百度的深度学习框架PaddlePaddle的基本概念和用法。本文主要参考了官方文档

快速上手

这部分通过一个线性回归的问题来感受一下PaddlePaddle的使用,读者不需要理解每一行代码(但是有必要尝试阅读和尽可能多的理解),后面的部分会详细的介绍。读者需要动手把环境搭建起来,把程序跑起来,这是最重要的第一步。

快速安装

我使用的是GPU的版本,所以使用如下的命令安装:

pip install -U paddlepaddle-gpu

为了避免冲突,建议使用virtualenv。当前最新的(1.5)PaddlePaddle需要CUDA 9 ,cuDNN 7.3 ,NCCL2 等依赖,如果出现安装问题请参考安装说明。如果读者没有GPU的机器或者安装GPU的版本遇到问题,可以先安装CPU的版本跑起来,CPU版本的安装通常更简单,等了解了PaddlePaddle之后可能更容易解决安装的问题。(注:其实学习一个新的东西最大的障碍是把Hello World跑起来,很多时候放弃的原因就是第一步搞不定)

CPU版本的安装可以使用:

pip install -U paddlepaddle

快速使用

我们首先需要导入paddle:

import paddle.fluid as fluid

下面是使用paddle操作tensor几个例子,请读者阅读其中的代码注释,尽量猜测它们的含义。

  • 使用Fluid创建5个元素的一维数组,其中每个元素都为1
# 定义数组维度及数据类型,可以修改shape参数定义任意大小的数组
data = fluid.layers.ones(shape=[5], dtype='int64')
# 在CPU上执行运算
place = fluid.CPUPlace()
# 创建执行器
exe = fluid.Executor(place)
# 执行计算
ones_result = exe.run(fluid.default_main_program(),
                        # 获取数据data
                        fetch_list=[data],
                        return_numpy=True)
# 输出结果
print(ones_result[0])

上面代码的结果为:

[1 1 1 1 1]

和Tensorflow等框架类似,paddle也需要定义tensor,比如fluid.layers.ones会创建一个全是1的tensor。同样如果需要查看它的值,我们也需要"运行"它。paddle里需要用执行器fluid.Executor来执行各种操作,另外定义Executor时需要指定在哪个设备上运行,这里用CPUPlace()来让代码则CPU上运行。和Tensorflow不同,paddle没有session,但是有程序(Program)的概念,我们定义的操作(比如定义的data)是默认添加到默认程序(fluid.default_main_program())。执行器执行时要指定运行哪个程序,Executor.run需要传入程序、feed参数(和Tensorflow的run类似,不过这里不需要feed)、输出参数fetch_list。return_numpy告诉Executor返回numpy数组而不是paddle的tensor,这便于我们打印结果(Tensorflow的session.run返回的就是numpy数组)。

  • 使用Fluid将两个数组按位相加

接着上面的例子,我们把data和它自己加起来:

# 调用 elementwise_op 将生成的一维数组按位相加
add = fluid.layers.elementwise_add(data,data)
# 定义运算场所
place = fluid.CPUPlace()
exe = fluid.Executor(place)
# 执行计算
add_result = exe.run(fluid.default_main_program(),
                 fetch_list=[add],
                 return_numpy=True)
# 输出结果
print (add_result[0]) 

结果为:

[2 2 2 2 2]
  • 使用Fluid转换数据类型

接着我们把int64的类型转换成float64:

# 将一维整型数组,转换成float64类型
cast = fluid.layers.cast(x=data, dtype='float64')
# 定义运算场所执行计算
place = fluid.CPUPlace()
exe = fluid.Executor(place)
cast_result = exe.run(fluid.default_main_program(),
                 fetch_list=[cast],
                 return_numpy=True)
# 输出结果
print(cast_result[0])

结果为:

[1. 1. 1. 1. 1.]

运行线性回归模型

这是一个简单的线性回归模型,来帮助我们快速求解4元一次方程。

#加载库
import paddle.fluid as fluid
import numpy as np
#生成数据
np.random.seed(0)
outputs = np.random.randint(5, size=(10, 4))
res = []
for i in range(10):
        # 假设方程式为 y=4a+6b+7c+2d
        y = 4*outputs[i][0]+6*outputs[i][1]+7*outputs[i][2]+2*outputs[i][3]
        res.append([y])
# 定义数据
train_data=np.array(outputs).astype('float32')
y_true = np.array(res).astype('float32')

#定义网络
x = fluid.layers.data(name="x",shape=[4],dtype='float32')
y = fluid.layers.data(name="y",shape=[1],dtype='float32')
y_predict = fluid.layers.fc(input=x,size=1,act=None)
#定义损失函数
cost = fluid.layers.square_error_cost(input=y_predict,label=y)
avg_cost = fluid.layers.mean(cost)
#定义优化方法
sgd_optimizer = fluid.optimizer.SGD(learning_rate=0.05)
sgd_optimizer.minimize(avg_cost)
#参数初始化
cpu = fluid.CPUPlace()
exe = fluid.Executor(cpu)
exe.run(fluid.default_startup_program())
##开始训练,迭代500次
for i in range(500):
        outs = exe.run(
                feed={'x':train_data,'y':y_true},
                fetch_list=[y_predict.name,avg_cost.name])
        if i%50==0:
                print ('iter={:.0f},cost={}'.format(i,outs[1][0]))
#存储训练结果
params_dirname = "result"
fluid.io.save_inference_model(params_dirname, ['x'], [y_predict], exe)

# 开始预测
infer_exe = fluid.Executor(cpu)
inference_scope = fluid.Scope()
# 加载训练好的模型
with fluid.scope_guard(inference_scope):
        [inference_program, feed_target_names,
         fetch_targets] = fluid.io.load_inference_model(params_dirname, infer_exe)

# 生成测试数据
test = np.array([[[9],[5],[2],[10]]]).astype('float32')
# 进行预测
results = infer_exe.run(inference_program,
                                                feed={"x": test},
                                                fetch_list=fetch_targets)
# 给出题目为 【9,5,2,10】 输出y=4*9+6*5+7*2+10*2的值
print ("9a+5b+2c+10d={}".format(results[0][0]))

上面的代码就是简单的线性回归,和Tensorflow的有很多类似的地方。不同之处为:

  • 不需要运行时指定操作

    在Tensorflow里,我们通过session.run告诉引擎计算哪些操作,但是在paddlepaddle里,我们只需要告诉它执行哪个程序,我们默认的操作都是按照顺序添加到程序里的,它会自动执行最后的操作(sgd_optimizer.minimize(avg_cost))。这个操作会首先进行前向的计算,然后反向计算梯度,最后更新参数。

  • save_inference_model自动裁剪

    训练介绍后我们需要用save_inference_model函数保存模型,我们只需要告诉它输入和预测的输出,它会自动裁剪计算图,只保留从输入到输出的子图。

  • fluid.scope_guard

    为了避免把预测的模型也加载到默认程序里,我们用fluid.scope_guard构造一个新的Scope,然后在这里加载保存的模型来预测。如果不构造新的Scope,虽然代码依然可以运行,但是训练的模型和预测的模型混在一起很容易混淆。

使用指南

LoD-Tensor

大部分深度学习框架都有Tensor的概念,但是PaddlePaddle除了普通的Tensor之外还有一个特殊的LoD-Tensor,它的作用是解决变长序列。在其它的框架里我们通常使用Padding把变长的序列变成定长的序列来解决这个问题,这会导致我们的代码需要有特殊的逻辑来处理padding。而PaddlePaddle则通过Lod-Tensor来系统解决变成的问题。

变长序列的挑战

大多数的深度学习框架使用Tensor表示一个mini-batch。

例如一个mini-batch中有10张图片,每幅图片大小为32x32,则这个mini-batch是一个10x32x32的 Tensor。

或者在处理NLP任务中,一个mini-batch包含N个句子,每个字都用一个D维的one-hot向量表示,假设所有句子都用相同的长度L,那这个mini-batch可以被表示为NxLxD的Tensor。

上述两个例子中序列元素都具有相同大小,但是在许多情况下,训练数据是变长序列。基于这一场景,大部分框架采取的方法是确定一个固定长度,对小于这一长度的序列数据以0填充。

在Fluid中,由于LoD-Tensor的存在,我们不要求每个mini-batch中的序列数据必须保持长度一致,因此您不需要执行填充操作,也可以满足处理NLP等具有序列要求的任务需求。

Fluid引入了一个索引数据结构(LoD)来将张量分割成序列。

LoD索引

为了更好的理解LoD的概念,本节提供了几个例子供您参考:

  • 句子组成的mini-batch

假设一个mini-batch中有3个句子,每个句子中分别包含3个、1个和2个单词。我们可以用(3+1+2)xD维Tensor 加上一些索引信息来表示这个mini-batch:

3       1   2
| | |   |   | |

上述表示中,每一个 | 代表一个D维的词向量,数字3,1,2构成了 1-level LoD。

  • 递归序列

让我们来看另一个2-level LoD-Tensor的例子:假设存在一个mini-batch中包含3个句子、1个句子和2个句子的文章,每个句子都由不同数量的单词组成,则这个mini-batch的样式可以看作:

3            1 2
3   2  4     1 2  3
||| || ||||  | || |||

表示的LoD信息为:

[[3,1,2]/level=0/,[3,2,4,1,2,3]/level=1/]

  • 视频的mini-batch

在视觉任务中,时常需要处理视频和图像这些元素是高维的对象,假设现存的一个mini-batch包含3个视频,分别有3个,1个和2个帧,每个帧都具有相同大小:640x480,则这个mini-batch可以被表示为:

3     1  2
口口口 口 口口

最底层tensor大小为(3+1+2)x640x480,每一个 口 表示一个640x480的图像

  • 图像的mini-batch

在传统的情况下,比如有N个固定大小的图像的mini-batch,LoD-Tensor表示为:

1 1 1 1     1
口口口口 ... 口

在这种情况下,我们不会因为索引值都为1而忽略信息,仅仅把LoD-Tensor看作是一个普通的张量:

口口口口 ... 口
  • 模型参数

模型参数只是一个普通的张量,在Fluid中它们被表示为一个0-level LoD-Tensor。

LoDTensor的偏移表示

为了快速访问基本序列,Fluid提供了一种偏移表示的方法——保存序列的开始和结束元素,而不是保存长度。

在上述例子中,您可以计算基本元素的长度:

3 2 4 1 2 3

将其转换为偏移表示:

0  3  5   9   10  12   15
   =  =   =   =   =    =
   3  2+3 4+5 1+9 2+10 3+12

所以我们知道第一个句子是从单词0到单词3,第二个句子是从单词3到单词5。

类似的,LoD的顶层长度

3 1 2

可以被转化成偏移形式:

0 3 4   6
  = =   =
  3 3+1 4+2

因此该LoD-Tensor的偏移表示为:

0       3    4      6
  3 5 9   10   12 15
LoD-Tensor

一个LoD-Tensor可以被看作是一个树的结构,树叶是基本的序列元素,树枝作为基本元素的标识。

在 Fluid 中 LoD-Tensor 的序列信息有两种表述形式:原始长度和偏移量。在 Paddle 内部采用偏移量的形式表述 LoD-Tensor,以获得更快的序列访问速度;在 python API中采用原始长度的形式表述 LoD-Tensor 方便用户理解和计算,并将原始长度称为: recursive_sequence_lengths 。

以上文提到的一个2-level LoD-Tensor为例:

3           1  2
3   2  4    1  2  3
||| || |||| |  || |||

以偏移量表示此 LoD-Tensor:[ [0,3,4,6] , [0,3,5,9,10,12,15] ]。

以原始长度表达此 Lod-Tensor:recursive_sequence_lengths=[ [3-0 , 4-3 , 6-4] , [3-0 , 5-3 , 9-5 , 10-9 , 12-10 , 15-12] ]。

以文字序列为例: [3,1,2] 可以表示这个mini-batch中有3篇文章,每篇文章分别有3、1、2个句子,[3,2,4,1,2,3] 表示每个句子中分别含有3、2、4、1、2、3个字。

recursive_seq_lens 是一个双层嵌套列表,也就是列表的列表,最外层列表的size表示嵌套的层数,也就是lod-level的大小;内部的每个列表,对应表示每个lod-level下,每个元素的大小。

下面三段代码分别介绍如何创建一个LoD-Tensor,如何将LoD-Tensor转换成Tensor,如何将Tensor转换成LoD-Tensor:

  • 创建 LoD-Tensor
#创建lod-tensor
import paddle.fluid as fluid
import numpy as np

a = fluid.create_lod_tensor(np.array([[1],[1],[1],
                                  [1],[1],
                                  [1],[1],[1],[1],
                                  [1],
                                  [1],[1],
                                  [1],[1],[1]]).astype('int64') ,
                          [[3,1,2] , [3,2,4,1,2,3]],
                          fluid.CPUPlace())

#查看lod-tensor嵌套层数
print (len(a.recursive_sequence_lengths()))
# output:2

#查看最基础元素个数
print (sum(a.recursive_sequence_lengths()[-1]))
# output:15 (3+2+4+1+2+3=15)
  • LoD-Tensor 转 Tensor
import paddle.fluid as fluid
import numpy as np

# 创建一个 LoD-Tensor
a = fluid.create_lod_tensor(np.array([[1.1], [2.2],[3.3],[4.4]]).astype('float32'), 
					[[1,3]], fluid.CPUPlace())

def LodTensor_to_Tensor(lod_tensor):
  # 获取 LoD-Tensor 的 lod 信息
  lod = lod_tensor.lod()
  # 转换成 array
  array = np.array(lod_tensor)
  new_array = []
  # 依照原LoD-Tensor的层级信息,转换成Tensor
  for i in range(len(lod[0]) - 1):
      new_array.append(array[lod[0][i]:lod[0][i + 1]])
  return new_array

new_array = LodTensor_to_Tensor(a)

# 输出结果
print(new_array)
  • Tensor 转 LoD-Tensor
import paddle.fluid as fluid
import numpy as np

def to_lodtensor(data, place):
  # 存储Tensor的长度作为LoD信息
  seq_lens = [len(seq) for seq in data]
  cur_len = 0
  lod = [cur_len]
  for l in seq_lens:
      cur_len += l
      lod.append(cur_len)
  # 对待转换的 Tensor 降维
  flattened_data = np.concatenate(data, axis=0).astype("int64")
  flattened_data = flattened_data.reshape([len(flattened_data), 1])
  # 为 Tensor 数据添加lod信息
  res = fluid.LoDTensor()
  res.set(flattened_data, place)
  res.set_lod([lod])
  return res

# new_array 为上段代码中转换的Tensor
lod_tensor = to_lodtensor(new_array,fluid.CPUPlace())

# 输出 LoD 信息
print("The LoD of the result: {}.".format(lod_tensor.lod()))

# 检验与原Tensor数据是否一致
print("The array : {}.".format(np.array(lod_tensor)))

LoD会让序列的处理变得简单,读者可以在下面的情感分析示例里体会到这带来的好处。如果现在还不是特别明白LoD的用处,可以在阅读后面的情感分析的代码时回过头来参考本节内容。

准备数据

使用PaddlePaddle Fluid准备数据分为三个步骤:

  • Step1: 自定义Reader生成训练/预测数据

生成的数据类型可以为Numpy Array或LoDTensor。根据Reader返回的数据形式的不同,可分为Batch级的Reader和Sample(样本)级的Reader。

Batch级的Reader每次返回一个Batch的数据,Sample级的Reader每次返回单个样本的数据

如果您的数据是Sample级的数据,我们提供了一个可以数据预处理和组建batch的工具:Python Reader 。

  • Step2: 在网络配置中定义数据层变量

用户需使用 fluid.layers.data 在网络中定义数据层变量。定义数据层变量时需指明数据层的名称name、数据类型dtype和维度shape。例如:

import paddle.fluid as fluid

image = fluid.layers.data(name='image', dtype='float32', shape=[28, 28])
label = fluid.layers.data(name='label', dtype='int64', shape=[1])

需要注意的是,此处的shape是单个样本的维度,PaddlePaddle Fluid会在shape第0维位置添加-1,表示batch_size的维度,即此例中image.shape为[-1, 28, 28], label.shape为[-1, 1]。

若用户不希望框架在第0维位置添加-1,则可通过append_batch_size=False参数控制,即:

import paddle.fluid as fluid

image = fluid.layers.data(name='image', dtype='float32', shape=[28, 28], append_batch_size=False)
label = fluid.layers.data(name='label', dtype='int64', shape=[1], append_batch_size=False)

此时,image.shape为[28, 28],label.shape为[1]。

  • Step3: 将数据送入网络进行训练/预测

Fluid提供两种方式,分别是异步PyReader接口方式或同步Feed方式,具体介绍如下:

异步PyReader接口方式

用户需要先使用 fluid.io.PyReader 定义PyReader对象,然后通过PyReader对象的decorate方法设置数据源。 使用PyReader接口时,数据传入与模型训练/预测过程是异步进行的,效率较高,推荐使用。

同步Feed方式

用户自行构造输入数据,并在 fluid.Executor 或 fluid.ParallelExecutor 中使用 executor.run(feed=…) 传入训练数据。数据准备和模型训练/预测的过程是同步进行的, 效率较低。

这两种准备数据方法的比较如下:

对比项 同步Feed方式 异步PyReader接口方式
API接口 executor.run(feed=…) fluid.io.PyReader
数据格式 Numpy Array或LoDTensor Numpy Array或LoDTensor
数据增强 Python端使用其他库完成 Python端使用其他库完成
速度
推荐用途 调试模型 工业训练
数据预处理工具

在模型训练和预测阶段,PaddlePaddle程序需要读取训练或预测数据。为了帮助您编写数据读取的代码,我们提供了如下接口:

  • reader: 样本级的reader,用于读取数据的函数,数据可来自于文件、网络、随机数生成器等,函数每次返回一个样本数据项。
  • reader creator: 接受一个或多个reader作为参数、返回一个新reader的函数。
  • reader decorator: 一个函数,接受一个或多个reader,并返回一个reader。
  • batch reader: 用于读取数据的函数,数据可来自于文件、网络、随机数生成器等,函数每次返回一个batch大小的数据项。

此外,还提供了将reader转换为batch reader的函数,会频繁用到reader creator和reader decorator。

Data Reader接口

Data read

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值