第三章 深度神经网络
第三章 深度神经网络
1. 随机梯度下降
通常,我们需要设计一个模型的损失函数来约束我们的训练过程,让其向着使用者想要的方向发展。针对分类问题可以用交叉熵,针对回归问题可以用均方根误差。
模型的训练并不是漫无目的的,而是需要向着损失函数最少的方向发展。此时就需要利用梯度下降算法。
梯度下降算法(gradient descent)是一个一阶最优化算法,通常也称为最速下降法。使用规定步长距离向着函数当前点梯度的反方向前进,不断迭代搜索直到陷入局部最优解。
由于普通的梯度下降算法每次更新都需要使用所有样本,当样本总量特别大时,对算法的速度影响特别大,于是就有了随机梯度下降(stochastic gradient descent,SGD)算法,每次只随机取部分样本进行优化。
使用梯度下降时,通常会指定一个学习率 ,但该学习率比较难确定,且各个参数使用的学习率还有可能不同。针对这种情况,可以采用变化学习率 进行训练,如在网络前期使用大学习率进行更新,而后期则采用较小的学习率。SGD的另一个缺点就是容易陷入局部最优解,针对这种情况,学者提出了基于动量的随机梯度算法。在优化时引入动量,能够加速学习,特别是面对小而连续且有很多噪声的梯度。引入动量在一定程度上不仅增加了学习参数的稳定性,而且会更快学习到收敛的参数。
原本的梯度下降算法更新仅与当前梯度值相关,而不涉及之前的梯度。而动量梯度下降法则对各个mini-batch求得梯度,使用指数加权平均得到,。并使用新的参数进行更新。(滚雪球)
使用指数加权平均之后的梯度代替原梯度进行参数更新,每个指数加权平均后的梯度都含有之前所有梯度的信息。
在训练开始的时候,参数会与最终的最优值点距离较远,因此需要使用较大的学习率。而经过几轮的训练后,则需要减小学习率。有些算法是优化了学习率等参数的变化,如一系列自适应算法Adadelta、RMSProp和Adam。
2. 优化器
Pytorch中的optim模块,提供了多种可直接使用的优化算法。
| API | FUC |
|---|---|
| torch.optim.Adadelta() | Adadelta算法 |
| torch.optim.Adagrad() | - |
| torch.optim.Adam() | - |
| torch.optim.Adamax() | - |
| torch.optim.ASGD() | - |
| torch.optim.LBFGS() | - |
| torch.optim.RMSprop() | - |
| torch.optim.SGD() | - |
| torch.optim.Rprop() | - |
以Adam类为例,其参数的使用情况如下:
1 | torch.optim.Adam( |
案例
下面构建一个简单的测试网络,用于演示优化器。
-
咱先搞一个测试网络
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class TestNet(nn.Module):
def __init__(self):
# 继承下module类
super(TestNet,self).__init__()
# 定义一下隐藏层
self.hidden=nn.Sequential(
nn.Linear(13,10),
nn.ReLU(),
)
# 定义预测回归层
self.regression=nn.Linear(10,1)
# 定义向前传播路径
def forward(self,x):
x=self.hidden(x)
output=self.regression(x)
return output
# 创建个对象
testnet=TestNet() -
然后去定义我们的优化器
1
optimizer=Adam(testnet.parameters(),lr=0.001)
-
当然如果想要给每一层添加不同的学习率,可以指定参数
1
2
3
4
5
6optimizer=Adam(
[ {"params":testnet.hidden.parameters(),"lr":0.001},
{"params":testnet.regression.parameters(),"lr":0.01},
],
lr=1e-2
) -
有了优化器后,需要对参数进行更新,一般的步骤是梯度清空、反向传播。
1
2
3
4
5
6
7
8
9
10
11for input, target in dataset:
# 每次都需要进行梯度清零
optimizer.zero_grad()
# 计算预测值
output=testnetst(input)
# 计算损失
loss=loss_fn(output,target)
# 反向传播
loss.backward()
# 更新参数
optimizer.step() -
针对学习率的调整,可以使用torch.optim.lr_scheduler模块下的调整方式,不同的epoch可以设置大小不同的学习率。
-
lr_scheduler.LambdaLR(optimize,lr_lambda,last_epoch=-1)last_epoch表示合适开始调整学习率,-1表示设置为初始值,可以直接调整 -
lr_scheduler.StepLR(optimizer,step_size,gamma=0.1,last_epoch=-1)等间隔调整学习率,学习率每经过step_size指定的时间间隔调整为原来的gamma倍(间隔指的一般是epoch的间隔)1
2
3
4
5
6
7
8# 一般在此时定义学习率调整方法
# 将优化器封装进去
scheduler=...
for epoch in range(100):
train(...)
validate(...)
# 更新学习率
scheduler.step(...)
-
3. 损失函数
损失函数是优化对象,PyTorch提供了部分API
| 类 | 算法 | 适用问题 |
|---|---|---|
| nn.L1Loss() | 平均绝对值误差 | 回归 |
| nn.MSELoss() | 均方误差 | 回归 |
| nn.CrossEntropyLoss() | 交叉熵 | 多分类 |
| nn.NLLLoss() | 负对数似然函数 | 多分类 |
| nn.NLLLoss2d() | 图片负对数似然函数 | 图像分割 |
| nn.KLDivLoss() | KL散度 | 回归 |
| nn.BCELoss() | 二分类交叉熵 | 二分类 |
| nn.MarginRankingLoss() | 评价相似度 | |
| nn.MultiLabelMarginLoss() | 多标签分类 | 多标签分类 |
| nn.SmoothL1Loss() | 平滑L1 | 回归 |
| nn.SoftMarginLoss() | 多标签二分类 | 多标签分类 |
4. 过拟合
在统计学上,过拟合是指过于精确匹配特定数据集,导致模型不能良好拟合其他数据或预测未来观测结果的现象。
4.1 过拟合的概念
深度学习模型的过拟合通常是指针对设计好的深度学习网络,在使用训练数据集训练时,在训练数据集上能够获得很高的识别精度,或者很低的均方根误差,但该模型对测试集的测试结果反而不尽人意。
由于深度学习存在大量参数,使得其在各领域的表现优异,但也会导致其更容易过拟合。
4.2 防止过拟合的方法
- 增加数据量
- 合理的数据切分
- 正则化方法
- 在损失函数上添加对训练参数的惩罚范数,通过惩罚范数对咬训练的参数进行约束,防止模型过拟合。范数目的是让参数的绝对值最小,趋向于使用更少的参数,而范数目的是将参数的平方和最小化,会趋向于使用更多的小参数。
- Dropout
- 引入Dropout层,每个训练批次中,通过忽略一定百分比神经元数量,来减少网络的过拟合现象。即:在网络向前传播时,让某一神经元依照概率停止工作。这样能够得到泛化能力更强的网络,鲁棒性也更好,但是有可能过度依赖某些局部特征。
- 提前结束训练
5. 参数初始化
使用初始化方法能够获得更加稳定的结果。
| API | FUC |
|---|---|
| nn.init.uniform_(tensor,a=0.0,b=1.0) | 从均匀分布U(a,b)中生成值 |
| nn.init.normal_(tensor,mean=0.0,std=1.0) | 从给定均值mean和标准差std的正态分布中填充值 |
| nn.init.constant_(tensor,val) | 使用预设的常量来填充 |
| nn.init.eye_(tensor) | 用单位矩阵来填充 |
| nn.init.dirac_(tensor) | 使用Dirac delat函数填充 |
| nn.init.xavier_uniform_(tensor,gain=1.0) | 使用Glorot initialization均匀分布生成 |
| nn.init.xavier_normal_(tensor,gain=1.0) | 使用Glorot initialization正态分布生成 |
案例
针对某一层权重进行初始化
1 | # 定义一个卷积层 |
上述方法定义了其权重,我们也能对其偏置进行初始化。
1 | nn.init.constant(conv1.bias,val=0.1) |
针对整个网络的权重初始化方法
1 | # 建立一个测试网络 |
模型的结构以如下方式存储:
1 | TestNet( |
我们可以通过自定义函数的方式,对网络的权重进行初始化。
1 | def init_weight(m): |
最后通过apply方法运用
1 | # 然后module自带一个apply接口,我们通过这个接口就能将定义的初始化方法逐层运用 |
6. 自定义网络
本章以案例的形式进行。
我们创建一个简单的全连接神经网络。
模块导入
1 | import torch |
数据加载
我们以sk自带的波士顿房价信息数据作为数据源
1 | # 波士顿房价信息 |
房价信息分布情况如下:
我们先给数据做一个标准化,然后通过转化成Tensor读入数据加载器中。
1 | # 接着做一个标准化 |
多层感知机与模型训练
1 |
|
损失函数最终收敛且效果还不错
或者我们在定义模型的时候,可以采用层级容器nn.Sequential
1 | class MLPmodel(nn.Module): |
7. 模型加载与保存
一般有两种方式保存模型
方法一是整个模型一起保存了
1 | # 模型的保存 |
结果如下
1 | ''' |
方法二则是存储模型的参数
1 | # 或者只保存模型的参数 |
结果如下
1 | ''' |





