Pytorch 2 :Basic Conception & Dataset & Dataloader
导言
- 训练推理相关基本概念
- 数据集与数据加载器:学习如何使用torch.utils.data.Dataset和DataLoader来加载和处理数据。
- 数据预处理:介绍常用的数据预处理方法,如归一化、数据增强等。
什么是一“次”训练和推理?¶
在深度学习中,一次训练 或 一次推理 的范围取决于你关注的层次,主要有三种常见的粒度:
- Inference(推理):使用训练好的模型,在不计算梯度的情况下,对输入数据进行前向传播,得到输出结果。
- Iteration(迭代):一次前向传播 + 计算损失 + 反向传播 + 更新参数,处理 一个 batch 的数据。
- Epoch(轮次):遍历整个数据集 一次,即所有 batch 都被处理了一遍。
推理(Inference)¶
推理就是使用训练好的模型进行前向传播,但 不需要计算梯度,也不更新参数,通常用于测试或实际应用中。例如:
推理时,batch size 也决定了一次推理处理多少样本。Iteration(迭代)¶
当我们训练一个神经网络时,一般不会直接在整个数据集上进行计算,而是将数据集拆分成多个 batch,然后逐个 batch 送入模型进行训练。
一次迭代指的是:
- 取出一个 batch(批量)数据 送入神经网络。
- 前向传播(Forward Pass):计算模型输出。
- 计算损失(Loss)。
- 反向传播(Backward Pass):计算梯度。
- 更新参数(Optimizer Step)。
例如
for batch_data, batch_labels in dataloader: # dataloader 按 batch 迭代数据
optimizer.zero_grad() # 清空梯度
outputs = model(batch_data) # 前向传播
loss = loss_fn(outputs, batch_labels) # 计算损失
loss.backward() # 反向传播
optimizer.step() # 更新参数
每次 for
循环的执行就是 一次迭代(Iteration),处理的是 一个 batch 的数据。
如果你的数据集有 1000 个样本,batch size=32,那么:
- 训练完整个数据集 需要
1000/32 = 32
次 iteration(迭代)。 - 每次 iteration 仅处理 32 个样本,而不是整个数据集。
Epoch(轮次)¶
一次 epoch 指的是 整个数据集都被训练一次,也就是说 所有 batch 都被训练过一遍。
- 假设你的数据集有 1000 个样本,batch size = 32,
- 需要
1000 / 32 = 32
次 iteration 才能遍历完整个数据集,即完成 1 个 epoch。 - 如果训练 10 轮(
epochs=10
),则总共会进行32 × 10 = 320
次 iteration。
代码示例:
epochs = 10 # 训练 10 轮
for epoch in range(epochs):
for batch_data, batch_labels in dataloader: # 每个 batch 训练一次
optimizer.zero_grad()
outputs = model(batch_data)
loss = loss_fn(outputs, batch_labels)
loss.backward()
optimizer.step()
这个代码 完整遍历数据集 10 次,即 10 个 epoch,总共会执行 iteration = (数据集大小 / batch_size) × epochs
。
多个 epoch 的必要性¶
一次 epoch 确实遍历了整个数据集,但它 并不意味着模型已经学好了。在深度学习中,我们通常需要多个 epoch 来让模型逐步优化,找到更好的参数。
1. 训练神经网络的本质¶
神经网络的训练本质上是一个优化问题,目标是找到一组最优的参数,使得模型的损失函数最小(即预测尽可能准确)。
训练的过程类似于爬山,我们需要沿着损失函数的梯度下降,不断调整模型参数,逐渐收敛到最优解。
如果只进行 1 个 epoch,那么:
- 参数更新次数较少,模型可能还没有足够的训练,误差仍然很大。
- 梯度下降未收敛,即模型还没有达到最优状态。
- 模型可能无法充分学习数据特征,导致性能较差。
因此,我们需要多次遍历数据集(多个 epoch),不断优化参数,使模型的表现更好。
2. 为什么一次 epoch 不够?¶
假设你的数据集有 10,000 个样本,batch size = 32,那么 1 个 epoch 需要 10,000 / 32 ≈ 312
次 iteration。
但在实际情况中:
- 神经网络一开始参数是随机的,它需要多个 epoch 来逐步学习数据中的模式。
- 优化算法(如梯度下降)不会在一次遍历后立即找到最优解,而是通过不断迭代找到更好的参数。
- 损失函数(Loss)下降的趋势通常是逐步收敛的,如下图所示:
可以看到,在多个 epoch 之后,损失才会下降并趋于稳定。
3. 训练多个 epoch 会发生什么?¶
假设你训练 10 个 epoch,每个 epoch 的损失可能如下: | Epoch | Training Loss | |--------|-------------| | 1 | 1.2 | | 2 | 0.9 | | 3 | 0.7 | | 4 | 0.5 | | 5 | 0.4 | | ... | ... | | 10 | 0.2 |
可以看到,损失在逐渐降低,模型在逐步学习数据模式。如果只训练 1 个 epoch,损失可能仍然较高,模型的预测能力较弱。
4. 什么时候停止训练?¶
一般来说,你不会一直增加 epoch,因为: 1. 太少的 epoch → 欠拟合(Underfitting),模型还没学会数据中的模式。 2. 太多的 epoch → 过拟合(Overfitting),模型在训练集上效果很好,但在测试集上表现变差。
早停(Early Stopping)¶
常见的做法是:
- 监控验证集(Validation Set)的损失,如果发现连续几轮损失不再下降,就提前停止训练。
- 例如:
import torch
import torch.nn as nn
import torch.optim as optim
model = ... # 定义模型
loss_fn = nn.CrossEntropyLoss() # 损失函数
optimizer = optim.Adam(model.parameters(), lr=0.001) # 优化器
best_val_loss = float('inf') # 记录最小的验证损失
patience = 3 # 如果验证损失连续 3 次没有下降,则停止训练
counter = 0
for epoch in range(50): # 最多训练 50 个 epoch
train_loss = train_one_epoch(model) # 训练一个 epoch
val_loss = validate(model) # 计算验证集损失
if val_loss < best_val_loss:
best_val_loss = val_loss
counter = 0 # 重新计数
else:
counter += 1
if counter >= patience:
print(f"Early stopping at epoch {epoch}")
break # 提前停止训练
Batch size¶
batch size(批大小) 指的是 一次训练或推理时输入到神经网络中的样本数量。
Mini-batch
- Mini-batch:小批量数据,介于批量梯度下降(全数据)和随机梯度下降(单样本)之间的折中方案。
为什么需要 batch size?¶
在深度学习中,训练神经网络时,我们通常不会一次性把所有数据都输入,而是分批(batch)输入。这样做的原因包括:
- 计算资源限制:一次性处理整个数据集可能会超出 GPU/CPU 的内存,而分批次处理可以减少内存占用。
- 加速训练:小批量数据可以在 GPU 上并行计算,提高效率。
- 更稳定的梯度下降:批量梯度下降(mini-batch gradient descent)可以在计算梯度时减少单个样本带来的噪声,使优化过程更加稳定。
Batch size 的不同取值影响¶
- 小 batch size(如 8, 16)
- 内存占用小,适用于小显存设备(如笔记本 GPU)。
- 噪声较大,梯度更新不稳定,但可能有助于泛化能力(即模型在未见过的数据上表现更好)。
- 大 batch size(如 128, 256, 1024)
- 计算效率高,但需要较大显存。
- 梯度更新更平稳,适用于大规模数据集。
代码示例
假设我们有一个数据集,并用 PyTorch 的 DataLoader
来加载数据:
import torch
from torch.utils.data import DataLoader, TensorDataset
# 假设我们有 1000 个样本,每个样本有 10 个特征
data = torch.randn(1000, 10) # 1000 个样本,10 维特征
labels = torch.randint(0, 2, (1000,)) # 1000 个 0/1 标签
# 创建数据集
dataset = TensorDataset(data, labels)
# 设置 batch size = 32
batch_size = 32
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)
# 迭代 DataLoader
for batch_data, batch_labels in dataloader:
print(f"Batch shape: {batch_data.shape}") # 输出 batch 大小
break # 只看第一个 batch
如果 batch_size=32
,那么 dataloader
每次会返回 32 个样本,直到数据集遍历完。
BatchNorm¶
Batch Normalization(简称 BatchNorm)是一种加速神经网络训练的方法,它通过对每个 batch 内的数据进行归一化,使网络更稳定、更容易训练,并减少对超参数(如学习率、权重初始化等)的敏感性。
简单来说,BatchNorm 通过在每一层对数据进行归一化,使得数据分布更加平稳,避免梯度消失或梯度爆炸。
BN层只是效果会变好,因为感受到了细节。不是有batch就一定有BN层的意思。
核心思想¶
在深度神经网络中,数据流经每一层时,其分布可能会发生剧烈变化,导致训练变得困难(称为内部协变量偏移 Internal Covariate Shift)。BatchNorm 通过在每一层对输入数据进行标准化(归一化),让数据保持稳定的均值和方差,从而加速训练。
BatchNorm 公式
对于某一层的输入 x
,我们计算它在当前 batch 内的均值和方差:
然后对 x
进行归一化:
其中 ε
是一个很小的数,防止除零错误。
最后,BatchNorm 引入可学习参数 γ
(缩放)和 β
(平移):
这使得网络即使在归一化之后,仍然可以恢复原始的特性表示能力。
BatchNorm 和 Batch Size 的关系¶
BatchNorm 的计算基于 当前 batch 内的均值和方差,因此Batch Size 直接影响 BatchNorm 的效果:
- Batch Size 大(如 128,256)
- 计算的均值和方差更加稳定,因为 batch 内样本多,统计量准确。
-
训练更稳定,BatchNorm 能有效加速训练。
-
Batch Size 小(如 2,4,8)
- Batch 内样本少,均值和方差的计算误差大,导致 BatchNorm 不稳定。
- 可能需要使用 Group Normalization(GN) 或 Layer Normalization(LN) 作为替代方案。
一般来说,BatchNorm 需要 batch size 至少大于 16 或 32 才能较好地工作。如果 Batch Size 太小(如 2~8),BatchNorm 可能会导致梯度不稳定,此时可以考虑:
- 使用 LayerNorm 或 GroupNorm,它们不依赖 batch 统计量。
- 使用 BatchNorm 的 running statistics(即
track_running_stats=True
)。 - 使用 SyncBatchNorm(跨多个 GPU 计算 batch 统计量)。
代码示例
在 PyTorch 中,你可以使用 nn.BatchNorm1d
(1D 数据,如全连接层)、nn.BatchNorm2d
(2D 数据,如 CNN)、nn.BatchNorm3d
(3D 数据,如 3D 卷积)。
import torch
import torch.nn as nn
# 定义一个带有 BatchNorm 的网络
class MyModel(nn.Module):
def __init__(self):
super(MyModel, self).__init__()
self.fc = nn.Linear(10, 20)
self.bn = nn.BatchNorm1d(20) # 1D 全连接层的 BatchNorm
self.relu = nn.ReLU()
def forward(self, x):
x = self.fc(x)
x = self.bn(x) # 归一化
x = self.relu(x)
return x
# 测试
model = MyModel()
x = torch.randn(32, 10) # Batch Size = 32
output = model(x)
print(output.shape) # 输出: torch.Size([32, 20])
多通道¶
一般是任务特征很多维度时,拓展描述参数用的。参考教程。
比如:图像一般包含三个通道/三种原色(红色、绿色和蓝色)。 实际上,图像不是二维张量,而是一个由高度、宽度和颜色组成的三维张量。所以第三维通过通道表示。