目录

PyTorch 训练流程入门

总结

统计建模、机器学习与深度学习要解决的核心问题是一致的:如何在数据中拟合一个有效的函数,用于预测未知结果。

PyTorch 将这一过程拆解为结构清晰、可控、可组合的模块,使模型的定义、训练与评估具备统一的逻辑框架。

一、PyTorch 在整个建模体系中的位置

在讨论 PyTorch 之前,有必要将其放回 Machine Learning 与 Deep Learning 的整体建模语境中理解。

1. 从 Machine Learning 说起

在传统的 Machine Learning(机器学习) 中,问题通常被表述为:

  • 一个二维表结构的数据集,每一行对应一条样本
  • 多列作为特征 $X$
  • 一列作为结果 $y$

其中:

  • $y$ 可以是 分类结果(例如二分类 0/1 或多分类)
  • 也可以是 连续值(回归问题)

这类问题通常使用 scikit-learn 建模,典型流程为:

1model.fit(X_train, y_train)
2pred = model.predict(X_test)

这些模型具有一些共同特征:

  • 在概念上属于 estimator(预测器)
  • 有明确的评价指标(accuracy、recall、precision、RMSE 等)
  • 可通过调参优化模型性能

从建模角度看,模型的本质是一个函数近似器给定输入 $X$,输出对 $y$ 的预测。

2. Deep Learning 与 Tensor 的引入

Deep Learning 的目标依然是“预测”,但扩展了可处理的数据形态

与主要处理二维表数据的传统 Machine Learning 不同,Deep Learning 更多处理的是:

  • 图像(像素矩阵,通常带有通道信息)
  • 文本(序列结构)
  • 语音、时间序列等高维数据

这些数据不再适合用简单的二维表表示,而是具有更复杂的结构。

因此,引入了一个新的核心概念:Tensor(张量)

Tensor 可以理解为:

  • 多维数组
  • 同时具备自动求导能力
  • 可在 GPU 上高效计算

在 PyTorch 中,所有数据、模型参数、模型输出以及损失函数的计算结果,最终都会以 Tensor 的形式存在。

常见的 Tensor 形状包括:

  • 一维(标签)

    1y.shape == [B]
    
  • 二维(结构化特征)

    1x.shape == [B, num_features]
    
  • 四维(图像)

    1x_image.shape == [B, 1, 64, 64]
    

其中:

  • B 表示 batch size
  • 1 表示通道数(灰度图)
  • 64 × 64 表示图像尺寸

二、One-hot 编码与类别变量的表示

在深度学习任务中,输入往往包含 类别型特征(如 type、状态或类别)。

如果直接使用整数编码,例如:

1A = 0, B = 1, C = 2

模型会隐含地认为:

  • C 比 A “大”
  • B 与 C 比 A “接近”

这种数值关系在大多数分类问题中并不合理。

因此通常使用 one-hot 编码,将同一变量的不同取值,展开为多个 0/1 变量。

1C → [0, 0, 1]

这种表示方式使模型只关注“属于哪一类”,而不引入类别之间的数值顺序或距离假设。

三、梯度、函数与优化的直觉理解

在数据 tensor 化之后,每一条样本都可以视为高维空间中的一个点。

对数据集进行建模,本质上是在 拟合一个多维空间中的函数

$$f_\theta(X) \approx y$$

其中:

  • $\theta$ 表示模型参数
  • 损失函数(loss)用于衡量预测结果与真实值之间的差距

梯度(gradient) 是损失函数对模型参数的偏导数,用于描述:

  • 参数发生微小变化时,损失函数变化的方向与幅度

梯度下降及其变体(如 Adam)正是利用这些导数信息,不断更新参数,使损失函数逐步减小。

四、PyTorch 的整体训练流程

PyTorch 将上述建模思想拆解为工程流程:

1数据(Dataset)
2→ 批量读取(DataLoader)
3→ 模型(nn.Module)
4→ 损失函数(Loss)
5→ 优化器(Optimizer)
6→ 训练循环(Training Loop)
7→ 评估循环(Evaluation Loop)

五、常用模块的导入方式

1import torch
2import torch.nn as nn
3from torch.utils.data import DataLoader

虽然 torch 已经包含所有子模块,但仍然单独导入:

  • torch.nn:集中放置神经网络相关组件
  • DataLoader:训练中使用频率极高

这样做的主要目的是提高代码的可读性与书写便利性。

六、Dataset 与 DataLoader:数据如何进入模型

1. Dataset:定义单条样本

Dataset 用于定义:在给定一个 index 的情况下,如何返回一条训练样本。

例如:

 1class WaterDataset(Dataset):
 2    def __init__(self, csv_path):
 3        super().__init__()
 4        df = pd.read_csv(csv_path)
 5        self.data = df.to_numpy()
 6
 7    def __len__(self):
 8        return self.data.shape[0]
 9
10    def __getitem__(self, idx):
11        features = self.data[idx, :-1]
12        label = self.data[idx, -1]
13        return features, label

其中:

  • Dataset 是 PyTorch 提供的基类
  • super().__init__() 用于正确初始化父类
  • __len__ 定义样本数量
  • __getitem__ 定义如何通过索引返回一条样本

具体的数据存储方式(如 CSV、图片、Tensor)取决于数据本身的形式。

2. DataLoader:从单条样本到批量训练

神经网络训练通常以 batch 为单位进行:

1train_loader = DataLoader(dataset, batch_size=64, shuffle=True)

这样做的原因包括:

  • 一次只喂一条数据效率较低
  • 固定顺序训练容易导致过拟合

使用 DataLoader 后,训练过程只需关注:

1for features, labels in train_loader:
2    ...

七、模型(nn.Module):PyTorch 的核心

在 PyTorch 中,模型并不是普通函数,而是一个:

  • 包含可学习参数
  • 支持自动求导
  • 可保存和加载状态

的对象。因此,模型通常通过 类(class) 的方式定义,并继承自 nn.Module

1. 使用 nn.Moduleforward 定义模型

下面是一个由三层全连接层组成的 Multilayer Perceptron(MLP)示例。 forward 方法明确规定了输入数据如何依次经过各层并生成输出

 1import torch.nn as nn
 2import torch.nn.functional as F
 3
 4class Net(nn.Module):
 5    def __init__(self):
 6        super().__init__()
 7        # Define network layers
 8        self.fc1 = nn.Linear(9, 16)
 9        self.fc2 = nn.Linear(16, 8)
10        self.fc3 = nn.Linear(8, 1)
11        
12    def forward(self, x):
13        # Pass x through linear layers with activations
14        x = F.relu(self.fc1(x))
15        x = F.relu(self.fc2(x))
16        return self.fc3(x)

其中:

  • nn.Module 是 PyTorch 提供的模型基类
  • forward 方法定义了数据在模型中的计算路径
  • 所有可学习参数均由 nn.Linear 等层自动注册并参与训练

2. 使用 nn.Sequential 简化模型定义

当网络结构是单一的线性流水线时,可以使用 nn.Sequential 将多层结构按顺序组合,从而简化代码。

 1class Net(nn.Module):
 2    def __init__(self):
 3        super().__init__()
 4        self.net = nn.Sequential(
 5            nn.Linear(9, 16),
 6            nn.ReLU(),
 7            nn.Linear(16, 8),
 8            nn.ReLU(),
 9            nn.Linear(8, 1)
10        )
11
12    def forward(self, x):
13        return self.net(x)

其逻辑可以概括为:

输入 → 线性变换 → 激活 → 再变换 → 输出

需要注意的是:

  • nn.Sequential 只是对层的组织方式进行封装
  • forward 方法依然存在,只是逻辑被简化为一次调用

3. 常用的 nn 层与算子(按应用场景分类)

(A)表格数据 / MLP 常用

  • nn.Linear:全连接层
  • nn.ReLU, nn.GELU, nn.Sigmoid, nn.Tanh:激活函数
  • nn.BatchNorm1d, nn.LayerNorm:归一化层(提高训练稳定性)

(B)图像(CNN)常用

  • nn.Conv2d:二维卷积
  • nn.MaxPool2d, nn.AvgPool2d:池化与下采样
  • nn.BatchNorm2d:卷积网络中常用的归一化
  • nn.Flatten:将特征图展平成一维向量
  • nn.AdaptiveAvgPool2d:自适应池化(适配不同输入尺寸)

(C)文本 / 序列常用

  • nn.Embedding:类别或词向量嵌入
  • nn.RNN, nn.LSTM, nn.GRU:循环神经网络
  • nn.TransformerEncoderLayer, nn.MultiheadAttention:Transformer 相关组件

(D)多分类输出常用

  • 最后一层通常使用

    1nn.Linear(hidden_dim, num_classes)
    

    输出 logits,并与 CrossEntropyLoss 等损失函数配合使用。

八、损失函数:模型优化的目标

损失函数用于量化模型预测与真实结果之间的差距。

例如多分类问题中常用的交叉熵损失:

1criterion = nn.CrossEntropyLoss()
2loss = criterion(outputs, labels)

其含义可以理解为:

  • 模型对正确类别的预测越不确定,loss 越大
  • 预测越接近真实结果,loss 越小

常见损失函数还包括:

  • MSELoss():回归问题
  • BCELoss() / BCEWithLogitsLoss():二分类问题

九、优化器(Optimizer):参数更新的策略

模型在前向传播中只能给出预测结果,本身不会自动调整参数。 参数如何根据损失函数的变化进行更新,由 优化器(Optimizer) 决定。

在 PyTorch 中,优化器通常这样定义:

1optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

其中:

  • model.parameters() 指定需要被更新的模型参数
  • lr(learning rate)控制每一步参数更新的幅度

Adam(Adaptive Moment Estimation)是一种 基于梯度的一阶随机优化方法,其核心思想是:

  • 利用 梯度的一阶矩(均值)二阶矩(方差)
  • 为不同参数自适应地调整学习率
  • 在实践中通常比普通梯度下降收敛更快、更稳定

因此,Adam 常作为深度学习任务中的默认优化器选择

除了 Adam,PyTorch 中还提供了多种常用优化器:

  • torch.optim.SGD:经典的随机梯度下降(可配合 momentum)
  • torch.optim.AdamW:Adam 的改进版本,常用于 Transformer 模型
  • torch.optim.RMSprop:早期常用于 RNN / 序列模型
  • torch.optim.Adagrad:对稀疏特征较友好

优化器的作用,本质上是定义 “如何使用梯度信息来更新参数”。 不同优化器在收敛速度、稳定性与泛化能力上存在差异,具体选择通常依赖于任务与模型结构。

十、Training Loop:学习发生的地方

训练循环重复以下过程:

  • 预测
  • 计算误差
  • 调整参数
1for epoch in range(num_epochs):
2    for data in dataloader:
3        optimizer.zero_grad()                   # 清空上一轮累积的梯度
4        features, labels = data
5        predictions = model(features)
6        loss = criterion(predictions, labels)
7        loss.backward()                         # 通过反向传播计算损失函数对参数的梯度
8        optimizer.step()                        # 根据优化器的更新规则调整参数

这一过程涉及的关键概念包括:

  • 反向传播(backpropagation):利用链式法则计算损失函数对各参数的偏导数
  • 梯度下降(gradient descent)及其变体:沿着梯度的反方向更新参数,使损失函数逐步减小

十一、Evaluation Loop:模型的评价

训练完成后,需要对模型进行评价,其评价方式与传统 Machine Learning 完全一致,包括:

  • accuracy
  • precision / recall
  • macro / micro / weighted average

评估时需切换至评估模式:

1model.eval()
2with torch.no_grad():
3    ...

以避免梯度计算并保证结果稳定。

附录:一个可运行的最小示例(ML vs DL)

任务说明

  • 数据集:MNIST 手写数字(0–9)
  • 输入:28×28 的灰度图像
  • 输出:10 个数字类别

对比两种方法:

  1. 传统 Machine Learning Logistic Regression,直接将图像像素摊平成向量
  2. Deep Learning 一个最小的 CNN,直接对图像进行卷积建模

安装依赖

1%pip -q install torch torchvision scikit-learn numpy

数据加载与子集抽样

 1import numpy as np
 2import torch
 3import torch.nn as nn
 4from torch.utils.data import DataLoader, Subset
 5from torchvision import datasets, transforms
 6from sklearn.linear_model import LogisticRegression
 7from sklearn.metrics import accuracy_score
 8
 9transform = transforms.ToTensor()
10train_ds = datasets.MNIST(root="./data", train=True, download=True, transform=transform)
11test_ds  = datasets.MNIST(root="./data", train=False, download=True, transform=transform)
12
13# 使用子集以加快运行
14rng = np.random.RandomState(42)
15train_idx = rng.choice(len(train_ds), size=12000, replace=False)
16test_idx  = rng.choice(len(test_ds), size=2000, replace=False)
17
18train_sub = Subset(train_ds, train_idx)
19test_sub  = Subset(test_ds, test_idx)

传统 Machine Learning:Logistic Regression

 1def to_numpy_flat(dataset_subset):
 2    X_list, y_list = [], []
 3    for x, y in dataset_subset:
 4        X_list.append(x.view(-1).numpy())  # 28*28 = 784
 5        y_list.append(y)
 6    return np.stack(X_list), np.array(y_list)
 7
 8X_train, y_train = to_numpy_flat(train_sub)
 9X_test, y_test = to_numpy_flat(test_sub)
10
11logreg = LogisticRegression(max_iter=200, solver="lbfgs", multi_class="auto")
12logreg.fit(X_train, y_train)
13
14pred = logreg.predict(X_test)
15acc_logreg = accuracy_score(y_test, pred)
16print(f"Logistic Regression accuracy: {acc_logreg:.3f}")
  • 每个像素被视为一个独立特征
  • 模型在高维空间中学习线性/近线性决策边界
  • 不利用图像的局部空间结构信息

Deep Learning:最小卷积神经网络(CNN)

 1train_loader = DataLoader(train_sub, batch_size=64, shuffle=True)
 2test_loader  = DataLoader(test_sub, batch_size=256, shuffle=False)
 3
 4class SmallCNN(nn.Module):
 5    def __init__(self):
 6        super().__init__()
 7        self.features = nn.Sequential(
 8            nn.Conv2d(1, 16, kernel_size=3, padding=1),
 9            nn.ReLU(),
10            nn.MaxPool2d(2),
11            nn.Conv2d(16, 32, kernel_size=3, padding=1),
12            nn.ReLU(),
13            nn.MaxPool2d(2),
14            nn.Flatten()
15        )
16        self.classifier = nn.Linear(32 * 7 * 7, 10)
17
18    def forward(self, x):
19        x = self.features(x)
20        return self.classifier(x)
21
22model = SmallCNN()
23criterion = nn.CrossEntropyLoss()
24optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
 1for epoch in range(3):
 2    model.train()
 3    running_loss = 0.0
 4    n = 0
 5    for xb, yb in train_loader:
 6        optimizer.zero_grad()
 7        logits = model(xb)
 8        loss = criterion(logits, yb)
 9        loss.backward()
10        optimizer.step()
11        running_loss += loss.item() * yb.size(0)
12        n += yb.size(0)
13    print(f"Epoch {epoch+1} - Loss: {running_loss/n:.4f}")

评估:对比Accuracy

 1model.eval()
 2correct = 0
 3total = 0
 4with torch.no_grad():
 5    for xb, yb in test_loader:
 6        preds = model(xb).argmax(dim=1)
 7        correct += (preds == yb).sum().item()
 8        total += yb.size(0)
 9
10acc_cnn = correct / total
11print(f"CNN accuracy: {acc_cnn:.3f}")
12print(f"Performance gap (CNN - LogReg): {acc_cnn - acc_logreg:.3f}")
  • Logistic Regression 只能基于原始像素做线性区分
  • CNN 通过卷积层自动学习边缘、笔画等局部模式
  • 即使在极简模型与少量训练轮数下,CNN 通常仍能取得更高准确率
  • 当特征本身难以人工定义时,Deep Learning 提供了一种更自然的建模方式。