第五章 深度学习
五、PaddlePaddle 基础
1. PaddlePaddle 简介
1.1 什么是 PaddlePaddle
PaddlePaddle(Parallel Distributed Deep Learning,中文名飞桨)是百度公司推出的开源、易学习、易使用的分布式深度学习平台
源于产业实践,在实际中有着优异表现
支持多种机器学习经典模型
1.2 为什么学习 PaddlePaddle
开源、国产
能更好、更快解工程决实际问题
1.3 PaddlePaddle 优点
易用性。语法简洁,API 的设计干净清晰
丰富的模型库。借助于其丰富的模型库,可以非常容易的复现一些经典方法
全中文说明文档。首家完整支持中文文档的深度学习平台
运行速度快。充分利用 GPU 集群的性能,为分布式环境的并行计算进行加速
1.4 PaddlePaddle 缺点
教材少
学习难度大、曲线陡峭
1.5 国际竞赛获奖情况
1.6 行业应用
1.7 学习资源
官网
- 地址:https://www.paddlepaddle.org.cn/
- 内容:学习指南、文档、API 手册
百度云智学院
- 地址:http://abcxueyuan.cloud.baidu.com/#/courseDetail?id=14958
- 内容:教学视频
AIStudio
- 地址:https://aistudio.baidu.com/aistudio/projectoverview/public/1
- 内容:项目案例
2. 体系结构
2.1 总体架构
2.2 编译与执行过程
用户编写的 python 程序通过调用 Paddle 提供的算子,向 Program 中添加变量(Tensor)以及对变量的操作(Operators 或者 Layers)
原始 Program 在框架内部转换为中间描述语言: ProgramDesc
Transpiler 接受一段 ProgramDesc ,输出一段变化后的 ProgramDesc ,作为后端 Executor 最终需要执行的 Program
执行 ProgramDesc 中定义的 Operator(可以类比为程序语言中的指令),在执行过程中会为 Operator 创建所需的输入输出并进行管理
2.3 三个重要术语
Fluid:定义程序执行流程
Program:对用户来说一个完整的程序
Executor:执行器,执行程序
2.4 案例 1:快速开始
3. 基本概念
3.1 张量
3.1.1 什么是张量
张量(Tensor): 多维数组或向量,同其它主流深度学习框架一样,PaddlePaddle 使用张量来承载数据
3.1.2 张量示例
灰度图像为二维张量(矩阵),彩色图像为三维张量
3.2 LoDTensor
LoD(Level-of-Detail) Tensor 是 Paddle 的高级特性,是对 Tensor 的一种扩充。LoDTensor 通过牺牲灵活性来提升训练的效率。
LoDTensor 用来处理变长数据信息,将长度不一致的维度拼接为一个大的维度,并引入了一个索引数据结构(LoD)来将张量分割成序列。
假设一个 mini-batch 中有 3 个句子,每个句子中分别包含 3 个、1 个和 2 个单词,我们可以用(3+1+2)xD 维 Tensor 加上一些索引信息来表示这个 mini-batch:
假设存在一个 mini-batch 中包含 3 个句子、1 个句子和 2 个句子的文章,每个句子都由不同数量的单词组成,则这个 mini-batch 的可以表示为 2-Level 的 LoDTensor:
3.3 Layer
表示一个独立的计算逻辑,通常包含一个或多个 operator(操作),如 layers.relu 表示 ReLU 计算;layers.pool2d 表示 pool 操作。Layer 的输入和输出为 Variable。
3.4 Variable
表示一个变量,在 paddle 中,Variable 基本等价于 Tensor 。
Variable 进入 Layer 计算,然后 Layer 返回 Variable。创建变量方式:
Paddle 中存在三种 Variable:
- 模型中的可学习参数:包括网络权重、偏置,生存期和整个训练任务一样长。通过 fluid.layers.create_parameter 来创建可学习参数
- 占位 Variable:Paddle 中使用 fluid.data 来接收输入数据,
fluid.data 需要提供输入 Tensor 的形状信息,当遇到无法确定的维度时,相应维度指定为 None - 常量 Variable:通过 fluid.layers.fill_constant 来实现常量 Variable
3.5 Program
Program 包含 Variable 定义的多个变量和 Layer 定义的多个计算,是一套完整的计算逻辑。从用户角度来看,Program 是顺序、完整执行的。program 的作用是存储网络结构,但不存储参数。
用户完成网络定义后,一段 Paddle 程序中通常存在 2 个 Program
- fluid.default_startup_program:定义了模型参数初始化、优化器参数初始化、reader 初始化等各种操作。该 program 可以由框架自动生成,使用时无需显式地创建
- fluid.default_main_program:定义了神经网络模型,前向反向计算,以及模型参数更新、优化器参数更新等各种操作
3.6 Scope
scope 在 paddle 里可以看作变量空间,存储 fluid 创建的变量。变量存储于 unordered_map 数据结构中,该结构类似于 python 中的 dict,键是变量的名字,值是变量的指针。
一 个 paddle 程序有一个默认的全局 scope (可以通过fluid.global_scope() 获取)。如果没有主动创建 scope 并且通过fluid.scope_guard() 替换当前 scope,那么所有参数都在全局 scope 中。参数创建的时机不是在组网时,而是在 executor.run() 执行时。
program 和 scope 配合,才能表达完整模型(模型=网络结构+参数)
3.7 Executor
Executor 用来接收并执行 Program,会一次执行 Program 中定义的所有计算。通过 feed 来传入参数,通过 fetch_list 来获取执行结果。
3.8 Place
PaddlePaddle 可以运行在 Intel CPU,Nvidia GPU,ARM CPU 和更多嵌入式设备上,可以通过 Place 用来指定执行的设备(CPU 或 GPU)。
3.9 Optimizer
优化器,用于优化网络,一般用来对损失函数做梯度下降优化,从而求得最小损失值。
3.10 案例 2:执行两个张量计算
3.11 程序执行步骤
3.12 案例 3:编写简单线性回归
任务:
- 给出输入样本
- 给出实际输出样本
- 找出 y = wx+b 公式中的 w 和 b
思路:
- 定义输入数据、实际输出结果
- 将数据送入神经网络进行训练(全连接网络,即分类器)
- 根据实际输出、预测输出之间的损失值,进行梯度下降,直到收敛到极小值为止
技术要点:
-
神经网络,选择 fluid.layers.fc(),该函数在神经网络中建立一个全连接层。接收多个输入,为每个输入分配一个权重 w, 并维护一个偏置值 b;预测时产生一个输出
-
损失函数:回归问题,选择均方差 fluid.layers. square_error_cost 和fluid.layers.mean() 作为损失函数
-
优化器:随机梯度下降优化器 fluild.SGD,做梯度下降计算
# 简单线性回归
import paddle
import paddle.fluid as fluid
import numpy as np
import matplotlib.pyplot as plt
train_data = np.array([[0.5], [0.6], [0.8], [1.1], [1.4]]).astype('float32')
y_true = np.array([[5.0], [5.5], [6.0], [6.8], [6.8]]).astype('float32')
# 定义数据数据类型
x = fluid.layers.data(name="x", shape=[1], dtype="float32")
y = fluid.layers.data(name="y", shape=[1], dtype="float32")
# 通过全连接网络进行预测
y_preict = fluid.layers.fc(input=x, size=1, act=None)
# 添加损失函数
cost = fluid.layers.square_error_cost(input=y_preict, label=y)
avg_cost = fluid.layers.mean(cost) # 求均方差
# 定义优化方法
optimizer = fluid.optimizer.SGD(learning_rate=0.01)
optimizer.minimize(avg_cost) # 指定最小化均方差值
# 搭建网络
place = fluid.CPUPlace() # 指定在CPU执行
exe = fluid.Executor(place)
exe.run(fluid.default_startup_program()) # 初始化系统参数
# 开始训练, 迭代100次
costs = []
iters = []
values = []
params = {"x": train_data, "y": y_true}
for i in range(200):
outs = exe.run(feed=params, fetch_list=[y_preict.name, avg_cost.name])
iters.append(i) # 迭代次数
costs.append(outs[1][0]) # 损失值
print("i:", i, " cost:", outs[1][0])
# 线性模型可视化
tmp = np.random.rand(10, 1) # 生成10行1列的均匀随机数组
tmp = tmp * 2 # 范围放大到0~2之间
tmp.sort(axis=0) # 排序
x_test = np.array(tmp).astype("float32")
params = {"x": x_test, "y": x_test} # y参数不参加计算,只需传一个参数避免报错
y_out = exe.run(feed=params, fetch_list=[y_preict.name]) # 预测
y_test = y_out[0]
# 损失函数可视化
plt.figure("Trainging")
plt.title("Training Cost", fontsize=24)
plt.xlabel("Iter", fontsize=14)
plt.ylabel("Cost", fontsize=14)
plt.plot(iters, costs, color="red", label="Training Cost") # 绘制损失函数曲线
plt.grid() # 绘制网格线
plt.savefig("train.png") # 保存图片
# 线性模型可视化
plt.figure("Inference")
plt.title("Linear Regression", fontsize=24)
plt.plot(x_test, y_test, color="red", label="inference") # 绘制模型线条
plt.scatter(train_data, y_true) # 原始样本散点图
plt.legend()
plt.grid() # 绘制网格线
plt.savefig("infer.png") # 保存图片
plt.show() # 显示图片