这一章的目标
理解为什么不能把 6 万张图一次性喂给模型
掌握 DataLoader 的用法(batch、shuffle)
搞清楚 归一化(Normalization) 做了什么、为什么有用
想象一下:你要背 6 万个单词。你会把整本词典从头到尾读 60000 遍吗?肯定不会。你会把单词分成一组一组(比如每组 64 个),一组一组背。
模型训练也是一样的道理:
一次性处理所有数据 → 内存爆炸、训练不稳定
一次只处理 1 张图 → 训练太慢、梯度(第 6 章会讲)震荡太厉害
一次处理一小批(batch) → ✅ 又快又稳定
📌 术语:Batch(批次)
把整个数据集切分成多个小份,每一份叫一个 batch。batch 里有多少条数据,叫 batch_size。
比如 MNIST 训练集有 6 万张图,设 batch_size = 64,那就分成 60000÷64 ≈ 938 个 batch。模型每 "看" 完一个 batch 就更新一次参数。
DataLoader 就是那个帮你 "切 batch" 的工具。你把 Dataset 丢给它,它帮你自动:
随机打乱数据(shuffle)—— 防止模型记住顺序
按 batch_size 分组
可选地使用多线程加速加载(num_workers)
简单示例:为了避免再次耗时下载数据集,可以直接将第 2 章下载的数据集拷贝到当前目录。
import torch
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
# 下载 MNIST 训练集
train_dataset = datasets.MNIST(
root="./data", # 数据存哪(会自动创建目录)
train=True, # True=训练集,False=测试集
download=True, # 本地没有就自动下载
transform=transforms.ToTensor() # 把PIL图片转成Tensor
)
# 把 Dataset 交给 DataLoader
train_loader = DataLoader(
train_dataset, # 前面创建好的 Dataset
batch_size=64, # 每个 batch 64 张图
shuffle=True # 每个 epoch 随机打乱顺序
)
# 取一个 batch 看看
images, labels = next(iter(train_loader))
print(f"一个 batch 的图片形状: {images.shape}") # → (64, 1, 28, 28)
print(f"一个 batch 的标签形状: {labels.shape}") # → (64,)运行示例,输出如下:
一个 batch 的图片形状: torch.Size([64, 1, 28, 28])
一个 batch 的标签形状: torch.Size([64])images.shape = (64, 1, 28, 28) 的含义:
64 → batch_size,这一批有 64 张图
1 → 通道数(灰度图是 1)
28 → 图片高度
28 → 图片宽度
💡 num_workers 设多少?
初学者建议先不管,默认 num_workers=0(主线程加载)最稳。设太大了可能在 Windows 上报莫名其妙的错。本教程就没用到这个参数。
先看一个简单的例子:假设两个人参加考试,A 考了 90 分(满分 100),B 考了 500 分(满分 600)。只看卷面分数,B 高得多。但按百分制算,A 是 90%,B 是 83.3%。如果不归一化,模型会 "高看" 那些数值范围大的特征。
MNIST 的图片经过 ToTensor() 处理后,像素值范围是 [0, 1]。本来已经在一个比较小的范围了,但我们通常还会再做一步标准化:减去均值、除以标准差。
对于 MNIST,均值约 0.1307,标准差约 0.3081(前人算好的,直接用):
# 不归一化:像素范围 [0, 1]
# 归一化后:像素范围大致 [-0.42, 2.82],均值为0,标准差为1
transform = transforms.Compose([
transforms.ToTensor(), # 1. 转 Tensor,值范围 [0,1]
transforms.Normalize((0.1307,), (0.3081,)) # 2. 标准化: (x - 均值) / 标准差
])📌 术语:标准化(Standardization / Z-score Normalization)
标准化(Standardization,Z‑score 标准化,Z 分数),深度学习图像预处理里非常常用。
公式:x' = (x - mean) / std
x:原始的一个数值
mean:均值(所有数据的平均数)
std:标准差(衡量数据离散程度)
x':标准化之后的新数值
效果:让数据的均值为 0、标准差为 1。这能让模型训练更稳定、收敛更快。
想象你在调音响,如果每个频道音量差别巨大,调起来很费劲。标准化就是先把所有频道调到差不多大,然后再微调。
直观理解两步:
(x- mean):减去均值,把整体平移,让新数据的中心落在 0 附近
(÷ std):除以标准差,缩放数据的散开程度
举个简单数字例子一组数据:[2,4,6]
mean = (2+4+6)/3 = 4
std = 2
x'_1 &= (2-4)/2 = -1
x'_2 &= (4-4)/2 = 0
x'_3 &= (6-4)/2 = +1
标准化结果:[-1, 0, 1]
👉 新均值 = 0,新标准差 = 1。
注意 Normalize((0.1307,), (0.3081,)) 的参数写法 —— 两个参数都是单元素元组。因为 MNIST 是灰度图,只有 1 个通道。如果是 RGB 彩色图,要写 Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225))(三个通道各一个均值和标准差)。
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
# 定义图像预处理流水线,多个变换按书写顺序依次执行
transform = transforms.Compose([
# ToTensor:PIL图片(0‑255)转为张量,像素值缩放到 [0, 1];通道维度放到第0维 (C, H, W)
transforms.ToTensor(),
# Normalize 标准化:x' = (x‑mean)/std
# MNIST单通道,填入该数据集统计得到的均值、标准差
transforms.Normalize((0.1307,), (0.3081,))
])
# 构建训练集Dataset对象
train_dataset = datasets.MNIST(
root="./data",
train=True, # True代表加载训练子集(60000张)
download=True, # 本地无文件时自动下载数据集
transform=transform# 对每一张图片执行上面定义的预处理流水线
)
# 构建测试集Dataset对象
test_dataset = datasets.MNIST(
root="./data",
train=False, # False代表加载测试子集(10000张)
download=True,
transform=transform
)
# 训练集 DataLoader:把 Dataset 包装成分批迭代器,训练时循环取一批一批数据
# 训练集 shuffle=True 打乱防止模型记住样本顺序
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
# 测试集DataLoader,shuffle=False 测试集不需要打乱顺序
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)
print(f"训练集 batch 数: {len(train_loader)}") # 60000 / 64 ≈ 938
print(f"测试集 batch 数: {len(test_loader)}") # 10000 / 64 ≈ 157运行代码,输出如下:
训练集 batch 数: 938
测试集 batch 数: 157📌 术语:Epoch(轮次)
一个 epoch 就是模型把全部训练数据看过一遍。比如训练集有 6 万张图,batch_size=64,那一个 epoch 包含 938 个 batch。
通常训练需要多个 epoch,因为模型"看一遍"通常学不会。
DataLoader 帮你把数据切分成 batch,支持随机打乱
batch_size 设得太小训练慢,设得太大可能超出内存(MNIST 推荐 32/64/128)
归一化 让不同特征处于同一量级,帮助模型更快更好地学习
一个 epoch = 模型看完所有训练数据一遍
点击查看完整的 DataLoader + 归一化 示例代码。