PyTorch基础
PyTorch 是一个基于 Python 的深度学习框架,由 Facebook 开发,以其灵活性和动态计算图而闻名。
PyTorch 主要有以下几个基础概念:张量(Tensor)、自动求导(Autograd)、神经网络模块(nn.Module)、优化器(optim)等。
- 张量(Tensor):PyTorch 的核心数据结构,支持多维数组,并可以在 CPU 或 GPU 上进行加速计算。
- 自动求导(Autograd):PyTorch 提供了自动求导功能,可以轻松计算模型的梯度,便于进行反向传播和优化。
- 神经网络(nn.Module):PyTorch 提供了简单且强大的 API 来构建神经网络模型,可以方便地进行前向传播和模型定义。
- 优化器(Optimizers):使用优化器(如 Adam、SGD 等)来更新模型的参数,使得损失最小化。
- 设备(Device):可以将模型和张量移动到 GPU 上以加速计算。
安装:
# CPU 版本
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
# GPU 版本(需匹配 CUDA 版本)
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
区别:
-
CPU 版本:仅使用计算机的中央处理器(CPU)进行计算,适用于所有设备(包括没有独立显卡的电脑)。安装简单,无需额外依赖,直接通过 pip/conda 即可安装。
-
GPU 版本:可以利用 NVIDIA 显卡(AMD/Intel 显卡暂不支持 PyTorch 的 GPU 加速)的 CUDA 核心进行计算(需显卡支持 CUDA),同时也能兼容 CPU 计算。需提前安装对应版本的 CUDA Toolkit 和 cuDNN 库(PyTorch 官网会提示匹配的版本)。
1. 张量(Tensors)
张量是 PyTorch 中最基本的数据结构,类似于类似于 NumPy 的数组,但具有 GPU 加速能力和自动求导功能,是神经网络的基本操作单元。
1. 张量的基本概念
- 张量可以看作是多维数组,0 维张量是标量,1 维是向量,2 维是矩阵,更高维度则是高阶张量
- 与 NumPy 数组相比,PyTorch 张量支持 GPU 计算和自动微分
2. 张量的创建
| 方法 | 说明 | 示例代码 |
|---|---|---|
torch.tensor(data) |
从 Python 列表或 NumPy 数组创建张量。 | x = torch.tensor([[1, 2], [3, 4]]) |
torch.zeros(size) |
创建一个全为零的张量。 | x = torch.zeros((2, 3)) |
torch.ones(size) |
创建一个全为 1 的张量。 | x = torch.ones((2, 3)) |
torch.empty(size) |
创建一个未初始化的张量。 | x = torch.empty((2, 3)) |
torch.rand(size) |
创建一个服从均匀分布的随机张量,值在 [0, 1)。 |
x = torch.rand((2, 3)) |
torch.randn(size) |
创建一个服从正态分布的随机张量,均值为 0,标准差为 1。 | x = torch.randn((2, 3)) |
torch.arange(start, end, step) |
创建一个一维序列张量,类似于 Python 的 range。 |
x = torch.arange(0, 10, 2) |
torch.linspace(start, end, steps) |
创建一个在指定范围内等间隔的序列张量。 | x = torch.linspace(0, 1, 5) |
torch.eye(size) |
创建一个单位矩阵(对角线为 1,其他为 0)。 | x = torch.eye(3) |
torch.from_numpy(ndarray) |
将 NumPy 数组转换为张量。 | x = torch.from_numpy(np.array([1, 2, 3])) |
import torch
# 创建张量的几种方式
tensor1 = torch.tensor([1, 2, 3]) # 从列表创建
tensor2 = torch.zeros(2, 3) # 创建一个 2x3 的全 0 张量
tensor3 = torch.ones(3, 3) # 创建一个 3x3 的全 1 张量
tensor4 = torch.rand(2, 2) # 创建一个 2x2 的随机张量(0-1之间)
tensor5 = torch.arange(0, 10, 2) # 创建等差数列张量,从0到10步长为2的一维张量,结果是tensor([0, 2, 4, 6, 8])
tensor6 = torch.linspace(0, 1, 5) # 创建线性等分张量,在指定区间上均匀分布的一维张量,结果是tensor([0.0000, 0.2500, 0.5000, 0.7500, 1.0000])
# 从NumPy数组创建
import numpy as np
np_array = np.array([[1, 2], [3, 4]])
tensor7 = torch.from_numpy(np_array)
# 将张量转换回NumPy数组
np_array_back = tensor7.numpy()
# 在指定设备(CPU/GPU)上创建张量
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
d = torch.randn(2, 3, device=device)
print(d)
3. 张量的属性
| 属性 | 说明 | 示例 |
|---|---|---|
.shape |
获取张量的形状 | tensor.shape |
.size() |
获取张量的形状 | tensor.size() |
.dtype |
获取张量的数据类型 | tensor.dtype |
.device |
查看张量所在的设备 (CPU/GPU) | tensor.device |
.dim() |
获取张量的维度数 | tensor.dim() |
.requires_grad |
是否启用梯度计算 | tensor.requires_grad |
.numel() |
获取张量中的元素总数 | tensor.numel() |
.is_cuda |
检查张量是否在 GPU 上 | tensor.is_cuda |
.T |
获取张量的转置(适用于 2D 张量) | tensor.T |
.item() |
获取单元素张量的值 | tensor.item() |
.is_contiguous() |
检查张量是否连续存储 | tensor.is_contiguous() |
tensor = torch.rand(3, 4)
print(tensor.shape) # 张量形状: torch.Size([3, 4])
print(tensor.size()) # 与shape相同
print(tensor.dtype) # 数据类型: torch.float32
print(tensor.device) # 设备: cpu或cuda:0等
print(tensor.requires_grad) # 是否需要梯度: False
4. 张量的操作
1. 基础操作
| 操作 | 说明 | 示例代码 |
|---|---|---|
+, -, *, / |
元素级加法、减法、乘法、除法。 | z = x + y |
torch.matmul(x, y) |
矩阵乘法。 | z = torch.matmul(x, y) |
torch.dot(x, y) |
向量点积(仅适用于 1D 张量)。 | z = torch.dot(x, y) |
torch.sum(x) |
求和。 | z = torch.sum(x) |
torch.mean(x) |
求均值。 | z = torch.mean(x) |
torch.max(x) |
求最大值。 | z = torch.max(x) |
torch.min(x) |
求最小值。 | z = torch.min(x) |
torch.argmax(x, dim) |
返回最大值的索引(指定维度)。 | z = torch.argmax(x, dim=1) |
torch.softmax(x, dim) |
计算 softmax(指定维度)。 | z = torch.softmax(x, dim=1) |
2. 形状操作
| 操作 | 说明 | 示例代码 |
|---|---|---|
x.view(shape) |
改变张量的形状(不改变数据)。 | z = x.view(3, 4) |
x.reshape(shape) |
类似于 view,但更灵活。 |
z = x.reshape(3, 4) |
x.t() |
转置矩阵。 | z = x.t() |
x.unsqueeze(dim) |
在指定维度添加一个维度。 | z = x.unsqueeze(0) |
x.squeeze(dim) |
去掉指定维度为 1 的维度。 | z = x.squeeze(0) |
torch.cat((x, y), dim) |
按指定维度连接多个张量。 | z = torch.cat((x, y), dim=1) |
tensor.flatten() |
将张量展平成一维 | z = tensor.glatten() |
# 形状操作
tensor = torch.rand(4, 4)
reshaped = tensor.view(2, 8) # 改变形状
flattened = tensor.flatten() # 展平张量
transposed = tensor.t() # 转置(仅适用于2维张量)
# 数学运算
a = torch.tensor([1, 2, 3])
b = torch.tensor([4, 5, 6])
print(a + b) # 加法 tensor([5, 7, 9])
print(torch.add(a, b)) # 加法的另一种形式 tensor([5, 7, 9])
print(a * b) # 乘法 tensor([ 4, 10, 18])
print(torch.matmul(a, b)) # 矩阵乘法(点积) tensor(32)
# 索引和切片
tensor = torch.rand(3, 4)
print(tensor[0]) # 第一行
print(tensor[:, 0]) # 第一列
print(tensor[1:3, 1:3]) # 切片
5. 自动求导相关
# 创建需要计算梯度的张量
x = torch.tensor([2.0], requires_grad=True)
y = x **2 + 3*x + 1
# 反向传播计算梯度
y.backward()
# 查看梯度
print(x.grad) # 输出 tensor([7.]),因为y对x的导数是2x+3,当x=2时为7
PyTorch 张量提供了丰富的操作接口,几乎可以实现所有深度学习所需的计算功能,并且通过 GPU 加速可以显著提高计算效率。
2. 神经网络(nn.Module)
神经网络(Neural Network)是一种模拟人类大脑神经元连接模式的计算模型,核心目标是让机器具备 “学习能力”—— 通过从数据中提取规律,实现分类、预测、决策等智能任务。它是人工智能(AI)和机器学习(Machine Learning)的核心技术之一,也是深度学习(Deep Learning)的基础。
1. 神经元
1. 模仿人类大脑
大脑由约 860 亿个神经元(Neuron) 组成,每个神经元通过 “树突” 接收信号,通过 “轴突” 将处理后的信号传递给其他神经元,神经元之间的连接称为 “突触”。
学习的本质,就是突触连接强度的调整(比如反复练习会强化某些神经元的连接,让知识更牢固)。
神经网络正是对这一过程的数学抽象:用 “人工神经元” 模拟生物神经元,用 “权重” 模拟突触连接强度,用 “激活函数” 模拟神经元是否被 “激活”(是否输出信号)。
2. 人工神经元
人工神经元是神经网络的基本单元,它接收输入信号,通过加权求和后与偏置(bias)相加,然后通过激活函数处理以产生输出。神经元的权重和偏置是网络学习过程中需要调整的参数。
人工神经元结构和功能可拆解为 3 步:
- 接收输入:接收多个来自上一层的输入信号(比如数据的特征,如图片的像素值、文本的向量),用
x₁, x₂, ..., xₙ表示。 - 加权求和:给每个输入信号分配一个 “权重”(
w₁, w₂, ..., wₙ,代表该输入的重要性),再加上一个 “偏置项”(b,用于调整神经元的激活阈值),计算总和z = w₁x₁ + w₂x₂ + ... + wₙxₙ + b。 - 激活输出:通过 “激活函数”(如 Sigmoid、ReLU)对总和
z进行处理,得到神经元的最终输出a = f(z)。激活函数的作用是引入非线性—— 如果没有它,多层网络的效果会退化成单层线性模型,无法处理图像、语音等复杂数据。
举个简单例子:判断一张图片 “是否是猫”,人工神经元可能接收 “是否有胡须(x₁)”“是否有尖耳朵(x₂)” 等输入,权重 w₁ 可能比 w₂ 更大(胡须对 “猫” 的判断更重要),最终通过激活函数输出 “是猫(a≈1)” 或 “不是猫(a≈0)”。
2. 神经网络的三层结构
单个神经元的能力有限,无法处理复杂任务(比如识别不同品种的猫)。因此,神经网络会将大量人工神经元按 “层” 组织,形成标准的三层结构(复杂任务会扩展为更多层,即 “深度学习”):
| 层级类型 | 功能描述 | 示例(以 “图片识别猫” 为例) |
|---|---|---|
| 输入层(Input Layer) | 直接接收原始数据,不进行计算,仅传递数据到下一层。输入层的神经元数量 = 数据的特征数。 | 接收图片的像素值(如 28×28 的图片,输入层有 784 个神经元) |
| 隐藏层(Hidden Layer) | 对输入层的信号进行复杂处理(加权、激活),是 “学习规律” 的核心层。可包含 1 层或多层。 | 第一层隐藏层提取 “边缘、纹理”,第二层提取 “眼睛、鼻子” 形状 |
| 输出层(Output Layer) | 输出最终结果,神经元数量 = 任务的目标类别数。 | 输出 “猫”“狗”“鸟” 3 个类别的概率(3 个神经元) |
3. PyTorch神经网络
神经网络在 PyTorch 中是通过 torch.nn 模块来实现的。torch.nn 模块提供了各种网络层(如全连接层、卷积层等)、损失函数和优化器,让神经网络的构建和训练变得更加方便。
1. 模块(nn.Module)
nn.Module 是 PyTorch 中所有神经网络模块的基类,任何自定义网络或层都需要继承它。它提供了:
- 自动跟踪网络参数的功能
- 方便的
forward()方法定义前向传播逻辑 - 模块嵌套能力(可在一个网络中包含其他子网络)
你需要定义以下两个部分:
__init__():定义网络层。forward():定义数据的前向传播过程。
import torch
import torch.nn as nn
class SimpleNet(nn.Module):
def __init__(self):
super().__init__() # 初始化父类
# 定义网络层(如全连接层)
self.fc = nn.Linear(in_features=10, out_features=2)
def forward(self, x):
# 定义前向传播:输入x → 经过fc层 → 输出
return self.fc(x)
2. 常用网络层
PyTorch 提供了许多常见的神经网络层,以下是几个常见的:
nn.Linear(in_features, out_features):全连接层,输入in_features个特征,输出out_features个特征。nn.Conv2d(in_channels, out_channels, kernel_size):2D 卷积层,用于图像处理。nn.MaxPool2d(kernel_size):2D 最大池化层,用于降维。nn.ReLU():ReLU 激活函数,常用于隐藏层。nn.Softmax(dim):Softmax 激活函数,通常用于输出层,适用于多类分类问题。
3. 激活函数(Activation Function)
激活函数决定了神经元是否应该被激活。它们是非线性函数,使得神经网络能够学习和执行更复杂的任务。
核心作用:
- 引入非线性,使网络能够拟合复杂的非线性关系(没有激活函数的网络本质上只是线性模型)。
- 控制信号的流动(决定神经元是否 "激活")。
常见的激活函数:
- Sigmoid:$f(x) = \frac{1}{1 + e^{-x}}$,用于二分类问题,输出值在 0 和 1 之间。
- Tanh:$f(x) = \frac{\exp(x) - \exp(-x)}{\exp(x) + \exp(-x)}$,输出值在 -1 和 1 之间,常用于输出层之前。
- ReLU(Rectified Linear Unit):目前最流行的激活函数之一,定义为
f(x) = max(0, x),有助于解决梯度消失问题。 - Softmax:$f(x_i) = \frac{\exp(x_i)}{\sum \exp(x_j)}$,常用于多分类问题的输出层,将输出转换为概率分布。
import torch.nn.functional as F
# ReLU 激活
output = F.relu(input_tensor)
# Sigmoid 激活
output = torch.sigmoid(input_tensor)
# Tanh 激活
output = torch.tanh(input_tensor)
4. 损失函数(Loss Function)
损失函数(或称代价函数)用于衡量模型预测值与真实值之间的差异,是模型训练的 "指南针"—— 优化器通过最小化损失函数来更新网络参数。
1. 回归任务(预测连续值)
- 均方误差(MSE, Mean Squared Error)
计算输出与目标值的平方差。
公式:$\text{loss} = \frac{1}{N} \times \sum(y_{\text{pred}} - y_{\text{true}})^2$
特点:对异常值敏感(平方放大误差)。
- 平均绝对误差(MAE, Mean Absolute Error) 公式:$\text{loss} = \frac{1}{N} \times \sum|y_{\text{pred}} - y_{\text{true}}|$ 特点:对异常值更稳健,但梯度在 0 点不连续。
2. 分类任务(预测离散类别)
- 交叉熵损失(Cross-Entropy Loss) 适用于多分类问题,结合了 Softmax 和负对数似然损失: 公式:$\text{loss} = -\sum(y_{\text{true}} \times \log(y_{\text{pred}}))$ 特点:直接优化类别概率分布,是分类任务的首选。
- 二元交叉熵(BCE, Binary Cross-Entropy) 适用于二分类问题: 公式:$\text{loss} = -\sum\left(y_{\text{true}} \times \log(y_{\text{pred}}) + (1-y_{\text{true}}) \times \log(1-y_{\text{pred}})\right)$
3. 其他特殊损失
- L1 损失:与 MAE 相同,常用于正则化。
- Huber 损失:结合 MSE 和 MAE 的优点,对异常值不敏感。
- Triplet Loss:用于度量学习(如人脸识别),拉近同类样本距离,拉远异类样本。
# 均方误差损失
criterion = nn.MSELoss()
# 交叉熵损失
criterion = nn.CrossEntropyLoss()
# 二分类交叉熵损失
criterion = nn.BCEWithLogitsLoss()
5. 优化器(Optimizer)
优化器(Optimizer)是神经网络训练的核心组件之一,负责根据损失函数的梯度调整网络参数(权重和偏置),以最小化损失函数。PyTorch 的torch.optim模块提供了多种优化器,适用于不同场景。
优化器更新参数的核心逻辑是基于损失函数的梯度信息,通过特定算法调整模型参数以最小化损失。所有优化器的底层思想都源于梯度下降:
参数更新的基本公式可简化为:新参数 = 旧参数 - 学习率 × 梯度
学习率越大,参数更新越剧烈,可能跳过损失函数的最小值,导致训练不稳定;
学习率越小,参数更新越平缓,收敛速度慢,需要更多迭代次数,甚至陷入局部最优。
初始学习率通常在 1e-5 ~ 1e-2 之间(即 0.00001 ~ 0.01)。
优化器的主要作用:
- 参数更新:根据反向传播计算的梯度(
param.grad),调整网络参数。 - 加速收敛:通过优化策略(如动量、自适应学习率)加快训练速度。
- 避免局部最优:通过特殊机制帮助模型跳出局部最优解。
常见的优化器包括:
1. 随机梯度下降(SGD)
最基础的优化器,每次使用一个批次的数据更新参数
- 公式:
param = param - lr * grad(lr为学习率) - 优点:简单、内存占用低。
- 缺点:收敛速度慢,易陷入局部最优。
改进版 SGD(带动量): 模拟物理中的动量概念,积累之前的梯度方向,加速收敛
2. Adam
自适应矩估计(Adaptive Moment Estimation),目前最常用的优化器之一,结合了动量和自适应学习率
- 特点:
- 维护参数的一阶矩(动量)和二阶矩(梯度平方的指数移动平均)。
- 自动调整每个参数的学习率(稀疏参数学习率更大)。
- 优点:收敛快、稳定性好,适用于大多数场景。
3. RMSprop
根均值平方传播,由 Hinton 提出,解决 AdaGrad 学习率随时间单调下降的问题
- 特点:使用梯度平方的指数移动平均,避免学习率过早衰减。
- 适用场景:递归神经网络(RNN)等序列模型。
4. Adagrad
自适应梯度算法,自适应调整学习率,对频繁出现的特征使用较小学习率,稀有特征使用较大学习率
- 优点:适合稀疏数据(如文本)。
- 缺点:学习率随训练过程单调下降,可能提前停止学习。
5. AdamW
带权重衰减的 Adam,Adam 的改进版,将权重衰减(Weight Decay)与梯度更新分离,解决 Adam 中权重衰减效果不佳的问题
import torch.optim as optim
# 使用 SGD 优化器
optimizer = optim.SGD(model.parameters(), lr=0.01)
# 使用 Adam 优化器
optimizer = optim.Adam(model.parameters(), lr=0.001)
4. 神经网络训练流程
我们使用torchvision.datasets中的MNIST手写数字数据集(0-9),进行示例操作。图像为28*28像素的灰度图,训练集包含60,000张手写数字图片,测试集包含10,000张图片。数据详细:MNIST — Torchvision 0.23 文档
1. 数据准备
# 定义转换流水线
transform = transforms.Compose([
transforms.ToTensor(), # 将图像转换为张量
# 对张量进行标准化处理,参数为均值和标准差,这里使用MNIST数据集的全局均值0.1307和标准差0.3081
transforms.Normalize((0.1307,), (0.3081,))
])
# 加载训练数据集
# root: 数据保存路径
# train=True: 加载训练集
# download=True: 如果数据不存在则自动下载
# transform: 应用上面定义好的预处理操作
train_dataset = datasets.MNIST(
root='./data',
train=True,
download=True,
transform=transform # 这里将数据应用了定义好的换行流水线transform进行数据预处理
)
# 加载测试数据集
# train=False: 加载测试集
test_dataset = datasets.MNIST(
root='./data',
train=False,
download=True,
transform=transform
)
# 创建数据加载器,用于批量加载数据
# batch_size=64: 每个批次包含64个样本
# shuffle=True: 训练时打乱数据顺序,增加随机性
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
# 测试数据加载器
# 批量更大(1000),不需要打乱顺序
test_loader = DataLoader(test_dataset, batch_size=1000, shuffle=False)
transforms.Compose: torchvision.transforms 模块提供的一个工具类,用于将多个数据转换操作组合成一个有序的转换流水线。它的核心作用是将一系列独立的图像预处理步骤(如缩放、裁剪、标准化等)串联起来,让数据按顺序依次经过所有转换,简化代码逻辑并提高效率。
from torchvision import transforms
# 定义转换流水线
transform_pipeline = transforms.Compose([
转换操作1,
转换操作2,
...,
转换操作N
])
transforms.Normalize: 是 PyTorch 中用于对张量(Tensor)数据进行标准化处理的转换操作,主要作用是将数据调整到特定的均值和标准差范围,使模型更容易训练和收敛。标准化的计算公式为:output = (input - mean) / std
DataLoader : PyTorch 中用于数据加载的核心工具,它可以帮助我们高效地迭代处理数据集。返回值是一个可迭代对象(Iterable)。每次迭代 DataLoader 时,会返回一个批次数据,具体形式取决于数据集和 collate_fn 参数的设置。
from torch.utils.data import DataLoader
data_loader = DataLoader(
dataset, # 必需参数:数据集对象
batch_size=1, # 批次大小,默认1
shuffle=False, # 是否打乱数据,默认False
sampler=None, # 采样器,用于自定义数据采样方式
batch_sampler=None,# 批次采样器,与batch_size、shuffle等互斥
num_workers=0, # 数据加载的进程数,默认0(主进程加载)
collate_fn=None, # 自定义批处理函数
pin_memory=False, # 是否将数据存入CUDA固定内存
drop_last=False, # 是否丢弃最后一个不完整批次
timeout=0, # 加载数据的超时时间
worker_init_fn=None, # 工作进程初始化函数
multiprocessing_context=None # 多进程上下文
)
# 创建 DataLoader 后,可以通过迭代器的方式获取数据
for batch_idx, (data, labels) in enumerate(train_loader):
# data: 一个批次的输入数据(形状为 [64, ...],取决于数据集)
# labels: 对应批次的标签
# 在这里编写模型训练代码(前向传播、计算损失、反向传播等)
2. 定义网络
继承 nn.Module 并实现 __init__(定义层)和 forward(前向传播)。__init__初始化网络层结构,forward则定义了数据在网络层中的传播过程。
# 神经网络模型
class MNISTNet(nn.Module):
# 初始化方法,定义网络结构
def __init__(self):
# 调用父类的初始化方法
super().__init__()
# 定义第一个全连接层
# 输入特征数:28*28(MNIST图像的像素展开)
# 输出特征数:256(自定义的隐藏层大小)
self.fc1 = nn.Linear(28 * 28, 256)
# 定义第二个全连接层
# 输入特征数:256(上一层的输出)
# 输出特征数:128
self.fc2 = nn.Linear(256, 128)
# 定义输出层
# 输入特征数:128
# 输出特征数:10(对应10个数字类别)
self.fc3 = nn.Linear(128, 10)
# 定义Dropout层,用于防止过拟合
# 参数0.2表示有20%的概率随机丢弃神经元
self.dropout = nn.Dropout(0.2)
# 定义前向传播过程
# x: 输入数据
def forward(self, x):
# 将输入图像展平:从(batch_size, 1, 28, 28)变为(batch_size, 784)
# -1表示自动计算该维度的大小,保持批次大小不变
x = x.view(-1, 28 * 28)
# 第一个全连接层的输出经过ReLU激活函数
x = torch.relu(self.fc1(x))
# 应用Dropout
x = self.dropout(x)
# 第二个全连接层的输出经过ReLU激活函数
x = torch.relu(self.fc2(x))
# 输出层,不使用激活函数(因为损失函数会处理)
x = self.fc3(x)
return x
为什么要这样设计神经网络模型?
神经网络层结构的设计并非依赖固定公式,而是先基于经验搭框架,再通过实验调优,通过不断的迭代优化,最终找到最适合当前任务的网络结构。当前案例的数据集用 3 层全连接已足够,避免计算资源浪费。
3. 初始化模型、损失函数和优化器
# 创建模型实例
model = MNISTNet()
# 定义损失函数:交叉熵损失
# 适用于多分类问题,内置了Softmax函数
criterion = nn.CrossEntropyLoss()
# 定义优化器:Adam优化器
# model.parameters(): 需要优化的模型参数
# lr=0.001: 学习率,控制参数更新的步长
optimizer = optim.Adam(model.parameters(), lr=0.001)
4. 定义训练函数
将训练集数据应用到模型中进行多轮学习,
# 4. 定义训练函数
def train(epochs):
# 将模型设置为训练模式
# 这会启用Dropout等训练时特有的功能
model.train()
# 训练指定的轮次
for epoch in range(epochs):
# 记录本轮的累计损失
running_loss = 0.0
# 遍历训练数据加载器中的每个批次,每次循环加载64个图像数据
# batch_idx: 批次索引
# data: 图像数据
# target: 对应的标签(真实值)
for batch_idx, (data, target) in enumerate(train_loader):
# 清零优化器的梯度
# 因为PyTorch会累积梯度,所以每次迭代前需要清零
optimizer.zero_grad()
# 前向传播:将数据输入模型,得到预测结果
output = model(data) # 模型基类nn.Module中定义了__call__方法,所以可以直接调用模型的forward函数
# 计算损失:预测结果与真实标签的差距
loss = criterion(output, target)
# 反向传播:计算损失对各参数的梯度
loss.backward() # 计算损失函数对所有模型参数的梯度,并将这些梯度存储在各参数的.grad属性中
# 更新参数:根据梯度调整模型参数
optimizer.step() # 会读取参数的梯度值,并使用指定Adam算法来更新每个参数的值
# 累加损失值
running_loss += loss.item()
# 每100个批次打印一次训练信息
if batch_idx % 100 == 99: # 索引从0开始,所以是99
# 计算平均损失并打印
print(f'第 {epoch + 1}轮, 第 {batch_idx + 1}个批次, 平均损失: {running_loss / 100:.4f}')
# 重置累计损失
running_loss = 0.0
5. 定义测试函数
# 5. 定义测试函数
def test():
# 将模型设置为评估模式
# 这会禁用Dropout等训练时特有的功能
model.eval()
# 记录正确预测的数量和总样本数
correct = 0
total = 0
# 关闭梯度计算,节省内存并加速计算
# 测试时不需要计算梯度,因为不需要更新参数
with torch.no_grad():
# 遍历测试数据加载器中的每个批次
for data, target in test_loader:
# 前向传播:得到预测结果
output = model(data)
# 找到预测概率最大的类别索引
# torch.max返回最大值和对应的索引,我们只需要索引
_, predicted = torch.max(output.data, 1) # output.data是output的数据部分,但不包含计算图信息
# 累加总样本数
total += target.size(0)
# 累加正确预测的数量
correct += (predicted == target).sum().item()
# 计算并打印测试准确率
print(f'准确率: {100 * correct / total:.2f}%')
output = model(data):每个批次1000个数据,向前传播得到预测结果,输出的数据应该是一个1000行10列的张量,每行中最大的值对应的索引就是预测的分类结果。示例输出
tensor([[0.2, 0.1, 5.3, 0.8, 2.1, 0.5, 1.2, 3.7, 0.9, 0.3], # 第1个样本的10类分数,索引2分数最高,预测类别为数字2
[1.5, 4.2, 0.1, 0.3, 0.8, 2.1, 0.7, 0.2, 3.5, 0.6], # 第2个样本的10类分数
[0.4, 0.2, 0.1, 6.8, 0.5, 1.2, 0.9, 0.3, 0.7, 2.1]]) # 第3个样本的10类分数
...
torch.max(output.data, 1)参数1是值维度1,也就是列方向,对每一行,在其所有列中找最大值。
(predicted == target).sum().item():
predicted == target会生成一个布尔张量(True/False),对应位置相同则为True。sum()会对布尔张量中的True数量求和。item()将张量转换为python标量。
6.执行训练和测试
# 训练5轮
train(5)
# 测试模型性能
test()
7.完整代码
# 导入必要的库
import torch
# 导入PyTorch的神经网络模块
import torch.nn as nn
# 导入PyTorch的优化器模块
import torch.optim as optim
# 导入数据加载工具
from torch.utils.data import DataLoader
# 导入计算机视觉相关的数据集和变换工具
from torchvision import datasets, transforms
# 1. 数据准备:加载MNIST数据集并进行预处理
# 定义数据预处理管道
# Compose用于将多个变换操作组合在一起
transform = transforms.Compose([
# 将PIL图像转换为PyTorch张量(Tensor)
# 同时会将像素值从0-255归一化到0.0-1.0
transforms.ToTensor(),
# 对张量进行标准化处理
# 参数为均值和标准差,这里使用MNIST数据集的全局均值和标准差
transforms.Normalize((0.1307,), (0.3081,))
])
# 加载训练数据集
# root: 数据保存路径
# train=True: 加载训练集
# download=True: 如果数据不存在则自动下载
# transform: 应用定义好的预处理
train_dataset = datasets.MNIST(
root='./data',
train=True,
download=True,
transform=transform
)
# 加载测试数据集
# train=False: 加载测试集
test_dataset = datasets.MNIST(
root='./data',
train=False,
download=True,
transform=transform
)
# 创建数据加载器,用于批量加载数据
# batch_size=64: 每个批次包含64个样本
# shuffle=True: 训练时打乱数据顺序,增加随机性
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
# 测试数据加载器
# 批量更大(1000),不需要打乱顺序
test_loader = DataLoader(test_dataset, batch_size=1000, shuffle=False)
# 2. 定义神经网络模型
# 继承nn.Module,这是PyTorch中所有神经网络模块的基类
class MNISTNet(nn.Module):
# 初始化方法,定义网络结构
def __init__(self):
# 调用父类的初始化方法
super().__init__()
# 定义第一个全连接层
# 输入特征数:28*28(MNIST图像的像素展开)
# 输出特征数:256(自定义的隐藏层大小)
self.fc1 = nn.Linear(28 * 28, 256)
# 定义第二个全连接层
# 输入特征数:256(上一层的输出)
# 输出特征数:128
self.fc2 = nn.Linear(256, 128)
# 定义输出层
# 输入特征数:128
# 输出特征数:10(对应10个数字类别)
self.fc3 = nn.Linear(128, 10)
# 定义Dropout层,用于防止过拟合
# 参数0.2表示有20%的概率随机丢弃神经元
self.dropout = nn.Dropout(0.2)
# 定义前向传播过程
# x: 输入数据
def forward(self, x):
# 将输入图像展平:从(batch_size, 1, 28, 28)变为(batch_size, 784)
# -1表示自动计算该维度的大小,保持批次大小不变
x = x.view(-1, 28 * 28)
# 第一个全连接层的输出经过ReLU激活函数
x = torch.relu(self.fc1(x))
# 应用Dropout
x = self.dropout(x)
# 第二个全连接层的输出经过ReLU激活函数
x = torch.relu(self.fc2(x))
# 输出层,不使用激活函数(因为损失函数会处理)
x = self.fc3(x)
return x
# 3. 初始化模型、损失函数和优化器
# 创建模型实例
model = MNISTNet()
# 定义损失函数:交叉熵损失
# 适用于多分类问题,内置了Softmax函数
criterion = nn.CrossEntropyLoss()
# 定义优化器:Adam优化器
# model.parameters(): 需要优化的模型参数
# lr=0.001: 学习率,控制参数更新的步长
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 4. 定义训练函数
def train(epochs):
# 将模型设置为训练模式
# 这会启用Dropout等训练时特有的功能
model.train()
# 训练指定的轮次
for epoch in range(epochs):
# 记录本轮的累计损失
running_loss = 0.0
# 遍历训练数据加载器中的每个批次,每次循环加载64个图像数据
# batch_idx: 批次索引
# data: 图像数据
# target: 对应的标签(真实值)
for batch_idx, (data, target) in enumerate(train_loader):
# 清零优化器的梯度
# 因为PyTorch会累积梯度,所以每次迭代前需要清零
optimizer.zero_grad()
# 前向传播:将数据输入模型,得到预测结果
output = model(data) # 模型基类nn.Module中定义了__call__方法,所以可以直接调用模型的forward函数
# 计算损失:预测结果与真实标签的差距
loss = criterion(output, target)
# 反向传播:计算损失对各参数的梯度
loss.backward() # 计算损失函数对所有模型参数的梯度,并将这些梯度存储在各参数的.grad属性中
# 更新参数:根据梯度调整模型参数
optimizer.step() # 会读取参数的梯度值,并使用指定Adam算法来更新每个参数的值
# 累加损失值
running_loss += loss.item()
# 每100个批次打印一次训练信息
if batch_idx % 100 == 99: # 索引从0开始,所以是99
# 计算平均损失并打印
print(f'第 {epoch + 1}轮, 第 {batch_idx + 1}个批次, 平均损失: {running_loss / 100:.4f}')
# 重置累计损失
running_loss = 0.0
# 5. 定义测试函数
def test():
# 将模型设置为评估模式
# 这会禁用Dropout等训练时特有的功能
model.eval()
# 记录正确预测的数量和总样本数
correct = 0
total = 0
# 关闭梯度计算,节省内存并加速计算
# 测试时不需要计算梯度,因为不需要更新参数
with torch.no_grad():
# 遍历测试数据加载器中的每个批次
for data, target in test_loader:
# 前向传播:得到预测结果
output = model(data)
# 找到预测概率最大的类别索引
# torch.max返回最大值和对应的索引,我们只需要索引
_, predicted = torch.max(output.data, 1) # output.data是output的数据部分,但不包含计算图信息
# 累加总样本数
total += target.size(0)
# 累加正确预测的数量
correct += (predicted == target).sum().item()
# 计算并打印测试准确率
print(f'准确率: {100 * correct / total:.2f}%')
# 执行训练和测试
# 训练5轮
train(5)
# 测试模型性能
test()
发表评论