PyTorch基础(未完结)


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 步:

  1. 接收输入:接收多个来自上一层的输入信号(比如数据的特征,如图片的像素值、文本的向量),用 x₁, x₂, ..., xₙ 表示。
  2. 加权求和:给每个输入信号分配一个 “权重”(w₁, w₂, ..., wₙ,代表该输入的重要性),再加上一个 “偏置项”(b,用于调整神经元的激活阈值),计算总和 z = w₁x₁ + w₂x₂ + ... + wₙxₙ + b
  3. 激活输出:通过 “激活函数”(如 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)

激活函数决定了神经元是否应该被激活。它们是非线性函数,使得神经网络能够学习和执行更复杂的任务。

核心作用

  1. 引入非线性,使网络能够拟合复杂的非线性关系(没有激活函数的网络本质上只是线性模型)。
  2. 控制信号的流动(决定神经元是否 "激活")。

常见的激活函数

  • 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)。

优化器的主要作用:

  1. 参数更新:根据反向传播计算的梯度(param.grad),调整网络参数。
  2. 加速收敛:通过优化策略(如动量、自适应学习率)加快训练速度。
  3. 避免局部最优:通过特殊机制帮助模型跳出局部最优解。

常见的优化器包括:

1. 随机梯度下降(SGD)

最基础的优化器,每次使用一个批次的数据更新参数

  • 公式:param = param - lr * gradlr为学习率)
  • 优点:简单、内存占用低。
  • 缺点:收敛速度慢,易陷入局部最优。

改进版 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.Composetorchvision.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()

0 条评论

发表评论

暂无评论,欢迎发表您的观点!