【动手学深度学习】#6 卷积神经网络
主要参考学习资料:
《动手学深度学习》阿斯顿·张 等 著
【动手学深度学习 PyTorch版】哔哩哔哩@跟李牧学AI
由于本系列一开始跳过了第一章引言部分,因此系列编号比书本章节编号提前。现改为和书本统一(因为之前自己的原始笔记也是按照书本章节编的,每次发布都要修改有些麻烦)。
概述
- 由于多层感知机应用于图像会导致数以亿计的参数,因此卷积神经网络被提出。
- 卷积层的操作基于输入与卷积核的互相关运算。
- 卷积核的大小、步幅和图像的填充都会影响输出的大小。
- 一张图像一般具有多个通道,为输入和输出添加了一个维度。
- 汇聚层进一步降低了卷积层对位置的过度敏感。
- LeNet是最早发布的卷积神经网络之一。
- 下一章将介绍更多现代卷积神经网络。
目录
- 6.1 从全连接层到卷积
- 6.1.1 多层感知机的限制
- 1.平移不变性
- 2.局部性
- 6.1.2 卷积
- 6.1.3 通道
- 6.2 图像卷积
- 6.2.1 互相关运算
- 6.2.2 卷积层
- 6.2.3 图像中目标的边缘检测
- 6.2.4 学习卷积核
- 6.2.6 感受野
- 6.3 填充和步幅
- 6.3.1 填充
- 6.3.2 步幅
- 6.4 多输入多输出通道
- 6.4.1 多输入通道
- 6.4.2 多输出通道
- 6.4.3 1 × 1 1\times1 1×1卷积层
- 6.5 汇聚层
- 6.5.1 最大汇聚和平均汇聚
- 6.5.2 填充和步幅
- 6.5.3 多通道
- 6.6 卷积神经网络(LeNet)
- 6.6.1 LeNet
- 6.6.2 模型训练
6.1 从全连接层到卷积
(本节公式理解有困难可直接看下一节图像卷积)
6.1.1 多层感知机的限制
设计适合于计算机视觉的神经网络架构应该符合以下两个原则:
- 平移不变性:不管检测对象出现在图像中的哪个位置,神经网络的前几层应该对相同的图像区域具有相似的反应。
- 局部性:神经网络的前几层应该只探索输入图像中的局部区域,而不过度在意图像中相隔较远区域的关系,最终可以聚合这些局部特征以在整张图像级进行预测。
在多层感知机中,我们将二维图像 X \boldsymbol X X展平为一维向量 x \boldsymbol x x作为输入,相应地得到一个一维向量 h \boldsymbol h h作为输出(隐藏表示)。权重矩阵 W \boldsymbol W W的元素 w i , j w_{i,j} wi,j表示将输入向量的第 j j j个元素映射到输出向量的第 i i i个元素的权重。

为了体现输入和输出的空间结构,方便应用开头的两个原则,我们仍将它们看作是二维的,于是参数从权重矩阵替换为四阶权重张量 W \mathsf W W,其中元素 w i , j , k , l w_{i,j,k,l} wi,j,k,l表示将输入位置 ( k , l ) (k,l) (k,l)处的元素映射到输出位置 ( i , j ) (i,j) (i,j)处的元素的权重。假设 U \boldsymbol U U包含偏执参数,则全连接层可表示为:
h i , j = u i , j + ∑ k ∑ l w i , j , k , l x k , l = u i , j + ∑ a ∑ b v i , j , a , b x i + a , j + b h_{i,j}=u_{i,j}+\displaystyle\sum_k\sum_lw_{i,j,k,l}x_{k,l}=u_{i,j}+\displaystyle\sum_a\sum_bv_{i,j,a,b}x_{i+a,j+b} hi,j=ui,j+k∑l∑wi,j,k,lxk,l=ui,j+a∑b∑vi,j,a,bxi+a,j+b
其中 W \mathsf W W到 V \mathsf V V只是下标上作了参数代换,以此来引出卷积层。可以理解为看待 X \boldsymbol X X中元素的视角从其本身的绝对位置转换到了与目标输出元素坐标的相对位置。
1.平移不变性
现在引用平移不变性原则,即对象在 X \boldsymbol X X中的平移仅引起隐藏表示 H \boldsymbol H H中的平移,故计算 h i , j h_{i,j} hi,j的参数应该与 i i i、 j j j无关:
h i , j = u + ∑ a ∑ b v a , b x i + a , i + b h_{i,j}=u+\displaystyle\sum_a\sum_bv_{a,b}x_{i+a,i+b} hi,j=u+a∑b∑va,bxi+a,i+b
这就是卷积层中的卷积,这一步转化大大减少了神经网络所需的参数。
2.局部性
现在引用局部性原则,即我们不应该使用距离 ( i , j ) (i,j) (i,j)很远的位置来训练计算 h i , j h_{i,j} hi,j的参数,故通过限制 a a a、 b b b的取值范围来限制选取输入元素 x i + a , i + b x_{i+a,i+b} xi+a,i+b的范围:
h i , j = u + ∑ a = − Δ Δ ∑ b = − Δ Δ v a , b x i + a , j + b h_{i,j}=u+\displaystyle\sum^\Delta_{a=-\Delta}\sum^\Delta_{b=-\Delta}v_{a,b}x_{i+a,j+b} hi,j=u+a=−Δ∑Δb=−Δ∑Δva,bxi+a,j+b
这就是卷积层,其中新的权重矩阵 V \boldsymbol V V称为卷积核或滤波器,而卷积神经网络是包含卷积层的一类特殊神经网络。
更通俗地说,卷积层的特点就是让层中的每个神经元只关注图像中自己负责的一个局部。
卷积神经网络的参数大幅减少,但其代价是假设特征平移不变,并且在确定每个隐藏激活值时,每一层只包含局部的信息。
6.1.2 卷积
上述操作称为卷积的理由来源于数学中定义的卷积运算:
( f ∗ g ) ( x ) = ∫ f ( z ) g ( x − z ) d z \displaystyle(f*g)(\boldsymbol x)=\int f(\boldsymbol z)g(\boldsymbol x-\boldsymbol z)\mathrm d\boldsymbol z (f∗g)(x)=∫f(z)g(x−z)dz
当计算对象为离散的时,积分就变成求和:
( f ∗ g ) ( i ) = ∑ a f ( a ) g ( i − a ) (f*g)(i)=\displaystyle\sum_af(a)g(i-a) (f∗g)(i)=a∑f(a)g(i−a)
将其进一步拓展到二维:
( f ∗ g ) ( i , j ) = ∑ a ∑ b f ( a , b ) g ( i − a , j − b ) (f*g)(i,j)=\displaystyle\sum_a\sum_bf(a,b)g(i-a,j-b) (f∗g)(i,j)=a∑b∑f(a,b)g(i−a,j−b)
最终得到的式子与神经网络中的卷积操作类似,但区别是第二个函数/元素的索引。严格来说,卷积层中的卷积是一种错误叫法,实际上其表达的运算为互相关,运算符号为 ⊗ \otimes ⊗。
6.1.3 通道
前面为了简化问题,我们假定图像的每个像素可以用一个标量表示,由此将图像转化为二维张量。但一般情况下,图像包含3个通道(RGB三原色),每个通道都是一个二维张量,因此实际上图像是一个由高度、宽度和颜色通道组成的三维张量,所以我们对 X \mathsf X X和 H \mathsf H H均采用三维索引,同时卷积可以通过四维索引来进一步表示从 X \mathsf X X的通道 c c c到 H \mathsf H H的通道 d d d的映射关系:
h i , j , d = ∑ a = − Δ Δ ∑ b = − Δ Δ ∑ c v a , b , c , d x i + a , j + b , c h_{i,j,d}=\displaystyle\sum^\Delta_{a=-\Delta}\sum^{\Delta}_{b=-\Delta}\sum_cv_{a,b,c,d}x_{i+a,j+b,c} hi,j,d=a=−Δ∑Δb=−Δ∑Δc∑va,b,c,dxi+a,j+b,c
隐藏表示 H \mathsf H H中的通道也称为特征映射,因为每个通道都向后续层提供一组空间化的学习特征。直观上可以想象在靠近输出的底层,一些通道专门识别边缘,一些通道专门识别纹理。
6.2 图像卷积
6.2.1 互相关运算
暂时忽略表示通道的第三维,我们以二维图像数据为例更直观地理解一下上一节的互相关运算是如何操作的。

假设输入是一个 3 × 3 3\times3 3×3的矩阵,我们用 2 × 2 2\times2 2×2的卷积核窗口去套输入矩阵上 2 × 2 2\times2 2×2的区域,每个区域会通过按元素乘法并求和的方法得到一个输出。遍历整个输入矩阵,我们能套到左上角、右上角、左下角和右下角的 4 4 4个 2 × 2 2\times2 2×2的区域,这 4 4 4个区域最终输出一个 2 × 2 2\times2 2×2的矩阵。所有计算如下:
0 × 0 + 1 × 1 + 3 × 2 + 4 × 3 = 19 0\times0+1\times1+3\times2+4\times3=19 0×0+1×1+3×2+4×3=19
1 × 0 + 2 × 1 + 4 × 2 + 5 × 3 = 25 1\times0+2\times1+4\times2+5\times3=25 1×0+2×1+4×2+5×3=25
3 × 0 + 4 × 1 + 6 × 2 + 7 × 3 = 37 3\times0+4\times1+6\times2+7\times3=37 3×0+4×1+6×2+7×3=37
4 × 0 + 5 × 1 + 7 × 2 + 8 × 3 = 43 4\times0+5\times1+7\times2+8\times3=43 4×0+5×1+7×2+8×3=43
假设输入大小为 n h × n w n_h\times n_w nh×nw,卷积核大小为 k h × k w k_h\times k_w kh×kw,则卷积核在输入中从顶部到底部需竖直移动 n h − k h + 1 n_h-k_h+1 nh−kh+1次,从左侧到右侧需水平移动 n w − k w + 1 n_w-k_w+1 nw−kw+1次,因此输出大小为:
( n h − k h + 1 ) × ( n w − k w + 1 ) (n_h-k_h+1)\times(n_w-k_w+1) (nh−kh+1)×(nw−kw+1)
用代码实现互相关运算:
import torch
from torch import nn
from d2l import torch as d2l def corr2d(X, K): """输入张量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
6.2.2 卷积层
对于卷积层来说,被训练的参数为卷积核和标量偏置,而超参数为卷积核的大小。在训练基于卷积层的模型时,我们随机初始化卷积核权重。
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
6.2.3 图像中目标的边缘检测
卷积核的一个简单应用是边缘检测:通过找到像素变化的位置来检测图像中不同颜色的边缘。我们以一个 6 × 8 6\times8 6×8的黑白图像为例,其中 0 0 0为黑色, 1 1 1为白色:
X = torch.ones(6, 8)
X[:, 2:6] = 0
X
tensor([[1., 1., 0., 0., 0., 0., 1., 1.],[1., 1., 0., 0., 0., 0., 1., 1.],[1., 1., 0., 0., 0., 0., 1., 1.],[1., 1., 0., 0., 0., 0., 1., 1.],[1., 1., 0., 0., 0., 0., 1., 1.],[1., 1., 0., 0., 0., 0., 1., 1.]])
现在构造一个可以检测垂直边缘的 1 × 2 1\times2 1×2的卷积核 K \mathsf K K,当进行互相关运算时,若水平相邻两元素相同则输出零,否则输出非零:
K = torch.tensor([[1.0, -1.0]])
Y = corr2d(X, K)
Y
tensor([[ 0., 1., 0., 0., 0., -1., 0.],[ 0., 1., 0., 0., 0., -1., 0.],[ 0., 1., 0., 0., 0., -1., 0.],[ 0., 1., 0., 0., 0., -1., 0.],[ 0., 1., 0., 0., 0., -1., 0.],[ 0., 1., 0., 0., 0., -1., 0.]])
可见在从左到右的方向上, Y \mathsf Y Y中的1表示从白色到黑色的边缘, − 1 -1 −1表示从黑色到白色的边缘。
6.2.4 学习卷积核
通过内置的二维卷积层,我们来学习上述目标检测例子中的卷积核,损失函数采用 Y \mathsf Y Y与输出的平方误差:
#构造具有1个输入通道和1个输出通道、卷积核大小为(1, 2)的卷积层,为简单起见不带偏置
conv2d = nn.Conv2d(1, 1, kernel_size=(1, 2), bias=False) #该二维卷积层使用四维输入输出格式(批量大小、通道、高度、宽度)
X = X.reshape(1, 1, 6, 8)
Y = Y.reshape(1, 1, 6, 7)
#学习率
lr = 0.03 for i in range(10): Y_hat = conv2d(X) l = (Y - Y_hat) ** 2 conv2d.zero_grad() l.sum().backward() #迭代卷积核conv2d.weight.data -= lr * conv2d.weight.grad if (i + 1) % 2 == 0: print(f'epoch {i + 1}, loss {l.sum():.3f}') print(conv2d.weight.data.reshape(1, 2))
运行结果:
epoch 2, loss 6.546
epoch 4, loss 2.196
epoch 6, loss 0.818
epoch 8, loss 0.322
epoch 10, loss 0.129
tensor([[ 0.9557, -1.0296]])
可见学习到的卷积核与上一小节定义的卷积核 K \mathsf K K十分接近。
6.2.6 感受野
对于某一层的任意元素,其感受野是指在前向传播期间可能影响其计算的所有元素。例如本节最初的例子中输出矩阵每个元素的感受野是输入矩阵中对应的 4 4 4个元素。若在输出矩阵后再作用一个卷积核大小为 2 × 2 2\times2 2×2的卷积层,则最终得到的单个输出元素的感受野除了包含输出矩阵中的 4 4 4个元素外,还包含输入矩阵中的 9 9 9个元素。
对于更深的网络,感受野的大小甚至可能大于输入的实际大小。
6.3 填充和步幅
除了输入和卷积核的大小,填充和步幅也会影响输出的大小。
6.3.1 填充
在应用多层卷积时,边缘像素的信息会在前向传播的过程中逐渐丢失。解决该问题的简单方法是填充:在输入图像的边缘填充元素(通常为 0 0 0):

如果我们添加 p h p_h ph行填充和 p w p_w pw列填充,则输出形状为:
( n h − k h + p h + 1 ) × ( n w − k w + p w + 1 ) (n_h-k_h+p_h+1)\times(n_w-k_w+p_w+1) (nh−kh+ph+1)×(nw−kw+pw+1)
通常我们设置 p h = k h − 1 p_h=k_h-1 ph=kh−1和 p w = k w − 1 p_w=k_w-1 pw=kw−1使输入和输出的形状相同,这样构建网络时可以更容易地预测每个层的输出形状。宽度和高度的两侧会设置相等的填充数目,如果 p h p_h ph或 p w p_w pw为奇数则尽量在两侧均匀分配。因此卷积核的高度和宽度通常为奇数来使 p h p_h ph或 p w p_w pw为偶数。
pytorch卷积层的填充使用padding关键字参数配置:
#高度和宽度两侧边各自的填充分别为2和1
conv2d = nn.Conv2d(1, 1, kernel_size=(5, 3), padding=(2, 1))
6.3.2 步幅
在前面的例子中,我们默认卷积核每次滑动一个元素。有时候为了高效计算或者缩减采样次数,卷积核可以每次滑动多个元素。每次滑动元素的数量称为步幅。
在 5 × 5 5\times5 5×5的输入中,使用 2 × 2 2\times2 2×2的卷积核和高度为 3 3 3、宽度为 2 2 2的步幅,卷积层的计算过程如下:

如果垂直步幅为 s h s_h sh、水平步幅为 s w s_w sw,输出形状为:
⌊ ( n h − k h + p h + s h ) / s h ⌋ × ⌊ ( n w − k w + p w + s w ) / s w ⌋ \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⌋
如果我们设置 p h = k h − 1 p_h=k_h-1 ph=kh−1和 p w = k w − 1 p_w=k_w-1 pw=kw−1,则输出形状简化为:
⌊ ( n h + s h − 1 ) / s h ⌋ × ⌊ ( n w + s w − 1 ) / s w ⌋ \lfloor(n_h+s_h-1)/s_h\rfloor\times\lfloor(n_w+s_w-1)/s_w\rfloor ⌊(nh+sh−1)/sh⌋×⌊(nw+sw−1)/sw⌋
更进一步,如果输入的高度和宽度可以被垂直步幅和水平步幅整除,则输出形状为:
( n h / s h ) × ( n w / s w ) (n_h/s_h)\times(n_w/s_w) (nh/sh)×(nw/sw)
pytorch卷积层的填充使用stride关键字参数配置:
#垂直步幅和水平步幅分别为3和4
conv2d = nn.Conv2d(1, 1, kernel_size=(5, 3), padding=(2, 1), stride=(3, 4))
6.4 多输入多输出通道
6.4.1 多输入通道
在通道维度为多输入单输出的情况下,需要构造一个具有与输入数据相同输入通道数的卷积核。每个通道的输入和卷积核各自进行互相关运算,再对所有通道的运算结果求和得到输出:

import torch
from d2l import torch as d2l def corr2d_multi_in(X, K): #按第0个维度(通道维度)遍历X和K进行互相关运算再求和,其中x和k是X和K每个通道下的二维张量return sum(d2l.corr2d(x, k) for x, k in zip(X, K))
6.4.2 多输出通道
在通道维度为多输入多输出的情况下,需要构造与输出通道同等数量的三维卷积核,每个卷积核以上述方式生成一个通道的输出张量。
def corr2d_multi_in_out(X, K): #遍历四维张量K每个通道的三维卷积核k与X进行多输入单输出互相关运算#再将每个通道的二维运算结果在第0维上堆叠成三维输出张量return torch.stack([corr2d_multi_in(X, k) for k in K], 0)
6.4.3 1 × 1 1\times1 1×1卷积层
k h = k w = 1 k_h=k_w=1 kh=kw=1的 1 × 1 1\times1 1×1卷积在复杂深层网络的设计中很流行,它不识别空间模式,只融合通道信息。下图使用两个三维卷积核将 3 3 3个输入通道转化为 2 2 2个输出通道:

1 × 1 1\times1 1×1卷积相当于将每个像素看作一个样本,而每个通道看作一种特征,以此对每个像素位置应用一次全连接层,得到每个像素的单通道输出。多通道输出则相当于应用了多次权重不同的全连接层。该全连接层的输入形状为 n h n w × c i n_hn_w\times c_i nhnw×ci,权重矩阵形状为 c o × c i c_o\times c_i co×ci,输出形状为 n h n w × c o n_hn_w\times c_o nhnw×co。
def corr2d_multi_in_out_1x1(X, K): c_i, h, w = X.shape c_o = K.shape[0] X = X.reshape(c_i, h * w) K = K.reshape(c_o, c_i) #应用全连接层Y = torch.matmul(K, X) return Y.reshape(c_o, h, w)
总结一下,对于多输入多输出的卷积层来说:
- 输入 X \boldsymbol X X: c i × n h × n w c_i\times n_h\times n_w ci×nh×nw
- 核 W \boldsymbol W W: c o × c i × k h × k w c_o\times c_i\times k_h\times k_w co×ci×kh×kw
- 偏置 B \boldsymbol B B: c o × c i c_o\times c_i co×ci
- 输出 Y \boldsymbol Y Y: c o × m h × m w c_o\times m_h\times m_w co×mh×mw
Y = X ⊗ W + B \boldsymbol Y=\boldsymbol X\otimes\boldsymbol W+\boldsymbol B Y=X⊗W+B
其中 c o c_o co和 c i c_i ci的表示可以更通俗地理解为将通道 i i i映射到通道 o o o的参数。
6.5 汇聚层
汇聚层具有双重目的:降低卷积层对位置的敏感性,同时降低对空间降采样表示的敏感性。
6.5.1 最大汇聚和平均汇聚
与卷积层类似,汇聚层的运算也依赖一个固定形状的窗口,该窗口根据其步幅大小再输入的所有区域上滑动,为窗口遍历的每个位置计算一个输出。不同的是,汇聚成不包含学习参数,我们通常计算汇聚窗口中所有元素的最大值和平均值,分别称为最大汇聚和平均汇聚。

通过求最值或平均,即使图像的像素在一定范围内移动,或者随机抽去图像中的一部分行和列,对最终输出也不会产生显著影响。
汇聚窗口形状为 p × q p\times q p×q的汇聚层称为 p × q p\times q p×q汇聚层,其操作称为 p × q p\times q p×q汇聚。汇聚层也称为池化层。
汇聚层的代码实现除了计算输出的操作,其余与卷积层类似:
import torch
from torch import nn
from d2l import torch as d2ldef pool2d(X, pool_size, mode='max'):p_h, p_w = pool_sizeY = 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
6.5.2 填充和步幅
与卷积层一样,汇聚层也可以通过填充和步幅获得所需的输出形状。以最大汇聚为例,pytorch通过padding和stride关键字参数对填充和步幅进行设置:
#当参数只传入一个整数时,设定的形状为高宽相等的方阵
pool2d = nn.MaxPool2d(3, padding=1, stride=2)
6.5.3 多通道
在处理多通道输入数据时,汇聚层在每个输入通道上单独运算,而不像卷积层一样将通道进行融合,因此汇聚层的输出通道数和输入通道数相同。
6.6 卷积神经网络(LeNet)
LeNet是最早发布的卷积神经网络之一,在1989年以识别手写数字为目的被AT&T贝尔实验室的研究员Yann LeCun提出。
6.6.1 LeNet
LeNet-5由以下两个部分组成:
- 卷积编码器:由两个卷积层组成。
- 全连接层稠密块:由三个全连接层组成。

每个卷积层使用 5 × 5 5\times5 5×5卷积核和一个sigmoid激活函数,并紧随一个步幅为 2 2 2的 2 × 2 2\times2 2×2平均汇聚操作。第一个卷积层有 6 6 6个输出通道,第二个卷积层有 16 16 16个输出通道。虽然ReLU和最大汇聚层更有效,但它们在20世纪90年代还没有出现。
为了将卷积块的输出传递给稠密块,我们必须在小批量中展平每个样本。稠密块的三个全连接层分别有 120 120 120、 84 84 84和 10 10 10个输出,输出层的 10 10 10维分别是数字被识别为 0 ∼ 9 0\sim9 0∼9的概率。
import torch
from torch import nn
from d2l import torch as d2lnet = nn.Sequential(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))
除了去掉了最后一层的高斯激活,这个网络与最初的LeNet-5一致。
6.6.2 模型训练
本章的模型训练的函数中包括了计算设备的选择。
batch_size = 256
#使用Fashion-MNIST数据集
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)#使用GPU计算模型在数据集上的精度
def evaluate_accuracy_gpu(net, data_iter, device=None):if isinstance(net, nn.Module):net.eval()#如果没有指定计算设备,则检测网络中第一个参数所在的设备if not device:device = next(iter(net.parameters())).device#统计正确预测的数量和总预测的数量metric = d2l.Accumulator(2)with torch.no_grad():for X, y in data_iter:#将数据移动到指定的计算设备#X可能为list或tensor,需分情况操作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_epochs, lr, device):#采用Xavier均匀分布初始化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', 'text acc'])#timer用于计算训练效率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) 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] #每20%的batch或最后一个batch更新训练曲线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_epochs / timer.sum():.1f} examples/sec 'f'on {str(device)}') lr, num_epochs = 0.9, 10
train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
plt.show()
训练结果:
loss 0.457, train acc 0.829, test acc 0.822
37214.7 examples/sec on cuda:0

相关文章:
【动手学深度学习】#6 卷积神经网络
主要参考学习资料: 《动手学深度学习》阿斯顿张 等 著 【动手学深度学习 PyTorch版】哔哩哔哩跟李牧学AI 由于本系列一开始跳过了第一章引言部分,因此系列编号比书本章节编号提前。现改为和书本统一(因为之前自己的原始笔记也是按照书本章节编…...
认识一家公司:瑞芯微(Rockchip Electronics Co., Ltd.)以及旗下的两款芯片RK3288\RK3588
瑞芯微(Rockchip Electronics Co., Ltd.)简介 一、公司概况 瑞芯微电子股份有限公司(简称“瑞芯微”)成立于2001年,总部位于中国福建省福州市,是一家专注于集成电路设计与研发的高新技术企业。公司采用Fa…...
爬虫面试题
总结一下最近面试遇到的笔试题 1、解释Python中的init方法的作用。 在Python中,__init__方法是一种特殊的构造方法,主要用于在创建类的实例时初始化对象。至少接受至少一个参数:self,它是对当前实例的引用,可以通过添加其他参数…...
Netty——零拷贝
文章目录 1. 什么是零拷贝?2. 为什么需要零拷贝?2.1 传统 I/O 的拷贝流程2.2 零拷贝的优化2.2.1 通过 sendfile 系统调用2.2.2 通过 mmap (内存映射) 系统调用 3. Netty 实现零拷贝的方式3.1 文件传输优化:FileRegion 封装3.2 直接内存 (Dire…...
Java制作简单的聊天室(复习)
设计的知识点:几乎包含java基础的全部知识点(java基础语法,java基础进阶:双列集合,io流,多线程,网络编程等) 代码如下 客户端: 服务器采用的时多线程的循环多线程的方式…...
ES 字段的映射定义了字段的类型及其行为
在 Elasticsearch 中,字段的映射定义了字段的类型及其行为。你提供的 content_answer 字段映射如下: Json 深色版本 "content_answer": { "type": "text", "fields": { "keyword": { …...
Android开发点击字符串web链接跳到系统浏览器上
Android开发点击字符串web链接跳到系统浏览器上 直接上代码:用到你就拿去用 public static void performItemUrlClick(View view, String contentUrl) {if (!TextUtils.isEmpty(contentUrl)) {Intent intent new Intent();if (!contentUrl.startsWith("http…...
运维规则之总结(Summary of Operation and Maintenance Rules)
运维规则之总结 在运维领域,经验和流程往往决定了系统的稳定性与可靠性。一个运维人,总结出了以下10条运维规则,涵盖了从基础管理到高级策略的全面内容,旨在帮助运维人员更好地应对各种挑战,确保系统的平稳运行。 1.…...
智能家居赋能宠物经济:未来宠物行业的另一片蓝海
一、引言:宠物经济的范式转移 随着城市化进程的加速,宠物在现代家庭中的地位日益重要,宠物经济蓬勃发展。近年来,智能家居技术的兴起为宠物行业带来了新的变革,从传统的情感消费模式向技术赋能的精细化养宠模式转变。…...
C++Primer学习(13.6 对象移动)
13.6 对象移动 新标准的一个最主要的特性是可以移动而非拷贝对象的能力。如我们在13.1.1节(第440页)中所见,很多情况下都会发生对象拷贝。在其中某些情况下,对象拷贝后就立即被销毁了。在这些情况下,移动而非拷贝对象会大幅度提升性能。 如我…...
RHCE工程师特训指南
RHCE(红帽认证工程师)是Linux领域极具含金量的认证之一,其考试以实操为主,注重系统管理、网络服务配置及自动化运维能力。以下内容可帮助对RHCE考生高效规划学习路径。 一、RHCE认证概述 认证结构 RHCE认证分为两部分ÿ…...
内核、进程和线程---操作系统
操作系统 操作系统位于用户程序和硬件之间,通过系统调用提供接口可以让应用程序去使用硬件,但是硬件资源的管理和安全控制由操作系统负责。 用户空间和内存空间 在计算机系统中,内存可以分为两大区域:内核空间(Ker…...
如何在 Postman 中上传图片并在请求中正确引用?
Postman 是一款常用的 API 测试工具,它不仅可以测试 API 的请求和响应,还支持多种数据格式包括图片。如何在 Postman 中传输图片? Postman 如何上传图片并在请求中使用教程...
平板实现 adb connect 连接的步骤
1. 检查设备的开发者选项 确保平板设备已开启开发者模式,并启用了USB调试。 2. 检查设备和电脑的网络连接 确保平板和电脑连接到同一个Wi-Fi网络,确认设备的 IP 地址是否正确。 通过 ping 命令测试: ping 192.168.3.243. 通过USB线进行初…...
安全+低碳+高效:Acrel-3000助力企业打造未来型电能管理体系-安科瑞黄安南
一 背景 电能因为方便传输、易于转换、便于控制等特性,成为广大企事业单位生产、办公最主要的能量来源。双碳背景下,由于电能清洁、高效、零排放的特点,能源消费侧将逐步以电代煤、以电代油、以电代气,形成以电为中心的能源消费体…...
专注自习室:番茄工作法实践
专注自习室:番茄工作法实践 我需要一个任务管理工具,但在网上找了很多都找不到合适的工具。市面上的大多数产品过于强调任务完成性,给我带来了很强的心理压力,这种压力最终反而降低了我的工作效率。于是我决定自己动手࿰…...
docker save如何迁移镜像更节省空间?
文章目录 方法一:使用docker save命令方法二:直接保存多个镜像到一个tar文件哪个方法更节省磁盘空间?空间效率对比实际测试示例其他优势结论 如何用脚本迁移加载镜像 迁移镜像时候,往往会碰到基础镜像相同的很多镜像需要迁移&…...
LeetCode算法题(Go语言实现)_16
题目 给定一个二进制数组 nums 和一个整数 k,假设最多可以翻转 k 个 0 ,则返回执行操作后 数组中连续 1 的最大个数 。 一、代码实现 func longestOnes(nums []int, k int) int {left, zeroCnt, maxLen : 0, 0, 0for right : 0; right < len(nums); …...
CORDIC算法:三角函数的硬件加速革命——从数学原理到FPGA实现的超高效计算方案
计算机该如何求解三角函数?或许你的第一印象是采用泰勒展开,或者采用多项式进行逼近。对于前者,来回的迭代计算开销成本很大;对于后者,多项式式逼近在较窄的范围內比较接近,超过一定范围后,就变…...
JVM 面经
1、什么是 JVM? JVM 就是 Java 虚拟机,它是 Java 实现跨平台的基石。程序运行之前,需要先通过编译器将 Java 源代码文件编译成 Java 字节码文件;程序运行时,JVM 会对字节码文件进行逐行解释,翻译成机器码指令&#x…...
SEO(搜索引擎优化)详解
SEO(搜索引擎优化)详解 SEO是Search Engine Optimization的缩写,中文称为"搜索引擎优化"。它是指通过一系列技术和方法,提高网站在搜索引擎自然(非付费)搜索结果中的排名,从而获得更…...
Ubuntu平台下安装Node相关环境
说明:在进行VUE、TS等开发需要用到NodeJS相关环境,不同的项目有时候需要不同的Node版本支撑。本文将详细讲解NVM、Node、Yarn、PM2等环境安装的实施步骤。 测试服务器环境:22.04 LTS。 1. NVM 定义:Node Version Manager&#x…...
deepseek大模型一体机与deepseek的关系
deepseek大模型一体机与deepseek的关系 一、deepseek大模型一体机是什么 DeepSeek大模型一体机是由深度求索(DeepSeek)公司推出的软硬件一体化AI解决方案,旨在为企业提供高效、便捷的大模型部署和应用能力。 二、deepseek大模型一体机与de…...
Windows Server 2025 使用 IIS 搭建 ASP.NET 3.5 网站
开启远程桌面 参考文章Windows server开启远程桌面教程打开服务管理器。ECS 配置安全组,开启 3389Telnet 验证网络联通性 telnet x.x.x.x 338安装 Windows App,登录验证 安装 ASP.NET 3.5 1.参考文章Windows Server 2012安装 .NET Framework 3.5和 Wi…...
高等数学-第七版-上册 选做记录 习题7-2
1. 2....
基于Promise链式调用的多层级请求性能优化
代码优化-循环嵌套关联请求 1. 背景 在实际开发中,我们经常会遇到需要嵌套关联请求的场景,比如: 获取项目列表获取项目详情获取项目进度 2. 问题 在这种场景下,我们可能会遇到以下问题: 串行请求瀑布流ÿ…...
【强化学习】基于深度强化学习的微能源网能量管理与优化策略研究【Python】
目录 主要内容 程序要点 2.1 微能源网系统组成 2.2 强化学习及Q学习算法 部分代码 运行结果 下载链接 主要内容 该程序借助深度 Q 网络(DQN),学习预测负荷、风 / 光可再生能源功率输出及分时电价等环境信息,运用…...
楼宇自控借何种技术,驱动建筑迈向高效绿色
在全球积极倡导可持续发展的大背景下,建筑行业作为能源消耗和碳排放的大户,实现高效绿色发展迫在眉睫。楼宇自控系统凭借其先进的技术手段,成为推动建筑向高效绿色转型的关键力量。那么,楼宇自控究竟借助哪些技术,让建…...
监控易一体化运维:监控易机房管理,打造高效智能机房
在数字化浪潮中,企业对数据中心和机房的依赖程度与日俱增,机房的稳定运行成为业务持续开展的关键支撑。信息化的变迁,见证了机房管理从传统模式向智能化、精细化转变的过程。今天,就为大家深度剖析监控易在机房管理方面的卓越表现…...
简记_FPGA 硬件最小系统设计
一、FPGA板级设计的五要素 1.1、电源电路 核心电压:一般为固定值 IO电压:FPGA的IO分为多个bank,同一个bank的不同IO引脚电压相同,不同bank的电压可以不同 辅助电压:除了核心电压和IO电压,FPGA工作所需的…...
