课程是b站刘二大人的课,算是速成吧,基础知识不是很多,专注于应用,中间不懂得可以再补

GoogleNet

image-20241018145146213

Inception Module

image-20241018144916751

上图的Inception模型是GoogleNet模型中的小块,红色圈中即为Inception

Concatenate将每一路径得到的张量拼接成一个张量

在进行Average Pooling池化时,可以进行相应的padding和stride来保证张量的w和h不会改变

1*1卷积核的作用是什么?

在1*1卷积核中,我们可以对不同通道的相同位置的像素值进行计算并加和,有此将CxWxH的张量转化为1xWxH的张量,实现降通道操作,具体图示如下:

image-20241018150809527

为什么需要1*1的卷积核?

可以对图像数据进行降通道的操作,在卷积运算中显著的减少运算量

image-20241019132722039

再上图的卷积运算中,我们将192x28x28的数据卷积成32x28x28的数据,需要进行的运算操作数为
$$
5^2\times28^2\times192\times32=120422400
$$
image-20241019133516579

如果先对原数据进行1*1的卷积降低通道数,再进行5x5卷积,所需的运算操作数为
$$
1^2\times28^2\times192\times16+5^2\times28^2\times16\times32=12433648
$$
由此可见运算操作数明显减少

代码实现及注释

image-20241019150142932

池化层对应代码如下:

1
2
3
4
self.branch_pool = torch.nn.Conv2d(in_channels, 24, kernel_size=1)

branch_pool = F.avg_pool2d(x, kernel_size=3, stride=1, padding=1)
branch_pool = self.branch_pool(branch_pool)

image-20241019150259362

1x1卷积层对应代码如下:

1
2
3
self.branch1x1 = torch.nn.Conv2d(in_channels, 16, kernel_size=1)

branch1x1 = self.branch1x1(x)

image-20241019150648224

5x5卷积层对应代码如下:

1
2
3
4
5
self.branch5x5_1 = torch.nn.Conv2d(in_channels, 16, kernel_size=1)
self.branch5x5_2 = torch.nn.Conv2d(16, 24, kernel_size=5, padding=2)

branch5x5 = self.branch5x5_1(x)
branch5x5 = self.branch5x5_2(branch5x5)

image-20241019151101886

3x3卷积层对应代码如下:

1
2
3
4
5
6
7
self.branch3x3_1 = torch.nn.Conv2d(in_channels, 16, kernel_size=1)
self.branch3x3_2 = torch.nn.Conv2d(16, 24, kernel_size=3, padding=1)
self.branch3x3_3 = torch.nn.Conv2d(24, 24, kernel_size=3, padding=1)

branch3x3 = self.branch3x3_1(x)
branch3x3 = self.branch3x3_2(branch3x3)
branch3x3 = self.branch3x3_3(branch3x3)

最后对每一块得到的卷积张量在通道维度进行合并形成新的张量

1
2
outputs = [branch1x1, branch3x3, branch5x5, branch_pool]
return torch.cat(outputs, dim=1) # 将卷积结果按照通道维度进行拼接

dim指定在某一维度进行合并张量,包含(batch,通道,宽,高)四个维度

总体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class InceptionA(torch.nn.Module):
def __init__(self, in_channels):
super(InceptionA, self).__init__()
# 1x1块
self.branch1x1 = torch.nn.Conv2d(in_channels, 16, kernel_size=1)
# 5x5块
self.branch5x5_1 = torch.nn.Conv2d(in_channels, 16, kernel_size=1)
self.branch5x5_2 = torch.nn.Conv2d(16, 24, kernel_size=5, padding=2)
# 3x3块
self.branch3x3_1 = torch.nn.Conv2d(in_channels, 16, kernel_size=1)
self.branch3x3_2 = torch.nn.Conv2d(16, 24, kernel_size=3, padding=1)
self.branch3x3_3 = torch.nn.Conv2d(24, 24, kernel_size=3, padding=1)
# 池化块
self.branch_pool = torch.nn.Conv2d(in_channels, 24, kernel_size=1)

def forward(self, x):
branch1x1 = self.branch1x1(x)

branch5x5 = self.branch5x5_1(x)
branch5x5 = self.branch5x5_2(branch5x5)

branch3x3 = self.branch3x3_1(x)
branch3x3 = self.branch3x3_2(branch3x3)
branch3x3 = self.branch3x3_3(branch3x3)

branch_pool = F.avg_pool2d(x, kernel_size=3, stride=1, padding=1)
branch_pool = self.branch_pool(branch_pool)

outputs = [branch1x1, branch3x3, branch5x5, branch_pool]
return torch.cat(outputs, dim=1) # 将卷积结果按照通道维度进行拼接

image-20241020154033087

残差网络Resnet

随着神经网络层数的不断增加,在反向传播的过程中可能出现梯度爆炸或者梯度消失的情况

为什么会出现梯度消失或者梯度爆炸?

现在假设一个模型,其中连接层数为4层,反向传播(Backpropagation)用于计算每一层的权重 WWW 的梯度。这是通过链式法则计算的。假设神经网络的层数从1到4,每一层的输入、权重和激活函数可以定义如下

image-20241020163910140

在计算离输入层较近的层的梯度时,会发现梯度的计算来源于上层的计算总和

image-20241020184804511

链式法则是一个连乘的操作,在多次乘积操作后,最后一层的梯度可能趋近于零,也就是梯度消失,但若偏导数值大则会出现梯度爆炸的情况。

梯度消失/梯度爆炸的解决方法

  1. 梯度剪切:针对于梯度爆炸的情况,将梯度限制在一个限定的阈值内,如果更新梯度时梯度超过了阈值则将梯度改为阈值的边界,防止梯度过大的情况出现。

  2. 权重正则化:通过对权重做正则化来避免过拟合的情况出现,在梯度爆炸是权重的值可能会非常高,用正则化项来限制权重的大小,防止梯度爆炸的情况发生。

    image-20241021141852661

    其中α为正则化项的系数

  3. 用Relu代替sigmoid作为激活函数:使用sigmoid函数作为激活函数时梯度值小于等于0.25,在连乘操作后显然会出现梯度消失或梯度爆炸的问题,但如果我们使用Relu函数作为激活函数,在对激活函数求导时,大于0的部分导数是恒等于1的,连乘不会出现梯度消失问题。

  4. Batchnorm:通过规范化操作将输出信号x规范化到均值为0,方差为1保证网络的稳定性。

  5. 残差网络

残差网络如何解决梯度消失问题?

image-20241021145340356

在原始的堆叠模型中,我们将需要的底层映射结果设为H(x),则H(x)=F(x)

但是在残差学习块中,我们假设需要的底层映射结果仍为H(x),在堆叠的非线性层我们让其训练另一个映射:F(x)=H(x)-x,则原始的底层映射结果改变为H(x)=F(x)+x,那么在上文的反向传播梯度计算中,公式可变为:

image-20241021151441848

由此可见,连乘的因式有大于一的项,能够有效避免梯度消失问题。

如果还没有理解可以参考链接:http://www.atait.se.ritsumei.ac.jp/AIArc/WangZC/ResNet.pdf

模型实现及代码复现

image-20241021160358150

与原始的堆叠模型不同的是,在堆叠块中添加跳连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class ResidualBlock(torch.nn.Module):
def __init__(self, channels):
super(ResidualBlock, self).__init__()
self.channels = channels
self.conv1 = torch.nn.Conv2d(channels, channels, kernel_size=3, padding=1) # padding来保证维度不会改变
self.conv2 = torch.nn.Conv2d(channels, channels, kernel_size=3, padding=1)

def forward(self, x):
y = F.relu(self.conv1(x))
y = self.conv2(y)
return F.relu(x + y)


class ResNet(torch.nn.Module):
def __init__(self):
super(ResNet, self).__init__()
self.conv1 = torch.nn.Conv2d(1, 16, kernel_size=5)
self.conv2 = torch.nn.Conv2d(16, 32, kernel_size=5)
self.mp = torch.nn.MaxPool2d(2)
self.rblock1 = ResidualBlock(16)
self.rblock2 = ResidualBlock(32)
self.fc = torch.nn.Linear(512, 10)

def forward(self, x):
in_size = x.size(0)
x = self.mp(F.relu(self.conv1))
x = self.rblock1(x)
x = self.mp(F.relu((self.conv2)))
x = self.rblock2(x)
x = x.view(in_size, -1)
return self.fc(x)

留言

2024-10-18

⬆︎TOP