《动手深度学习》总结
0. 推荐资料
首先感谢在Datawhale的组织下,联合了李沐老师一起举办了本次的学习计划,在本次计划下也记录一下自己的学习过程。
本次学习教程的参考书籍:动手学深度学习
本次学习教程的参考视频:李沐老师b站视频
1. 前言
本书个人认为需要有线性、高等、概率论的基础,以及掌握一些基础的Pytorch框架知识,至少达到能看懂代码的程度。
关于线性回归的总结,链接在这:《动手深度学习》之线性回归神经网络
下面将总结一下《动手深度学习》的第四章内容。
2. 深度学习
多层感知机
将下面的两张图进行对比,可以看出左边的图是将我们的输入直接映射到输出,然后进行softmax操作,但是这种做法有些许不太合理,即输入的某个特征相比于其他特征较大时,会导致模型输出的增大,这也就是为什么叫“线性”的原因,呈单调性。
为了克服这种缺点,可以在网络中加入隐藏层,使其能处理更普遍的函数关系模型,如下面右边的图,就是一个简单的多层感知机:这个多层感知机有4个输入,3个输出,隐藏层包含5个隐藏单元。该多层感知机中的层数为2,且这两层都是全连接的。每个输入都会影响隐藏层中的每个神经元,而隐藏层中的每个神经元又会影响输出层中的每个神经元。
激活函数
为什么要使用激活函数?
- a. 不使用激活函数,每一层输出都是上层输入的线性函数,无论神经网络有多少层,输出都是输入的线性组合。
- b. 使用激活函数,能够给神经元引入非线性因素,使得神经网络可以任意逼近任何非线性函数,这样神经网络就可以利用到更多的非线性模中。
什么是激活函数?
激活函数(activation function):通过计算加权和并加上偏置来确定神经元是否应该被激活,它们将输入信号转换为输出的可微运算。
常用的激活函数
- ReLU (Rectified linear unit)
ReLU提供了一种非常简单的非线性变换,使用ReLU的原因是,它求导表现得特别好:要么让参数消失,要么让参数通过(当输入为负时,ReLU函数的导数为0,而当输入为正时,ReLU函数的导数为1)。这使得优化表现得更好,并且ReLU减轻了困扰以往神经网络的梯度消失问题。其定义为:
ReLU
(
x
)
=
max
(
x
,
0
)
\operatorname{ReLU}(x) = \max(x, 0)
ReLU(x)=max(x,0)
- Sigmoid
sigmoid函数将输入变换为区间(0, 1)上的输出,sigmoid常用来做二分类,其缺点是计算量大,且反向传播时容易出现梯度消失。其公式为:
sigmoid
(
x
)
=
1
1
+
e
−
x
d
d
x
sigmoid
(
x
)
=
exp
(
−
x
)
(
1
+
exp
(
−
x
)
)
2
=
sigmoid
(
x
)
(
1
−
sigmoid
(
x
)
)
.
\operatorname{sigmoid}(x) = \frac{1}{1 + e^{-x}} \\ \frac{d}{dx} \operatorname{sigmoid}(x) = \frac{\exp(-x)}{(1 + \exp(-x))^2} = \operatorname{sigmoid}(x)\left(1-\operatorname{sigmoid}(x)\right).
sigmoid(x)=1+e−x1dxdsigmoid(x)=(1+exp(−x))2exp(−x)=sigmoid(x)(1−sigmoid(x)).
- tanh
tanh(双曲正切)函数也能将其输入压缩转换到区间(-1,1)上,当输入在0附近时,tanh函数接近线性变换。函数的形状类似于sigmoid函数,不同的是tanh函数关于坐标系原点中心对称。
tanh
(
x
)
=
1
−
e
−
2
x
1
+
e
−
2
x
d
d
x
tanh
(
x
)
=
1
−
tanh
2
(
x
)
.
\operatorname{tanh}(x) = \frac{1 - e^{-2x}}{1 + e^{-2x}} \\ \frac{d}{dx} \operatorname{tanh}(x) = 1 - \operatorname{tanh}^2(x).
tanh(x)=1+e−2x1−e−2xdxdtanh(x)=1−tanh2(x).
模型评估与选择
到目前为止,深度学习的“大致”流程已经了解的差不多了:
- ①是收集数据,并用代码加载
- ②是定义模型(这部分还未讲解),自己实现一个神经网络模型
- ③是定义超参数,并训练数据
- ④是模型评估与选择
在评估一个模型时,首先要了解两个概念“欠拟合”和“过拟合”。如下面这张图,就很好了反映了何为欠拟合,何为过拟合:
- 欠拟合:训练集和验证集效果都差。简单理解就是模型未学到东西,就要让人家去考试。
- 解决方法:增加特征数或增加数据集,增加模型复杂度(如添加网络层数),减小正则化系数。
- 过拟合:在训练集上效果好,在验证集上效果差。简单理解就是模型在平时小测都表现的很好,期末考试就不行。
- 解决方法:常见的有数据增强,降低模型复杂度,早停,正则化,Dropout等。
下面教如何判断是属于哪种情况:
参考链接:欠拟合(Under fitting)和过拟合 (Overfitting) - 一抹烟霞 - 博客园 (cnblogs.com)
-
第一种:这种情况loss曲线还是在下降的,因此只需要进行更多更高效的训练即可:
- 增大batch-size,增加训练epoch,增大learning rate等,也可换一种激活函数或优化器
- 增大batch-size,增加训练epoch,增大learning rate等,也可换一种激活函数或优化器
-
第二种:这种情况loss曲线已经不再下降,因此必须优化模型:
- 增加网络复杂度:增加层数;增加卷积层输出的通道数;增加全连接层的节点数
- 完善训练集和测试集的分布情况,若采用随机分割的话,有可能出现训练集上并未出现这个类别,但是测试集上又出现了;使用数据增强
-
第三种:
书本4.5及4.6总结*
正则化思想就是在原来的损失L(W)上,加上了一个参数 λ \lambda λ,来控制正则化的大小。
L1正则化: 常被用来进行特征选择,主要原因在于L1正则化会使得较多的参数为0,从而产生稀疏解,我们可以将0对应的特征遗弃,进而用来选择特征。公式如下:
- 当
w
i
w_i
wi>0,sign(
w
i
w_i
wi)=1;
w
i
w_i
wi<0,sign(
w
i
w_i
wi)=-1
L = L ( W ) + λ ∑ i = 1 n ∣ ω i ∣ ω i = ω i − η ∂ L ( W ) ∂ w i − η λ s i g n ( w i ) L=L(W)+λ\sum_{i=1}^{n}|\omega_i| \\ \omega_i=\omega_i-\eta\frac{\partial{L(W)}}{\partial{w_i}}-\eta\lambda{sign(w_i)} L=L(W)+λi=1∑n∣ωi∣ωi=ωi−η∂wi∂L(W)−ηλsign(wi)
L2正则化: 主要用来防止模型过拟合,直观上理解就是L2正则化是对于大数值的权重向量进行严厉惩罚。鼓励参数是较小值,如果 w w w小于1,那么 w 2 w^2 w2会更小。
- 当
w
i
w_i
wi<1的时,L2的惩罚项会越来越小,而L1还是会非常大,所以L1会使参数为0,而L2很难。
L = L ( W ) + λ ∑ i = 1 n w i 2 w i = w i − η ∂ L ( W ) ∂ w i − 2 η λ w i L=L(W)+λ\sum_{i=1}^{n}{w_i}^2 \\ w_i = w_i - \eta\frac{\partial{L(W)}}{\partial{w_i}}-2\eta\lambda{w_i} L=L(W)+λi=1∑nwi2wi=wi−η∂wi∂L(W)−2ηλwi
Dropout: 思想是在前向传播的过程中,丢弃某些神经元(实际上是让某个神经元的激活值以一定的概率p停止工作),这样可以使模型泛化性更强,因为它不会太依赖某些局部的特征。Dropout其实使用的会比较多,在训练一个深度神经网络时,通常会出现过拟合以及费时两种问题,dropout的思想就是丢弃某些神经元来达到减小过拟合现象,同时还减少了计算参数。另:通常p=0.5
这里推荐一篇我学习时找的文章:深度学习-Dropout详解_深度学习dropout_Tc.小浩的博客-CSDN博客
下面代码展示Dropout:
import torch
def dropout_layer(X, dropout):
assert 0 <= dropout <= 1
# Dropout=1表示将所有元素丢弃
if dropout == 1:
return torch.zeros_like(X)
# Dropout=0表示将所有元素保留
if dropout == 0:
return X
mask = (torch.rand(X.shape) > dropout).float()
# 可以查看哪些被丢弃了
print("mask:", mask)
return mask * X / (1.0 - dropout)
# 生成一个2×8大小的矩阵,元素为0~15
X = torch.arange(16, dtype = torch.float32).reshape((2, 8))
print("数据集:",X)
print("Dropout=0的时候", dropout_layer(X, 0.))
print("Dropout=0.5的时候", dropout_layer(X, 0.5))
print("Dropout=1的时候", dropout_layer(X, 1.))
前向传播与反向传播
前向传播是按顺序(从输入层到输出层)计算和存储神经网络中每层的结果。假设输入样本为
x
∈
R
d
\mathbf{x}\in \mathbb{R}^d
x∈Rd,则:
z
=
W
(
1
)
x
\mathbf{z}= \mathbf{W}^{(1)} \mathbf{x}
z=W(1)x
W
(
1
)
∈
R
h
×
d
\mathbf{W}^{(1)} \in \mathbb{R}^{h \times d}
W(1)∈Rh×d是隐藏层的权重参数
,
z
是中间变量。将中间变量
,z是中间变量。将中间变量
,z是中间变量。将中间变量
z
∈
R
h
\mathbf{z}\in \mathbb{R}^h
z∈Rh通过激活函数
ϕ
\phi
ϕ 后,得到长度为ℎ的隐藏激活向量:
h
=
ϕ
(
z
)
.
o
u
t
p
u
t
=
W
(
2
)
h
.
\mathbf{h}= \phi (\mathbf{z}). \\ \mathbf{output}= \mathbf{W}^{(2)} \mathbf{h}.
h=ϕ(z).output=W(2)h.
h也是一个中间变量,输出层的参数只有权重
W
(
2
)
∈
R
q
×
h
\mathbf{W}^{(2)} \in \mathbb{R}^{q \times h}
W(2)∈Rq×h,可以得到输出变量output,这时候假设损失函数为l,样本标签为y,计算单个数据样本的损失项:
L
=
l
(
o
u
t
p
u
t
,
y
)
.
L = l(\mathbf{output}, y).
L=l(output,y).
根据L2正则化的定义,给定超参数
λ
\lambda
λ,正则化项为
s
=
λ
2
(
∥
W
(
1
)
∥
F
2
+
∥
W
(
2
)
∥
F
2
)
,
s = \frac{\lambda}{2} \left(\|\mathbf{W}^{(1)}\|_F^2 + \|\mathbf{W}^{(2)}\|_F^2\right),
s=2λ(∥W(1)∥F2+∥W(2)∥F2),
最后的目标函数为
J
=
L
+
s
J = L + s
J=L+s
反向传播计算神经网络参数梯度的方法,该方法根据微积分中的链式规则,按相反的顺序从输出层到输入层遍历网络。第一步:计算目标函数
J
=
L
+
s
J=L+s
J=L+s相对于损失项L和正则项s的梯度:
∂
J
∂
L
=
1
and
∂
J
∂
s
=
1.
\frac{\partial J}{\partial L} = 1 \; \text{and} \; \frac{\partial J}{\partial s} = 1.
∂L∂J=1and∂s∂J=1.
链式法则计算目标函数关于输出层变量o的梯度:
∂
J
∂
o
=
prod
(
∂
J
∂
L
,
∂
L
∂
o
)
=
∂
L
∂
o
∈
R
q
.
∂
s
∂
W
(
1
)
=
λ
W
(
1
)
and
∂
s
∂
W
(
2
)
=
λ
W
(
2
)
.
\frac{\partial J}{\partial \mathbf{o}} = \text{prod}\left(\frac{\partial J}{\partial L}, \frac{\partial L}{\partial \mathbf{o}}\right) = \frac{\partial L}{\partial \mathbf{o}} \in \mathbb{R}^q. \\ \frac{\partial s}{\partial \mathbf{W}^{(1)}} = \lambda \mathbf{W}^{(1)} \; \text{and} \; \frac{\partial s}{\partial \mathbf{W}^{(2)}} = \lambda \mathbf{W}^{(2)}.
∂o∂J=prod(∂L∂J,∂o∂L)=∂o∂L∈Rq.∂W(1)∂s=λW(1)and∂W(2)∂s=λW(2).
现在计算最接近输出层的模型参数的梯度:
∂
J
∂
W
(
2
)
=
prod
(
∂
J
∂
o
,
∂
o
∂
W
(
2
)
)
+
prod
(
∂
J
∂
s
,
∂
s
∂
W
(
2
)
)
=
∂
J
∂
o
h
⊤
+
λ
W
(
2
)
.
\frac{\partial J}{\partial \mathbf{W}^{(2)}}= \text{prod}\left(\frac{\partial J}{\partial \mathbf{o}}, \frac{\partial \mathbf{o}}{\partial \mathbf{W}^{(2)}}\right) + \text{prod}\left(\frac{\partial J}{\partial s}, \frac{\partial s}{\partial \mathbf{W}^{(2)}}\right)= \frac{\partial J}{\partial \mathbf{o}} \mathbf{h}^\top + \lambda \mathbf{W}^{(2)}.
∂W(2)∂J=prod(∂o∂J,∂W(2)∂o)+prod(∂s∂J,∂W(2)∂s)=∂o∂Jh⊤+λW(2).
为了获得关于
W
(
1
)
\mathbf{W}^{(1)}
W(1)的梯度,需要继续沿着输出层到隐藏层反向传播
∂
J
∂
h
=
prod
(
∂
J
∂
o
,
∂
o
∂
h
)
=
W
(
2
)
⊤
∂
J
∂
o
.
\frac{\partial J}{\partial \mathbf{h}} = \text{prod}\left(\frac{\partial J}{\partial \mathbf{o}}, \frac{\partial \mathbf{o}}{\partial \mathbf{h}}\right) = {\mathbf{W}^{(2)}}^\top \frac{\partial J}{\partial \mathbf{o}}.
∂h∂J=prod(∂o∂J,∂h∂o)=W(2)⊤∂o∂J.
由于激活函数
ϕ
\phi
ϕ是按元素计算的,计算中间变量z的梯度
∂
J
/
∂
z
∈
R
h
\partial J/\partial \mathbf{z} \in \mathbb{R}^h
∂J/∂z∈Rh 需要使用按元素乘法运算符,用⊙表示:
∂
J
∂
z
=
prod
(
∂
J
∂
h
,
∂
h
∂
z
)
=
∂
J
∂
h
⊙
ϕ
′
(
z
)
.
\frac{\partial J}{\partial \mathbf{z}} = \text{prod}\left(\frac{\partial J}{\partial \mathbf{h}}, \frac{\partial \mathbf{h}}{\partial \mathbf{z}}\right) = \frac{\partial J}{\partial \mathbf{h}} \odot \phi'\left(\mathbf{z}\right).
∂z∂J=prod(∂h∂J,∂z∂h)=∂h∂J⊙ϕ′(z).
最后,可以得到最接近输入层的模型参数的梯度
∂
J
/
∂
W
(
1
)
∈
R
h
×
d
\partial J/\partial \mathbf{W}^{(1)} \in \mathbb{R}^{h \times d}
∂J/∂W(1)∈Rh×d:
∂
J
∂
W
(
1
)
=
prod
(
∂
J
∂
z
,
∂
z
∂
W
(
1
)
)
+
prod
(
∂
J
∂
s
,
∂
s
∂
W
(
1
)
)
=
∂
J
∂
z
x
⊤
+
λ
W
(
1
)
.
\frac{\partial J}{\partial \mathbf{W}^{(1)}} = \text{prod}\left(\frac{\partial J}{\partial \mathbf{z}}, \frac{\partial \mathbf{z}}{\partial \mathbf{W}^{(1)}}\right) + \text{prod}\left(\frac{\partial J}{\partial s}, \frac{\partial s}{\partial \mathbf{W}^{(1)}}\right) = \frac{\partial J}{\partial \mathbf{z}} \mathbf{x}^\top + \lambda \mathbf{W}^{(1)}.
∂W(1)∂J=prod(∂z∂J,∂W(1)∂z)+prod(∂s∂J,∂W(1)∂s)=∂z∂Jx⊤+λW(1).
这部分内容尽量结合上面的图去理解会比较好。
3. 总结
关于《动手深度学习》第四章的学习内容如上,如果写的不正确的地方也欢迎评论指正,另附上学习链接:第四章多层感知机。