day1初步认识神经网络
神经网络的基本概念
神经元:神经网络中的每个节点,由加权和和非线性变换(激活函数)组成。
多层链接:大量神经元按层次分布,形成多层结构成为神经网络。
前向计算:由输入->输出。
模型结构三要素
模型假设->评价函数(损失函数)->优化算法
模型损失和优化
Loss函数以参数(w, b) 为自变量,令Loss取极小值使得更符合样本数据。
方法1:取导求极小
方法2: 梯度下降法
步骤:
1.随机选一组初始值。
2.选取下一个点使得Loss’<Loss,且下降趋势尽量快->梯度的反方向是函数值下降最快方向
3.重复步骤2直到损失函数几乎不下降。
day2
梯度下降的代码实现
定义损失函数如下:
L
=
1
2
N
∑
i
=
1
n
(
y
i
−
z
i
)
2
.
L =\frac{1}{2N}\sum_{i=1}^n (y_i-z_i)^2.
L=2N1i=1∑n(yi−zi)2.
其中,
z
i
=
∑
j
=
0
n
x
i
j
∗
w
j
+
b
z_i=\sum_{j=0}^nx_i^j*w_j+b
zi=j=0∑nxij∗wj+b是网络对第i个样本的预测值(共有n个参数)
梯度
g
r
a
d
i
e
n
t
=
(
∂
L
∂
w
j
,
.
.
.
∂
L
∂
w
n
,
∂
L
∂
b
)
gradient=(\frac{\partial L}{\partial w_j}, ...\frac{\partial L}{\partial w_n},\frac{\partial L}{\partial b})
gradient=(∂wj∂L,...∂wn∂L,∂b∂L)
带入损失函数,求偏导得
∂
L
∂
w
j
=
1
N
∑
i
=
1
N
(
z
i
−
y
i
)
x
i
j
\frac{\partial L}{\partial w_j}=\frac{1}{N}\sum_{i=1}^N(z_i-y_i)x_i^j
∂wj∂L=N1i=1∑N(zi−yi)xij
∂
L
∂
b
=
1
N
∑
i
=
1
N
(
z
i
−
y
i
)
\frac{\partial L}{\partial b}=\frac{1}{N}\sum_{i=1}^N(z_i-y_i)
∂b∂L=N1i=1∑N(zi−yi)
得梯度值。梯度方向是LOSS函数下降最快的方向,根据梯度函数更新参数值。
代码实现步骤:
前向计算->计算损失值->计算梯度->更新参数值
使用飞桨重写房价模型
飞桨支持的两种深度学习建模编写方式。
-
静态图模式(声明式编程范式,类比C++):先编译后执行。用户需预先定义完整的网络结构,再对网络结构进行编译优化后,才能执行获得计算结果,性能更好,便于部署。
-
动态图模式(命令式编程范式,类比Python):解析式的执行方式。用户无需预先定义完整的网络结构,每写一行网络代码,即可同时获得计算结果,方便调试。
数据处理不依赖框架,与使用Python构建相同。
在飞桨框架下实现梯度下降只需定义SGD。前向计算、计算损失和反向传播梯度,每个操作1-2行代码即可实现。
day3-4手写数字识别
使用飞桨框架构建神经网络的步骤:
数据处理->模型设计->训练配置->训练过程->模型保存
数据处理
使用飞桨提供的多个封装好的数据集API可直接获取处理好的训练集。
trainset = paddle.dataset.mnist.train()
train_reader = paddle.batch(trainset, batch_size=8)
将MINIST数据集分批次,每批读取数量为8.
for batch_id, data in enumerate(train_reader()):
img_data = np.array([x[0] for x in data]).astype('float32')
label_data = np.array([x[1] for x in data]).astype('float32')
print("图像数据形状和对应数据为:", img_data.shape, img_data[0])
print("图像标签形状和对应数据为:", label_data.shape, label_data[0])
break
迭代读取数据。有一个还待解决的问题就是如何打乱数据顺序,以便提高训练的有效性。这里我在实践作业里尝试多次,因为不太掌握数据读取器的使用方法,目前还未能修改成功。
模型设计
- 网络模型
面对不同的问题,应采用不同的模型进行训练。房价预测是简单的线性问题,而数字识别却涉及到二维像素的关系信息,因此应选用更复杂的模型,也就是卷积神经网络。
比较经典全连接神经网络和卷积神经网络的损失变化,可以发现卷积神经网络的损失值下降更快,且最终的损失值更小。 - 损失函数
房价预测是回归任务,而手写数字识别是分类任务,使用均方误差作为分类任务的损失函数存在逻辑和效果上的缺欠。
房价可以是大于0的任何浮点数,而手写数字识别的输出只可能是0-9之间的10个整数,相当于一种标签。
在房价预测的案例中,由于房价本身是一个连续的实数值,因此以模型输出的数值和真实房价差距作为损失函数(loss)是符合道理的。但对于分类问题,真实结果是分类标签,而模型输出是实数值,导致以两者相减作为损失不具备物理含义。
分类任务本质上是“某种特征组合下的分类概率”,在模型输出为分类标签的概率时,直接以标签和概率做比较也不够合理,人们更习惯使用交叉熵误差作为分类问题的损失衡量。 交叉熵损失函数的设计是基于最大似然思想:最大概率得到观察结果的假设是真的。
训练配置
单GPU训练
飞桨动态图通过fluid.dygraph.guard(place=None)里的place参数,设置在GPU上训练还是CPU上训练。
with fluid.dygraph.guard(place=fluid.CPUPlace())
with fluid.dygraph.guard(place=fluid.CUDAPlace(0))
分布式训练
- 模型并行
将一个网络模型拆分为多份,拆分后的模型分到多个设备上(GPU)训练,每个设备的训练数据是相同的。
应用场景:模型架构过大,网络模型的结构设计相对独立。 - 数据并行
读取多份数据,读取到的数据输入给多个设备(GPU)上的模型,每个设备上的模型是完全相同的,飞桨采用的就是这种方式。
由于输入数据不同,计算出的梯度是不同的,训练过程中应同步各设备上的梯度。梯度同步有两种方式:PRC通信方式和NCCL2通信方式(Nvidia Collective multi-GPU Communication Library)。
训练过程
优化的五个关键环节:
- 计算分类准确率,观测模型训练效果。
交叉熵损失函数只能作为优化目标,无法直接准确衡量模型的训练效果。准确率可以直接衡量训练效果,但由于其离散性质,不适合做为损失函数优化神经网络。 - 检查模型训练过程,识别潜在问题。
如果模型的损失或者评估指标表现异常,通常需要打印模型每一层的输入和输出来定位问题,分析每一层的内容来获取错误的原因。 - 加入校验或测试,更好评价模型效果。
理想的模型训练结果是在训练集和验证集上均有较高的准确率,如果训练集上的准确率高于验证集,说明网络训练程度不够;如果验证集的准确率高于训练集,可能是发生了过拟合现象。通过在优化目标中加入正则化项的办法,解决过拟合的问题。 - 加入正则化项,避免模型过拟合。
飞桨框架支持为整体参数加入正则化项,这是通常的做法。此外,飞桨框架也支持为某一层或某一部分的网络单独加入正则化项,以达到精细调整参数训练的效果。 - 可视化分析。
用户不仅可以通过打印或使用matplotlib库作图,飞桨还提供了更专业的可视化分析工具VisualDL,提供便捷的可视化分析方法。
保存模型
将训练好的模型保存到磁盘文件的方法。应用程序可以随时加载模型,完成预测任务。但是在日常训练工作中,我们会遇到一些突发情况,导致训练过程主动或被动的中断。飞桨支持从上一次保存状态开始继续训练,只要我们随时保存训练过程中的模型状态,就不用从初始状态重新训练。
进行恢复训练的程序不仅要保存模型参数,还要保存优化器参数。这是因为某些优化器含有一些随着训练过程变换的参数,例如Adam, AdaGrad等优化器采用可变学习率的策略,随着训练进行会逐渐减少学习率。这些优化器的参数对于恢复训练至关重要。
要点:
- 保存模型时同时保存模型参数和优化器参数。
- 恢复参数时同时恢复模型参数和优化器参数。
总结
通过这几天的学习,我对利用飞桨完成一个深度学习模型有了一个初步的了解和实践。在讲师们的指导下,每日完成打卡作业和学习笔记,我不仅感觉到开始踏入一个全新,有趣,实用的领域,也感受到自己一点一点进步的快乐。接下来还会和同学们一起学习,大家都加油呀!