《神经网络与深度学习》学习笔记(2)性能优化、卷积神经网络(上半)
目录
前言
作者目前为某高校在读研究生,本文是应《神经网络与深度学习》课程的授课教师要求撰写的课程总结,如有纰漏欢迎指正。本文内容主要是对文献[1]和授课教师课程PPT的归纳与分析,因此使用了上述文献的部分内容,若存在侵权恳请联系作者及时删除。
1 性能优化
随机梯度下降(Stochastic Gradient Descent, SGD)是一种经典的优化器,但是它也存在一定的问题,例如在损失函数的“沟壑”中可能会产生振荡;此外还可能在鞍点处出现质量快速衰减,从而无法离开这块平坦的区域。
1.1 动量法
动量法能够有效解决上述问题,在鞍点处因为惯性的作用,更有可能脱离平坦的区域。更新公式见式(1.1)。
{
θ
←
θ
+
v
v
←
α
v
−
ϵ
g
(1.1)
\left \{ \begin{array}{l} {\boldsymbol \theta} \leftarrow {\boldsymbol \theta} + {\boldsymbol v} \\ {\boldsymbol v} \leftarrow {\alpha}{\boldsymbol v} - {\epsilon}{\boldsymbol g} \\ \end{array} \right. \tag{1.1}
{θ←θ+vv←αv−ϵg(1.1)其中,
θ
{\boldsymbol \theta}
θ是网络参数;
v
{\boldsymbol v}
v是速度;
g
{\boldsymbol g}
g是梯度,计算方法为
1
m
∇
θ
∑
i
L
(
f
(
x
(
i
)
;
θ
)
,
y
(
i
)
)
{\frac{1}{m}}{\nabla_{\boldsymbol \theta}\sum_{i}L(f({\boldsymbol x^{(i)}}; {\boldsymbol \theta}), {\boldsymbol y^{(i)}})}
m1∇θ∑iL(f(x(i);θ),y(i));
α
{\alpha}
α是动量参数;
ϵ
{\epsilon}
ϵ是学习率。
1.2 自适应梯度法
自适应梯度(Adaptive Gradient, AdaGrad)指参数能自适应变化,即具有较大偏导的参数相应有一个较大的学习率,而具有小偏导的参数则对应一个较小的学习率,每个参数的学习率会缩放各参数反比于其历史梯度平方值总和的平方根。更新公式见式(1.2)。
{
r
←
r
+
g
⊙
g
Δ
θ
=
−
ϵ
δ
+
r
⊙
g
θ
←
θ
+
Δ
θ
(1.2)
\left \{ \begin{array}{l} {\boldsymbol r} \leftarrow {\boldsymbol r} + {\boldsymbol g} \odot {\boldsymbol g} \\ \Delta{\boldsymbol \theta} = -\frac{\epsilon}{{\delta} + {\sqrt r}} \odot {\boldsymbol g} \\ {\boldsymbol \theta} \leftarrow {\boldsymbol \theta} + \Delta{\boldsymbol \theta} \\ \end{array} \right. \tag{1.2}
⎩
⎨
⎧r←r+g⊙gΔθ=−δ+rϵ⊙gθ←θ+Δθ(1.2)其中,
r
{\boldsymbol r}
r是梯度累积变量,设置初值为
0
{0}
0;
ϵ
{\epsilon}
ϵ是全局学习率;
δ
{\delta}
δ是一个较小的常数,例如设置为
10
−
7
{{10}^{-7}}
10−7。
AdaGrad方法的学习率是单调递减的,因此训练后期学习率过小会导致训练困难,甚至提前结束。而RMSProp能解决AdaGrad方法中学习率过度衰减的问题,RMSProp使用指数衰减平均以丢弃遥远的历史,使其能够快速收敛;此外,RMSProp还加入了超参数
ρ
\rho
ρ控制衰减速率。更新公式见式(1.3)。
{
r
←
ρ
r
+
(
1
−
ρ
)
g
⊙
g
Δ
θ
=
−
ϵ
δ
+
r
⊙
g
θ
←
θ
+
Δ
θ
(1.3)
\left \{ \begin{array}{l} {\boldsymbol r} \leftarrow {\rho}{\boldsymbol r} + ({1} - {\rho}){\boldsymbol g} \odot {\boldsymbol g} \\ \Delta{\boldsymbol \theta} = -\frac{\epsilon}{{\delta} + {\sqrt r}} \odot {\boldsymbol g} \\ {\boldsymbol \theta} \leftarrow {\boldsymbol \theta} + \Delta{\boldsymbol \theta} \\ \end{array} \right. \tag{1.3}
⎩
⎨
⎧r←ρr+(1−ρ)g⊙gΔθ=−δ+rϵ⊙gθ←θ+Δθ(1.3) Adam在RMSProp的基础上进行了进一步改良。除了加入历史梯度平方的指数衰减平均
(
r
)
({\boldsymbol r})
(r)外,还保留了历史梯度的指数衰减平均
(
s
)
({\boldsymbol s})
(s),相当于动量。更新公式见式(1.4)。
{
s
←
ρ
1
s
+
(
1
−
ρ
1
)
g
r
←
ρ
2
r
+
(
1
−
ρ
2
)
g
⊙
g
s
^
←
s
1
−
ρ
2
t
Δ
θ
=
−
ϵ
s
^
δ
+
r
^
θ
←
θ
+
Δ
θ
(1.4)
\left \{ \begin{array}{l} {\boldsymbol s} \leftarrow {\rho_1}{\boldsymbol s} + ({1} - {\rho_1}){\boldsymbol g} \\ {\boldsymbol r} \leftarrow {\rho_2}{\boldsymbol r} + ({1} - {\rho_2}){\boldsymbol g} \odot {\boldsymbol g} \\ \hat{\boldsymbol s} \leftarrow \frac{\boldsymbol s}{{1} - {\rho_2^t}} \\ \Delta{\boldsymbol \theta} = -{\epsilon}{\frac{\hat{\boldsymbol s}}{{\delta} + {\sqrt{\hat r}}}} \\ {\boldsymbol \theta} \leftarrow {\boldsymbol \theta} + \Delta{\boldsymbol \theta} \\ \end{array} \right. \tag{1.4}
⎩
⎨
⎧s←ρ1s+(1−ρ1)gr←ρ2r+(1−ρ2)g⊙gs^←1−ρ2tsΔθ=−ϵδ+r^s^θ←θ+Δθ(1.4)其中,
ϵ
\epsilon
ϵ是步长,例如设置为
0.001
{0.001}
0.001;
ρ
1
{\rho_1}
ρ1、
ρ
2
{\rho_2}
ρ2为矩估计的指数衰减速率,在区间
[
0
,
1
)
[{0}, {1})
[0,1)内;
t
{t}
t为时间步,初值设置为
0
{0}
0;
s
{\boldsymbol s}
s、
r
{\boldsymbol r}
r分别为一阶和二阶矩变量,初值设置为
0
{0}
0。
1.3 性能优化问题描述
性能优化问题即希望选择合适的权重 w {\mathbf w} w使得指标函数 J ( w ) J(\mathbf w) J(w)最小,与最优化方法中的函数最小化问题类似。进一步,我们希望建立迭代形式,并且形式尽量简单,类似于BP算法: w k + 1 = w k + α k p k {\mathbf w_{k+1}} = {\mathbf w_{k}} + {\alpha_{k}}{\mathbf p_{k}} wk+1=wk+αkpk,需要选取合适的 α k {\alpha_{k}} αk、 p k {\mathbf p_{k}} pk构成优化核心内容。以下以二次型函数为例。
1.4 二阶算法
对于二次型,有
f
(
w
k
+
1
)
=
f
(
w
k
)
+
g
k
⊤
Δ
w
k
+
1
2
(
Δ
w
k
)
⊤
A
k
(
Δ
w
k
)
+
⋯
(1.5)
f(\mathbf w_{k+1}) = f(\mathbf w_{k}) + {\mathbf g_{k}^{\top}}\Delta{\mathbf w_{k}} + \frac{1}{2}(\Delta{\mathbf w_{k}})^{\top}{A_{k}}(\Delta{\mathbf w_{k}}) +\cdots \tag{1.5}
f(wk+1)=f(wk)+gk⊤Δwk+21(Δwk)⊤Ak(Δwk)+⋯(1.5) 为求得
w
k
{\mathbf w_k}
wk使得
f
f
f最小,令
d
f
d
Δ
w
k
=
0
\frac{df}{d\Delta{\mathbf w_{k}}} = 0
dΔwkdf=0有
g
k
+
A
k
Δ
w
k
=
0
(1.6)
{\mathbf g_{k}} + {A_k}\Delta{\mathbf w_{k}} = 0 \tag{1.6}
gk+AkΔwk=0(1.6)于是可得
w
k
+
1
=
w
k
−
A
k
−
1
g
k
(1.7)
{\mathbf w_{k+1}} = {\mathbf w_{k}} - {A_{k}^{-1}}{\mathbf g_{k}} \tag{1.7}
wk+1=wk−Ak−1gk(1.7)这样就将问题转化成了二次导数项(Hessian矩阵)求逆,这种方法也被称为牛顿法。此外,常见的二阶算法还有高斯-牛顿法、Levenberg-Marquardt算法等等。
1.5 常用技巧
以下将主要介绍网络训练中的一些基本概念和常见技巧。
在模型初始化中,若简单的考虑,可以把所有权值在
[
−
1
,
1
]
[{-1}, {1}]
[−1,1]区间内按均值或高斯分布进行初始化。此外,还可以采用Xavier初始化,为了使得网络中信息更好的流动,每一层输出的方差应该尽量相等。因此需要实现如式(1.8)所示的均匀分布。
W
∼
U
[
−
6
n
j
+
n
j
+
1
,
6
n
j
+
n
j
+
1
]
(1.8)
{\mathbf W} \sim U\Big[{-\frac{\sqrt 6}{\sqrt{{n_{j}} + {n_{j+1}}}}}, \frac{\sqrt 6}{\sqrt{{n_{j}} + {n_{j+1}}}}\Big] \tag{1.8}
W∼U[−nj+nj+16,nj+nj+16](1.8) 数据通常可以划分成训练数据、验证数据以及测试数据,通常三者比例为
70
%
70\%
70%、
15
%
15\%
15%、
15
%
15\%
15%或
60
%
60\%
60%、
20
%
20\%
20%、
20
%
20\%
20%,当数据很多时训练和验证数据可适当减少。
K
K
K折交叉验证:原始训练数据被分成
K
K
K个不重叠的子集。 然后执行
K
K
K次模型训练和验证,每次在
K
−
1
{K-1}
K−1个子集上进行训练, 并在剩余的一个子集(在该轮中没有用于训练的子集)上进行验证。 最后,通过对
K
K
K 次实验的结果取平均来估计训练和验证误差。
欠拟合指误差一直比较大。过拟合指在训练数据集上误差小而在测试数据集上误差大。实例如图1-1所示,其中实线代表训练误差,虚线代表测试误差。
权重衰减。为了防止过拟合和权值震荡,可以在指标函数中加入新的函数项:
J
(
w
)
+
λ
2
∥
w
∥
2
J(\mathbf w) + {\frac{\lambda}{2}}{\Vert \mathbf w \Vert^{2}}
J(w)+2λ∥w∥2。并且第二项约束了权值不能过大。在梯度下降时,导数容易计算:
d
J
(
w
)
d
w
+
λ
w
\frac{d J(\mathbf w)}{d \mathbf w}+\lambda\mathbf w
dwdJ(w)+λw。
Dropout层会随机地让一定比例的神经元失活,可以在一定程度上抑制神经网络的过拟合,并丰富网络的拓扑结构,从而在一定程度上达到集成学习的效果。Dropout层的工作原理如图1-2所示。
2 卷积神经网络(上半)
2.1 深度学习平台介绍
PyTorch是一个Python的深度学习库。它最初由Facebook人工智能研究小组开发,而优步的Pyro软件则用于概率编程。最初,PyTorch由Hugh Perkins开发,作为基于Torch框架的LusJIT的Python包装器。PyTorch在Python中重新设计和实现Torch,同时为后端代码共享相同的核心C库。除了Facebook之外,Twitter、GMU和Salesforce等机构都采用了PyTorch。到目前,据统计已有80%的研究采用PyTorch,包括Google。以下将介绍一些PyTorch需要用到的基本概念。
张量(Tensor)是一个物理量,对高维(维数大于等于2) 的物理量进行“量纲分析” 的一种工具。简单的可以理解为:一维数组称为矢量,二维数组为二阶张量,三维数组为三阶张量等等。
计算图用“结点”(nodes)和“线”(edges)的有向图来描述数学计算的图像。“节点” 一般用来表示施加的数学操作,但也可以表示数据输入的起点/输出的终点,或者是读取/写入持久变量的终点。“线”表示“节点”之间的输入/输出关系。这些数据“线”可以输运“size可动态调整”的多维数据数组,即“张量”(tensor)。
程序示例1:
import torch
x_const = torch.tensor([1.0, 2.0, 3.0])
y = torch.tensor([3.0, 4.0, 5.0])
output = x_const + y
print(x_const, '\n', y, '\n', output)
输出:
[out]:
tensor([1., 2., 3.])
tensor([3., 4., 5.])
tensor([4., 6., 8.])
程序示例2,有100个散点样本,求出回归方程:
import torch
import torch.nn as nn
import numpy as np
import matplotlib.pyplot as plt
# 定义网络
class LinearRegression(nn.Module):
def __init__(self):
super(LinearRegression, self).__init__()
self.linear = nn.Linear(1, 1) # 输入和输出的维数都为1
def forward(self, x):
out = self.linear(x)
return out
# 进行训练
model = LinearRegression()
params = list(model.named_parameters())
(_, w) = params[0]
(_, b) = params[1]
criterion = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.3)
x = torch.unsqueeze(torch.linspace(-1, 1, 100), dim=1)
y = 2*x + 10 + torch.rand(x.size())
for epoch in range(20):
inputs = x
target = y
out = model(inputs) # 前向传播
loss = criterion(out, target) # 反向传播
optimizer.zero_grad() # 注意每次迭代都需要清零
loss.backward()
optimizer.step()
print('Epoch: {}, w: {:.4f},b:{:.4f}'.format(epoch + 1, float(w.data), float(b.data)))
model.eval()
plt.scatter(x, y)
x_line = np.linspace(-1, 1, 100)
y_line = y = float(w.data) * x_line + float(b.data)
plt.plot(x_line, y_line, 'r')
plt.show()
运行结果为:
[out]:
Epoch: 1, w: 0.3780,b:6.3994
Epoch: 2, w: 0.7053,b:8.8647
Epoch: 3, w: 0.9659,b:9.8509
Epoch: 4, w: 1.1733,b:10.2453
Epoch: 5, w: 1.3383,b:10.4031
Epoch: 6, w: 1.4697,b:10.4662
Epoch: 7, w: 1.5743,b:10.4915
Epoch: 8, w: 1.6575,b:10.5016
Epoch: 9, w: 1.7238,b:10.5056
Epoch: 10, w: 1.7765,b:10.5072
Epoch: 11, w: 1.8185,b:10.5079
Epoch: 12, w: 1.8519,b:10.5081
Epoch: 13, w: 1.8785,b:10.5082
Epoch: 14, w: 1.8997,b:10.5083
Epoch: 15, w: 1.9165,b:10.5083
Epoch: 16, w: 1.9299,b:10.5083
Epoch: 17, w: 1.9406,b:10.5083
Epoch: 18, w: 1.9491,b:10.5083
Epoch: 19, w: 1.9559,b:10.5083
Epoch: 20, w: 1.9613,b:10.5083
2.2 卷积神经网络基础
卷积核有时也会被称为滤波器,它在图像中以设定的步长(Stride)为间隔滑动,对图像数据进行乘积累加运算,具有提取图像特征的效果。卷积核在前向计算中的工作原理如图2-2所示。
其中,需要注意的是,由细虚线包围的是用于填充(Padding)的数据,由于直接进行卷积操作会导致图像的尺寸变小,为了维持图像的尺寸,这里采用了0填充的方法。
池化层(Pooling Layer)的运算也与滑动窗口类似,一般窗口的长、宽尺寸为偶数,运算结果可取滑动窗口中像素值的最大值或平均值。最大池化层最为常用,其原理如图2-3所示。
卷积神经网络的学习算法一般采用误差反向传播算法,该方法的具体内容在上一篇学习笔记中有具体描述。
2.3 LeNet-5网络
LeNet-5的结构如图2-3(图片来自文献[1])所示。
其中,C1是卷积层,由
6
6
6个卷积核组成,尺寸为
5
×
5
5\times5
5×5,填充宽度为
2
2
2,激活函数采用Sigmoid;S2层是平均池化层,尺寸为
2
×
2
2\times2
2×2,步长为
2
2
2;C3是卷积层,由
16
16
16个卷积核组成,尺寸为
5
×
5
5\times5
5×5,不进行填充,激活函数采用Sigmoid;S4层是平均池化层,尺寸为
2
×
2
2\times2
2×2,步长为
2
2
2;之后使用nn.Flatten将张量平坦化;F5、F6都为全连接层,神经元数量分别为
120
120
120、
84
84
84,激活函数采用Sigmoid;F7为输出层有
10
10
10个神经元,即每一张输入图像对应
10
10
10个输出,分别为属于
0
−
9
0-9
0−9的概率,并使用Softmax限制每个输出在
[
0
,
1
]
[0, 1]
[0,1]且和为
1
1
1。
LeNet的程序和注释如下,其中程序来自文献[1]:
# 导入库
import torch
from torch import nn
from d2l import torch as d2l
# 为了进行评估对evaluate_accuracy函数进行修改
# 在模型使用GPU计算数据集之前,将内存中的数据集复制到显存中
def evaluate_accuracy_gpu(net, data_iter, device=None):
"""使用GPU计算模型在数据集上的精度"""
if isinstance(net, nn.Module):
net.eval() # 设置为评估模式
if not device:
device = next(iter(net.parameters())).device
# 正确预测的数量,总预测的数量
metric = d2l.Accumulator(2)
with torch.no_grad():
for X, y in data_iter:
if isinstance(X, list):
# BERT微调所需的(之后将介绍)
X = [x.to(device) for x in X]
else:
X = X.to(device)
y = y.to(device)
metric.add(d2l.accuracy(net(X), y), y.numel())
return metric[0] / metric[1]
# 定义训练过程
def train_ch6(net, train_iter, test_iter, num_epochs, lr, device):
"""用GPU训练模型(在第六章定义)"""
def init_weights(m):
if type(m) == nn.Linear or type(m) == nn.Conv2d:
nn.init.xavier_uniform_(m.weight)
net.apply(init_weights)
print('training on', device)
net.to(device)
optimizer = torch.optim.SGD(net.parameters(), lr=lr)
loss = nn.CrossEntropyLoss()
animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs],
legend=['train loss', 'train acc', 'test acc'])
timer, num_batches = d2l.Timer(), len(train_iter)
for epoch in range(num_epochs):
# 训练损失之和,训练准确率之和,样本数
metric = d2l.Accumulator(3)
net.train()
for i, (X, y) in enumerate(train_iter):
timer.start()
optimizer.zero_grad()
X, y = X.to(device), y.to(device)
y_hat = net(X)
l = loss(y_hat, y)
l.backward()
optimizer.step()
with torch.no_grad():
metric.add(l * X.shape[0], d2l.accuracy(y_hat, y), X.shape[0])
timer.stop()
train_l = metric[0] / metric[2]
train_acc = metric[1] / metric[2]
if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1:
animator.add(epoch + (i + 1) / num_batches,
(train_l, train_acc, None))
test_acc = evaluate_accuracy_gpu(net, test_iter)
animator.add(epoch + 1, (None, None, test_acc))
print(f'loss {train_l:.3f}, train acc {train_acc:.3f}, '
f'test acc {test_acc:.3f}')
print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec '
f'on {str(device)}')
# 搭建网络
net = nn.Sequential(
nn.Conv2d(1, 6, kernel_size=5, padding=2), nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2),
nn.Conv2d(6, 16, kernel_size=5), nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2),
nn.Flatten(),
nn.Linear(16 * 5 * 5, 120), nn.Sigmoid(),
nn.Linear(120, 84), nn.Sigmoid(),
nn.Linear(84, 10))
# 显示网络各层输出的形状
X = torch.rand(size=(1, 1, 28, 28), dtype=torch.float32)
for layer in net:
X = layer(X)
print(layer.__class__.__name__, 'output shape: \t', X.shape)
# 对Fashion-MNIST数据集进行批处理
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)
# 进行训练
lr, num_epochs = 0.9, 10
train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
训练记录如图2-5所示。
参考文献
[1] 阿斯顿·张. 动手学深度学习[M]. 北京: 人民邮电出版社, 2023: 85-389.