《一文读懂PyTorch核心模块:开启深度学习之旅》
- 一、PyTorch 入门:深度学习的得力助手
- 二、核心模块概览:构建深度学习大厦的基石
- 三、torch:基础功能担当
- (一)张量操作:多维数组的神奇变换
- (二)自动微分:梯度求解的幕后英雄
- (三)设备管理:CPU 与 GPU 的高效调度
- 四、torch.nn:神经网络的 “魔法工坊”
- (一)神经网络模块:层层堆叠搭建智能模型
- (二)损失函数:模型优化的 “指南针”
- 五、torch.optim:模型优化的 “加速引擎”
- (一)优化算法:梯度下降的 “升级版”
- (二)学习率调度器:精细调整学习步伐
- 六、torch.utils.data:数据处理的 “流水线”
- (一)数据集:定制专属数据来源
- (二)数据加载器:高效批量输送数据 “燃料”
- 七、torchvision:计算机视觉的 “百宝箱”
- (一)数据集:图像数据的便捷获取
- (二)预训练模型:站在巨人肩膀上创新
- 八、案例实战:用核心模块打造图像分类器
- (一)项目准备:加载数据与必要模块导入
- (二)模型构建:精心雕琢神经网络架构
一、PyTorch 入门:深度学习的得力助手
在当今的科技领域,深度学习无疑是最炙手可热的研究方向之一,它正以前所未有的速度改变着我们的生活。从智能语音助手的精准回应,到自动驾驶汽车的安全行驶;从医疗影像的精准诊断,到金融风险的智能预测,深度学习的应用场景无处不在。而在深度学习的蓬勃发展背后,PyTorch 作为一款极具影响力的开源深度学习框架,扮演着至关重要的角色。
PyTorch 由 Facebook 的人工智能研究团队(FAIR)开发,自 2017 年发布以来,迅速在学术界和工业界获得了广泛的认可与应用。它以其简洁优雅的设计、动态计算图的特性、与 Python 无缝融合的优势,以及强大的社区支持,为深度学习开发者们提供了一个高效且易用的工具。无论是刚刚踏入深度学习领域的初学者,还是经验丰富的专业研究人员,PyTorch 都能满足他们的需求,助力他们将创新的想法快速转化为实际的模型。
在学术研究中,PyTorch 已成为众多研究人员的首选工具。根据 arXiv 上的论文统计数据,近年来使用 PyTorch 的论文数量呈现出爆发式增长,其在顶会中的引用率不断攀升,许多前沿的研究成果都基于 PyTorch 实现。在工业界,各大科技公司纷纷将 PyTorch 应用于实际产品的开发中,从图像识别、自然语言处理到推荐系统等诸多领域,PyTorch 都展现出了卓越的性能。
接下来,让我们一同深入探索 PyTorch 的核心模块,揭开其强大功能的神秘面纱,开启深度学习的精彩之旅。
二、核心模块概览:构建深度学习大厦的基石
PyTorch 的核心模块宛如一座宏伟建筑的基石,它们相互协作,共同支撑起深度学习模型从构建、训练到部署的整个流程。这些模块涵盖了张量运算、神经网络构建、优化算法、数据处理等多个关键领域,每一个模块都发挥着不可或缺的作用。
首先是 torch 模块,它作为 PyTorch 的基础核心,提供了张量这一基本数据结构,如同建筑中的砖块。张量支持各种数学运算,无论是简单的加减乘除,还是复杂的线性代数操作,都能轻松应对。同时,它还具备自动微分功能,为反向传播算法提供了有力支持,能够自动计算梯度,这就像是给模型训练安装了一台智能导航仪,指引模型朝着最优的方向前进。并且,torch 模块还负责设备管理,使得张量能够在 CPU 和 GPU 之间灵活迁移,充分利用 GPU 的强大计算能力,大幅提升计算效率,如同为建筑施工配备了高效的起重机,加速工程进度。
torch.nn 模块则专注于神经网络的构建,是模型的 “设计师”。它提供了丰富多样的神经网络层,如卷积层、池化层、全连接层等,这些层就像是建筑中的不同结构部件,通过合理组合可以搭建出各种复杂精巧的网络架构。此外,常见的激活函数和归一化层也包含其中,激活函数为模型引入非线性特性,使其能够学习到复杂的数据模式,而归一化层则有助于稳定模型训练,提高收敛速度。同时,一系列损失函数也在这个模块中,它们如同建筑的质量评判标准,用于衡量模型预测结果与真实标签之间的差异,为模型优化提供目标导向。
当涉及到模型的训练优化时,torch.optim 模块就派上了用场,它是模型的 “训练师”。这个模块提供了多种优化算法,如随机梯度下降(SGD)、Adam、RMSprop 等,这些算法就像是不同风格的教练,各有其训练策略,能够根据模型的特点和数据的特性,精准地调整模型参数,让模型在训练过程中不断提升性能,逐步接近最优解。
数据是深度学习的 “燃料”,而 torch.utils.data 模块则承担着数据处理的重任,它是数据的 “搬运工” 和 “加工师”。通过 Dataset 类,我们可以轻松自定义数据集,将各种原始数据整理成模型能够读取的格式。DataLoader 则负责批量加载数据,支持多线程加载,就像高效的输送带,源源不断地将数据输送给模型,并且还能对数据进行打乱、分组等操作,确保模型在训练过程中充分接触到不同的数据样本,避免过拟合。
在计算机视觉领域,torchvision 模块大放异彩,它为图像相关的深度学习任务提供了一站式解决方案。一方面,它内置了诸多常用的计算机视觉数据集,如 MNIST、CIFAR-10、ImageNet 等,这些数据集就像是精心准备的素材库,为模型训练提供了丰富的图像资源。另一方面,一系列预训练模型,如 ResNet、VGG、AlexNet 等,宛如已经搭建好的半成品建筑,我们可以基于这些模型进行迁移学习,快速应用到自己的任务中,节省大量的训练时间和计算资源。同时,它还提供了便捷的数据变换功能,能够对图像进行大小调整、裁剪、归一化等操作,确保数据符合模型的输入要求。
torch.jit 模块则专注于模型的部署环节,它像是一位 “翻译官”,将 Python 模型转换为 TorchScript 模型。通过脚本化和追踪技术,它能够提高模型的执行效率,并且支持跨平台部署,让模型能够在不同的环境中稳定运行,真正将深度学习的成果推向实际应用的舞台。
这些核心模块相互配合,紧密协作,为深度学习开发者们提供了一个强大且便捷的工具集,使得我们能够在各个领域中充分发挥深度学习的潜力,创造出更多具有价值的应用。
三、torch:基础功能担当
(一)张量操作:多维数组的神奇变换
在 PyTorch 中,张量(Tensor)是最为基础且核心的数据结构,它就如同建筑中的砖块,是搭建深度学习模型大厦的基石。张量可以被视为是一个多维数组,涵盖了从简单的标量(零维张量)、向量(一维张量),到矩阵(二维张量),乃至更高维度的数组形式,能够灵活地表示各种复杂的数据。
创建张量的方式丰富多样,满足了不同场景下的需求。比如,我们可以使用 torch.tensor() 函数,通过传入 Python 的列表、元组或 NumPy 数组等数据结构来创建张量,就像是将原材料加工成统一规格的砖块。示例代码如下:
import torch# 通过列表创建一维张量
vector = torch.tensor([1, 2, 3])
print(vector) # 通过列表的列表创建二维张量,类似矩阵
matrix = torch.tensor([[1, 2], [3, 4]])
print(matrix) # 利用 NumPy 数组创建张量,实现二者的无缝对接
import numpy as np
numpy_array = np.array([[5, 6], [7, 8]])
tensor_from_numpy = torch.tensor(numpy_array)
print(tensor_from_numpy)
PyTorch 还提供了一系列便捷的函数来创建特定形状和数值分布的张量。例如,torch.zeros() 可以创建全零张量,常用于初始化模型参数,为模型搭建提供初始的 “空白画布”;torch.ones() 则能生成全一张量,在某些需要初始化为固定值的场景大有用处;torch.randn() 能够从标准正态分布中随机采样生成张量,为模型训练引入随机性,避免陷入局部最优,就像是为模型训练的 “探索之旅” 提供了多样的路径选择。以下是具体示例:
# 创建一个形状为 (3, 3) 的全零张量
zeros_tensor = torch.zeros((3, 3))
print(zeros_tensor)# 生成一个形状为 (2, 4) 的全一张量
ones_tensor = torch.ones((2, 4))
print(ones_tensor)# 从标准正态分布中随机生成一个形状为 (5, 5) 的张量
randn_tensor = torch.randn((5, 5))
print(randn_tensor)
对于已创建的张量,我们可以像操作多维数组一样对其进行索引、切片操作,精准地获取或修改张量中的部分数据,满足模型在数据处理过程中的各种精细需求。同时,张量支持丰富的数学运算,无论是简单的加减乘除四则运算,还是复杂的线性代数操作,如矩阵乘法(torch.mm() 或 @ 运算符)、向量点积(torch.dot())、张量的转置(.T)等,都能高效完成。这使得我们在构建模型时,可以方便地对数据进行各种变换和处理,就如同熟练的工匠运用工具对砖块进行雕琢、拼接,打造出精巧的结构。示例如下:
# 定义两个张量
a = torch.tensor([[1, 2], [3, 4]])
b = torch.tensor([[5, 6], [7, 8]])# 张量加法
c = a + b
print("加法结果:", c) # 张量乘法(对应元素相乘)
d = a * b
print("对应元素相乘结果:", d) # 矩阵乘法
e = torch.mm(a, b)
print("矩阵乘法结果:", e) # 向量点积
vector_a = torch.tensor([1, 2, 3])
vector_b = torch.tensor([4, 5, 6])
dot_product = torch.dot(vector_a, vector_b)
print("向量点积结果:", dot_product) # 张量转置
transposed_a = a.T
print("转置结果:", transposed_a)
值得一提的是,PyTorch 的张量运算在 GPU 上能够实现显著的加速。当系统配备 NVIDIA GPU 且安装了相应的 CUDA 驱动时,只需简单地将张量转移到 GPU 上,后续的计算操作就能利用 GPU 的强大并行计算能力,大幅缩短计算时间,如同为模型训练配备了一台超级引擎,让模型 “飞速奔跑”。示例代码展示了如何轻松实现 CPU 到 GPU 的切换:
# 检查 GPU 是否可用
if torch.cuda.is_available():device = torch.device('cuda')
else:device = torch.device('cpu')# 创建张量并移动到 GPU
tensor = torch.randn((1000, 1000)).to(device)
# 在 GPU 上进行矩阵乘法运算
result = torch.mm(tensor, tensor)
(二)自动微分:梯度求解的幕后英雄
自动微分机制是 PyTorch 的一大核心亮点,它为深度学习模型的训练优化提供了强大的支持,宛如模型训练过程中的智能导航仪,精准指引模型参数调整的方向。
在深度学习中,模型的训练本质上是一个优化问题,我们需要通过不断调整模型的参数,使得模型的预测结果尽可能地接近真实标签。而要实现这一目标,关键在于能够高效、准确地计算损失函数相对于模型参数的梯度。PyTorch 的自动微分功能正是基于这一需求而设计,它能够自动追踪张量的所有操作,并构建一个动态的计算图(Computational Graph),记录从输入数据到输出结果的完整计算流程。
在这个计算图中,每个张量操作都被视为一个节点,而张量之间的依赖关系则构成了边。当我们需要计算梯度时,只需调用 backward() 方法,自动微分机制就会沿着计算图的反向路径,依据链式法则,自动且精确地计算出每个参与运算的张量的梯度。这一过程就像是沿着一条精心铺设的回溯轨道,从最终的输出结果一步步回溯到最初的输入,将沿途的梯度信息一一收集起来。
让我们通过一个简单的线性回归示例来深入理解自动微分的工作原理。假设我们有一组输入数据 x 和对应的真实标签 y,模型的预测值 y_pred 由线性函数 y_pred = w * x + b 给出,其中 w 和 b 是需要学习的模型参数,我们的目标是通过最小化预测值与真实标签之间的均方误差损失函数 loss = ((y_pred - y) ** 2).mean() 来调整 w 和 b 的值。
在 PyTorch 中,实现上述过程的代码如下:
import torch# 模拟输入数据和真实标签
x = torch.tensor([1., 2., 3., 4.], requires_grad=False)
y = torch.tensor([2., 4., 6., 8.], requires_grad=False)# 初始化模型参数,设置 requires_grad=True 以追踪梯度
w = torch.tensor(0.5, requires_grad=True)
b = torch.tensor(0.5, requires_grad=True)# 前向传播计算预测值
y_pred = w * x + b# 计算损失函数
loss = ((y_pred - y) ** 2).mean()# 反向传播计算梯度
loss.backward()# 输出梯度值
print("w 的梯度:", w.grad)
print("b 的梯度:", b.grad)
在上述代码中,我们首先定义了输入数据 x 和真实标签 y,并将模型参数 w 和 b 的 requires_grad 属性设置为 True,告知 PyTorch 需要追踪这些张量的操作以计算梯度。接着进行前向传播,得到预测值 y_pred 并计算损失函数 loss。最后,调用 loss.backward() 触发反向传播过程,PyTorch 会自动计算出 w 和 b 的梯度,并将结果存储在它们的 .grad 属性中。
自动微分的强大之处不仅在于其自动化的计算过程,还在于它能够与各种复杂的模型结构和计算流程无缝结合。无论是简单的多层感知机,还是复杂的卷积神经网络、循环神经网络,PyTorch 的自动微分机制都能准确无误地计算出梯度,为模型的训练提供坚实的基础。这使得研究者和开发者们能够将更多的精力聚焦于模型架构的创新和应用场景的拓展,而无需在繁琐的梯度计算细节上耗费大量时间。
(三)设备管理:CPU 与 GPU 的高效调度
在深度学习的计算任务中,合理地管理计算设备,充分发挥 CPU 和 GPU 的优势,是提升模型训练效率的关键一环。PyTorch 提供了简洁而强大的设备管理功能,让我们能够轻松地在 CPU 和 GPU 之间进行切换,实现高效的计算资源调度。
首先,我们可以通过 torch.cuda.is_available() 函数快速查询当前系统是否配备了可用的 NVIDIA GPU 以及相应的 CUDA 驱动。这一函数就像是一位贴心的 “硬件管家”,提前帮我们探知计算环境的硬件配置情况,为后续的设备选择提供依据。示例代码如下:
import torchif torch.cuda.is_available():print("GPU 可用")
else:print("GPU 不可用,将使用 CPU 进行计算")
当确定 GPU 可用后,我们可以使用 torch.device() 函数创建一个代表 GPU 设备的对象,通常表示为 ‘cuda’ 或 ‘cuda:X’,其中 X 为 GPU 的编号(当系统有多块 GPU 时)。类似地,‘cpu’ 则代表 CPU 设备。这就好比为数据和模型指明了计算的 “工作场地”,确保它们能够在合适的硬件环境中运行。
在创建张量或模型时,我们可以通过 .to() 方法将它们轻松地转移到指定的设备上。例如,将张量从 CPU 迁移到 GPU 上,后续对该张量的计算操作就会在 GPU 上执行,充分利用 GPU 的强大并行计算能力,大幅提升计算速度。反之,若 GPU 不可用或出于某些特殊需求,我们也可以将数据和模型转移回 CPU 进行计算。以下是具体的示例代码:
import torch# 检查 GPU 是否可用
if torch.cuda.is_available():device = torch.device('cuda')
else:device = torch.device('cpu')# 创建张量并移动到指定设备
tensor = torch.randn((1000, 1000)).to(device)# 创建模型并移动到指定设备
model = MyModel().to(device)
在实际应用中,我们需要根据模型的规模、数据的大小以及硬件的配置情况,灵活地选择使用 CPU 还是 GPU。一般来说,对于大规模的深度学习模型训练,GPU 凭借其数百个甚至数千个核心,能够并行处理海量的数据,从而显著缩短计算时间,就像是一支训练有素的 “超级战队”,高效应对复杂的计算任务。然而,当模型较小或者数据量不大时,CPU 的计算能力或许已经足够,而且使用 CPU 无需考虑 GPU 的显存限制等问题,更加便捷灵活。
此外,在多 GPU 环境下,PyTorch 还提供了更为高级的分布式训练功能,允许我们将模型和数据并行地分配到多个 GPU 上进行计算,进一步提升训练效率,如同组建了一支多兵种协同作战的 “集团军”,全方位加速模型的训练进程。不过,这需要对分布式计算的原理和 PyTorch 的相关 API 有更深入的了解和掌握,后续我们可以进一步探讨这一高级话题。
四、torch.nn:神经网络的 “魔法工坊”
(一)神经网络模块:层层堆叠搭建智能模型
torch.nn 模块作为 PyTorch 构建神经网络的核心工具,提供了丰富多样的组件,让我们能够像搭建积木一样,轻松构建出复杂而强大的神经网络模型。
常见的神经网络层是构建模型的基础单元。以全连接层(nn.Linear)为例,它的作用是将输入张量的每个元素与权重矩阵相乘,再加上偏置向量,实现线性变换,如同在信号传输过程中对电信号进行放大、衰减或相位调整。在构建一个简单的多层感知机(MLP)时,我们可以这样使用:
import torch.nn as nn# 定义输入维度、隐藏层维度和输出维度
input_dim = 784
hidden_dim = 256
output_dim = 10# 构建多层感知机模型
model = nn.Sequential(nn.Linear(input_dim, hidden_dim), # 第一层全连接层nn.ReLU(), # 激活函数,引入非线性特性nn.Linear(hidden_dim, output_dim) # 第二层全连接层
)
在上述代码中,nn.Linear 的第一个参数指定输入特征的数量,第二个参数指定输出特征的数量,即神经元的个数。通过连续堆叠多个这样的全连接层,并在中间插入激活函数(如 nn.ReLU)来增加非线性,模型就能够学习到复杂的数据模式,就像为电路串联多个功能各异的电子元件,使其具备处理复杂信号的能力。
卷积层(nn.Conv2d)在计算机视觉领域扮演着举足轻重的角色。它通过滑动卷积核在图像上进行卷积操作,能够自动提取图像的局部特征,就如同用不同形状的滤镜观察一幅画,捕捉画面中的线条、纹理、形状等细节。例如,在构建一个简单的图像分类模型时:
import torch.nn as nn# 定义卷积神经网络模型
class CNNModel(nn.Module):def __init__(self):super(CNNModel, self).__init__()self.conv1 = nn.Conv2d(3, 16, kernel_size=3, stride=1, padding=1) # 第一层卷积层self.relu = nn.ReLU()self.pool = nn.MaxPool2d(kernel_size=2, stride=2) # 池化层,用于降维self.conv2 = nn.Conv2d(16, 32, kernel_size=3, stride=1, padding=1) # 第二层卷积层self.fc = nn.Linear(32 * 8 * 8, 10) # 全连接层,将卷积特征映射到分类结果def forward(self, x):x = self.conv1(x)x = self.relu(x)x = self.pool(x)x = self.conv2(x)x = self.relu(x)x = self.pool(x)x = x.view(-1, 32 * 8 * 8)x = self.fc(x)return xmodel = CNNModel()
这里,nn.Conv2d 的参数 3 表示输入图像的通道数(如 RGB 图像为 3 通道),16 是卷积核的数量,也就是输出通道数,kernel_size 定义了卷积核的大小,stride 和 padding 分别控制卷积的步长和填充方式。通过多层卷积和池化操作,逐步提取图像的高级特征,最后通过全连接层将这些特征映射到具体的分类类别上,就像对一幅画进行层层剖析,从线条、色彩等基本元素,提炼出画作的风格、主题等高级信息,最终判断画作的类型。
池化层(nn.MaxPool2d 或 nn.AvgPool2d)主要用于对特征图进行降维,减少计算量,同时保留关键信息。最大池化(nn.MaxPool2d)会在每个池化窗口内选取最大值作为输出,就像是从一幅缩略图中挑选最具代表性的像素点,突出图像的主要特征;平均池化(nn.AvgPool2d)则是计算池化窗口内的平均值,使特征更加平滑。它们在卷积神经网络中常与卷积层交替使用,进一步优化特征提取的效果。
除了这些基本层,torch.nn 模块还包含许多其他实用的层,如循环神经网络层(nn.LSTM、nn.GRU)用于处理序列数据,能够像一个经验丰富的读者一样,理解文本、语音等序列中的上下文信息,捕捉先后顺序之间的逻辑关联;批归一化层(nn.BatchNorm2d 等)用于对每一批数据的特征进行归一化处理,使得模型训练更加稳定、快速,就像是为运动员在比赛前进行身体状态的标准化调整,确保他们在最佳状态下发挥实力。
通过合理地选择和组合这些神经网络层,我们可以构建出适用于各种任务的复杂神经网络模型,从图像识别、自然语言处理到语音识别等领域,发挥出深度学习的强大威力。
(二)损失函数:模型优化的 “指南针”
在神经网络的训练过程中,损失函数起着至关重要的作用,它就像是模型优化的 “指南针”,为模型参数的调整指明方向。torch.nn 模块提供了一系列常用的损失函数,用于衡量模型预测值与真实标签之间的差异程度。
均方误差损失(nn.MSELoss)是一种常见的损失函数,常用于回归任务,比如预测房价、股票价格等连续数值。它计算预测值与真实值之间误差的平方的平均值,直观地反映了预测值偏离真实值的程度。数学表达式为:,其中 是真实值, 是预测值, 是样本数量。在 PyTorch 中使用如下:
import torch
import torch.nn as nn# 模拟预测值和真实值
predictions = torch.tensor([1.2, 2.5, 3.7], dtype=torch.float)
targets = torch.tensor([1., 2., 4.], dtype=torch.float)# 定义均方误差损失函数
loss_function = nn.MSELoss()# 计算损失
loss = loss_function(predictions, targets)
print(loss)
交叉熵损失(nn.CrossEntropyLoss)则广泛应用于分类任务,无论是二分类还是多分类问题。它基于信息论中的交叉熵概念,衡量了模型预测的概率分布与真实标签的概率分布之间的差异。对于多分类问题,假设模型输出的是经过 softmax 激活后的概率向量,真实标签是对应的类别索引,交叉熵损失能够有效地促使模型将正确类别的概率推向更高。示例代码如下:
import torch
import torch.nn as nn# 模拟模型输出的概率分布(经过 softmax 激活)和真实标签
outputs = torch.tensor([[0.2, 0.3, 0.5], [0.7, 0.1, 0.2], [0.1, 0.8, 0.1]], dtype=torch.float)
labels = torch.tensor([2, 0, 1], dtype=torch.long)# 定义交叉熵损失函数
loss_function = nn.CrossEntropyLoss()# 计算损失
loss = loss_function(outputs, labels)
print(loss)
这里,nn.CrossEntropyLoss 在内部会自动结合 softmax 函数进行计算,所以在使用时,模型输出层无需再额外添加 softmax 激活,简化了代码实现。
此外,还有二元交叉熵损失(nn.BCELoss)用于二分类问题,特别是当模型输出直接为概率值(未经过 softmax)时;KL 散度损失(nn.KLDivLoss)常用于衡量两个概率分布的相似性,在一些生成式模型如变分自编码器(VAE)中有广泛应用。
当模型进行前向传播得到预测值后,通过计算损失函数的值,我们就能够定量地知道模型当前的预测效果有多好(或多差)。而在训练过程中,优化算法会依据损失函数的梯度信息,反向调整模型的参数,使得损失值逐渐减小,就像指南针指引船只朝着目的地航行一样,引导模型不断优化,逐步提高预测的准确性,直至达到较好的性能表现。不同的损失函数适用于不同的任务场景,合理选择损失函数是构建有效神经网络模型的关键一步。
五、torch.optim:模型优化的 “加速引擎”
(一)优化算法:梯度下降的 “升级版”
在深度学习模型训练过程中,优化算法起着举足轻重的作用,它如同模型训练的 “导航仪”,决定了模型参数如何根据损失函数的梯度信息进行更新,以逐步逼近最优解。torch.optim 模块提供了多种强大的优化算法,其中最为基础且广泛应用的是随机梯度下降(SGD)及其衍生算法,还有自适应学习率算法如 Adam 等。
随机梯度下降(SGD)是优化算法的基石。它的核心思想是在每次迭代时,随机选取一个小批量(mini-batch)的数据样本,计算这些样本上损失函数的梯度,并据此更新模型参数。数学表达式为:,其中 是当前模型参数, 是学习率, 是小批量样本 上损失函数关于参数的梯度。SGD 的优点在于计算简单,对大规模数据集具有良好的可扩展性,能够在每次迭代中快速得到一个近似的梯度方向,推动模型前进。然而,它也存在一些明显的缺点,例如在较为平坦的区域,梯度值较小,参数更新缓慢,导致收敛速度变慢;而在陡峭的区域,梯度值较大,参数更新步长过大,可能会在最小值附近来回震荡,甚至跳过最优解,难以收敛到精确的最优值。
为了克服 SGD 的这些问题,一系列改进算法应运而生。动量法(SGD with Momentum)是其中一种经典的改进策略。它引入了一个动量项,类似于物理学中的惯性概念。在更新参数时,不仅考虑当前的梯度,还将上一次的更新量以一定的权重(动量系数 ,通常取值在 0.5 到 0.99 之间)累加到当前的更新中。具体公式为:
其中 是当前时刻的速度向量,代表参数更新的方向和大小。动量法使得模型在更新参数时,能够保留之前的更新趋势,在平坦区域可以借助惯性加速前进,在陡峭区域也能因为惯性而减少震荡,更快地收敛到最优解。
Nesterov 动量则是在动量法的基础上进一步优化。它在计算梯度时,不是基于当前参数位置,而是先沿着上一次的速度方向 “向前看一步”,即先对参数进行一个临时更新 ,然后在这个临时位置计算梯度,再进行参数的正式更新。这样的改进使得模型能够更加智能地预判梯度方向,提前调整更新步伐,进一步提升收敛速度和稳定性。
除了基于动量的改进,自适应学习率算法也是优化领域的一大突破。Adagrad 算法为每个参数单独调整学习率,它根据参数历史梯度的平方累计和来动态缩放学习率。具体而言,对于每个参数 ,维护一个累积梯度平方和变量 ,每次迭代时,学习率的更新公式为: ,其中 是一个极小的正数,用于防止分母为零。Adagrad 的优点在于能够根据参数的重要性(由梯度大小反映)自适应地调整学习率,对于频繁更新、梯度较大的参数,学习率会逐渐减小,避免过度调整;而对于梯度较小、更新不频繁的参数,学习率相对较大,保证其能够得到充分的学习。然而,由于累积梯度平方和是单调递增的,这可能导致学习率过早地衰减到极小值,使得模型在训练后期学习能力不足,无法进一步优化。
RMSprop 算法针对 Adagrad 的问题进行了改进,它采用指数加权移动平均的方式来计算梯度平方的累积量。引入一个衰减率 (通常取值在 0.9 到 0.999 之间),更新公式为: ,其中 是当前梯度,
是梯度平方的指数加权移动平均。参数更新公式为: 。这样,RMSprop 能够在一定程度上保留 Adagrad 的自适应学习率优势,同时避免学习率过早衰减,使得模型在训练过程中能够根据不同阶段的梯度情况灵活调整学习率,在鞍点等梯度变化缓慢的区域也能保持较好的学习能力。
Adam 算法则结合了动量法和 RMSprop 的优点,堪称自适应学习率算法的集大成者。它为每个参数分别维护一个一阶矩估计(梯度的平均值,用 表示)和二阶矩估计(梯度平方的平均值,用 表示),通过对这两个矩估计进行指数加权移动平均来调整学习率。更新公式如下:
其中, 和 是衰减系数,通常分别取值为 0.9 和 0.999。为了修正矩估计在初始阶段的偏差,还引入了偏差修正项:
最终参数更新公式为:
。Adam 算法能够在不同的参数维度上自适应地调整学习率,同时利用动量项加速收敛,对于大多数深度学习任务都表现出了出色的性能,尤其在处理复杂的非凸优化问题和大规模神经网络时,能够快速且稳定地收敛到较好的解。
不同的优化算法适用于不同的场景,在实际应用中,需要根据模型的复杂度、数据集的规模和特点等因素,合理选择优化算法。对于简单的线性模型或数据集较小的情况,SGD 及其简单变体可能已经足够;而对于深度神经网络、大规模数据集以及复杂的优化问题,自适应学习率算法如 Adam 等往往能取得更好的效果。同时,还可以通过调整算法的超参数(如学习率、动量系数、衰减率等)来进一步优化模型的训练过程,这需要结合具体的实验和调优经验,不断探索最适合的参数组合。
(二)学习率调度器:精细调整学习步伐
学习率作为优化算法中的关键超参数,对模型训练的收敛速度和性能有着深远的影响。在训练初期,较大的学习率可以让模型参数快速更新,迅速接近最优解所在的区域;然而,随着训练的推进,当模型逐渐接近最优解时,过大的学习率会导致模型在最优解附近震荡,无法精确收敛。此时,就需要逐渐减小学习率,让模型能够更加精细地调整参数,稳定地收敛到最优解。PyTorch 的 torch.optim.lr_scheduler 模块提供了一系列灵活多样的学习率调度器,帮助我们在模型训练过程中动态调整学习率,实现高效的优化。
阶梯式学习率调整是一种简单而常用的策略,以 StepLR 为例。它允许我们设定一个固定的步长 step_size,每隔 step_size 个训练周期(epoch),学习率就会按照一个固定的衰减因子 gamma(通常小于 1)进行衰减。例如,若初始学习率为 lr = 0.1,设置 step_size = 30,gamma = 0.1,那么在第 30、60、90 等 epoch 结束后,学习率会依次变为 0.01、0.001、0.0001。这种策略的优点在于简单直观,易于理解和实现,在许多经典的深度学习模型训练中都有广泛应用。特别是在训练前期,能够利用较大的学习率快速推动模型收敛,而在后期随着学习率的逐步衰减,模型也能逐渐稳定下来,避免在最优解附近过度震荡。
多阶梯式学习率调整策略则在 StepLR 的基础上进行了扩展,如 MultiStepLR。它允许我们指定多个里程碑式的 epoch 索引列表 milestones,在这些特定的 epoch 时刻,学习率按照给定的衰减因子 gamma 进行衰减。比如,设置 milestones = [50, 100, 150],gamma = 0.5,则学习率会在第 50、100、150 个 epoch 结束后分别衰减为原来的一半。这种策略相较于简单的 StepLR 更加灵活,能够根据模型训练的不同阶段,更精细地调整学习率,适应复杂的优化过程。例如,在一些复杂的深度学习任务中,模型在不同阶段对学习率的敏感度不同,通过设置多个里程碑,可以让模型在关键阶段及时调整学习步伐,提高收敛效率。
指数衰减学习率调度器 ExponentialLR 采用指数形式对学习率进行连续衰减。在每个训练周期结束后,学习率乘以一个固定的衰减因子 gamma,使得学习率随着训练的进行呈指数级下降。公式为: ,其中 是当前周期的学习率, 是下一个周期的学习率。这种衰减方式相对平滑,能够持续地降低学习率,让模型在训练后期逐渐精细地调整参数。在一些需要精细优化的任务中,如生成对抗网络(GAN)的训练,指数衰减学习率可以帮助模型在对抗博弈过程中稳定收敛,避免因学习率突变而导致的训练不稳定问题。
余弦退火学习率调度器 CosineAnnealingLR 则借鉴了余弦函数的特性,为学习率调整带来了一种周期性的变化模式。它引入了两个关键参数:T_max 表示学习率变化的半个周期长度,即从初始学习率开始下降到最低学习率,再回升到初始学习率所经历的 epoch 数;eta_min 是学习率的最小值,学习率在一个周期内会从初始值逐渐下降到 eta_min,然后再上升回到初始值。这种调度策略的优势在于,在训练过程中,学习率会在一个相对较大的值和一个较小的值之间周期性地变化,使得模型在不同阶段既能利用较大学习率快速探索参数空间,又能在接近最优解时利用较小学习率进行精细调整。例如,在图像分类任务中,对于一些复杂的数据集,使用余弦退火学习率调度器可以让模型在多次迭代中不断跳出局部最优解,寻找更优的全局最优解,从而提高模型的准确率。
下面通过一个简单的示例来展示如何使用学习率调度器。假设我们使用 torch.optim.SGD 优化器训练一个简单的神经网络模型,结合 CosineAnnealingLR 学习率调度器:
import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim.lr_scheduler import CosineAnnealingLR
from torchvision import datasets, transforms# 定义简单的神经网络模型
class SimpleNet(nn.Module):def __init__(self):super(SimpleNet, self).__init__()self.fc1 = nn.Linear(784, 128)self.relu = nn.ReLU()self.fc2 = nn.Linear(128, 10)def forward(self, x):x = x.view(-1, 784)x = self.fc1(x)x = self.relu(x)x = self.fc2(x)return x# 加载 MNIST 数据集
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))])
train_dataset = datasets.MNIST('./data', train=True, download=True, transform=transform)
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=64, shuffle=True)# 创建模型、损失函数和优化器
model = SimpleNet()
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.1, momentum=0.9)# 设置余弦退火学习率调度器
scheduler = CosineAnnealingLR(optimizer, T_max=10)# 训练过程
num_epochs = 20
for epoch in range(num_epochs):model.train()running_loss = 0.0for inputs, labels in train_loader:optimizer.zero_grad()outputs = model(inputs)loss = criterion(outputs, labels)loss.backward()optimizer.step()# 每个 epoch 结束后更新学习率scheduler.step()print(f"Epoch {epoch + 1}/{num_epochs}, Learning Rate: {scheduler.get_last_lr()[0]:.6f}")
在上述代码中,我们首先定义了一个简单的神经网络模型用于 MNIST 手写数字分类任务,接着创建了优化器 SGD 并设置初始学习率为 0.1,动量为 0.9。然后实例化了 CosineAnnealingLR 学习率调度器,指定 T_max = 10,即学习率将在每 10 个 epoch 完成一次从最大值到最小值再回到最大值的余弦退火过程。在每个 epoch 的训练循环结束后,通过调用 scheduler.step() 来更新学习率,并打印出当前的学习率。通过观察训练过程中的学习率变化以及模型的损失值、准确率等指标,我们可以直观地感受到学习率调度器对模型训练的影响。
不同的学习率调度器各有优劣,在实际应用中,需要根据模型的特点、数据集的规模以及训练的目标等因素综合考虑,选择最合适的学习率调度策略。同时,还可以结合超参数调优方法,如网格搜索、随机搜索等,对学习率调度器的相关参数(如步长、衰减因子、周期长度等)进行优化,进一步提升模型的训练效果。
六、torch.utils.data:数据处理的 “流水线”
(一)数据集:定制专属数据来源
在深度学习的实践中,我们面对的往往是各种各样的原始数据,这些数据的格式和结构各不相同,无法直接被模型所使用。torch.utils.data 模块中的 Dataset 类为我们提供了一个强大的工具,使得我们能够轻松地自定义数据集,将原始数据转化为模型能够理解和处理的格式。
自定义数据集类需要继承自 Dataset 类,并至少重写两个关键方法:len 和 getitem。len 方法用于返回数据集的大小,即样本的数量,这使得我们可以像使用 Python 内置的序列类型一样,通过 len() 函数获取数据集的长度。getitem 方法则负责根据给定的索引,返回对应的数据样本,并且通常还会在此方法中进行数据的预处理操作。
以图像数据为例,假设我们有一个包含多个图像类别的文件夹,每个类别下存放着相应类别的图像文件。我们可以创建一个自定义的图像数据集类,如下所示:
import os
from PIL import Image
from torch.utils.data import Datasetclass CustomImageDataset(Dataset):def __init__(self, root_dir, transform=None):self.root_dir = root_dirself.transform = transformself.image_files = []self.labels = []for class_name in os.listdir(root_dir):class_dir = os.path.join(root_dir, class_name)if os.path.isdir(class_dir):for file_name in os.listdir(class_dir):self.image_files.append(os.path.join(class_dir, file_name))self.labels.append(class_name)def __len__(self):return len(self.image_files)def __getitem__(self, index):image_path = self.image_files[index]image = Image.open(image_path).convert('RGB')label = self.labels[index]if self.transform:image = self.transform(image)return image, label
在上述代码中,init 方法用于初始化数据集,遍历给定的根目录,收集所有图像文件的路径和对应的标签。len 方法返回图像文件的总数,即数据集的大小。getitem 方法根据索引读取图像文件,使用 PIL 库将其转换为 RGB 格式的图像对象,若定义了数据变换 transform,则对图像进行相应的预处理操作,最后返回处理后的图像和标签。
对于文本数据,同样可以通过自定义数据集类来进行处理。例如,我们有一个文本文件,每行包含一个文本样本和对应的标签,以制表符分隔。以下是一个简单的文本数据集类示例:
import torch
from torch.utils.data import Datasetclass CustomTextDataset(Dataset):def __init__(self, file_path, tokenizer, max_length):self.file_path = file_pathself.tokenizer = tokenizerself.max_length = max_lengthself.texts = []self.labels = []with open(file_path, 'r', encoding='utf-8') as file:for line in file:text, label = line.strip().split('\t')self.texts.append(text)self.labels.append(int(label))def __len__(self):return len(self.texts)def __getitem__(self, index):text = self.texts[index]label = self.labels[index]encoded_text = self.tokenizer(text, max_length=self.max_length, padding='max_length', truncation=True, return_tensors='pt')input_ids = encoded_text['input_ids'].squeeze()attention_mask = encoded_text['attention_mask'].squeeze()return input_ids, attention_mask, label
在这个文本数据集类中,init 方法读取文本文件,将文本和标签分别存储在列表中。getitem 方法使用预定义的分词器 tokenizer 对文本进行分词、填充、截断等预处理操作,将文本转换为模型所需的输入格式(如 input_ids 和 attention_mask),并返回处理后的文本数据和标签。
无论是图像数据还是文本数据,数据预处理都是至关重要的环节。对于图像,常见的预处理操作包括调整大小、裁剪、归一化、数据增强(如随机翻转、旋转、裁剪等),这些操作可以通过 torchvision.transforms 模块来实现。例如:
from torchvision import transforms# 定义图像预处理变换
image_transform = transforms.Compose([transforms.Resize((224, 224)), # 将图像调整为固定大小transforms.RandomHorizontalFlip(), # 随机水平翻转图像transforms.ToTensor(), # 将图像转换为张量transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) # 对图像进行归一化
])
对于文本,预处理可能涉及分词、去除停用词、词干提取或词形还原、构建词表等操作,以及将文本转换为数字序列(如通过词嵌入或独热编码),以便模型能够处理。合适的数据预处理能够提高模型的训练效率和泛化能力,使得模型能够更好地学习数据中的特征和模式。
(二)数据加载器:高效批量输送数据 “燃料”
当我们定义好自定义数据集后,为了能够高效地将数据输送给模型进行训练,torch.utils.data 模块提供了 DataLoader 类。它就像是一个数据的 “输送带”,能够按照我们指定的方式,将数据集分批、有序地加载到模型中,并且支持多线程加载,极大地提高了数据加载的效率,从而加速模型的训练过程。
DataLoader 的使用非常灵活,它提供了多个参数来满足不同的需求。其中,dataset 参数指定要加载的数据集对象,即我们之前自定义的继承自 Dataset 类的对象。batch_size 参数用于设置每个批次加载的数据样本数量,合适的批次大小可以在模型训练的收敛速度和内存占用之间取得平衡。一般来说,较小的批次大小会使模型在每次更新参数时更加频繁,但可能会增加训练时间;而较大的批次大小可以利用硬件的并行计算能力,加速训练,但可能需要更多的内存资源,并且在某些情况下可能导致模型收敛不稳定。
shuffle 参数决定是否在每个训练周期(epoch)开始时对数据进行随机打乱。随机打乱数据可以使得模型在每次训练时接触到不同顺序的样本,避免模型对数据顺序产生依赖,从而提高模型的泛化能力,防止过拟合。在训练过程中,通常会将 shuffle 设置为 True;而在测试或验证阶段,为了保证结果的一致性和可重复性,一般将其设置为 False。
num_workers 参数用于指定加载数据的线程数。通过多线程加载,可以充分利用多核处理器的性能,并行地从数据集中读取数据,进一步提升数据加载的速度。例如,当 num_workers 设置为 4 时,数据加载器会启动 4 个线程同时进行数据的读取和预处理工作。但需要注意的是,线程数并非越多越好,过多的线程可能会导致系统资源竞争加剧,反而降低效率,一般根据硬件配置和数据集的大小来合理选择线程数,通常设置为 CPU 核心数的倍数。
下面是一个使用 DataLoader 的示例代码:
from torch.utils.data import DataLoader# 假设已经定义好了自定义数据集 custom_dataset
batch_size = 32
shuffle = True
num_workers = 4data_loader = DataLoader(dataset=custom_dataset, batch_size=batch_size, shuffle=shuffle, num_workers=num_workers)for batch in data_loader:images, labels = batch# 将数据输入模型进行训练outputs = model(images)loss = criterion(outputs, labels)loss.backward()optimizer.step()
在上述代码中,我们首先创建了一个 DataLoader 对象,指定了数据集、批次大小、是否打乱数据以及线程数等参数。然后,通过遍历 DataLoader,每次迭代都会返回一个批次的数据,我们可以直接将这些数据输入到模型中进行前向传播、计算损失、反向传播以及参数更新等操作,实现了高效的模型训练流程。
通过合理地使用 Dataset 和 DataLoader,我们能够将各种原始数据进行有效的整理和加载,为模型提供源源不断的、高质量的数据 “燃料”,使得模型能够在训练过程中充分学习数据中的知识,从而发挥出最佳的性能。无论是处理大规模的图像数据集、复杂的文本语料库,还是其他类型的数据,torch.utils.data 模块都为我们提供了便捷而强大的解决方案。
七、torchvision:计算机视觉的 “百宝箱”
(一)数据集:图像数据的便捷获取
在计算机视觉领域,数据是驱动模型学习与成长的核心要素。torchvision 模块为我们提供了便捷访问诸多常用视觉数据集的途径,犹如一座蕴藏丰富的宝藏,极大地便利了模型训练与研究工作。
以经典的 MNIST 数据集为例,它是手写数字识别领域的 “标杆” 数据集,由 60,000 张训练图像和 10,000 张测试图像构成,每张图像均为 28×28 像素的灰度图。在 torchvision 中,获取 MNIST 数据集仅需寥寥数行代码:
import torchvision# 下载训练集,若本地已存在则直接读取
train_dataset = torchvision.datasets.MNIST(root='./data', train=True, download=True)# 下载测试集
test_dataset = torchvision.datasets.MNIST(root='./data', train=False, download=True)
这里,root 参数指定了数据集的存储路径,train 参数区分训练集与测试集,download 参数控制是否自动下载数据集。通过这种简洁的方式,我们能迅速将 MNIST 数据集引入项目,为后续模型训练奠定基础。
CIFAR-10 数据集同样备受瞩目,它涵盖 60,000 张 32×32 像素的彩色图像,分为 10 个类别,每个类别包含 6,000 张图像,广泛应用于图像分类任务的基准测试。使用 torchvision 加载 CIFAR-10 数据集同样便捷:
import torchvision# 加载训练集,设置数据变换(如转换为张量、归一化)
train_dataset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True,transform=torchvision.transforms.Compose([torchvision.transforms.ToTensor(),torchvision.transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]))# 加载测试集
test_dataset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True,transform=torchvision.transforms.Compose([torchvision.transforms.ToTensor(),torchvision.transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]))
上述代码不仅下载了数据集,还借助 transform 参数对数据进行了预处理。torchvision.transforms 模块提供了丰富多样的数据变换操作,如 ToTensor 能将图像数据从 PIL 格式转换为 PyTorch 张量,Normalize 则依据给定均值和标准差对数据进行归一化处理,确保数据分布契合模型训练要求,有效提升模型训练效率与稳定性。
对于更为庞大复杂的 ImageNet 数据集,torchvision 同样提供了支持。尽管完整的 ImageNet 数据集规模巨大,但 torchvision 提供了其子集的便捷下载方式,方便研究人员在有限资源下快速开展模型原型设计与调试工作。这些子集保留了 ImageNet 的多样性与代表性,能够满足大多数研究场景的需求。
在获取数据集后,通常需结合 torch.utils.data.DataLoader 进行数据加载,实现高效的批量数据输送。以下是示例代码:
from torch.utils.data import DataLoader# 假设已定义好 train_dataset 和 test_dataset
batch_size = 64
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)for batch in train_loader:images, labels = batch# 将数据输入模型进行训练
通过合理设置 batch_size 和 shuffle 等参数,DataLoader 能够以指定批次大小、有序或随机打乱的方式向模型提供数据,充分发挥硬件性能,加速模型训练进程,让模型在海量图像数据中高效学习特征与模式,提升模型的准确性与泛化能力。
(二)预训练模型:站在巨人肩膀上创新
torchvision 不仅提供了丰富的数据集,还内置了一系列在 ImageNet 等大规模数据集上预先训练好的经典模型,涵盖 ResNet、VGG、AlexNet 等,这些预训练模型宛如熠熠生辉的明珠,为我们开启了迁移学习的大门,助力我们在各类计算机视觉任务中事半功倍。
以 ResNet 系列模型为例,它凭借独特的残差结构,有效解决了深层神经网络的梯度消失与退化问题,在图像分类、目标检测等诸多领域展现出卓越性能。在 torchvision 中,使用预训练的 ResNet-18 模型只需简单几行代码:
import torchvision.models as models# 加载预训练的 ResNet-18 模型
resnet18 = models.resnet18(pretrained=True)当获取预训练模型后,可依据具体任务需求对其进行微调。假设我们面对一个新的图像分类任务,类别数与 ImageNet 不同,需对模型的全连接层进行适应性调整:
import torch.nn as nn# 假设新任务有 20 个类别
num_classes = 20# 加载预训练的 ResNet-18 模型,冻结除全连接层外的所有层参数
model = models.resnet18(pretrained=True)
for param in model.parameters():param.requires_grad = False# 修改全连接层,使其输出与新任务类别数匹配
model.fc = nn.Linear(model.fc.in_features, num_classes)
在上述代码中,首先冻结了预训练模型除全连接层外的所有层参数,使其在微调过程中保持不变,仅更新全连接层参数,避免对已学习到的通用特征造成破坏。随后,根据新任务的类别数重新定义全连接层,确保模型能够输出符合任务要求的预测结果。
再以 VGG 模型为例,它以结构简洁、特征提取能力强著称。同样,在 torchvision 中加载预训练的 VGG 模型易如反掌:
import torchvision.models as models# 加载预训练的 VGG-16 模型
vgg16 = models.vgg16(pretrained=True)
若要将预训练的 VGG 模型应用于目标检测任务,可在其基础上添加检测头,并结合特定的目标检测算法进行训练。例如,基于 Faster R-CNN 框架,利用预训练的 VGG 特征提取器,快速构建一个高效的目标检测模型:
import torchvision.models.detection as detection# 加载预训练的 VGG-16 模型作为特征提取器
backbone = models.vgg16(pretrained=True).features# 构建 Faster R-CNN 模型,传入特征提取器
model = detection.fasterrcnn_resnet50_fpn(backbone=backbone, num_classes=num_classes)
这里,借助 torchvision.models.detection 模块,将预训练的 VGG 模型无缝嵌入到 Faster R-CNN 架构中,快速搭建起一个强大的目标检测模型,充分发挥预训练模型的优势,减少训练时间与资源消耗,同时在新任务上取得优异性能。
在实际应用中,利用 torchvision 的预训练模型进行迁移学习时,需注意一些关键要点。首先,根据任务特性谨慎选择合适的预训练模型,不同模型架构在特征提取、计算复杂度等方面各具优劣,需权衡取舍。其次,合理设置微调策略,精细调整学习率、优化器等超参数,确保模型在新任务上既能快速收敛,又能避免过拟合。此外,数据预处理环节同样不容忽视,务必保证输入数据的分布与预训练模型的训练数据分布相近,使预训练模型的优势得以充分发挥,助力我们在计算机视觉的探索之旅中一往无前,创造更多精彩成果。
八、案例实战:用核心模块打造图像分类器
(一)项目准备:加载数据与必要模块导入
在开启图像分类器的构建之旅前,充分的准备工作至关重要。首先是环境搭建,确保 PyTorch 及其相关依赖库已正确安装,这是项目顺利运行的基石。依据系统配置与需求,可选择合适的安装方式,如使用 pip 或 conda 进行安装,同时留意 GPU 支持情况,安装对应的 CUDA 驱动与 cuDNN 库,以便后续充分发挥 GPU 的强大计算能力。
数据集的准备是关键一环。本案例选用经典的 CIFAR-10 数据集,它涵盖 10 个不同类别的 60,000 张 32×32 像素的彩色图像,为图像分类模型训练提供了丰富素材。利用 torchvision 模块,可便捷地下载并加载该数据集,代码如下:
import torchvision
import torchvision.transforms as transforms# 定义数据变换,将图像转换为张量并归一化
transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])# 下载并加载训练集
trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=32, shuffle=True)# 下载并加载测试集
testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=32, shuffle=False)
这里,数据变换操作 transform 将图像数据转换为张量形式,并依据给定均值和标准差进行归一化处理,使数据分布更契合模型训练要求,有效提升训练效率与稳定性。DataLoader 则负责批量加载数据,以指定的批次大小(如 32)有序或随机打乱地将数据输送给模型,充分发挥硬件性能,加速模型训练进程。
与此同时,将后续模型构建、训练与评估所需的核心模块导入:
import torch
import torch.nn as nn
import torch.optim as optim
torch 模块作为基础,提供张量操作、自动微分与设备管理等关键功能;torch.nn 用于构建神经网络架构,涵盖各种网络层与损失函数;torch.optim 则承担模型优化重任,提供多种优化算法助力模型参数更新,这些模块协同发力,为后续模型开发搭建了稳固的平台。
(二)模型构建:精心雕琢神经网络架构
基于 torch.nn 模块,着手构建适用于 CIFAR-10 图像分类任务的卷积神经网络(CNN)模型。CNN 以其强大的自动特征提取能力,在图像领域表现卓越。
class ImageClassifier(nn.Module):def __init__(self):super(ImageClassifier, self).__init__()self.conv1 = nn.Conv2d(3, 16, kernel_size=3, stride=1, padding=1)self.relu1 = nn.ReLU()self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)self.conv2 = nn.Conv2d(16, 32, kernel_size=3, stride=1, padding=1)self.relu2 = nn.ReLU()self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)self.fc1 = nn.Linear(32 * 8 * 8, 128)self.relu3 = nn.ReLU()self.fc2 = nn.Linear(128, 10)def forward(self, x):x = self.conv1(x)x = self.relu1(x)x = self.pool1(x)x = self.conv2(x)x = self.relu2(x)x = self.pool2(x)x = x.view(-1, 32 * 8 * 8)x = self.fc1(x)x = self.relu3(x)x = self.fc2(x)return xmodel = ImageClassifier()
在上述模型中,init 方法定义了网络结构。首层卷积层 self.conv1 接收 3 通道(对应 RGB 图像)的输入,通过 16 个 3×3 卷积核进行特征提取,步长为 1 且填充为 1,确保输入输出图像尺寸一致。