第3章:数据预处理 —— DataLoader 与归一化

🎉摘要:手写数字识别从零开始教程,带你开启深度学习之旅!零基础讲解MNIST数据集、神经网络原理、图像分类核心知识,手把手搭建手写数字识别模型,新手也能轻松入门深度学习实战项目。

这一章的目标

  • 理解为什么不能把 6 万张图一次性喂给模型

  • 掌握 DataLoader 的用法(batch、shuffle)

  • 搞清楚 归一化(Normalization) 做了什么、为什么有用

为什么需要 DataLoader?

想象一下:你要背 6 万个单词。你会把整本词典从头到尾读 60000 遍吗?肯定不会。你会把单词分成一组一组(比如每组 64 个),一组一组背。

模型训练也是一样的道理:

  • 一次性处理所有数据 → 内存爆炸、训练不稳定

  • 一次只处理 1 张图 → 训练太慢、梯度(第 6 章会讲)震荡太厉害

  • 一次处理一小批(batch) → ✅ 又快又稳定

📌 术语:Batch(批次)

把整个数据集切分成多个小份,每一份叫一个 batch。batch 里有多少条数据,叫 batch_size

比如 MNIST 训练集有 6 万张图,设 batch_size = 64,那就分成 60000÷64 ≈ 938 个 batch。模型每 "看" 完一个 batch 就更新一次参数。

DataLoader 干什么的?

DataLoader 就是那个帮你 "切 batch" 的工具。你把 Dataset 丢给它,它帮你自动:

  1. 随机打乱数据(shuffle)—— 防止模型记住顺序

  2. 按 batch_size 分组

  3. 可选地使用多线程加速加载(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 上报莫名其妙的错。本教程就没用到这个参数。

数据归一化(Normalization)

为什么需要归一化?

先看一个简单的例子:假设两个人参加考试,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))(三个通道各一个均值和标准差)。

把 Dataset 和 DataLoader 串起来

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 + 归一化 示例代码。

 

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号