lec 1:Regression
1.5 Linear neural networks for regression线性神经网络的回归
1.5.1 Linear regression
输入特征的仿射变换Affine transformation(特征的加权和的线性变换和附加偏差的变换)
损失函数为平方误差squared error:
当训练模型时,找到在所有训练例子中使平均损失最小的参数(w∗,b∗):
即使我们不能解析地求解模型,我们仍然可以使用梯度下降算法来训练模型(迭代更新参数的方向,逐步降低损失函数)。
损失函数的导数是数据集中每个示例的损失的平均值。非常慢,因为我们必须通过整个数据集来进行更新。
1.5.2 Minibatch stochastic gradient descent 小批量随机梯度下降
策略:做一小批(Minibatch)观察。大小(size)取决于许多因素(内存、加速器的数量、图层的选择、总数据集的大小)。32到256之间,2^m(2的m次幂)这样的形式是一个好的开始。
●小批量随机梯度下降(SGD):
初始化参数。在每次迭代t中,
用|B|训练示例随机抽样一个小批量Bt;
计算小批量w.r.t.的平均损失梯度因素;
使用η作为学习速率,执行更新:
小批量处理的大小和学习率是由用户定义的。这种在训练循环中没有更新的可调参数被称为超参数(hyperparameters)。
经过多次迭代训练后,我们记录了估计的参数(ˆw,ˆb)。这些不是损失的最小化。
1.5.3 Maximum likelihood estimation最大似然估计
最小化平方误差损失相当于线性模型在加性高斯噪声下的最大似然估计:
线性回归是一个单层全连接的神经网络,有d个输入:x1,……,xd 和一个单一的输出。
1.5.4 Synthetic data合成数据
从标准正态set(X∈R^1000×2)中生成1000个examples,用于训练集和验证集。
设置w = [2,−3.4]^T,b=4.。绘制ε~N(0,0.01²)。
#Synthetic data
import random
import torch
from d2l import torch as d2l
data = d2l.SyntheticRegressionData(w=torch.tensor([2,-3.4]), b=4.2,
noise=0.01, num_train=1000, num_val=1000, batch_size=32)
模型训练需要多次传递一个数据集,每次使用一个小批处理来更新模型。数据处理器生成批量大小(batch_size)的小批量。每个小批处理都是一个特征和标签的元组。
在训练train模式下,我们以随机的顺序读取数据。在验证test模式下,按预定义的顺序读取数据可能对调试很重要。
X, y = next(iter(data.train_dataloader())) # inspect first minibatch
X.shape, y.shape # X: [32, 2], y: [32, 1]
len(data.train_dataloader()) # no. of batches: 32
1.5.5 Implementing linear regression 线性回归的实现
使用具有学习率lr=0.03和max_epochs=3的小批量SGD训练线性回归模型。在每个epoch中,我们遍历整个训练数据集,并通过每个示例传递一次。在每个epoch结束时使用验证数据集来测量模型的性能。
通过从N(0,0.01²)采样来初始化权重,并将偏差设置为0。
model = d2l.LinearRegression(lr=0.03)
trainer = d2l.Trainer(max_epochs=3)
trainer.fit(model, data)
w, b = model.get_w_b()
#compute differences
data.w - w.reshape(data.w.shape)
data.b - b
# error in estimating w: tensor([0.0025, -0.0070])
# error in estimating b: tensor([0.0096])
图中结果表明:估计的参数接近于真值。
•对于深度模型,不存在参数的唯一解。在机器学习中,我们不太关心恢复真正的基础参数,而是更关心能够导致准确预测的参数。
1.5.6 Generalization泛化
机器学习的基本问题是:发现能够泛化的模式。拟合更接近训练数据而不是基本分布的现象是过拟合overfitting,而对抗过拟合的技术被称为正则化regularization。
在标准的监督学习中,我们假设训练数据和测试数据是从相同的分布中独立地抽取的。
训练误差Training error:根据训练集计算的统计量,
泛化错误Generalization error:一个期望的w.r.t.基础分布,
通过将模型应用于独立的测试集来估计泛化误差.
当我们有简单的模型和丰富的数据时,训练误差和泛化误差往往会很接近。当我们使用更复杂的模型或更少的例子时,我们期望训练误差会减少,但泛化差距会增加。
深度神经网络在实践中可以很好地推广,但它们太强大了,我们无法仅根据训练错误就得出结论。我们必须依靠验证(保留数据)错误来证明泛化。
——————————————————————————————————————————
当训练误差和验证误差很大,且它们之间的差距很小时,可能会出现不拟合的情况underfitting(模型表达不足)。
训练误差显著低于验证误差表示过拟合。
使用一个d度的多项式:来估计给定单一特征x的标签y。
高阶多项式的训练误差总是较低的。一个具有d = n的多项式可以完美地拟合训练集。
随着我们增加训练数据时,泛化误差通常会减少。如果有更多的数据,我们可以拟合更复杂的模型。只有当有成千上万的训练例子可用时,深度学习才优于线性模型。
当训练数据稀缺时,我们可能没有足够的保留数据。在K倍交叉验证中,数据被分成K个不重叠的子集。模型训练和验证执行K次,每次在K−1个子集上进行训练,并对未使用的子集进行验证。训练和验证误差的估计是平均了K个实验的结果。
1.5.7 Weight decay 权重衰减
权重衰减可以通过限制参数采取的值来缓解过拟合。为了将w缩小到零,将其范数作为惩罚项(penalty term)添加到最小化损失的问题中:
正则化常数λ≥0是使用验证集选择的一个超参数。越小的λ对应于约束越小的w。我们通常不规范(正则化)偏差项b。
w的小批量SGD更新变为:
从上述方程式中生成数据,为了使过拟合效果更明显,设置n = 20和d = 200。
定义其中的类、数据和权重衰减:
#如何使用权重衰减(weight decay)来正则化模型
#定义了一个L2正则化项的函数l2_penalty,用来计算权重w的L2范数的平方除以2
def l2_penalty(w):
return (w**2).sum() / 2
#创建数据集data
data = Data(num_train=20, num_val=100,num_inputs=200, batch_size=5)
trainer = d2l.Trainer(max_epochs=10)
#创建了一个WeightDecay模型对象model,并将权重衰减参数wd设置为0,学习率lr设置为0.01。(对照)
model = WeightDecay(wd=0, lr=0.01)
#通过将model.board.yscale设置为'log',可以在训练过程中将损失函数的值以对数尺度显示
#有助于观察权重衰减的效果。
model.board.yscale='log'
trainer.fit(model, data)
l2_penalty(model.get_w_b()[0]) # 0.1318
#重新创建了一个WeightDecay模型对象model,将权重衰减参数wd设置为3,学习率lr设置为0.01(实验)
model = WeightDecay(wd=3, lr=0.01)
model.board.yscale='log'
trainer.fit(model, data)
l2_penalty(model.get_w_b()[0]) # 0.0145 比0.1318降低很多
在没有使用权值衰减时,我们非常糟糕的过拟合了。
随着权值的衰减,训练误差增加,而验证误差减小。
lec5:Convolutional neural networks
卷积神经网络
5.1 Padding and stride
在CNN中,经过多次连续卷积后,输出会比输入小得多。例如,10层5×5卷积将240×240图像减少到200×200像素,切割出30%的图像。
为了更多地控制输出大小,使用填充padding和分层卷积strided convolutions。
应用卷积层时图像周长perimeter 像素的损失:图描述了像素利用作为卷积核大小和图像中位置的函数。角落的像素很少使用。
在输入图像的边界周围添加额外的填充filler像素。将额外的像素设置为零。
垫Pad 3 x 3的输入,将其大小增加到5 x 5。输出大小增加到4 x 4。
5.1.1 Padding填充
如果我们添加ph行的填充(∼一半在上,一半在下)和pw列的填充(∼一半在左,一半在右),输出形状为:
输出的高度和宽度分别增加了ph和pw。
输入和输出相同大小:设置ph = kh−1,pw = kw−1。
①在构建网络时,更容易预测每层的输出形状。
②如果kh为奇数,则在位于高度的两侧填充ph/2行。
③如果kh为偶数,顶部填充ph/2行,底部填充ph/2c行。
④以相同的方式填充宽度的两侧。
CNN通常使用高度和宽度都为奇数(1、3、5或7)的卷积核。
#使用PyTorch中的nn.LazyConv2d模块来进行二维卷积操作
import torch
from torch import nn
from d2l import torch as d2l
#创建了一个随机初始化的8x8的张量X
X = torch.rand(size=(8, 8))
#形状变换为(1, 1, 8, 8),表示(batch_size, channels, height, width)的形状。
X = X.reshape((1, 1) + X.shape) # torch.Size([1, 1, 8, 8])
#创建了一个nn.LazyConv2d对象conv2d,指定输入通道数为1,卷积核大小为3x3,填充(padding)为1
#即在图像周围补充1个像素。对输入X进行卷积操作conv2d(X),输出的形状为(1, 1, 8, 8),与输入形状相同
conv2d = nn.LazyConv2d(1, kernel_size=3, padding=1) # 3 x 3 kernel, padding: 1 (all sides)
conv2d(X).shape # torch.Size([1, 1, 8, 8])
#创建了一个nn.LazyConv2d对象conv2d,指定输入通道数为1,卷积核大小为(5, 3),填充(padding)为(2, 1)
#即在高度上补充2个像素,在宽度上补充1个像素。输出的形状仍然为(1, 1, 8, 8),与输入形状相同。
conv2d = nn.LazyConv2d(1, kernel_size=(5,3), padding=(2,1)) # (5-1)/2 =2, (3-1)/2=1
conv2d(X).shape # torch.Size([1, 1, 8, 8])
case1:8×8→(四周+1pixel)→10×10
case2:8×8→(上下各加2pixel,左右各加1pixel)→12×10
5.1.2 Stride步幅
当计算互相关cross-correlation时,从输入张量左上角的卷积窗口开始,然后向下和向右滑动所有位置,每次一个元素。
为了提高计算效率或降采样,一次移动窗口为多个元素,跳过中间位置。如果卷积内核很大,因为它捕获了一个大面积的底层图像。
No. of rows and columns traversed per slide: stride步幅。
到目前为止,我们用步幅为1来表示高度和宽度。
垂直3步,水平2步。
当生成第一列的第2个元素时,卷积窗口会向下滑动3行。
当生成第一行的第2个元素时,卷积窗口会向右滑动2列。当卷积窗口继续向右滑动2列时,就没有输出。
当高度步幅为sh,宽度步幅为sw时,输出形状为:
如果并且
输出是
如果输入的高度和宽度可按高度和宽度上的步幅整除,则输出形状为
>将高度和宽度上的步幅设置为2,从而将输入的高度和宽度减半:
conv2d = nn.LazyConv2d(1, kernel_size=3, padding=1, stride=2) # (8-3+1+2)/2 = 4
conv2d(X).shape
# output:
# torch.Size([1, 1, 4, 4])
>更复杂的例子:
conv2d = nn.LazyConv2d(1, kernel_size=(3,5), padding=(0,1), stride=(3,4))
conv2d(X).shape # floor((8-3+0+3)/3)=2, floor((8-5+1+4))/4)=2
# output:
# torch.Size([1, 1, 2, 2])
5.2channels
5.2.1 Multiple input channels 多输入通道
如果卷积核的窗口是kh×kw,我们需要一个包含每个输入通道的kh×kw张量的核(当输入通道的数目 ci > 1)。连接ci张量得到一个ci×kh×kw卷积核。
由于输入核和卷积核都有ci通道,对每个通道的2维输入张量和2维卷积核张量进行互相关,并将ci个结果(通道之和sum over channels)相加得到2维张量。
#定义了一个函数corr2d_multi_in,用于计算多输入通道的二维互相关运算。
#函数接受两个输入:X表示输入张量,K表示卷积核张量,两者都是三维张量。
#函数的实现通过使用zip(X, K)将输入张量和卷积核张量按通道组合起来,
#然后对每对通道进行二维互相关运算,并将结果求和。
#最终返回的是一个二维张量,表示所有通道的互相关运算结果的和。
def corr2d_multi_in(X, K):
return sum(d2l.corr2d(x, k) for x, k in zip(X, K))
#输入X是一个形状为(2, 3, 3)的张量,表示有两个通道,每个通道的大小为3x3。
X = torch.tensor([[[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]],
[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]])
#卷积核张量K是一个形状为(2, 2, 2)的张量,表示有两个通道,每个通道的卷积核大小为2x2。
K = torch.tensor([[[0.0, 1.0], [2.0, 3.0]], [[1.0, 2.0], [3.0, 4.0]]])
#调用corr2d_multi_in(X, K)将对X的每个通道与K的对应通道进行二维互相关运算
#并将结果相加,最终返回一个形状为(2, 2)的张量,表示最终的互相关运算结果。
corr2d_multi_in(X, K) # tensor([[ 56., 72.], [104., 120.]])
到目前为止,我们只有一个输出通道,但在每一层都有多个通道是必要的。随着我们在网络中的深入,我们增加信道维度,降采样downsampling以权衡空间分辨率换取更大的信道深度。
让ci和co分别我输入通道和输出通道的个数,kh和kw分别为内核的高度和宽度。为了获得多个通道的输出,为每个输出通道创建一个ci×kh×kw核张量,并将它们连接concatenate 到输出通道维度上,得到一个卷积核。
每个输出通道的互相关结果是由输出通道的卷积核计算,并从输入张量的所有通道获取输入。
通过将K的核张量与K+1和K+2连接起来,构造具有3个输出通道的平凡卷积核。
#定义了一个函数corr2d_multi_in_out,用于计算多输入通道和多输出通道的二维互相关运算。
def corr2d_multi_in_out(X, K): # calculate output of multiple channels
#使用torch.stack将每个卷积核对应的互相关运算结果堆叠起来
#最终得到一个四维张量,表示多输出通道的互相关运算结果。
return torch.stack([corr2d_multi_in(X, k) for k in K], 0)
K = torch.stack((K, K + 1, K + 2), 0)
K.shape # torch.Size([3, 2, 2, 2])
X.shape # torch.Size([2, 3, 3])
#调用corr2d_multi_in_out(X, K)将对X的每个通道与K的对应通道进行二维互相关运算,并将结果堆叠起来
#最终返回一个形状为(3, 2, 2)的张量,表示三个输出通道的互相关运算结果。
corr2d_multi_in_out(X, K) # output contains 3 channels
# tensor([[[ 56., 72.], [104., 120.]], # result of 1st channel is consistent
# [[ 76., 100.], [148., 172.]],
# [[ 96., 128.], [192., 224.]]])
5.2.3 1 x 1 convolutional layer 1×1卷积层
1 x 1的卷积最初可能没有意义,因为卷积关联了相邻的像素,但在深度网络中很流行。
由于使用最小窗口,1 x 1卷积不能识别高度和宽度维度上相邻元素之间的交互模式。在信道维度上只进行1 x 1卷积的计算。
图:使用1 x 1卷积核计算3个输入通道和2个输出通道计算互相关。输入和输出具有相同的高度和宽度。输出中的每个元素都是由输入图像中相同位置的元素的线性组合推导出来的。
一个1 x 1的卷积层构成一个在每个像素位置应用的全连接层fully connected layer,将ci输入值转换为co输出值,权重在像素位置之间绑定。它需要共同的权重(加上偏差)。
使用完全连接的图层实现1×1卷积。需要对矩阵乘法前后的数据形状进行调整。
X = torch.normal(0, 1, (3, 3, 3)) # X.shape: torch.Size([3, 3, 3])
K = torch.normal(0, 1, (2, 3, 1, 1)) # K.shape: torch.Size([2, 3, 1, 1])
#函数corr2d_multi_in_out_1x1,用于实现1x1卷积操作。
#1x1卷积是指卷积核大小为1x1的卷积操作,通常用于调整通道数或者进行特征融合。
#函数接受两个输入:X表示输入张量,K表示卷积核张量,两者都是四维张量。
def corr2d_multi_in_out_1x1(X, K):
#函数首先获取输入张量X的通道数c_i,以及卷积核张量K的输出通道数c_o
c_i, h, w = X.shape
c_o = K.shape[0]
#将输入张量Xreshape为(c_i, h * w)的形状,其中h * w表示每个通道展平后的长度
#将卷积核张量Kreshape为(c_o, c_i)的形状,以便进行矩阵乘法操作
X = X.reshape((c_i, h * w)) # X.shape: torch.Size([3, 9])
K = K.reshape((c_o, c_i)) # K.shape: torch.Size([2, 3])
#使用torch.matmul进行矩阵乘法,得到一个形状为(c_o, h * w)的中间结果张量Y
Y = torch.matmul(K, X) # Y.shape: torch.Size([2, 9])
#将Yreshape为(c_o, h, w)的形状,即为最终的输出张量。
return Y.reshape((c_o, h, w)) # Y.shape: torch.Size([2, 3, 3]) 即返回形状为(2, 3, 3)
当执行1×1卷积时,上述函数相当于之前实现的互相关函数corr2d_multi_in_out
#使用corr2d_multi_in_out_1x1和corr2d_multi_in_out两个函数分别计算输入X和卷积核K的卷积结果
#并将结果分别存储在Y1和Y2中。
Y1 = corr2d_multi_in_out_1x1(X, K)
Y2 = corr2d_multi_in_out(X, K)
#使用torch.abs计算Y1和Y2之间的绝对差值,并使用sum将所有元素相加得到总的差值
#使用assert语句检查总的差值是否小于1e-6,如果满足条件,则不会抛出异常,表示两种卷积方法得到的结果在误差范围内一致。
assert float(torch.abs(Y1 - Y2).sum()) < 1e-6
5.3 Pooling 池化
终极任务经常会问一些关于图像的全局问题(它是否包含一只猫?)。因此,最后一层final layer的单位应该对整个输入都很敏感。
通过逐步聚合信息,我们学习了一个全局表示global representation,同时在处理的中间层保留了卷积层的优势。
网络越深,每个隐藏节点所敏感的感受域就越大。降低空间分辨率加速了这一过程,因为卷积核覆盖了一个更大的有效区域。
当检测低级特征(边)时,表示应该对平移不变。在现实中,物体很难出现在同一个地方。相机的振动可能会使一切改变一个像素。池化层Pooling layers降低了卷积层对位置和空间降样本表示的敏感性。
与卷积层一样,池运算符由一个固定形状的窗口组成,根据其步幅滑动到所有输入区域上,为池窗口pooling window遍历的每个位置计算输出。
与卷积层不同,池化层没有参数。池运算符计算池窗口中元素的最大(最大池max-pooling)或平均(平均池average pooling)。
5.3.1 Maximum pooling and average pooling 最大池化和平均池化
平均池化类似于对图像的降采样。由于我们结合了来自多个相邻像素的信息,因此我们对相邻像素进行平均,以获得信噪比更好的图像。
最大池化通常是首选的,因为它从图像中选择更亮的像素。平均池化平滑的图像和尖锐的特征可能不会被识别出来。
>最大池与2 x 2池窗口。
输出张量的高度和宽度均为2。这4个元素来自于每个池化窗口中的最大值。
#使用PyTorch中的nn.MaxPool2d和nn.AvgPool2d模块实现二维最大池化和平均池化操作。
X = torch.tensor([[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]])
X = X.reshape((1,1,3,3)) # input format: (batchsize, no. of channels, height, width)
#创建了一个nn.MaxPool2d对象pool2d,指定池化核大小为2x2,步幅为1。
#调用pool2d(X)进行最大池化操作,得到tensor([[[[4., 5.], [7., 8.]]]]),表示经过最大池化后的输出。
pool2d = nn.MaxPool2d(2, stride=1) # Max-pooling
pool2d(X) # tensor([[[[4., 5.], [7., 8.]]]])
#创建了一个nn.AvgPool2d对象pool2d,指定池化核大小为2x2,步幅为1。
#调用pool2d(X)进行平均池化操作,得到tensor([[[[2., 3.], [5., 6.]]]]),表示经过平均池化后的输出。
pool2d = nn.AvgPool2d(2, stride=1) # Average pooling
pool2d(X) # tensor([[[[2., 3.], [5., 6.]]]])
#最大池化和平均池化是常用的池化操作,用于降低特征图的空间维度,减少计算量,并且具有一定的平移不变性。
池化图层会改变输出端的形状。通过填充输入和调整步幅来实现期望的输出形状。
当池聚合来自一个区域的信息时,深度学习框架默认为匹配池窗口的大小和步幅matching pooling window sizes and stride。如果我们使用(3,3)池化窗口,默认情况下我们会得到一个(3,3)个步幅。可以手动指定过渡段和填充,以覆盖默认值。
#手动修改默认值
X = torch.arange(16,dtype=torch.float32).reshape((1,1,4,4))
# tensor([[[[ 0., 1., 2., 3.],
# [ 4., 5., 6., 7.],
# [ 8., 9., 10., 11.],
# [12., 13., 14., 15.]]]])
pool2d = nn.MaxPool2d(3)
pool2d(X) # tensor([[[[10.]]]])
pool2d = nn.MaxPool2d(3, padding=1, stride=2)
pool2d(X)
# tensor([[[[ 5., 7.], [13., 15.]]]])
pool2d = nn.MaxPool2d((2,3), stride=(2,3),padding=(0,1))
pool2d(X)
# tensor([[[[ 5., 7.], [13., 15.]]]])
5.3.2 Multiple channels 多通道
在处理多通道输入数据时,池化层将每个输入通道单独池化,而不是像卷积层那样在通道上的输入求和。因此池化层的输出通道的大小与输入通道的大小相同。
我们将张量X和X + 1连接在通道维数上,构造一个有2个通道的输入。池化后输出通道的大小仍然是2。
X = torch.cat((X, X + 1), 1)
X
pool2d = nn.MaxPool2d(3, padding=1, stride=2)
pool2d(X)
练习:输入图像的大小为3x1024x1024,应用于其上的卷积核为5(out_channels)x3×2X2。输出的大小是多少?
5X1023X1023
(3消掉因为channels求和,1023=1024-2+1)
5.4 LeNet
现在我们有了组装全功能CNN的所有成分。之前,我们将MLP应用于Fashion-MNIST数据。
用卷积层代替全连接的层,我们保留了图像中的空间结构,并以更少的参数享受了更简洁的模型。
LeNet(1998)是计算机视觉中第一个引起关注的CNN之一。它是由Yann LeCun介绍的,用于识别图像中的手写数字。
LeNet-5有两部分: (i)具有2个卷积层的卷积编码器和(ii)具有3个完全连接层的密集块。
每个卷积层使用一个5 x 5的核和sigmoid激活函数。第一卷积层有6个输出通道,第二层有16个输出通道。每个2 x 2的平均池(2步)减少4倍(ReLUs,尚未发现的最大池)。
卷积块输出形状:(batch size, no. of channel, height, width).
要将输出从卷积块convolutional block传递到密集块dense block,在小批量中将每个例子变平flatten(将4维输入转换为完全连接层期望的2维输入)。2-dim表示使用第一个dim来索引小批中的示例,第2个dim来给出每个示例的向量表示。
密集块有3个完全连接的层(分别为120、84和10个输出)。
LeNet模型可以使用d2l.Lenet()来实现,它使用Xavier初始化和通过顺序块将层链连接在一起。
要可视化LeNet,将一个单通道(黑白)28x28图像的形状通过它,并在每一层打印输出形状。
#打印LeNet模型在输入为(1, 1, 28, 28)时各层的输出形状信息
model = d2l.LeNet()
model.layer_summary((1, 1, 28, 28))
output:
第一个卷积层使用2的填充来补偿由5×5内核导致的高度和宽度的减少。第二个卷积层放弃了填充物。因此,高度和宽度都减少了4个像素。
当我们往层的上方走时,在第1卷积层后,通道数从1增加到6,在第2卷积层后增加16,但是每个池化层的高度和宽度都减半了。每一层完全连接的层降低维数,最终输出的维度与用于分类的类相匹配。
>>>在Fashion-MNIST上应用LeNet。与MLPs一样,损失函数是交叉熵cross-entropy的,我们通过小批量SGD将其最小化。
#应用LeNet
#使用了d2l库中的Trainer来训练LeNet模型在FashionMNIST数据集上
#创建了一个Trainer对象trainer,设置最大迭代次数为10,使用1个GPU进行训练
trainer = d2l.Trainer(max_epochs=10, num_gpus=1)
#创建了一个FashionMNIST数据集对象data,设置批量大小为128
data = d2l.FashionMNIST(batch_size=128)
#创建了一个LeNet模型对象model,并设置学习率为0.1
model = d2l.LeNet(lr = 0.1)
#调用data.get_dataloader(True)获取训练数据集的一个批量样本,
#然后将该批量样本作为参数调用model.apply_init方法,
#传入d2l.init_cnn函数,用于初始化LeNet模型的参数
model.apply_init([next(iter(data.get_dataloader(True)))[0]], d2l.init_cnn)
#使用trainer.fit方法训练模型
trainer.fit(model, data)
CNN具有更少的参数,但它们的计算成本可能比类似的深度MLPs更昂贵,因为每个参数都参与了更多的乘法。
访问GPU可以加速训练(d2l.Trainer 类处理所有细节,并在默认情况下初始化可用设备上的模型参数)。
通过对CNN基础的理解,我们研究了现代的CNN架构,它们也可以作为高级任务(分割、目标检测、风格转换)的特征生成器。
5.5 Modern convolutional neural networks 现代卷积神经网络
深度神经网络的概念很简单(将一堆层堆叠在一起),但性能因架构和超参数选择而不同。
AlexNet、VGG网络、网络网络(NiN)、谷歌网络和剩余网络(ResNet)是计算机视觉中流行的体系结构。
cnn在LeNet之后就广为人知了,但并没有立即主导该领域。在大数据集上训练cnn的可行性尚未建立,计算机视觉集中于手工工程特征提取管道。
神经网络训练的技巧(参数初始化,非压缩激活函数,有效的正则化,SGD变体)仍然缺失。
直到2012年,表示都是机械计算的,但一些研究人员认为,特征应该由多层分层组成,每个层都有可学习的参数。
对于图像来说,最低的层可以检测边缘、颜色和纹理,类似于动物的视觉系统处理其输入的方式。
5.6 Representation learning
5.6.1 Representation learning 表示学习
第一个现代CNN以其发明者之一亚历克斯·克里热夫斯基的名字命名为AlexNet ,是对LeNet的进化改进。它在2012年的ImageNet挑战中取得了优异的性能,并首次展示了学习到的特征可以超越手工设计的特征,打破了计算机视觉中的范式。
在最低层中,模型学习到了与传统过滤器相似的特征提取器。
更高的层次建立在这些表示形式之上,以代表更大的结构,比如眼睛、鼻子。
甚至更高的层次也代表了整个物体,如人、飞机、狗。
最终隐藏状态学习图像的紧凑表示,总结其内容,这样可以很容易地分离来自不同类别的数据。
AlexNet和LeNet有许多相同的架构元素,但AlexNet要大得多,接受了更多的数据和更快的gpu训练。
5.6.2 Missing ingredients 缺少成分
Data
具有许多层次的深度模型需要大量的数据才能显著优于传统方法(线性方法和核方法)。
鉴于20世纪90年代计算机的存储空间有限,研究依赖于微小的数据集。
ImageNet数据集于2009年发布,挑战研究人员从10^6个例子中学习模型,每个1000个来自1000个不同类别的物体的1000个。
这种规模是前所未有的,图像的高分辨率为224 x 224像素,具有更多的视觉细节,允许形成更高层次的特征。
相关竞赛(ImageNet大规模视觉识别)推动了计算机视觉和机器学习研究的向前发展。
5.6.4 AlexNet
AlexNet和LeNet的架构是相似的,但有显著的差异。
1.AlexNet比LeNet5更深,有8层:5个卷积层,2个完全连接的隐藏层(4096个单元)和1个完全连接的输出层。
2.AlexNet使用ReLU代替sigMIU作为激活函数。
#定义了一个AlexNet模型,继承自d2l库中的Classifier类。
#AlexNet是一个经典的卷积神经网络模型,常用于图像分类任务。
class AlexNet(d2l.Classifier):
def __init__(self, lr=0.1, num_classes=10):
#在初始化方法__init__中,调用了父类的__init__方法,并使用save_hyperparameters保存了超参数。
super().__init__()
self.save_hyperparameters()
#定义了一个包含多层卷积、激活函数、池化、全连接层和Dropout的网络结构
self.net = nn.Sequential(
#第一层卷积:96个输出通道,卷积核大小为11x11,步幅为4,填充为1
nn.LazyConv2d(96, kernel_size=11, stride=4, padding=1),
#ReLU激活函数;
nn.ReLU(),
#最大池化层:池化核大小为3x3,步幅为2
nn.MaxPool2d(kernel_size=3, stride=2),
#第二层卷积:256个输出通道,卷积核大小为5x5,填充为2
nn.LazyConv2d(256, kernel_size=5, padding=2),
#ReLU激活函数
nn.ReLU(),
#最大池化层:池化核大小为3x3,步幅为2
nn.MaxPool2d(kernel_size=3, stride=2),
#第三层卷积:384个输出通道,卷积核大小为3x3,填充为1
nn.LazyConv2d(384, kernel_size=3, padding=1),
#ReLU激活函数
nn.ReLU(),
#第四层卷积:384个输出通道,卷积核大小为3x3,填充为1
nn.LazyConv2d(384, kernel_size=3, padding=1),
#ReLU激活函数
nn.ReLU(),
#第五层卷积:256个输出通道,卷积核大小为3x3,填充为1
nn.LazyConv2d(256, kernel_size=3, padding=1),
#ReLU激活函数
nn.ReLU(),
#最大池化层:池化核大小为3x3,步幅为2
nn.MaxPool2d(kernel_size=3, stride=2),
#将特征图展平为一维向量
nn.Flatten(),
#全连接层:4096个神经元
nn.LazyLinear(4096),
#ReLU激活函数
nn.ReLU(),
#Dropout:丢弃概率为0.5
nn.Dropout(p=0.5),
#全连接层:4096个神经元
nn.LazyLinear(4096),
#ReLU激活函数
nn.ReLU(),
#Dropout:丢弃概率为0.5
nn.Dropout(p=0.5),
#全连接层:输出层,神经元数量为num_classes,即类别数
nn.LazyLinear(num_classes))
#使用了定义的AlexNet模型在FashionMNIST数据集上进行训练。
#首先,创建了一个AlexNet模型对象model,并设置学习率为0.01。
model = AlexNet(lr=0.01)
#创建了一个FashionMNIST数据集对象data,设置批量大小为128,
#并调整图像大小为(224, 224)以适应AlexNet的输入尺寸要求。
data = d2l.FashionMNIST(batch_size=128, resize=(224, 224))
#创建了一个Trainer对象trainer,设置最大迭代次数为10,使用1个GPU进行训练。
trainer = d2l.Trainer(max_epochs=10, num_gpus=1)
#对模型进行训练。
trainer.fit(model, data)
练习: