本文是李沐老师的【动手学深度学习】课程的学习记录,具体章节为卷积神经网络章节。

本文目录如下:

  • 从全连接层到卷积
  • 图像卷积
  • 填充和步幅度
  • 多输入多输出通道
  • 池化层(汇聚层)
  • 卷积神经网络(LeNet)
  • 深度卷积神经网络(AlexNet)
  • 使用块的网络(VGG)
  • 网络中的网络(NiN)
  • 含并行连接的网络(GoogLeNet)
  • 批量归一化
  • 残差网络(ResNet)
  • 图像分类竞赛

从全连接层到卷积

小结:

  • 图像的平移不变性使得我们以相同的方式处理局部图像,而不在乎它所在的位置
  • 局部性意味着计算相应的隐藏表示只需要一小部分的局部图像像素
  • 在图像处理中,卷积层通常比全连接层需要更少的参数,但依旧获得高效用的性能
  • 卷积神经网络CNN是一类特殊的神经网络,它可以包含多个卷积层
  • 多个输入和输出通道使模型造每个空间位置可以获得图像的多方面特征

图像卷积

import torch
from torch import nn
from d2l import torch as d2ldef corr2d(X, K):  # @saveh, 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 Yclass 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.biasX = torch.ones((6,8))
X[:,2:6] = 0
K = torch.tensor([[1.0,-1.0]])
Y = corr2d(X,K)
print(Y)

填充和步幅度

当当输入图像的形状为nh×nwn_h \times n_wnh​×nw​,卷积形状为kh×kwk_h \times k_wkh​×kw​时,那么输出形状为(nh−kh+1)×(nw−kw+1)(n_h-k_h+1)\times (n_w -k_w+1)(nh​−kh​+1)×(nw​−kw​+1)。

那么若填充php_hph​行和pwp_wpw​列(分别进行上下左右平均分类),那么最终输出的形状为:
(nh−kh+ph+1)×(nw−kw+pw+1)(n_h -k_h + p_h + 1)\times(n_w-k_w+p_w+1) (nh​−kh​+ph​+1)×(nw​−kw​+pw​+1)
若调整垂直步幅为shs_hsh​,水平步幅为sws_wsw​时,输出形状为:
⌊(nh−kh+ph+sh)/sh⌋×⌊(nw−kw+pw+sw)/sw⌋\lfloor (n_h-k_h+p_h+s_h)/s_h\rfloor \times \lfloor (n_w-k_w+p_w+s_w)/s_w \rfloor ⌊(nh​−kh​+ph​+sh​)/sh​⌋×⌊(nw​−kw​+pw​+sw​)/sw​⌋

import torch
from torch import nndef comp_conv2d(conv2d,x):x = x.reshape((1,1) + X.shape)  # 将维度弄成4个,前两个为填充和步幅y = conv2d(x)return y.reshape(y.shape[2:])conv2d = nn.Conv2d(1,1,kernel_size=(3,5),padding=(0,1),stride=(3,4))
X = torch.rand(size=(8,8))
print(comp_conv2d(conv2d,X).shape)

小结

  • 填充可以增加输出的高度和宽度,这常用来使得输出与输入具有相同的高和宽
  • 步幅可以减小输出的高和宽,例如输出的高和宽仅为输入的高和宽的1n\frac{1}{n}n1​
  • 填充和步幅可用于有效地调整数据的维度

多输入多输出通道

对于多输入通道来说,一般都有相同通道数的卷积核来跟其进行匹配,然后计算的过程就是对每个通道输入的二维张量和对应通道的卷积核的二维张量进行运算,每个通道得到一个计算结果,然后就将各个计算结果相加作为输出的单通道的那个位置的数值,如下图:

对于多输出通道来说,可以将每个通道看作是对不同特征的响应,假设ci、coc_i、c_oci​、co​分别为输入和输出通道的数目,那么为了得到这多个通道的输出,我们需要为每个输出通道创建一个形状为ci×kh×kwc_i\times k_h \times k_wci​×kh​×kw​大小的卷积核张量,因此总的卷积核的形状为co×ci×kh×kwc_o\times c_i \times k_h \times k_wco​×ci​×kh​×kw​

而还有一种特殊的卷积层,为1×11\times 11×1卷积层。因为高宽只有1,因此它无法造高度和宽度的维度上,识别相邻元素间相互作用的能力,它唯一的计算发生在通道上。如下图:

这种卷积层会导致输入和输出具有相同的高度和宽度,但是通道数发生了变化,输出中的每个元素都是从输入图像中同一位置的元素的线性组合,这就说明可以将这个卷积层起的作用看成是一个全连接层,输入的每个通道就是一个输入结点,然后卷积核的每一个通道就是对应的权重

因此1×11\times 11×1卷积层通常用于调整网络层的通 道数量和控制模型的复杂度

池化层(汇聚层)

池化层可以用来处理卷积对于像素位置尤其敏感的问题,例如下面:

那么池化有最大池化以及平均池化

具体实现为:

pool2d = nn.MaxPool2d((2,3),padding=(1,1),stride=(2,3))

如果应对多通道的场景,会保持输入和输出通道相等。

小结

  • 对于给定输入元素,最大池化层会输出该窗口内的最大值,平均池化层会输出该窗口内的平均值
  • 池化层的主要优点之一是减轻卷积层对位置的过度敏感
  • 可以指定池化层的填充和步幅
  • 使用最大池化层以及大于1的步幅,可以减小空间的维度
  • 池化层的输出通道数和输入通道数相同

卷积神经网络(LeNet)

import torch
from matplotlib import pyplot as plt
from torch import nn
from d2l import torch as d2lclass Reshape(torch.nn.Module):def forward(self, x):return x.view(-1, 1, 28, 28)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)
)# 载入数据集
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)# 修改评估函数,使用GPU来计算
def evaluate_accuracy_gpu(net, data_iter, device=None):  # @saveif isinstance(net, torch.nn.Module):net.eval()  # 转为评估模式if not device:  # 如果不是为Nonedevice = next(iter(net.parameters())).devicemetric = 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]# 对训练函数做改动,使其能够在GPU上跑def train_ch6(net, train_iter, test_iter, num_eopchs, lr, device):  #@ savedef 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_eopchs],legend=["train loss",'train acc', 'test,acc'])timer, num_batches = d2l.Timer(), len(train_iter)for epoch in range(num_eopchs):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)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()  # 停止计时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},'f'test acc{test_acc:.3f}')print(f'{metric[2] * num_eopchs / timer.sum():1f} examples / sec'f'on{str(device)}')lr, num_epoch = 0.5,20
train_ch6(net, train_iter, test_iter, num_epoch, lr ,d2l.try_gpu())
plt.show()

loss0.417,train acc0.847,test acc0.836
36144.960085 examples / seconcuda:0

小结

  • 卷积神经网络是一类使用卷积层的网络
  • 在卷积神经网络中,组合使用卷积层、非线性激活函数和池化层
  • 为了构造高性能的CNN,我们通常对卷积层进行排序,逐渐降低其表示的空间分辨率,同时增加通道数
  • 在传统的卷积神经网络中,卷积块编码得到的表征在输出之前需要由一个或多个全连接层进行处理
  • LeNet是最早发布的卷积神经网络之一

深度卷积神经网络(AlexNet)

import torch
from matplotlib import pyplot as plt
from torch import nn
from d2l import torch as d2lnet = 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)
)batch_size = 128
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=224)
# 读取数据然后将其高和宽都拉成224lr, num_epochs = 0.01, 10
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
plt.show()

跑了好久:

loss 0.328, train acc 0.881, test acc 0.881
666.9 examples/sec on cuda:0

使用块的网络(VGG)

VGG就是沿用了AlexNet的思想,将多个卷积层和一个池化层组成一个块,然后可以指定每个块内卷积层的数目,以及块的数目,经过多个块对图像信息的提取后再经过全连接层。

VGG块中包含以下内容:

  • 多个带填充以保持分辨率不变的卷积层
  • 每个卷积层后都带有非线性激活函数
  • 最后一个池化层

具体代码如下:

import torch
from matplotlib import pyplot as plt
from torch import nn
from d2l import torch as d2ldef vgg_block(num_convs, in_channels, out_channels):# 该函数用来创建单个的VGG块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_channelslayers.append(nn.MaxPool2d(kernel_size=2, stride=2))return nn.Sequential(*layers)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_channelsreturn nn.Sequential(*conv_blks,nn.Flatten(),nn.Linear(out_channels * 7 * 7, 4096),nn.ReLU(),nn.Dropout(p=0.5),nn.Linear(4096, 4096),nn.ReLU(),nn.Dropout(p=0.5),nn.Linear(4096, 10))conv_arch = ((1, 64), (1, 128), (2, 256), (2, 512), (2, 512))
# 第一个为块内卷积层个数,第二个为输出通道数ratio = 4
small_conv_arch = [(pair[0], pair[1] // ratio) for pair in conv_arch]
#  除以ratio减少通道数目
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())plt.show()

loss 0.170, train acc 0.936, test acc 0.912
378.0 examples/sec on cuda:0

小结

  • VGG-11使用可复用的卷积块来构造网络,不同的VGG模型可通过每个块中卷积层数量和输出通道数量的差异来定义
  • 块的使用导致网络定义得非常简洁,使用块可以有效地设计复杂的网络
  • 在研究中发现深层且窄的卷积(多层3×33\times 33×3)比浅层且宽(例如少层5×55\times 55×5)的效果更好

网络中的网络(NiN)

之前的网络都有一个共同的特点在于最后都会通过全连接层来对特征的表示进行处理,这就导致参数数量很大。那么NiN就是希望能够很其他的模块来替换掉全连接层,那么就用到了**1×11 \times 11×1的卷积层**,因此1个NiN块就是一个正常的卷积层和两个1×11 \times 11×1的卷积层,那么经过多个NiN块后,将通道数拓展到希望输出的类别数,然后用一个具有输出类别数目的通道数的全局平均池化层来进行处理,也就是对每个通道进行全部平均得到单个标量,那么有out_channelsout\_channelsout_channels个通道就有相应个数值,再经过softmax就可以作为输出了

import torch
from matplotlib import pyplot as plt
from torch import nn
from d2l import torch as d2ldef 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()  # 两个1*1的卷积层都不改变大小和通道)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(p=0.5),# 标签类别数为10,因此最后一个输出通道数设为10nin_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())
plt.show()

loss 0.383, train acc 0.857, test acc 0.847
513.3 examples/sec on cuda:0

小结

  • NiN使用由一个卷积层和多个1×11\times 11×1卷积层组成的块,该块可以在卷积神经网络中使用,以允许更多的像素非线性
  • NiN去除了容易造成过拟合的全连接层,将它们替换成全局平均池化层,该池化层通道数量为所需的输出数目
  • 移除全连接层可以减少过拟合,同时显著减少参数量

含并行连接的网络(GoogLeNet)

前面提到的各种网络,其中的问题在于各个卷积层的参数可能都是不一样的,而DNN的解释性如此之差,我们很难解释清楚哪一个超参数的卷积层才是我们需要的,才是最好的。因此在GoogLeNet网络中,其引入了Inception块,这种块引入了并行计算的思想,将常见的多种不同超参数的卷积层都放入,希望能够通过多种提取特征的方式来得到最理想的特征提取效果,如下图:

其具体的结构为:

import torch
from matplotlib import pyplot as plt
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2lclass Inception(nn.Module):def __init__(self, in_channels, c1,c2,c3, c4, **kwargs):super(Inception, self).__init__(**kwargs)# 线路1,单1*1卷积层self.p1_1 = nn.Conv2d(in_channels, c1, kernel_size=1)# 线路2,1*1卷积层后接3*3卷积层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)# 线路3,1*1卷积层后接上5*5卷积层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)# 线路4,3*3最大池化层后接上1*1卷积层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)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.ReLU(),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.05, 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())
plt.show()
"""
x = torch.rand(size=(1,1,96,96))
for layer in net:x = layer(x)print(layer.__class__.__name__, 'output shape \t', x.shape)
"""

loss 0.284, train acc 0.891, test acc 0.884
731.9 examples/sec on cuda:0

小结

  • Inception块相当于一个有4条路径的子网络,它通过不同窗口形状的卷积层和最大池化层来并行抽取信息,并使用1×11\times 11×1卷积层减少每像素级别上的通道维数从而降低模型复杂度
  • GoogLeNet将多个设计精细的Inception块与其他层(卷积层、全连接层)串联起来,其中Inception块的通道数分配之比是在ImageNet数据集上通过大量的实验得到的
  • GoogLeNet和它的后继者们一度是ImageNet上最有效的模型之一:它以较低的计算复杂度提供了类似的测试精度

批量归一化

在训练过程中,一般正常情况下,后面的层的梯度会比较大,而前面层的梯度会因为经过多层的传播一直相乘而变得比较小,而此时学习率如果固定的话,那么前面的层就会更新得比较慢,后面层会更新得比较快,那么当后面层更新即将完成时,会因为前面的层发生了变动,那么后面层就需要重新更新

那么批量规范化的思想是:在每一个卷积层或线型层后应用,将其输出规范到某一个分布之中(不同的层所归到的分布是不一样的,是各自学习的),那么限制到一个想要的分布后便可以使得收敛更快

假设当前批量B得到的样本为x=(x1,x2,...,xn)\pmb{x}=(x_1,x_2,...,x_n)xx=(x1​,x2​,...,xn​),那么:
μ^B=1∣B∣∑i∈Bxiσ^B2=1∣B∣∑i∈B(xi−μ^B)2+ϵ(ϵ防止方差为0)BN(xi)=γxi−μ^Bσ^B+β\hat{\mu}_B=\frac{1}{\vert B\vert}\sum_{i\in B}x_i\\ \hat{\sigma}^2_B=\frac{1}{\vert B \vert}\sum_{i\in B}(x_i -\hat{\mu}_B)^2+\epsilon~~(\epsilon防止方差为0)\\ BN(x_i)=\gamma \frac{x_i - \hat{\mu}_B}{\hat{\sigma}_B}+\beta μ^​B​=∣B∣1​i∈B∑​xi​σ^B2​=∣B∣1​i∈B∑​(xi​−μ^​B​)2+ϵ  (ϵ防止方差为0)BN(xi​)=γσ^B​xi​−μ^​B​​+β
可以认为γ、β\gamma、\betaγ、β分别为要规范到的分布的方差和均值,是两个待学习的参数。

研究指出,其作用可能就是通过在每个小批量中加入噪音来控制模型的复杂度,因为批量是随机取得的,因此批量的均值和方差也就不同,相当于对该次批量加入了随机偏移μ^B\hat{\mu}_Bμ^​B​和随机缩放σ^B\hat{\sigma}_Bσ^B​。需要注意的是它不需要与Dropout一起使用。

它可以作用的全连接层和卷积层的输出上,激活函数之前,也可以作用到全连接层和卷积层的输入上:

  • 对于全连接层来说,其作用在特征维
  • 对于卷积层,作用在通道维

而当我们在训练中采用和批量归一化,我们就需要记下来每个用到批量归一化的地方,其整个样本数据集的均值和方差是多少,这样才能够在进行预测的时候也对预测样本进行规范

import torch
from matplotlib import pyplot as plt
from torch import nn
from d2l import torch as d2ldef batch_norm(X, gamma, beta, moving_mean, moving_var, eps, momentum):if not torch.is_grad_enabled():  # 说明当前在预测X_hat = (X - moving_mean) / torch.sqrt(moving_var + eps)  # 防止方差为0# 这两个参数就是整个数据集的均值和方差else:assert len(X.shape) in (2,4)  # 维度数目为2,是全连接层,为4是卷积层if len(X.shape) == 2:mean = X.mean(dim = 0)var = ((X - mean) ** 2 ).mean(dim = 0)else:mean = X.mean(dim=(0,2,3),keepdim=True)# 每一个通道是一个不同的特征,其提取了图像不同的特征,因此对通道维计算均值方差var = ((X - mean) ** 2).mean(dim=(0,2,3), keepdim = True)# 当前在训练模式X_hat = (X - mean) / torch.sqrt(var + eps)moving_mean = momentum * moving_mean + (1.0 - momentum) * meanmoving_var = momentum * moving_var + (1.0 - momentum) * varY = gamma * X_hat + betareturn Y, moving_mean.data, moving_var.dataclass BatchNorm(nn.Module):def __init__(self,num_features, num_dims):super().__init__()if num_dims == 2:shape = (1, num_features)else:shape = (1, num_features, 1, 1)self.gamma = nn.Parameter(torch.ones(shape))self.beta = nn.Parameter(torch.zeros(shape))self.moving_mean = torch.zeros(shape)self.moving_var = torch.ones(shape)def forward(self, X):if self.moving_mean.device != X.device:self.moving_mean = self.moving_mean.to(X.device)self.moving_var = self.moving_var.to(X.device)Y,self.moving_mean, self.moving_var = batch_norm(X,self.gamma, self.beta, self.moving_mean,self.moving_var, eps=1e-5, momentum=0.9)return Ynet = nn.Sequential(nn.Conv2d(1, 6, kernel_size=5),BatchNorm(6, num_dims=4),nn.Sigmoid(),nn.MaxPool2d(kernel_size=2, stride=2),nn.Conv2d(6, 16,kernel_size=5),BatchNorm(16, num_dims=4),nn.Sigmoid(),nn.MaxPool2d(kernel_size=2, stride=2),nn.Flatten(),nn.Linear(16 * 4 * 4, 120),BatchNorm(120, num_dims=2),nn.Sigmoid(),nn.Linear(120, 84),BatchNorm(84, num_dims=2),nn.Sigmoid(),nn.Linear(84, 10))lr, num_epochs, batch_size = 1.0, 10 ,256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
d2l.train_ch6(net,train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
plt.show()

loss 0.251, train acc 0.908, test acc 0.883
17375.8 examples/sec on cuda:0

而nn中也有简单的实现方法:

net = nn.Sequential(nn.Conv2d(1, 6, kernel_size=5),nn.BatchNorm2d(6),nn.Sigmoid(),nn.MaxPool2d(kernel_size=2, stride=2),nn.Conv2d(6, 16,kernel_size=5),nn.BatchNorm2d(16),nn.Sigmoid(),nn.MaxPool2d(kernel_size=2, stride=2),nn.Flatten(),nn.Linear(16 * 4 * 4, 120),nn.BatchNorm2d(120),nn.Sigmoid(),nn.Linear(120, 84),nn.BatchNorm2d(84),nn.Sigmoid(),nn.Linear(84, 10))

小结

  • 在模型训练的过程中,批量归一化利用小批量的均值和标准差,不断调整神经网络的中间输出,使整个神经网络各层的中间输出更加稳定
  • 批量归一化在全连接层和卷积层的使用略有不同,需要注意作用的维度
  • 批量归一化和Dropout一样,在训练模式和预测模式下计算不同
  • 批量归一化有许多有益的副作用,主要是正则化

残差网络(ResNet)

我们需要讨论一个问题是:是否加入更多的层就能够使得精度进一步提高

因此ResNet就是这种思想,最具体的表现是:

那么将该块的输入连接到输出,就需要输入和输出的维度是相同的,可以直接相加,因此如果块内部对维度进行了改变,那么就需要对输入也进行维度的变化才能够相加:

那么一般来说,是先对输入进行多个高宽减半的ResNet块,后面再接多个高宽不变的ResNet块,可以使得后面提取特征的时候减少计算量:

那么整体的架构就是:

因此,代码为:

import torch
from matplotlib import pyplot as plt
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2lclass Residual(nn.Module):  #@savedef __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 = Noneself.bn1 = nn.BatchNorm2d(num_channels)self.bn2 = nn.BatchNorm2d(num_channels)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 += Xreturn F.relu(Y)# 第一个模块基本上在卷积神经网络中都是一样的
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))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 blkb2 = 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))
# *号代表把resnet_block返回的列表展开,可以理解为把元素都拿出来,不是单个列表了
net = nn.Sequential(b1,b2,b3,b4,b5,nn.AdaptiveAvgPool2d((1,1)),nn.Flatten(),nn.Linear(512,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)
"""
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())
plt.show()

loss 0.014, train acc 0.996, test acc 0.914
883.9 examples/sec on cuda:0

李沐老师后面又补充了一节关于ResNet的梯度计算的内容,具体如下:
假设y=f(x),则更新为  w=w−λ∂y∂w那么假设后面增加一个模块为y′=g(y)=g(f(x)),则此时输出关于参数的导数为 ∂y′∂w=∂g(y)∂y∂y∂w那么如果g是一个学习能力比较强的层(例如全连接层),那么就会更接近于真实输出,此时∂g(y)∂y较小从而导致∂y′∂w较小,那么f(x)层的更新就很慢,主要问题就是乘法中间一个比较小就会出现梯度消失的问题而ResNet它采用了残差的方式,即y′=f(x)+g(f(x)),那么∂y′∂w=∂y∂w+∂g(y)∂y∂y∂w就算第二部分较小,仍然有第一部分来提供较大的梯度。因此可以解决梯度消失的问题,在靠近数据部分的也能够进行更新假设y=f(x),则更新为~~w=w-\lambda \frac{\partial y}{\partial w}\\那么假设后面增加一个模块为y^{\prime}=g(y)=g(f(x)),则此时输出关于参数的导数为~\frac{\partial y^{\prime}}{\partial w}=\frac{\partial g(y)}{\partial y}\frac{\partial y}{\partial w}\\那么如果g是一个学习能力比较强的层(例如全连接层),那么就会更接近于真实输出,此时\frac{\partial g(y)}{\partial y}较小\\从而导致\frac{\partial y^{\prime}}{\partial w}较小,那么f(x)层的更新就很慢,主要问题就是乘法\\中间一个比较小就会出现梯度消失的问题\\而ResNet它采用了残差的方式,即y^{\prime}=f(x)+g(f(x)),那么\frac{\partial y^{\prime}}{\partial w}=\frac{\partial y}{\partial w}+\frac{\partial g(y)}{\partial y}\frac{\partial y}{\partial w}\\就算第二部分较小,仍然有第一部分来提供较大的梯度。\\因此可以解决梯度消失的问题,在靠近数据部分的也能够进行更新 假设y=f(x),则更新为  w=w−λ∂w∂y​那么假设后面增加一个模块为y′=g(y)=g(f(x)),则此时输出关于参数的导数为 ∂w∂y′​=∂y∂g(y)​∂w∂y​那么如果g是一个学习能力比较强的层(例如全连接层),那么就会更接近于真实输出,此时∂y∂g(y)​较小从而导致∂w∂y′​较小,那么f(x)层的更新就很慢,主要问题就是乘法中间一个比较小就会出现梯度消失的问题而ResNet它采用了残差的方式,即y′=f(x)+g(f(x)),那么∂w∂y′​=∂w∂y​+∂y∂g(y)​∂w∂y​就算第二部分较小,仍然有第一部分来提供较大的梯度。因此可以解决梯度消失的问题,在靠近数据部分的也能够进行更新

图像分类竞赛

本次我先是采用了李沐老师上课讲过的ResNet11去跑,结果达到了0.8多一点,具体的代码请见下:

# 首先导入包
import torch
import torch.nn as nn
import pandas as pd
import numpy as np
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms
from PIL import Image
import os
from d2l import torch as d2l
import matplotlib.pyplot as plt
from LeavesDataset import LeavesDataset  # 数据加载器

先是要对标签类的数据进行处理,将其从字符串转换为对应的类别数字,同时在这两者之间建立关系方便后续:

label_dataorgin = pd.read_csv("dataset/classify-leaves/train.csv")  # 读取csv文件
leaves_labels = sorted(list(set(label_dataorgin['label'])))  # 取出标签列然后set去重再列表排序
num_class = len(leaves_labels)  # 总共的类别数目
class_to_num = dict(zip(leaves_labels, range(num_class)))  # 建立字典,类别名称对应数字
num_to_class = {i:j for j,i in class_to_num.items()}  # 数字对应类别名称

接下来就是写我们数据加载器,因为我发现一个问题就是如果把数据加载器和整体的代码写在同样的文件中会报错,会在之后调用d2l的训练函数时说找不到这个数据加载器的定义,那么我们需要在另外的文件写数据加载器的定义然后引用,我在另外的LeavesDataset.py文件中为其定义:

class LeavesDataset(Dataset):def __init__(self, csv_path, file_path, mode = 'train', valid_ratio = 0.2,resize_height = 256, resize_width=256):self.resize_height = resize_height  # 拉伸的高度self.resize_width = resize_width  # 宽度self.file_path = file_path  # 文件路径self.mode = mode  # 模式self.data_csv = pd.read_csv(csv_path, header=None)  # 读取csv文件去除表头self.dataLength = len(self.data_csv.index) - 1  # 数据长度self.trainLength = int(self.dataLength * (1 - valid_ratio))  # 训练集的长度if mode == 'train':# 训练模式self.train_images = np.asarray(self.data_csv.iloc[1:self.trainLength, 0])  # 第0列为图像的名称self.train_labels = np.asarray(self.data_csv.iloc[1:self.trainLength, 1])  # 第1列为图像的标签self.image_arr = self.train_imagesself.label_arr = self.image_arrelif mode == 'valid':self.valid_images = np.asarray(self.data_csv.iloc[self.trainLength:, 0])self.valid_labels = np.asarray(self.data_csv.iloc[self.trainLength:, 1])self.image_arr = self.valid_imagesself.label_arr = self.valid_labelselif mode == 'test':self.test_images = np.asarray(self.data_csv.iloc[1:,0])  # 测试集没有标签列self.image_arr = self.test_imagesself.realLen_now = len(self.image_arr)print("{}模式下已完成数据载入,得到{}个数据".format(mode, self.realLen_now))def __getitem__(self, index):image_name = self.image_arr[index]  # 得到文件名img = Image.open(os.path.join(self.file_path, image_name))  # 拼接后得到当前访问图片的完整路径transform = transforms.Compose([transforms.Resize((224,224)),  # 更改为224*224transforms.ToTensor()])img = transform(img)if self.mode == 'test':return imgelse:label = self.label_arr[index]number_label = class_to_num[label]return img, number_labeldef __len__(self):return self.realLen_now

那么接下来就是加载各个数据集了:

train_path = "dataset/classify-leaves/train.csv"  # 根据你的实际情况修改
test_path = "dataset/classify-leaves/test.csv"
img_path = "dataset/classify-leaves/"train_dataset = LeavesDataset(train_path, img_path, mode = 'train')
valid_dataset = LeavesDataset(train_path, img_path, mode = 'valid')
test_dataset = LeavesDataset(test_path, img_path, mode = 'test')
batch_size = 64  # 这里如果显存不够可以调小train_loader = DataLoader(dataset=train_dataset,batch_size=batch_size, shuffle=False,num_workers=5)  # 不随机打乱,进程数为5
valid_loader = DataLoader(dataset=valid_dataset,batch_size=batch_size, shuffle=False,num_workers=5)
test_loader = DataLoader(dataset=test_dataset,batch_size=batch_size, shuffle=False,num_workers=5)

得到数据后接下来就是定义模型了,我先是采用了ResNet11:

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))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(d2l.Residual(input_channels, num_channels,use_1x1conv=True, strides=2))else:blk.append(d2l.Residual(num_channels, num_channels))return blkb2 = 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,176)
)

然后因为我希望如果模型能够达到要求的精度我就将其保存下来,因此修改了训练函数:

def train_ch6_save(net, train_iter, test_iter, num_epochs, lr, device, best_acc):  #@save"""Train a model with a GPU (defined in Chapter 6).Defined in :numref:`sec_lenet`"""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):# Sum of training loss, sum of training accuracy, no. of examplesmetric = 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)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()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 = d2l.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}, 'f'test acc {test_acc:.3f}')print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec 'f'on {str(device)}')if test_acc > best_acc:print("模型精度较高,值得保存!")torch.save(net.state_dict(), "Now_Best_Module.pth")else:print("模型精度不够,不值得保存")
lr, num_epochs,best_acc = 0.05, 25, 0.8  # epoch太小训练不完全
train_ch6_save(net, train_loader, valid_loader, num_epochs, lr, device=d2l.try_gpu(), best_acc=best_acc)
plt.show()

得到结果为:

那么我接下来希望加大ResNet的深度来提高模型复杂度,用了网上的ResNet50模型发现太大了,读完模型之后再读数据,就算把batch_size设置小也显存爆了,因此只能修改模型小一点

b2 = nn.Sequential(*resnet_block(64,64,2,first_block=True))
b3 = nn.Sequential(*resnet_block(64,256,2))
b4 = nn.Sequential(*resnet_block(256,512,2))
b5 = nn.Sequential(*resnet_block(512,2048,3))net = nn.Sequential(b1,b2,b3,b4,b5,nn.AdaptiveAvgPool2d((1,1)),nn.Flatten(),nn.Linear(2048,176)
)

跑了五个小时结果过拟合了…

loss 0.014, train acc 0.996, test acc 0.764
31.6 examples/sec on cuda:0

最终调试了好几个模型花费了一整天的时间,还是没有最开始的ResNet11的效果好,最终决定就用这个了。

因此完整的代码为:

# 首先导入包
import torch
import torch.nn as nn
import pandas as pd
import numpy as np
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms
from PIL import Image
import os
from d2l import torch as d2l
import matplotlib.pyplot as plt
from tqdm import tqdmfrom LeavesDataset import LeavesDatasetdef resnet_block(input_channels, num_channels, num_residuals, first_block=False):  # 这是ResNet定义用到的函数blk = []for i in range(num_residuals):if i == 0 and not first_block:blk.append(d2l.Residual(input_channels, num_channels, use_1x1conv=True, strides=2))else:blk.append(d2l.Residual(num_channels, num_channels))return blkdef train_ch6_save(net, train_iter, test_iter, num_epochs, lr, device, best_acc):  # @save"""Train a model with a GPU (defined in Chapter 6).这是因为我需要训练完保存因此将老师的训练函数进行了修改,就放在这里了Defined in :numref:`sec_lenet`"""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):# Sum of training loss, sum of training accuracy, no. of examplesmetric = 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)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()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 = d2l.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}, 'f'test acc {test_acc:.3f}')print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec 'f'on {str(device)}')if test_acc > best_acc:print("模型精度较高,值得保存!")torch.save(net.state_dict(), "Now_Best_Module.pth")  # 对模型进行保存else:print("模型精度不够,不值得保存")if __name__ == "__main__":  # 一定要将运行的代码放在这里!否则会报错,我目前还不知道原因label_dataorgin = pd.read_csv("dataset/classify-leaves/train.csv")  # 读取训练的csv文件leaves_labels = sorted(list(set(label_dataorgin['label'])))  # 取出标签列然后去重再排序num_class = len(leaves_labels)  # 类别的个数class_to_num = dict(zip(leaves_labels, range(num_class)))  # 转换为字典num_to_class = {i: j for j, i in class_to_num.items()}train_path = "dataset/classify-leaves/train.csv"test_path = "dataset/classify-leaves/test.csv"img_path = "dataset/classify-leaves/"submission_path = "dataset/classify-leaves/submission.csv"  # 最终要提交的文件的路径train_dataset = LeavesDataset(train_path, img_path, mode='train')valid_dataset = LeavesDataset(train_path, img_path, mode='valid')test_dataset = LeavesDataset(test_path, img_path, mode='test')#print("数据载入完成")batch_size = 64train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=False, num_workers=5)valid_loader = DataLoader(dataset=valid_dataset, batch_size=batch_size, shuffle=False, num_workers=5)test_loader = DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=False, num_workers=5)#print("数据已变换为loader")# 定义模型# 第一个模块基本上在卷积神经网络中都是一样的b1 = nn.Sequential(nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3),nn.BatchNorm2d(64), nn.ReLU(),nn.MaxPool2d(kernel_size=3, stride=2, padding=1))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, 176))lr, num_epochs, best_acc = 0.02, 15, 0.85device = d2l.try_gpu()train_ch6_save(net, train_loader, valid_loader, num_epochs, lr, device=device, best_acc=best_acc)plt.show()# 开始做预测net.load_state_dict(torch.load("Now_Best_Module.pth"))  # 载入模型# print("模型载入完成")net.to(device)net.eval()  # 开启预测模式predictions = []  # 用来存放结果类别对应的数字for i, data in enumerate(test_loader):imgs = data.to(device)with torch.no_grad():logits = net(imgs)  # 计算结果是一个176长的向量predictions.extend(logits.argmax(dim=-1).cpu().numpy().tolist())# 取出最大的作为结果,并且放回cpu中,再转换成列表方便插入到predictions中preds = []for i in predictions:preds.append(num_to_class[i])  # 转换为字符串test_csv = pd.read_csv(test_path)test_csv['label'] = pd.Series(preds)  # 将结果作为一个新的列添加submission = pd.concat([test_csv['image'], test_csv['label']], axis=1)  # 拼接submission.to_csv(submission_path, index=False)  # 写入文件

提交的分数为:

虽然结果不是很好,但自己还是非常开心的!第一次完完整整地完成了一个项目,真正地学到了很多东西!只有自己动手从零开始才真正明白自己哪部分欠缺,因此才能够有进步!

请继续努力吧

【动手学深度学习】李沐——卷积神经网络相关推荐

  1. 动手学深度学习(李沐)的pytorch版本(包含代码和PDF版本)

    目录 网址(特别适合自学) 说明: 代码与PDF版 网址(特别适合自学) 传送门 界面一览: 说明:   github上一个项目将<动手学深度学习>从mxnet改为了pytorch实现.感 ...

  2. pytorch卷积神经网络_知识干货-动手学深度学习(pytorch)-06 卷积神经网络基础

    卷积神经网络基础 本节我们介绍卷积神经网络的基础概念,主要是卷积层和池化层,并解释填充.步幅.输入通道和输出通道的含义. 二维卷积层 本节介绍的是最常见的二维卷积层,常用于处理图像数据. 二维互相关运 ...

  3. 假设训练数据集中有10万个词,四元语法需要存储多少词频和多词相邻频率?《动手学深度学习 李沐》 转

    假设训练数据集中有10万个词,四元语法需要存储多少词频和多词相邻频率? 循环神经网络 Notes 1. 语言模型 语言模型(language model)是自然语言处理的重要技术.自然语言处理中最常见 ...

  4. 动手学深度学习08:卷积中的填充与步幅

    动手学深度学习08:卷积中的填充与步幅 一个240×240像素的图像,经过10层5×5的卷积后,将减少到200×200像素. 如此一来,原始图像的边界丢失了许多有用信息.而填充是解决此问题最有效的方法 ...

  5. 《动手学深度学习》task3_3 循环神经网络进阶

    目录 GRU GRU 重置门和更新门 候选隐藏状态 隐藏状态 GRU的实现 载入数据集 初始化参数 GRU模型 训练模型 简洁实现 LSTM 长短期记忆 输入门.遗忘门和输出门 候选记忆细胞 记忆细胞 ...

  6. 动手学深度学习PyTorch版-循环神经网络基础

    循环神经网络基础 从零开始实现循环神经网络 import torch import torch.nn as nn import time import math import sys sys.path ...

  7. 动手学深度学习(十一) NLP循环神经网络

    循环神经网络 本节介绍循环神经网络,下图展示了如何基于循环神经网络实现语言模型.我们的目的是基于当前的输入与过去的输入序列,预测序列的下一个字符.循环神经网络引入一个隐藏变量,用表示在时间步的值.的计 ...

  8. MXNet动手学深度学习笔记:卷积计算

    为什么80%的码农都做不了架构师?>>>    #coding:utf-8 ''' 卷积计算 ''' import mxnet as mx from mxnet.gluon impo ...

  9. 【动手学深度学习】(task123)注意力机制剖析

    note 将注意力汇聚的输出计算可以作为值的加权平均,选择不同的注意力评分函数会带来不同的注意力汇聚操作. 当查询和键是不同长度的矢量时,可以使用可加性注意力评分函数.当它们的长度相同时,使用缩放的& ...

  10. 李沐动手学深度学习V2-全卷积网络FCN和代码实现

    一.全卷积网络FCN 1. 介绍 语义分割是对图像中的每个像素分类,全卷积网络(fully convolutional network,FCN)采用卷积神经网络实现了从图像像素到像素类别的变换 ,与前 ...

最新文章

  1. Servlet和HTTP请求协议-学习笔记02【Servlet_体系结构与urlpartten配置、HTTP请求协议】
  2. 前端学习(1334):mongodb增2
  3. SequoiaDB 系列之六 :源码分析之coord节点
  4. 遥感图像数据类型的区别
  5. 浅谈tcpwrapper的基本使用方法
  6. CSS:前端布局——网格布局Grid
  7. 你想面试运维看一下你合格了吗?
  8. 阿里天池-“Python绘制月饼,云上中秋”:Python元祖冰皮月饼海报制作
  9. 站在知乎肩上-做更强的自己(4)
  10. 蜂巢科技获A轮千万美元融资,启明创投领投
  11. Android使用FFmpeg 解码H264并播放(三)
  12. element-ui图标不显示问题(已解决)
  13. 用友OA/NC/NCCloud漏洞集合
  14. 华为笔记本键盘说明图_没有键盘的笔记本:华为 MateBook上手图赏
  15. ROBOMASTER比赛作品集_邹俊添
  16. python画五角星和六角星程序_python画五角星和六角星程序
  17. 红米路由器ac2100怎样设置ipv6_红米(Redmi)路由器AC2100怎么设置
  18. css样式单位px,em,pt,ex,pc,in,mm,cm详解
  19. 测试用例之性能测试用例
  20. 深度学习之五:稀疏编码

热门文章

  1. 构建 Web 应用之 Service Worker 初探
  2. 离职/辞职通知书模板
  3. 360怎样修改wifi服务器地址,360路由器怎么重新设置?
  4. vue中的activated和deactivated
  5. CTF 实验吧 变异凯撒 writeup
  6. Ps|无规律渐变效果
  7. VMware14 kali linux安装教程
  8. 思科2960交换机光口激活失败,提示has bad crc,解决方法
  9. 小学生python编程写游戏_小学生C++趣味编程上下全2册+Python少儿趣味编程适合6-12岁儿童编程教程中小学生计算机程序设计创意零基础游戏编程书籍DK...
  10. CentOS7安装IT资产管理系统Snipe-IT