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 size1表示通道数(灰度图)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.Module 与 forward 定义模型
下面是一个由三层全连接层组成的 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 个数字类别
对比两种方法:
- 传统 Machine Learning Logistic Regression,直接将图像像素摊平成向量
- 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 提供了一种更自然的建模方式。