本周观看了李沐老师的《动手学深度学习》,李沐老师很注重代码的编写,但是对概念的讲解并不是特别多,所以还要去结合一些其他的资料去进行了解,下面是本周的所看的课程总结。
池化层
- 我们要了解卷积层对位置十分的敏感,如下图,X输入的第一列和第二列为1,所以这个垂直边缘在第二列和第三列中间,经过一个卷积核(卷积核为1*2的-1和1),输出得到Y,在第二列,边缘全部检测出来了,但是这一列的左侧和右侧都为0,也就是说它对位置是非常敏感的,一个像素的偏移就会导致0输出 。
- 我们不希望这样,因为边缘在真实图片中不像我们举的这个例子那么规整,可能会有弯曲的地方,所以我们需要一定的平移不变性,输入稍微有一点改动,输出不会有太大的变化,减低卷积核对位置的敏感程度 。
二维最大池化的工作原理与卷积同理,它所得到的输出形状和卷积核同理,在池化层中不需要做其他运算,只需要将这次窗口中的最大值取出就可以,如下图所示:
- 因此卷积层提取特征,池化层压缩,如下图,我们进行垂直边缘检测,在经过卷积输出之后,再经过2*2最大池化得到输出(2*2最大池化的输出在这里是写错的,应该为3*3的矩阵,去掉最后一行,和全1的一列).
- 我们可以发现2*2得最大池化层可以容忍最大1个像素的偏移 。
- 因此最大池化层允许输入有较小的偏移。
- 填充、步幅、多个通道与之前的卷积同理,最大池化层没有可以学习的参数,它只是取最大值。
- 并且与卷积层不同的是,卷积层的输入通道数目与输出通道数目可能不相同,卷积核可以有多个,并且每个可以有多个通道;而最大池化层的输入通道数目与输出通道数目相同,因为池化不会融合多个通道,每个通道做一次池化层,所以输入通道数目与输出通道数目相同。
- 池化层也可以相当于常数,梯度为1或者0。
- 平均池化层顾名思义,之前的最大池化层是取当前窗口的最大值,平均池化层是取当前窗口的平均值。
- 并且最大池化层输出的是每个窗口中的最强信号;平均池化层输出的是每个窗口的平均信号强度。
总结
- 池化层是用来缓解卷积层位置的敏感性。
- 池化层通常作用在卷积层的后面。
- 并且池化层没有可以学习的参数,不会让模型变得更大。
池化层的代码实现
1、实现池化层的正向传播
与卷积层同理,输出的大小的公式与卷积层是一样的,只不过这里换成了取最大值和平均值
import torch
from torch import nn
from d2l import torch as d2l
def pool2d(X,pool_size,mode='max'):
p_h,p_w = pool_size
Y = torch.zeros((X.shape[0]-p_h+1,X.shape[1]-p_w+1))
for i in range(Y.shape[0]):
for j in range(Y.shape[1]):
if mode == 'max':
Y[i,j] = X[i:i+p_h,j:j+p_w].max()
elif mode == 'avg':
Y[i,j] = X[i:i+p_h,j:j+p_w].mean()
return Y
2、验证二维池化层的输出
# 验证最大池化层
X = torch.tensor([[0.0,1.0,2.0],[3.0,4.0,5.0],[6.0,7.0,8.0]])
pool2d(X,(2,2))
'''
tensor([[4., 5.],
[7., 8.]])
'''
# 验证平均池化层
pool2d(X,(2,2),'avg')
'''
tensor([[2., 3.],
[5., 6.]])
'''
3、深度学习框架中的步幅与池化窗口的大小相同
并且我们需要注意使用pytorch中的nn.MaxPool2d函数,输入至少要是三维的张量。
pytorch中使用nn.MaxPool2d函数,若不指定stride步长,则默认与池化层的长度一致。
X = torch.arange(16,dtype=torch.float32).reshape((1,1,4,4))
pool2d = nn.MaxPool2d(3) # 默认stride也为3,下一个窗口跟现在没有重叠部分,没有中间的平滑过度
pool2d(X)
'''
tensor([[[[10.]]]])
'''
4、设定一个任意大小的矩形池化窗口,并分别设定填充和步幅的高度和宽度
输出形状与之前的公式计算一致,[(输入长度-池化长度+2*填充)/步长]向下取整 + 1
pool2d = nn.MaxPool2d((2,3),padding=(1,1),stride=(2,3))
pool2d(X)
'''
tensor([[[[ 1., 3.],
[ 9., 11.],
[13., 15.]]]])
'''
5、池化层在每个通道上单独运算
我们需要注意torch.cat是在现有的维度上连接,是等维连接,不改变维度;而torch.stack是在新维度上堆叠,是升维连接,会改变维度。
X是四维张量(1,1,4,4),X+1也是四维张量(1,1,4,4),所以X = torch.cat((X,X+1),1)是dim=1上进行连接,增加一个通道,所以连接后的维度为(1,2,4,4)。
X = torch.cat((X,X+1),1)
X
'''
tensor([[[[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.],
[12., 13., 14., 15.]],
[[ 1., 2., 3., 4.],
[ 5., 6., 7., 8.],
[ 9., 10., 11., 12.],
[13., 14., 15., 16.]]]])
'''
X.shape
'''
torch.Size([1, 2, 4, 4])
'''
pool2d = nn.MaxPool2d(3,padding=1,stride=2)
pool2d(X)
'''
tensor([[[[ 5., 7.],
[13., 15.]],
[[ 6., 8.],
[14., 16.]]]])
'''
LeNet
LeNet,它是最早发布的卷积神经网络之一,因其在计算机视觉任务中的高效性能而受到广泛关注。 LeNet被广泛用于自动取款机(ATM)机中,帮助识别处理支票的数字。
如下图,为LeNet的架构
- 进行输入的是一个1通道的,28*28的图片。
- 之后进入卷积层,通过5*5的卷积核和填充,将其变为6个通道,再进行sigmoid激活函数,将其变为非线性。
- 在进行池化层改变其大小,变为6通道,14*14。
- 再次经过卷积层,通过5*5的卷积核,改变输出的形状和通道数目,变为16通道,10*10,再经过sigmoid激活函数。
- 之后进行池化层改变其大小,变为16通道,5*5。
- 将其展开为一维张量,进行Flatten()操作。
- 进行全连接层模块Linear,将16*5*5输入,最终变为10的输出。
一般都是高宽减半,通道数翻倍,像素信息密度增加了,像素中信息分散到通道。
6 ->16,每个通道都会对前面通道的卷积信息做融合
如下图是LeNet的简化版
总结
LeNet是通过卷积层学习图片的空间信息,通过池化层降低图片信息敏感度,通过全连接层转换到类别空间。
LeNet的代码实现
1、LeNet由两个部分组成,卷积编码器和全连接层密集块
先将输入重塑为1通道,28*28的形状。
import torch
from torch import nn
from d2l import torch as d2l
# reshape不需要依赖目标tensor是否在内存连续,view需要
class Reshape(nn.Module):
def forward(self,x):
return x.view(-1,1,28,28) # x.view只是改变张量的视图不复制数据
2、构建LeNet网络
net = nn.Sequential(Reshape(),
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)
)
3、检查模型 一般都是让通道变多,高宽变小
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)
'''
Reshape output shape: torch.Size([1, 1, 28, 28])
Conv2d output shape: torch.Size([1, 6, 28, 28])
Sigmoid output shape: torch.Size([1, 6, 28, 28])
AvgPool2d output shape: torch.Size([1, 6, 14, 14])
Conv2d output shape: torch.Size([1, 16, 10, 10])
Sigmoid output shape: torch.Size([1, 16, 10, 10])
AvgPool2d output shape: torch.Size([1, 16, 5, 5])
Flatten output shape: torch.Size([1, 400])
Linear output shape: torch.Size([1, 120])
Sigmoid output shape: torch.Size([1, 120])
Linear output shape: torch.Size([1, 84])
Sigmoid output shape: torch.Size([1, 84])
Linear output shape: torch.Size([1, 10])
'''
4、LeNet在Fashion-MNIST数据集上的表现,构建训练迭代器和测试迭代器
batch_size = 256
train_iter,test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)
5、对evaluate_accuracy函数进行轻微的修改
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)
for X,y in data_iter:
if isinstance(X,list):
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]
6、为了使用GPU,要进行一点小改动
def train_ch6(net,train_iter,test_iter,num_epochs,lr,device):
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)
dl = loss(y_hat,y)
l.backward()
optimizer.step()
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},test acc {test_acc:.3f}')
print(f'{metric[2]*num_epochs/timer.sum():.1f} example/sec, on {str(device)}')
7、训练和评估LeNet-5 模型
lr,num_epochs = 0.9,10
train_ch6(net,train_iter,test_iter,num_epochs,lr,d2l.try_gpu())
AlexNet
AlexNet是由Alex Krizhevsky、Ilya Sutskever和Geoffrey Hinton设计的深度卷积神经网络架构,是在2012年ImageNet大规模视觉识别挑战赛中取得突破性成果的关键因素之一。AlexNet的出现引领了深度学习在计算机视觉领域的新篇章。
ImageNet数据集是一个大规模的视觉识别数据集,由斯坦福大学视觉实验室创建和维护。它包含超过1400万张图像,这些图像被手动标注为超过2万个不同的类别或类别组。
AlexNet赢得了ImageNet竞赛,相当于更深更大的LeNet,采用了丢弃法、激活函数由Sigmoid转为ReLu,池化层由平均池化层转为最大池化层(MaxPooling使得数据更大,梯度更大,更好训练)。
AlexNet的架构,相比LeNet,它有更大的核窗口和步长,更大的池化窗口
并且AlexNet比LeNet新加了3层卷积层,有更多的输出通道
dense 稠密层 也称作全连接层 把特征提取成一维帮助最后分类
- 并且AlexNet的激活函数由LeNet的sigmoid变为ReLu,减缓了梯度消失。
- 并且隐藏全连接层后加入了丢弃层。
- 最后也进行了数据增强,提高了训练难度,让模型鲁棒性更强。
- 当然,AlexNet的效果很好,同时也增加了复杂度,AlexNet的无论是参数个数还是复杂度都远高于LeNet。
- 在第一个卷积层的参数个数为3输入通道*96输出通道*11卷积核高度*11卷积核宽度约等于35K,后面以此类推。
总结
AlexNet相比LeNet多了10倍的参数个数和260倍多计算复杂度,也引入了一些新的方法(Drop out、ReLU、MaxPooling、数据增强),AlexNet标志着神经网络热潮的开始。
AlexNet代码实现
1、构建AlexNet网络
- 进行输入的是一个1通道的,224*224的图片。
- 之后进入卷积层,通过11*11的卷积核和填充,将其变为96个通道,再进行ReLU激活函数,将其变为非线性,大小为54*54。
- 在进行最大池化层改变其大小,变为96通道,26*26。
- 再次经过卷积层,通过5*5的卷积核,改变输出的形状和通道数目,变为256通道,26*26,再经过ReLU激活函数。
- 之后进行最大池化层改变其大小,变为256通道,12*12。
- 经过卷积层,通过3*3的卷积核,填充为1,变为384通道,12*12。
- 经过卷积层,通过3*3的卷积核,填充为1,变为384通道,12*12。
- 经过卷积层,通过3*3的卷积核,填充为1,变为256通道,12*12。
- 经过最大池化层改变大小,变为256通道,5*5。
- 将其展开为一维张量,进行Flatten()操作。
- 进行全连接层模块Linear,将256*5*5输入,最终变为10的输出。
import torch
from torch import nn
from d2l import torch as d2l
net = nn.Sequential(nn.Conv2d(1,96,kernel_size=11,stride=4,padding=1),nn.ReLU(),
nn.MaxPool2d(kernel_size=3,stride=2),
nn.Conv2d(96,256,kernel_size=5,padding=2),nn.ReLU(),
nn.MaxPool2d(kernel_size=3,stride=2),
nn.Conv2d(256,384,kernel_size=3,padding=1),nn.ReLU(),
nn.Conv2d(384,384,kernel_size=3,padding=1),nn.ReLU(),
nn.Conv2d(384,256,kernel_size=3,padding=1),nn.ReLU(),
nn.MaxPool2d(kernel_size=3,stride=2),nn.Flatten(),
nn.Linear(6400,4096),nn.ReLU(),nn.Dropout(p=0.5),
nn.Linear(4096,4096),nn.ReLU(),nn.Dropout(p=0.5),
nn.Linear(4096,10))
2、构造一个单通道数据,来观察每一层输出的形状
X = torch.rand(size=(1,1,224,224))
for layer in net:
X = layer(X)
print(layer.__class__.__name__,'output shape:\t',X.shape)
'''
Conv2d output shape: torch.Size([1, 96, 54, 54])
ReLU output shape: torch.Size([1, 96, 54, 54])
MaxPool2d output shape: torch.Size([1, 96, 26, 26])
Conv2d output shape: torch.Size([1, 256, 26, 26])
ReLU output shape: torch.Size([1, 256, 26, 26])
MaxPool2d output shape: torch.Size([1, 256, 12, 12])
Conv2d output shape: torch.Size([1, 384, 12, 12])
ReLU output shape: torch.Size([1, 384, 12, 12])
Conv2d output shape: torch.Size([1, 384, 12, 12])
ReLU output shape: torch.Size([1, 384, 12, 12])
Conv2d output shape: torch.Size([1, 256, 12, 12])
ReLU output shape: torch.Size([1, 256, 12, 12])
MaxPool2d output shape: torch.Size([1, 256, 5, 5])
Flatten output shape: torch.Size([1, 6400])
Linear output shape: torch.Size([1, 4096])
ReLU output shape: torch.Size([1, 4096])
Dropout output shape: torch.Size([1, 4096])
Linear output shape: torch.Size([1, 4096])
ReLU output shape: torch.Size([1, 4096])
Dropout output shape: torch.Size([1, 4096])
Linear output shape: torch.Size([1, 10])
'''
3、Fashion-MNIST图像的分辨率 低于ImageNet图像。我们将它们增加到224*224
batch_size = 128
train_iter,test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size,resize=224)
4、训练AlexNet
lr,num_epochs = 0.01,10
d2l.train_ch6(net,train_iter,test_iter,num_epochs,lr,device=d2l.try_gpu())
使用块的网络VGG
在上节学习的AlexNet,我们了解到AlexNet的缺点是结构不规则,不规范,不清晰。
若要向更大规模的网络发展,必须用更规范的方式去构建网络。
VGG比AlexNet有更多的卷积层。
- VGG思想是通过堆叠卷积块构建网络。
- 使用卷积核更小、层数更深的网络效果优于卷积核更大、层数较浅的网络。
- 之所以选择3*3的卷积核,是因为3*3能学习到更多的特征而且参数更少,更多的3*3比更少的5*5效果更好。
注:卷积层是用来提取特征的,用全连接层来进行分类
从最初的LeNet,有两个卷积层,池化层,有2个全连接层;再到后来的AlexNet,更大更深,激活函数变成了ReLU,也添加了Dropout,数据增强;再到现在的VGG是更大更深的AlexNet,有重复的VGG块。
并且VGG的准确度要比AlexNet要更高。
总结
VGG的代码实现
1、构建VGG块
该函数有三个参数,分别对应于卷积层的数量num_convs、输入通道的数量in_channels 和输出通道的数量out_channels。
并且每经过一次卷积核,它的输入形状和输出形状相同。
import torch
from torch import nn
from d2l import torch as d2l
def vgg_block(num_convs, in_channels, out_channels):
layers = []
for _ in range(num_convs):
layers.append(nn.Conv2d(in_channels, out_channels,
kernel_size=3, padding=1))
layers.append(nn.ReLU())
in_channels = out_channels
layers.append(nn.MaxPool2d(kernel_size=2,stride=2))
return nn.Sequential(*layers)
2、VGG网络可以分为两部分:第一部分主要由卷积层和汇聚层组成,第二部分由全连接层组成。
其中有超参数变量conv_arch。该变量指定了每个VGG块里卷积层个数和输出通道数。
原始VGG网络有5个卷积块,其中前两个块各有一个卷积层,后三个块各包含两个卷积层。 第一个模块有64个输出通道,每个后续模块将输出通道数量翻倍,直到该数字达到512。由于该网络使用8个卷积层和3个全连接层,因此它通常被称为VGG-11。
conv_arch = ((1,64),(1,128),(2,256),(2,512),(2,512))
# 共有5块,8个卷积层,3个全连接层 故为VGG-11
3、VGG网络
def vgg(conv_arch):
conv_blks = []
in_channels = 1
# 卷积层部分
for (num_convs,out_channels) in conv_arch:
conv_blks.append(vgg_block(num_convs,in_channels,out_channels))
in_channels = out_channels
return nn.Sequential(*conv_blks,nn.Flatten(),
# 全连接部分
nn.Linear(out_channels*7*7,4096),nn.ReLU(),nn.Dropout(0.5),
nn.Linear(4096,4096),nn.ReLU(),nn.Dropout(0.5),
nn.Linear(4096,10))
net = vgg(conv_arch)
4、观察每个层的输出形状,通道数翻倍,高宽减半。
X = torch.rand(size=(1,1,224,224))
for blk in net:
X = blk(X)
print(blk.__class__.__name__,'output shape:\t',X.shape)
'''
Sequential output shape: torch.Size([1, 64, 112, 112])
Sequential output shape: torch.Size([1, 128, 56, 56])
Sequential output shape: torch.Size([1, 256, 28, 28])
Sequential output shape: torch.Size([1, 512, 14, 14])
Sequential output shape: torch.Size([1, 512, 7, 7])
Flatten output shape: torch.Size([1, 25088])
Linear output shape: torch.Size([1, 4096])
ReLU output shape: torch.Size([1, 4096])
Dropout output shape: torch.Size([1, 4096])
Linear output shape: torch.Size([1, 4096])
ReLU output shape: torch.Size([1, 4096])
Dropout output shape: torch.Size([1, 4096])
Linear output shape: torch.Size([1, 10])
'''
5、由于VGG-11比AlexNet计算量更大,构建一个通道数较少的网络,方便计算。
ratio = 4
small_conv_arch = [(pair[0],pair[1]//ratio) for pair in conv_arch]
net = vgg(small_conv_arch)
6、比之前的输出通道减少了4倍。
X = torch.rand(size=(1,1,224,224))
for blk in net:
X = blk(X)
print(blk.__class__.__name__,'output shape:\t',X.shape)
'''
Sequential output shape: torch.Size([1, 16, 112, 112])
Sequential output shape: torch.Size([1, 32, 56, 56])
Sequential output shape: torch.Size([1, 64, 28, 28])
Sequential output shape: torch.Size([1, 128, 14, 14])
Sequential output shape: torch.Size([1, 128, 7, 7])
Flatten output shape: torch.Size([1, 6272])
Linear output shape: torch.Size([1, 4096])
ReLU output shape: torch.Size([1, 4096])
Dropout output shape: torch.Size([1, 4096])
Linear output shape: torch.Size([1, 4096])
ReLU output shape: torch.Size([1, 4096])
Dropout output shape: torch.Size([1, 4096])
Linear output shape: torch.Size([1, 10])
'''
7、训练网络
lr,num_epochs,batch_size = 0.05,10,128
train_iter,test_iter = d2l.load_data_fashion_mnist(batch_size,resize=224)
d2l.train_ch6(net,train_iter,test_iter,num_epochs,lr,'mps')
网络中的网络NiN
在之前学习了LeNet,AlexNet和VGG,它们都是通过一系列的卷积层与汇聚层来提取空间结构特征,之后通过全连接层对特征的表征进行处理。
AlexNet和VGG都是对LeNet对改进在于扩大加深这两个模块。
如下图,我们可以看到,卷积层需要的参数不会很多,它们的输入通道,输出通道,高宽不会太大,但是在卷积层后的第一个全连接层,将它展平过后,它们的参数需要的越来越多,且非常占用空间。
于是网络中的网络(NiN)提供了一个很简单的解决方案,在每个像素位置应用一个全连接层,可以将其视为1*1的卷积层,1*1卷积代替全连接的意识就是在做加权求和,并且使用1*1卷积充当全连接层,并放在网络中间。
- NiN块以一个普通卷积层开始,后面是两个1*1的卷积层,这两个1*1卷积层充当带有ReLU激活函数的逐像素全连接层,第一个层的卷积窗口形状由用户设置,随后的卷积窗口形状固定为1*1,简单来说就是替换全连接,有多少个类,就有多少个核的1*1。
- NiN交替使用NiN块和步幅为2的最大池化层,进行逐步减小高宽和增大通道数目,最后使用全局平均池化层得到输出,输入通道数目是类别数,其中全局平均池化层为该池化层的高宽等于输入的高宽,等价于对每一个通道取平均值。
总结
- NiN的核心思想是NiN块,NiN块是一个卷积层加上两个1 * 1的卷积层作为全连接层来使用。
- NiN使用全局平均池化层(通道数量为所需的输出数量)来替代VGG和AlexNet中的全连接层。
- NiN归根到底是为了减小参数,使用全局平均池化层来代替VGG和AlexNet中的全连接层且不容易过拟合。
NiN的代码实现
1、实现NiN块,其中NiN块最开始的输入通道和输出通道是由用户决定,在经过一次卷积操作后,经过ReLU激活函数,经过连个1*1的卷积层,我们可以看到,输入通道和输出通道一致且输入形状与输出形状一致。
import torch
from torch import nn
from d2l import torch as d2l
def nin_block(in_channels,out_channels,kenerl_size,strides,padding):
return nn.Sequential(
nn.Conv2d(in_channels,out_channels,kenerl_size,strides,padding),
nn.ReLU(),
nn.Conv2d(out_channels,out_channels,kernel_size=1),nn.ReLU(),
nn.Conv2d(out_channels,out_channels,kernel_size=1),nn.ReLU()
)
2、
- NiN使用窗口形状为11*11,5*5和3*3的卷积层,输出通道数量与AlexNet中的相同。 每个NiN块后有一个最大汇聚层,汇聚窗口形状为3*3,步幅为2。
- NiN和AlexNet之间的一个显著区别是NiN完全取消了全连接层。 相反,NiN使用一个NiN块,其输出通道数等于标签类别的数量。最后放一个全局平均汇聚层,生成一个对数几率 ,大大减少了模型所需的参数数量。
net = nn.Sequential(
nin_block(1,96,kenerl_size=11,strides=4,padding=0),
nn.MaxPool2d(3,stride=2),
nin_block(96,256,kenerl_size=5,strides=1,padding=2),
nn.MaxPool2d(3,stride=2),
nin_block(256,384,kenerl_size=3,strides=1,padding=1),
nn.MaxPool2d(3,stride=2),
nn.Dropout(0.5),
nin_block(384,10,kenerl_size=3,strides=1,padding=1),
nn.AdaptiveAvgPool2d((1,1)),
nn.Flatten()
)
3、查看每个层的输出形状,其中MaxPooling改变高宽不改变通道数目;drop减少参数,防止过拟合;全局平均汇聚层为最终每个channel的尺寸;最终为(batch_size,10,1,1)->Flatten->(batch_size,10)
X = torch.rand(size=(1,1,224,224))
for layer in net:
X = layer(X)
print(layer.__class__.__name__,'output shape:\t',X.shape)
'''
Sequential output shape: torch.Size([1, 96, 54, 54])
MaxPool2d output shape: torch.Size([1, 96, 26, 26])
Sequential output shape: torch.Size([1, 256, 26, 26])
MaxPool2d output shape: torch.Size([1, 256, 12, 12])
Sequential output shape: torch.Size([1, 384, 12, 12])
MaxPool2d output shape: torch.Size([1, 384, 5, 5])
Dropout output shape: torch.Size([1, 384, 5, 5])
Sequential output shape: torch.Size([1, 10, 5, 5])
AdaptiveAvgPool2d output shape: torch.Size([1, 10, 1, 1])
Flatten output shape: torch.Size([1, 10])
'''
4、训练网络
lr,num_epochs,batch_size = 0.1,10,128
train_iter,test_iter = d2l.load_data_fashion_mnist(batch_size,resize=224)
d2l.train_ch6(net,train_iter,test_iter,num_epochs,lr,'mps')
并行连接的网络GoogLeNet
在2014年的ImageNet图像识别挑战赛中,一个名叫GoogLeNet的网络架构大放异彩。 GoogLeNet吸收了NiN中串联网络的思想,并在此基础上做了改进。
在我们之前学习的网络,有小到1*1的卷积核,有大到11*11的卷积核,那我们最好的卷积层参数是什么呢?
在GoogLeNet中,基本的卷积块被称为Inception块,Inception块的基本思想是"我全要"。
- Inception块由四条并行路径组成。
- 前三条路径使用窗口大小为1*1,3*3和5*5的卷积层,从不同空间大小中提取信息。
- 中间的两条路径在输入上执行1*1卷积,以减少通道数,从而降低模型的复杂性。
- 第四条路径使用3*3最大汇聚层,然后使用1*1卷积层来改变通道数。
- 这四条路径都使用合适的填充来使输入与输出的高和宽一致,最后我们将每条线路的输出在通道维度上连结,并构成Inception块的输出。
- 在Inception块中,通常调整的超参数是每层输出通道数。
- 因此最后没有改变高宽,最后进行输出通道合并从而增加了通道数目。
- 里面白色的窗口代表减少通道数,蓝色的窗口代表提取信息,从而增加鲁棒性。
如下图的第一个Inception块的计算如下,输入为192通道,高宽为28*28。
- 最左边的路径进入1*1的卷积层,将通道数目压缩为64。
- 第二条路径,先经过1*1的卷积层,将通道数目压缩为96,再经过3*3的卷积层通道数目增加为128,且高宽不改变。
- 第三条路径,先经过1*1的卷积层,将通道数目压缩为16,再经过5*5的卷积层通道数目增加为32,且高宽不改变。
- 第四条路径,先经过3*3的最大池化层,不改变通道数目,也不改变高宽,提取信息,再经过1*1的卷积,将通道数目压缩为32。
- 所以,这四条路径没有改变高宽,第一条路径最后的输出通道为64;第二条路径最后的输出通道为128;第三条路径最后的输出通道为32;第四条路径最后的输出通道为32;最后将它们合并输出为256通道,高宽为28*28。
- 每条路径的输出通道都不一样,将有限的输出通道分配给不同路径,第二条路径分配的更多,因为3*3的卷积层计算量不大且可以很好的抽取信息,再剩下的分给1*1的卷积层,最后再第三、四条路径平分。
并且使用Inception块比单使用3*3或者5*5的卷积层比,Inception块有更少的参数个数和计算复杂度。
GoogLeNet的架构由5段组成,有9个Inception块,其中在这5段中宽高减半即为一个stage,以池化层做划分的,且最后一个Inception块的的输出通道为标签的类别个数。每一段都在进行通道数目增加,高宽减小的操作,如下图所示:
在我们的stage1和stage2中,没有Inception块,主要做的是更小的高宽和更多的通道,输入为3通道,224*224的高宽,经过stage1和stage2,输出为192通道,高宽为28*28,如下图所示:
在stage3中,串联了两个完整的Inception块和一个最大池化层,输入为192通道,高宽28*28,经过第一个Inception块,通过每条路径的输出通道分配,输出为256通道,高宽不变;紧接着经过第二个Inception块,输入为256通道,最后输出为280通道,高宽不变;再经过3*3的池化层,高宽减半,为480通道,高宽14*14。
在stage4和stage5中,共有7个Inception块,输入为480通道,高宽为14*14,经过stage4的5个Inception块的输出通道为832通道,高宽不变;在经过stage4的3*3最大池化层,高宽减半,最后输出为832通道,高宽为7*7;stage5的输入通道为832,高宽为7*7,经过stage5的2个Inception块的输出通道为1024,有1024维特征输出;再经过全局平均汇聚层,将每个通道的高宽变成1;最后输出变成二维,再通过全连接层为(1024,10)。
到目前为止,Inception块也有很多变种,其中Inception-V3/Inception-V4现在使用也很广泛。
总结:
GoogLeNet的代码实现
1、实现Inception块,其中Inception块分为四条路径,第一条路径为一个1*1的卷积层;第二条路径为一个1*1的卷积层和3*3的卷积层;第三条路径为一个1*1的卷积层和5*5的卷积层;第四条路径为一个3*3的最大池化层和一个1*1的卷积层。
通过我们传递过来的输入通道数进行计算,最后将这四条路径所得到的数据,通道数目拼接起来,返回即可。
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l
class Inception(nn.Module):
# c1--c4是每条路径的输出通道数目
def __init__(self, in_channels,c1,c2,c3,c4):
super().__init__()
# road 1
self.p1_1 = nn.Conv2d(in_channels,c1,kernel_size=1)
# road 2
self.p2_1 = nn.Conv2d(in_channels,c2[0],kernel_size=1)
self.p2_2 = nn.Conv2d(c2[0],c2[1],kernel_size=3,padding=1)
# road 3
self.p3_1 = nn.Conv2d(in_channels,c3[0],kernel_size=1)
self.p3_2 = nn.Conv2d(c3[0],c3[1],kernel_size=5,padding=2)
# road 4
self.p4_1 = nn.MaxPool2d(kernel_size=3,stride=1,padding=1)
self.p4_2 = nn.Conv2d(in_channels,c4,kernel_size=1)
def forward(self,x):
p1 = F.relu(self.p1_1(x))
p2 = F.relu(self.p2_2(F.relu(self.p2_1(x))))
p3 = F.relu(self.p3_2(F.relu(self.p3_1(x))))
p4 = F.relu(self.p4_2(self.p4_1(x)))
# connatation
return torch.cat((p1,p2,p3,p4),dim=1)
2、实现GoogLeNet的5个stage
第一个stage没有Inception块,输入通道为1,高宽为96*96,经过卷积层,激活函数和最大池化层,最后的输出为(1,64,24,24)
b1 = nn.Sequential(nn.Conv2d(1,64,kernel_size=7,stride=2,padding=3),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3,stride=2,padding=1))
3、第二个stage也没有Inception块,输入为(1,64,24,24),最后输出为(1,192,12,12)
b2 = nn.Sequential(nn.Conv2d(64,64,kernel_size=1),
nn.ReLU(),
nn.Conv2d(64,192,kernel_size=3,padding=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3,stride=2,padding=1))
4、第三个stage,经过两个Inception块,输入为(1,192,12,12),输出为(1,480,6,6)
b3 = nn.Sequential(Inception(192,64,(96,128),(16,32),32),
Inception(256,128,(128,192),(32,96),64),
nn.MaxPool2d(kernel_size=3,stride=2,padding=1))
5、第四个stage,经过5个Inception块,输入为(1,480,6,6),输出为(1,832,6,6)
b4 = nn.Sequential(Inception(480,192,(96,208),(16,48),64),
Inception(512,160,(112,224),(24,64),64),
Inception(512,128,(128,256),(24,64),64),
Inception(512,112,(144,288),(32,64),64),
Inception(528,256,(160,320),(32,128),128),
nn.MaxPool2d(kernel_size=3,stride=2,padding=1))
6、第五个stage,经过2个Inception块,输入为(1,832,6,6),输出为(1,1024,1,1),进行展平为(1,1024)
b5 = nn.Sequential(Inception(832,256,(160,320),(32,128),128),
Inception(832,384,(192,384),(48,128),128),
nn.AdaptiveAvgPool2d((1,1)),
nn.Flatten())
7、将这5个stage连接一起,形成GoogLeNet
net = nn.Sequential(b1,b2,b3,b4,b5,nn.Linear(1024,10))
8、查看每一个stage的形状
X = torch.rand(size=(1,1,96,96))
for layer in net:
X = layer(X)
print(layer.__class__.__name__,'output shape:\t',X.shape)
'''
Sequential output shape: torch.Size([1, 64, 24, 24])
Sequential output shape: torch.Size([1, 192, 12, 12])
Sequential output shape: torch.Size([1, 480, 6, 6])
Sequential output shape: torch.Size([1, 832, 3, 3])
Sequential output shape: torch.Size([1, 1024])
Linear output shape: torch.Size([1, 10])
'''
9、进行训练
lr,num_epochs,batch_size = 0.1,10,128
train_iter,test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size,resize=96)
d2l.train_ch6(net,train_iter,test_iter,num_epochs,lr,d2l.try_gpu())
个人总结
这周主要学习了CNN的一些经典网络架构,下周将继续学习其他网络架构,比如ResNet,还有其他的算法,并且阅读相应的文献,理论与实践相结合。