目录
- 一、全连接层(MLP)存在的问题:
- 二、全连接层是如何优化成卷积层的:
- 三、单神经元的卷积层:
- 四、卷积层的填充和步幅:
- 五、多神经元(输出通道)的卷积层:
- 六、卷积核:
- 七、池化层:
-
- 1.为什么使用池化层:
- [2. 定义:](#2. 定义:)
- 3.池化层类型:
- 4.池化层代码:
- 八、卷积层池化层输入输出:
- 九、卷积神经网络训练过程举例******:
- 十、LeNet:第一个CNN
- 十一、AlexNet:更深的CNN
- 十二、VGG:提出Block块的概念
- 十三、NiN:舍弃全连接层减少参数的思想
- 十四、GoogLeNet:提出卷积层并联
-
- 1.Inception块(并联卷积层):
- 2.GoogLeNet架构:
-
- [2.1 Stage1&Stage2](#2.1 Stage1&Stage2)
- [2.2 Stage3](#2.2 Stage3)
- [2.3 Stage4&Stage5](#2.3 Stage4&Stage5)
- 3.Inception变种:
-
- [3.1 Inception-V3:](#3.1 Inception-V3:)
- 4.代码:
- 十五、ResNet残差网络:f(x)=g(x)+x
一、全连接层(MLP)存在的问题:
如果样本有3600万个特征,对于隐藏层具有100个神经元的MLP,隐藏层的权重参数W就会达到3600万×100=36亿个,存储这些参数就需要占用14G磁盘空间,这对于资源的消耗非常大,导致训练非常困难。
为了应对这些问题,引入了卷积神经网络(CNN)的概念。卷积神经网络通过两个原理很好的解决了特征数量庞大的问题:平移不变性、局部性。
二、全连接层是如何优化成卷积层的:
注!!!!!!:以下操作是以隐藏层仅有一个神经元的情况为例进行解释的,这里请不要把传统的卷积层情景带入,不然公式会看不明白。并且解释一下,虽然图中有多个神经元,但是具体操作时只涉及了一个神经元,公式也是只适用于隐藏层仅有一个神经元的情况。
通过在全连接层加入平移不变性、局部性两个特性得到了卷积层的概念 。
如何从全连接层优化成卷积层?具体过程如下:
1.传统全连接层概述:
传统全连接层构造如下,其中参数w11,特征x1都是一个数值。
2.对全连接层输入输出进行优化:
- 解释如下:
- (1)传统的全连接层的输入需要flatting展平为一维向量,现在我们进行优化,保留输入的二维特性,不需要展平成一维,相应的参数w也应该扩为二维。
此时参数w11,特征x1都是一个二维矩阵。 - (2)对输入和参数进行如下变换后,每个神经元oij有n个二维参数矩阵wpq,因此一个神经元的Wij维度为三维,整个隐藏层有m个神经元,因此整个隐藏层参数W维度为四维Wijkl 。其中kl表示每个3×3的输入矩阵\二维参数矩阵的宽和高索引,ij表示一个神经元oij的三维参数矩阵(每个神经元对应多个卷积核)。每个卷积核wpq和一个特征矩阵运算的输出为一个数值。
举个例子,对于h12(上图对应h1)的计算过程如下:
- (1)传统的全连接层的输入需要flatting展平为一维向量,现在我们进行优化,保留输入的二维特性,不需要展平成一维,相应的参数w也应该扩为二维。
3.对全连接层使用平移不变性优化:
- 解释如下:
(1)平移不变性的思想是因为每个神经元都是为了提取某一固定的特征,所以每个神经元不应该因为特征矩阵X的位置(i,j)变化而使用不同的卷积核进行计算,而是一个神经元对应一个二维卷积核(不考虑通道)。
例如上图中神经元h1不应该因为是对不同位置的特征矩阵x1、x2进行特征提取而使用不同的卷积核w11、w21分别计算。
为什么对于不同位置的特征矩阵不能使用不同的卷积核进行计算?
例如在上述的图像分类问题中,神经元h1具有单一的功能,就是为了提取图片中的红帽子头特征(虽然我们不知道红帽子头特征对于图像分类有什么用),可以看到不同位置的特征矩阵具有相同的红帽子头特征,所以如果h1是以提取图片中的红帽子头特征为目的的话,我们对不同位置的特征矩阵应该使用相同的卷积核(即相同的权重参数W)。
(2)因此,使用平移不变性,每个神经元不再具有多个二维卷积核,而是一个神经元对应一个卷积核(不考虑通道),这样就确保了神经元对于特征提取时的平移不变性。
(3)此时公式中的Vab表示每个卷积核有唯一的一个二维卷积核(不考虑通道) ,每个神经元的卷积核与特征矩阵xij进行计算后的数值相加作为输出矩阵h中索引为i,j位置的值。
4.对全连接层使用局部性优化:
- 解释如下:
-(1) 局部性是指输入矩阵i,j位置的特征提取结果hij仅与该位置附近(宽高距离a,b内)的特征有关,与其余位置无关。
5.总结:
!!!!!!注:以下卷积层的公式是仅有一个神经元的情况:
三、单神经元的卷积层:
1.定义:
!!!!!!注:以下定义的卷积层是仅有一个神经元的情况:
!!!!!!注:以下卷积层的公式是仅有一个神经元的情况:
-
二维卷积公式:
-
一维卷积公式:
一维卷积适用于文本,语言,时序序列的特征提取。
-
三维卷积公式:
2.二维卷积层代码:
该卷积层中仅有一个神经元。
python
import torch
from torch import nn
from d2l import torch as d2l
# 二维互相关运算,X是特征矩阵,K是二维卷积核
def corr2d(X, K):
h, w = K.shape
Y = torch.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1))
for i in range(Y.shape[0]):
for j in range(Y.shape[1]):
Y[i, j] = (X[i:i + h, j:j + w] * K).sum()
return Y
# 二维卷积层,其中kernel_size作为超参数需要提前指定,并且该卷积层中仅有一个神经元
class Conv2D(nn.Module):
def __init__(self, kernel_size):
super().__init__()
self.weight = nn.Parameter(torch.rand(kernel_size))
self.bias = nn.Parameter(torch.zeros(1))
def forward(self, x):
return corr2d(x, self.weight) + self.bias
四、卷积层的填充和步幅:
卷积层的填充、步幅都是超参数。
1.填充:
2.步幅:
五、多神经元(输出通道)的卷积层:
1.多输入通道:
彩色图片输入会有RGB三个通道。
在多输入通道中,每个神经元的卷积核是一个三维矩阵 ,其中该神经元的每个通道对应三维卷积核中的一个二维卷积核,该神经元的输出结果是该神经元在所有通道卷积结果的和,每个神经元的输出永远都是一个二维矩阵 。
1.1文字定义:
1.2作用:
输入通道核可以识别并组合不同输入通道中的特征。
2.多输出通道:
多输出通道即多个神经元 ,每个神经元对应一个卷积核,每个神经元分别负责从不同角度提取特征,每个神经元输出一个二维矩阵 ,多个神经元的输出组合成三维多通道输出。
2.1文字定义:
2.2作用:
不同输出通道(不同神经元,输出通道与神经元一一对应)可以识别不同特征。
3.多输入多输出通道:
3.1文字定义:
六、卷积核:
1.神经元卷积核大小:
每个神经元对应一个卷积核,如果该隐藏层输入通道数为cin,二维卷积核大小为kernel_size,则该神经元的卷积核大小为(cin,kernel_size,kernel_size)
2.隐藏层卷积核大小:
隐藏层卷积核大小为该隐藏层所有神经元卷积核的集合,若该隐藏层的输出通道数为cout,表示该隐藏层有cout个卷积核,则该隐藏层卷积核大小为(cout,cin,kernel_size,kernel_size)
七、池化层:
1.为什么使用池化层:
卷积层对位置信息特别敏感,加入池化层是为了缓解卷积层对位置的敏感性,增加一些对特征的平移不变性。
2. 定义:
以最大池化层为例:
- 可以看到池化层作用在输入输出之间仅仅做了最大值计算,仅需要向前传播,不涉及参数计算,不需要反向传播,因此池化层没有可以学习的参数。
- 使用池化层前后的输入通道数=输出通道数,且池化层分别作用在每个输入通道获取其输出通道。
- 虽然池化层没有可学系的参数,但是池化层的窗口大小、填充、步幅都是超参数。
3.池化层类型:
4.池化层代码:
python
import torch
from torch import nn
from d2l import torch as d2l
# 池化层的向前传播,其中池化层窗口大小pool_size作为超参数需要提前指定
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
八、卷积层池化层输入输出:
1.输入格式:
每层输入格式为(batch_size, channels, height, width),表示输入有batch_size个样本,每个样本有channels个输入通道,每个通道都是一个二维矩阵,高度为height,宽度为width。
2.输出格式:
每层输出格式为(batch_size, channels, height, width),表示输出有batch_size个样本,每个样本有channels个输出通道,每个通道都是一个二维矩阵,高度为height,宽度为width。
九、卷积神经网络训练过程举例******:
1.获取一个batch,里面包含batch_size张图片样本。
2.每张图片样本有3个输入通道,每个通道尺寸为224×224,则该batch输入维度为:batch_size×3×224×224(样本数×样本维度[输入通道数×通道高度×通道宽度])。
3.batch(batch_size×3×224×224)作为第一个二维卷积层的输入,该卷积层输出通道数为96,所以有96个神经元,分别来提取不同特征,每个神经元的卷积核大小为3×11×11(输入通道数×卷积核高度×卷积核宽度)。
4.对于每个样本,该卷积层进行如下操作:每个神经元使用各自的卷积核对该样本进行特征提取,获得3×54×54的输出,然后3个通道的输出进行相加获得该神经元的输出特征图54×54,最后96个神经元的输出特征图叠加作为该样本的输出,维度为96×54×54。
5.对于batch_size个样本,该卷积层的输出为batch_size×96×54×54(样本数×样本维度[输出通道数×通道高度×通道宽度]),作为下一层隐藏层的输入。
6.每个卷积层之后需要经过一个ReLU激活函数来获得非线性特征。
7.进入最大池化层,池化核对每个样本的每个通道一次进行最大池化操作降维,获得输出96×26×26,batch_size个样本的输出组合为池化层的输出,维度为batch_size×96×26×26(样本数×样本维度[输出通道数×通道高度×通道宽度]),作为下一层隐藏层的输入。
8.然后相同的步骤执行多个卷积层和多个池化层。
9.使用多个卷积层和池化层进行特征提取后,通过flatten展平操作对每个样本将维度为256×5×5的特征展平成一维,获得输出batch_size×6400(256×5×5=6400)
10.该输出进入线性全连接层进行降维,该全连接层有4096个神经元,每个神经元与特征之间都有一个权重参数w和偏置值b,该全连接层参数W维度计算为6400×4096,参数b维度计算为1×4096。
11.每个神经元都对每个样本的6400个输入特征进行学习,获得一个输出值,4096个神经元对每个样本获得4096个输出值,是一个一维向量。
12.batch_size个样本分别进入全连接层,全连接层输出为batch_size×4096。
13.经过多个全连接层后,最终输出为batch_size×1000,其中batch_size个一维向量记录了每个样本在1000个分类上的概率。
14.使用交叉熵损失函数计算batch中所有图片的概率损失,并取均值。
15.反向传播算法(自动求导)计算各层各个参数wmn、bn关于损失函数的梯度。
16.梯度下降算法算法修改参数值。
17.输入下一个batch进行训练。
十、LeNet:第一个CNN
1.模型架构:
- LeNet是最早的神经网络。
- 该神经网络先使用卷积层提取图像的局部特征,比如边缘、角点等。
- 卷积层后面一般跟随一个池化层用于下采样和减少特征维度。
- 然后使用全连接层负责将提取到的特征映射到不同的类别空间。
- 最后使用Softmax回归通过激活函数将预测值转换为各个类别的预测概率并输出。
2.手写数字识别代码:
2.1代码:
python
import torch
from torch import nn
from d2l import torch as d2l
# 输入格式转换,将输入转换成(batch_size,channel=1,W=28,H=28)
class Reshape(torch.nn.Module):
def forward(self, x):
return x.view(-1, 1, 28, 28)
# 1.模型架构
net = torch.nn.Sequential(
Reshape(),# 输入格式转换
nn.Conv2d(1, 6, kernel_size=5, padding=2),# 二维卷积,输入通道1,输出通道6,根据输入通道和卷积大小自动创建相应格式的6个卷积核(每个卷积核二维:5×5)
nn.Sigmoid(),# 卷积后加入激活函数,得到非线性特征
nn.AvgPool2d(kernel_size=2, stride=2),# 平均池化层
nn.Conv2d(6, 16, kernel_size=5),# 二维卷积,输入通道6,输出通道16,根据输入通道和卷积大小自动创建相应格式的6个卷积核(每个卷积核三维:6×5×5)
nn.Sigmoid(),# 卷积后加入激活函数
nn.AvgPool2d(kernel_size=2, stride=2),# 平均池化层
nn.Flatten(),# 将卷积4D输出展成一维16 * 5 * 5
nn.Linear(16 * 5 * 5, 120),# 线性层,将输入一维向量尺寸降到120
nn.Sigmoid(),# 线性层激活函数
nn.Linear(120, 84),# 线性层,将输入一维向量尺寸从120降到84
nn.Sigmoid(),# 线性层激活函数
nn.Linear(84, 10)# 线性层,将输入一维向量尺寸从84降到10
)
# 2.获取dataloader
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)
# 3.模型测试
def evaluate_accuracy_gpu(net, data_iter, device=None):
"""使用GPU计算模型在数据集上的精度。"""
if isinstance(net, torch.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]
# 4.模型训练
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)
# 将模型放到GPU上运行
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)
# 执行epoch轮
for epoch in range(num_epochs):
metric = d2l.Accumulator(3)
net.train()
# 每次拿一个batch
for i, (X, y) in enumerate(train_iter):
timer.start()
# 梯度清零
optimizer.zero_grad()
X, y = X.to(device), y.to(device)
# 计算batch中所有样本的预测值
y_hat = net(X)
# 计算batch的损失
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()
# 该batch的训练损失和训练精度
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))
# 该epoch后模型在测试集上的精度
test_acc = evaluate_accuracy_gpu(net, test_iter)
animator.add(epoch + 1, (None, None, test_acc))
# 输出所有epoch后的总损失和在训练集、测试集上的精度
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)}')
lr, num_epochs = 0.9, 5
train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
2.2各层的输出尺寸:
python
X = torch.rand(size=(1, 1, 28, 28), dtype=torch.float32)# 输入样本(1, 1, 28, 28)
for layer in net:
X = layer(X)
print(layer.__class__.__name__, 'output shape: \t', X.shape)
十一、AlexNet:更深的CNN
1.模型架构:
2.相比于LeNet的改进:
- 模型隐藏层层次更深,输出通道数更多,有利于提取更深的特征
- 全连接层层之间使用丢弃法防止过拟合
- Sigmoid激活函数修改为ReLU激活函数
- 加入了最大池化层
- 使用了数据增强
3.手写数字识别代码:
3.1代码:
python
import torch
from torch import nn
from d2l import torch as d2l
# 1.模型架构
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.获取dataloader
batch_size = 128
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=224)
lr, num_epochs = 0.01, 10
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
3.2各层的输出尺寸:
十二、VGG:提出Block块的概念
1.模型架构:
VGG的思想是将多个卷积层组成VGG块,不同数量的VGG块重复堆叠可以获得不同的架构:VGG-16、VGG-19...
不同卷积块个数和超参数可以得到不同的模型。
1.1VGG块:
VGG块:注意VGG块中每个卷积层的输出通道数、卷积核大小是不变的
1.2VGG架构:
VGG块拼接后加入全连接层构成VGG模型
VGG模型其实就是更大更深的AlexNet。
2.代码:
2.1VGG块:
python
import torch
from torch import nn
from d2l import torch as d2l
# 可以指定VGG块中的卷积层个数、VGG块的输入通道数、VGG块的输出通道数
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# 其中第i层卷积层的输出为第i+1层卷积层的输入
layers.append(nn.MaxPool2d(kernel_size=2, stride=2))
return nn.Sequential(*layers)
3.2VGG模型:
python
conv_arch = ((1, 64), (1, 128), (2, 256), (2, 512), (2, 512))# 五个VGG块,各个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)
# 实例化模型
ratio = 4
small_conv_arch = [(pair[0], pair[1] // ratio) for pair in conv_arch]
net = vgg(small_conv_arch)
# 开始训练,注意该模型层数更多,因此计算量较大,需要占用较多计算机资源
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, d2l.try_gpu())
3.3各层的输出尺寸:
十三、NiN:舍弃全连接层减少参数的思想
1.模型架构:
因为全连接层需要较多的参数,相比来说卷积层和池化层所需要的参数就比较少,NiN的思想就是能不能用卷积层和池化层来替代全连接层的功能进而减少模型中可学习的参数?
1.1NiN块(卷积核为1的卷积层):
NiN块架构如上,一个卷积层后跟两个卷积核大小为1的卷积,使用卷积核大小为1的卷积来代替全连接层进行改变通道数 。具体原理如下:
多个NiN块拼接可以将通道数减少到1。
1.2NiN架构:
使用多个NiN块来减少通道数,使用多个平均池化层来减少特征图尺寸可以达到全连接层的效果,最后输出的一维特征图即为各分类上的预测值,最后通过一步展平操作即可得到最终在一维输出上的预测。
2.代码:
2.1NiN块:
python
import torch
from torch import nn
from d2l import torch as d2l
# NiN块架构
def nin_block(in_channels, out_channels, kernel_size, strides, padding):
return nn.Sequential(
nn.Conv2d(in_channels, out_channels, kernel_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.2NiN模型:
python
# 模型架构
net = nn.Sequential(
nin_block(1, 96, kernel_size=11, strides=4, padding=0),
nn.MaxPool2d(3, stride=2),
nin_block(96, 256, kernel_size=5, strides=1, padding=2),
nn.MaxPool2d(3, stride=2),
nin_block(256, 384, kernel_size=3, strides=1, padding=1),
nn.MaxPool2d(3, stride=2), nn.Dropout(0.5),
nin_block(384, 10, kernel_size=3, strides=1, padding=1),
nn.AdaptiveAvgPool2d((1, 1)),
nn.Flatten())
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, d2l.try_gpu())
2.3各层的输出尺寸:
十四、GoogLeNet:提出卷积层并联
1.Inception块(并联卷积层):
Inception块使用四条不同超参数的卷积层和池化层路线来提取不同的特征
其中卷积核尺寸为1的卷积层起到改变通道数的作用。
Inception块具有更少的可学习参数个数。
2.GoogLeNet架构:
- 通过Inception块改变通道数
- 通过池化层改变特征图尺寸
- 最后全连接层进行特征整合
2.1 Stage1&Stage2
2.2 Stage3
2.3 Stage4&Stage5
3.Inception变种:
3.1 Inception-V3:
4.代码:
4.1Inception块:
python
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l
# Inception块
class Inception(nn.Module):
# 需要给定输入通道数和四条路上每条路的通道数c1、c2、c3、c4
def __init__(self, in_channels, c1, c2, c3, c4, **kwargs):
super(Inception, self).__init__(**kwargs)
self.p1_1 = nn.Conv2d(in_channels, c1, kernel_size=1)
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)
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)
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)))
return torch.cat((p1, p2, p3, p4), dim=1)
4.2GoogLeNet模型:
python
# 模型架构
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))
b2 = nn.Sequential(nn.Conv2d(64, 64, kernel_size=1), nn.ReLU(),
nn.Conv2d(64, 192, kernel_size=3, padding=1),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
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))
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))
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())
net = nn.Sequential(b1, b2, b3, b4, b5, nn.Linear(1024, 10))
# 开始训练
lr, num_epochs, batch_size = 0.1, 10, 128
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
4.3各个Inception块的输出尺寸:
十五、ResNet残差网络:f(x)=g(x)+x
残差神经网络可以保证堆叠再多的层也不会导致模型性能变差。
1.残差块:
残差块将每层的输出定义为之前所有层的输出(即该层的输入)+该层的输出 ,相比于传统的神经网络加入了右边的快速通道,这样可以保证不管残差块堆叠多少层,都不会降低模型的精度。
若残差快的输入与输出通道数不符,可以通过卷积核大小为1的卷积来进行通道数的调整
2.ResNet架构:
3.代码:
3.1残差块:
python
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l
class Residual(nn.Module):
def __init__(self, input_channels, num_channels, use_1x1conv=False,
strides=1):
super().__init__()
self.conv1 = nn.Conv2d(input_channels, num_channels, kernel_size=3,
padding=1, stride=strides)
self.conv2 = nn.Conv2d(num_channels, num_channels, kernel_size=3,
padding=1)
if use_1x1conv:
self.conv3 = nn.Conv2d(input_channels, num_channels,
kernel_size=1, stride=strides)
else:
self.conv3 = None
self.bn1 = nn.BatchNorm2d(num_channels)
self.bn2 = nn.BatchNorm2d(num_channels)
self.relu = nn.ReLU(inplace=True)
def forward(self, X):
Y = F.relu(self.bn1(self.conv1(X)))
Y = self.bn2(self.conv2(Y))
if self.conv3:
X = self.conv3(X)
Y += X # 这是关键,传统神经网络每个Block的输出Y不会+之前的输出X
return F.relu(Y)
3.2ResNet模型:
python
b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
nn.BatchNorm2d(64), nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
# 这不是残差块,而是残差快组合成的,可以理解成Stage,每个Stage有num_residuals个残差块
def resnet_block(input_channels, num_channels, num_residuals,
first_block=False):
blk = []
for i in range(num_residuals):
if i == 0 and not first_block:
blk.append(
Residual(input_channels, num_channels, use_1x1conv=True,
strides=2))
else:
blk.append(Residual(num_channels, num_channels))
return blk
b2 = nn.Sequential(*resnet_block(64, 64, 2, first_block=True))
b3 = nn.Sequential(*resnet_block(64, 128, 2))
b4 = nn.Sequential(*resnet_block(128, 256, 2))
b5 = nn.Sequential(*resnet_block(256, 512, 2))
net = nn.Sequential(b1, b2, b3, b4, b5, nn.AdaptiveAvgPool2d((1, 1)),
nn.Flatten(), nn.Linear(512, 10))
# 开始训练
lr, num_epochs, batch_size = 0.05, 10, 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())