神经网络与深度学习
卷积神经网络
为什么BP神经网络没有继续发展下去,总的来说有以下几个原因:
- 全连接网络算的慢,难收敛
- 可能进入局部极小值,也容易产生过拟合问题。
- 参数量极高
因此随着目标检测,图像分类,语义分割的发展,卷积神经网络得到了进一步发展。
深度学习平台简介
目前,最主流的平台就是这些,其中最被大家熟知的就是TensorFlow和Pytorch,而目前使用最广泛的就是pytorch,所以如果大家想入手深度学习,那么就可以从Pytorch入手了。
pytorch简介
Pytorch 是一个Python的深度学习库。最初由Facebook人工智能研究小组开发,除了Facebook之外,Twitter、GWU和Sslesforce等机构都采用了Pytorch。到目前,据统计已有80%的研究采用PyTorch,包括Google.
Pytorch 与 TensorFlow2的的对比
PyTorch资源:
- 60分钟快速入门:https://www.jianshu.com/p/889dbc684622
- 官方教程:https://www.pytorch123.com/
- 动手学深度学习:https://zh-v2.d2l.ai/
- Deep learning with pytorch:https://pytorch.org/assets/deeplearning/Deep-Learning-with-PyTorch.pdf
卷积神经网络进化史
大家看到这些网络结构的时候,一开始可能会有点蒙。但完全没必要,因为完全就是搭积木,一点一点搭建起来的。这不过在新的模型中会出现一些新的算法,例如残差网络,洗牌操作,多通道卷积,上采样特征融合等等。这张图片有点老,还应该加入YOLO,GPT等等。
卷积神经网络基础
卷积
卷积其实很好理解,就是一个卷积核在输入数据上进行移动,每移动一次,计算一次,最终生成一个新的数据。在创建神经网络时,能够进行设定的就是卷积核的大小,例如1*1,3*3,5*5,7*7。
在这里所涉及的一个就是卷积核的计算,基本上每初学者都会遇到这个问题,以输入数据的尺寸为64*256*256,输出为128*256*256,卷积核大小为3*3为例:
- 首先可以看到输入输出的形状没变,这里就需要进行padding操作,接下来会讲解,先不急!
- 可以看到输入的通道数是64,输出的通道数是128,那么卷积核的尺寸就是128*64*3*3,这种是标准卷积下对于卷积核的计算。
- 还用一种卷积方法就是Depthwise,为了防止大家造成混乱,这里就先不讲解了,其实也不难,后续会陆续讲解。
池化
池化操作主要分成两种:最大值池化和均值池化。池化层也叫下采样层(Upsampling)均值池化就是对指定区域求均值作为说出,最大值池化就是对指定区域取最大值操作。同样,在搭建模型的时候,也可以使用下面步长公式进行计算,原理相同。
padding
好了,到了刚才提到的padding了。这个操作就是对输入数据的一周进行填充,当输入数据的形状是5*5,卷积核大小为3*3,为了让输出的形状是5*5,我们就需要对输入数据进行填充,这样就可以保证输出的形状不变,这是如何计算的呢,请接着看!
stride
这部分其实没啥可讲的,就是卷积核移动的步长,在建立模型的时候,需要通过设定stride,来获得不同尺寸输出,在这里只要记住一个公式就行:
H
o
u
t
=
⌊
H
i
n
−
K
+
2
P
S
⌋
+
1
H_{out}=\left\lfloor\frac{H_{in}-K+2P}S\right\rfloor+1
Hout=⌊SHin−K+2P⌋+1
W
o
u
t
=
⌊
W
i
n
−
K
+
2
P
S
⌋
+
1
W_{out}=\left\lfloor\frac{W_{in}-K+2P}S\right\rfloor+1
Wout=⌊SWin−K+2P⌋+1
其中
H
i
n
H_{in}
Hin代表输入数据的长,
K
K
K代表卷积核的大小,
P
P
P代表padding设置的值,
S
S
S代表步长,
H
o
u
t
H_{out}
Hout代表输出数据的的长,下一个公式也是同样的。
前向计算
结合之前的图,前向传播定义为:
z
[
l
]
(
x
,
y
)
=
∑
u
=
0
p
∑
v
=
0
q
a
[
l
−
1
]
(
x
+
u
,
y
+
v
)
w
[
l
]
,
k
(
u
,
v
)
a
[
l
]
(
x
,
y
)
=
f
(
z
[
l
]
(
x
,
y
)
)
\begin{gathered}z^{[l]}(x,y)=\sum_{u=0}^p\sum_{v=0}^qa^{[l-1]}(x+u,y+v)w^{[l],k}(u,v)\\a^{[l]}(x,y)=f\left(z^{[l]}(x,y)\right)\end{gathered}
z[l](x,y)=u=0∑pv=0∑qa[l−1](x+u,y+v)w[l],k(u,v)a[l](x,y)=f(z[l](x,y))
这其实就是对filter内进行计算。
如果第L层是卷积+池化层,则:
a
[
l
]
(
x
,
y
)
=
downsample
(
∑
u
=
0
p
∑
v
=
0
q
a
[
l
−
1
]
(
x
+
u
,
y
+
v
)
w
s
(
u
,
v
)
)
\begin{aligned}&a^{[l]}(x,y)=\text{downsample}\left(\sum_{u=0}^p\sum_{v=0}^qa^{[l-1]}(x+u,y+v)w_s(u,v)\right)\end{aligned}
a[l](x,y)=downsample(u=0∑pv=0∑qa[l−1](x+u,y+v)ws(u,v))
误差方向传播
对于卷积神经网络的误差反向传播,其实很好理解。首先需要结合上一篇文章讲解的BP算法的误差反向传播。
从这张图中可以看出,卷积神经网络的计算过程,其实就是一种局部连接计算,也是一种类似BP神经网络的计算过程。所以它的误差反向传播算法如下所示:
- 如果当前是卷积层,下一层为下采样层:
- 如果当前是下采样层,下一层是卷积层:
- 卷积层+卷积层
a [ l ] ( x 1 , y 1 ) = f ( ∑ u = 0 p ∑ v = 0 q a [ l − 1 ] ( x 1 + u , y 1 + v ) w [ l ] , k ( u , v ) ) . . . . . . . a [ l + 1 ] ( x i , y i ) = f ( ∑ u = 0 p ∑ v = 0 q a [ l ] ( x i + u , y i + v ) w [ l + 1 ] , k ( u , v ) ) \begin{gathered} a^{[l]}(x_1,y_1)=f\left(\sum_{u=0}^p\sum_{v=0}^qa^{[l-1]}(x_1+u,y_1+v)w^{[l],k}(u,v)\right) \\.......\\ \begin{aligned}a^{[l+1]}(x_i,y_i)&=f\left(\sum_{u=0}^p\sum_{v=0}^qa^{[l]}(x_i+u,y_i+v)w^{[l+1],k}(u,v)\right)\end{aligned} \end{gathered} a[l](x1,y1)=f(u=0∑pv=0∑qa[l−1](x1+u,y1+v)w[l],k(u,v)).......a[l+1](xi,yi)=f(u=0∑pv=0∑qa[l](xi+u,yi+v)w[l+1],k(u,v))
因此有:
∂ a [ l + 1 ] ( x i , y i ) ∂ w [ l ] , k = ∑ u = 0 p ∑ v = 0 q ∂ a [ l + 1 ] ( x i , y i ) ∂ a [ l ] ( x i + u , y i + v ) ⋅ ∂ a [ l ] ( x i + u , y i + v ) ∂ w [ l ] , k \frac{\partial a^{[l+1]}(x_i,y_i)}{\partial w^{[l],k}}=\sum_{u=0}^{p}\sum_{v=0}^{q}\frac{\partial a^{[l+1]}(x_i,y_i)}{\partial a^{[l]}(x_i+u,y_i+v)}\cdot\frac{\partial a^{[l]}(x_i+u,y_i+v)}{\partial w^{[l],k}} ∂w[l],k∂a[l+1](xi,yi)=u=0∑pv=0∑q∂a[l](xi+u,yi+v)∂a[l+1](xi,yi)⋅∂w[l],k∂a[l](xi+u,yi+v) - 卷积层+全连接层:
卷积层:
a [ l ] ( x 1 , y 1 ) = f ( ∑ u = 0 p ∑ v = 0 q a [ l − 1 ] ( x 1 + u , y 1 + v ) w [ l ] , k ( u , v ) ) . . . . . . a^{[l]}(x_1,y_1)=f\left(\sum_{u=0}^p\sum_{v=0}^qa^{[l-1]}(x_1+u,y_1+v)w^{[l],k}(u,v)\right)\\...... a[l](x1,y1)=f(u=0∑pv=0∑qa[l−1](x1+u,y1+v)w[l],k(u,v))......
全连接层:
a [ l + 1 ] ( x i , y i ) = f ( ∑ u = 0 n ∑ v = 0 n a [ l ] ( x i + u , y i + v ) w [ l + 1 ] , k ( u , v ) ) a^{[l+1]}(x_i,y_i)=f\left(\sum_{u=0}^n\sum_{v=0}^na^{[l]}(x_i+u,y_i+v)w^{[l+1],k}(u,v)\right) a[l+1](xi,yi)=f(u=0∑nv=0∑na[l](xi+u,yi+v)w[l+1],k(u,v))
因此有:
∂ a [ l + 1 ] ( x i , y i ) ∂ w [ l ] , k = ∑ u = 0 n ∑ v = 0 n ∂ a [ l + 1 ] ( x i , y i ) ∂ a [ l ] ( x i + u , y i + v ) ⋅ ∂ a [ l ] ( x i + u , y i + v ) ∂ w [ l ] , k \frac{\partial a^{[l+1]}(x_i,y_i)}{\partial w^{[l],k}}=\sum_{u=0}^n\sum_{v=0}^n\frac{\partial a^{[l+1]}(x_i,y_i)}{\partial a^{[l]}(x_i+u,y_i+v)}\cdot\frac{\partial a^{[l]}(x_i+u,y_i+v)}{\partial w^{[l],k}} ∂w[l],k∂a[l+1](xi,yi)=u=0∑nv=0∑n∂a[l](xi+u,yi+v)∂a[l+1](xi,yi)⋅∂w[l],k∂a[l](xi+u,yi+v)
LeNet - 5网络
这其实是一个非常简单的网络模型,但也是非常经典的网络模型。
首先对输入数据做卷积->池化->卷积->池化->全连接->全连接层->输出层
与现在网络的区别:
- 卷积时不进行填充
- 池化层选用平均池化而非最大值池化
- 选用Sigmoid或tanh而非ReLU作为非线性环节激活函数
- 层数较浅,参数数量小
用PyTorch生成的LeNet网络模型:
import torch
import torch.nn as nn
import torch.nn.functional as F
class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
# 定义LeNet的各个层
self.conv1 = nn.Conv2d(1, 6, kernel_size=5)
self.conv2 = nn.Conv2d(6, 16, kernel_size=5)
self.fc1 = nn.Linear(16*4*4, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10) # 10类输出,对应10个数字
def forward(self, x):
# 定义数据流向
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
x = F.max_pool2d(F.relu(self.conv2(x)), (2, 2))
x = x.view(-1, self.num_flat_features(x))
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
def num_flat_features(self, x):
size = x.size()[1:] # 所有维度除了batch维度之外的维度
num_features = 1
for s in size:
num_features *= s
return num_features
# 创建LeNet模型实例
net = LeNet()
print(net)
基本卷积神经网络
AlexNet
改进:
- 池化层均采用最大池化
- 选用ReLU作为非线性环节激活函数
- 网络规模扩大,参数数量接近6000万
- 出现“多个卷积层+一个池化层”的结构
用PyTorch实现AlexNet网络模型:
import torch
import torch.nn as nn
class AlexNet(nn.Module):
def __init__(self, num_classes=1000):
super(AlexNet, self).__init__()
self.features = nn.Sequential(
nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2),
nn.Conv2d(64, 192, kernel_size=5, padding=2),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2),
nn.Conv2d(192, 384, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(384, 256, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(256, 256, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2),
)
self.avgpool = nn.AdaptiveAvgPool2d((6, 6))
self.classifier = nn.Sequential(
nn.Dropout(),
nn.Linear(256 * 6 * 6, 4096),
nn.ReLU(inplace=True),
nn.Dropout(),
nn.Linear(4096, 4096),
nn.ReLU(inplace=True),
nn.Linear(4096, num_classes),
)
def forward(self, x):
x = self.features(x)
x = self.avgpool(x)
x = torch.flatten(x, 1)
x = self.classifier(x)
return x
# 创建AlexNet模型实例
net = AlexNet()
print(net)
VGG16
改进
- 网络规模进一步增大,参数数量为1.38亿
- 卷积层、池化层的超参数基本相同,整体结构呈现出规整的特点
import torch
import torch.nn as nn
# 定义VGG16网络结构
class VGG16(nn.Module):
def __init__(self, num_classes=1000):
super(VGG16, self).__init__()
self.features = nn.Sequential(
nn.Conv2d(3, 64, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(64, 64, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(64, 128, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(128, 128, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(128, 256, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(256, 256, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(256, 256, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(256, 512, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
)
self.avgpool = nn.AdaptiveAvgPool2d((7, 7))
self.classifier = nn.Sequential(
nn.Linear(512 * 7 * 7, 4096),
nn.ReLU(True),
nn.Dropout(),
nn.Linear(4096, 4096),
nn.ReLU(True),
nn.Dropout(),
nn.Linear(4096, num_classes),
)
def forward(self, x):
x = self.features(x)
x = self.avgpool(x)
x = torch.flatten(x, 1)
x = self.classifier(x)
return x
# 实例化VGG16网络
vgg16 = VGG16()
# 打印网络结构
print(vgg16)
残差网络
梯度消失问题是指在深层神经网络中,梯度在反向传播过程中逐渐变小,最终接近零,导致深层层次的权重更新变得非常缓慢,甚至无法进行有效的学习。这通常发生在网络层数较深、激活函数导数接近于0的情况下。解决梯度消失问题,最好的方法莫过于残差连接。
传统连接方式的计算过程:
z
[
l
+
1
]
=
w
[
l
+
1
]
a
[
l
]
+
b
[
l
+
1
]
,
a
[
l
+
1
]
=
g
[
l
+
1
]
(
z
[
l
+
1
]
)
,
z
[
l
+
2
]
=
w
[
l
+
2
]
a
[
l
+
1
]
+
b
[
l
+
2
]
,
a
[
l
+
2
]
=
g
[
l
+
2
]
(
z
[
l
+
2
]
)
z^{[l+1]}=w^{[l+1]}a^{[l]}+b^{[l+1]},a^{[l+1]}=g^{[l+1]}(z^{[l+1]}),z^{[l+2]}=w^{[l+2]}a^{[l+1]}+b^{[l+2]},a^{[l+2]}=g^{[l+2]}(z^{[l+2]})
z[l+1]=w[l+1]a[l]+b[l+1],a[l+1]=g[l+1](z[l+1]),z[l+2]=w[l+2]a[l+1]+b[l+2],a[l+2]=g[l+2](z[l+2])
残差连接方式的计算过程:
z
[
l
+
1
]
=
w
[
l
+
1
]
a
[
l
]
+
b
[
l
+
1
]
,
a
[
l
+
1
]
=
g
[
l
+
1
]
(
z
[
l
+
1
]
)
,
z
[
l
+
2
]
=
w
[
l
+
2
]
a
[
l
+
1
]
+
b
[
l
+
2
]
,
a
[
l
+
2
]
=
g
[
l
+
2
]
(
a
[
l
]
+
z
[
l
+
2
]
)
z^{[l+1]}=w^{[l+1]}a^{[l]}+b^{[l+1]},a^{[l+1]}=g^{[l+1]}(z^{[l+1]}),z^{[l+2]}=w^{[l+2]}a^{[l+1]}+b^{[l+2]},a^{[l+2]}=g^{[l+2]}(a^{[l]}+z^{[l+2]})
z[l+1]=w[l+1]a[l]+b[l+1],a[l+1]=g[l+1](z[l+1]),z[l+2]=w[l+2]a[l+1]+b[l+2],a[l+2]=g[l+2](a[l]+z[l+2])
残差连接可以通过跨层的信息流动来缓解梯度消失问题,使得深层网络的训练更加稳定。