首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >深度学习初学者容易犯的数据bug

深度学习初学者容易犯的数据bug

原创
作者头像
九年义务漏网鲨鱼
发布2025-09-03 14:08:49
发布2025-09-03 14:08:49
16600
代码可运行
举报
文章被收录于专栏:tencent cloudtencent cloud
运行总次数:0
代码可运行

图像分类精度拉胯复盘:train/val 预处理不一致(Normalize 按 0–255 写错)三步定位与修复

深度学习常常以黑盒模型著称,在训练效果差劲时也很难发现是数据问题还是模型问题,经常会出现训练集上 loss 下降、acc 还能凑合,但验证集 acc 时好时坏、甚至比随机还差。以本人在最初学习时经常踩到的坑为例,写一篇关于模型训练debug的文章。

❓ Bug 现象

  • 训练过程(train):loss 缓慢下降,acc 能到 70% 左右。
  • 验证(val):acc 大幅波动,40%~60% 之间随机跳;有时直接 10% 左右(≈随机猜)。

在这个训练过程中,也尝试了许多其他的方法,但是依然旧不回来,例如一些超参数的调整,正则化的加入等等,都于事无补。但是在采用 torchvision.models.resnet18(weights=IMAGENET1K_V1) 预训练模型也救不回来后,我大概知道了应该是数据上的问题。

📽️ 场景重现

问题 1:Normalize 的 mean/std 写成了 0–255 标度(常见于直接复制某些检测/分割配置)。 问题 2:验证集没做 Normalize(或与训练不同)。

代码语言:python
代码运行次数:0
运行
复制
from torchvision import transforms

MEAN = [123.675, 116.28, 103.53]
STD  = [58.395, 57.12, 57.375]

train_tfms = transforms.Compose([
    transforms.RandomResizedCrop(224, scale=(0.6, 1.0)),
    transforms.RandomHorizontalFlip(),
    transforms.ToTensor(),
    transforms.Normalize(MEAN, STD),
])

val_tfms = transforms.Compose([
    transforms.Resize(256),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
])

以上代码有两个错误,是不是很难观察出来:

❌ vald_transforms后忽略了 Normalize,和 train 不一致

❌ To.tensor()代码已经帮助我们实现了归一化的操作了,因此mean和std也不再是0-255的均值和标准差之了

直觉解释:ToTensor() 已把像素缩放到 [0,1];再用 0–255 的 mean/std 去标准化,结果几乎把所有像素推到接近一个常数(例如 (0 - 123.675)/58.395 ≈ -2.12(1 - 123.675)/58.395 ≈ -2.11),通道方差极小 → 可学习信号被“压扁”。更糟糕的是,验证集没 Normalize,分布与训练完全不匹配

排查步骤(真实过程)

1️⃣ Step 1:直接看“进模型前的张量统计”

代码语言:python
代码运行次数:0
运行
复制
imgs, _ = next(iter(train_loader))        # [B, 3, H, W], float32
print("train:", imgs.min().item(), imgs.max().item(),
      imgs.mean().item(), imgs.std().item())

imgs_val, _ = next(iter(val_loader))
print("val  :", imgs_val.min().item(), imgs_val.max().item(),
      imgs_val.mean().item(), imgs_val.std().item())
  • 现象:
    • train:mean ≈ -2.1,std ≪ 0.1(异常小,像素几乎被压成常数)。
    • val:min≈0,max≈1,std≈0.25(正常 ToTensor 后的分布)。
  • 结论:两个域的输入分布完全不一致

2️⃣ Step 2:可视化“反归一化后的图片”

代码语言:python
代码运行次数:0
运行
复制
import torch

# 反归一化函数(把标准化后的张量还原到 0–1)
def denorm(x, mean, std):
    mean = torch.tensor(mean, device=x.device).view(1, -1, 1, 1)
    std  = torch.tensor(std,  device=x.device).view(1, -1, 1, 1)
    return (x * std + mean).clamp(0, 1)

# 如果你怀疑 mean/std 写错,试试可视化 denorm(x, 正确的 mean/std)
  • 现象:训练集里的图像“几乎黑成一片/对比度极低”,验证集正常。
  • 结论:Normalize 参数明显不对

3️⃣ Step 3:把 train/val 的 transforms 打印出来逐行核对

  • 发现 val 没 Normalize,而 train 的 Normalize 用的是 0–255 标度 的参数。

解决方案(附代码)

1️⃣ 方案 A:统一成“0–1 标度 + ImageNet 标准化”

适合大多数用预训练模型(ResNet/ViT)的入门实验。

代码语言:python
代码运行次数:0
运行
复制
from torchvision import transforms

IMAGENET_MEAN = [0.485, 0.456, 0.406]
IMAGENET_STD  = [0.229, 0.224, 0.225]

common_norm = transforms.Normalize(IMAGENET_MEAN, IMAGENET_STD)

train_tfms = transforms.Compose([
    transforms.RandomResizedCrop(224, scale=(0.6, 1.0)),
    transforms.RandomHorizontalFlip(),
    transforms.ToTensor(),            # -> 0–1
    common_norm,                      # ✅ 与预训练保持一致
])

val_tfms = transforms.Compose([
    transforms.Resize(256),
    transforms.CenterCrop(224),
    transforms.ToTensor(),            # -> 0–1
    common_norm,                      # ✅ 与训练一致
])
2️⃣ 方案 B:如果你的读取是 0–255 的 Tensor(如 torchvision.io.read_image)

这种函数返回 uint8(0–255),不能直接 Normalize。需要先转浮点并缩放。

代码语言:python
代码运行次数:0
运行
复制
from torchvision.transforms import v2 as T  # 或用经典 transforms 组合
val_tfms = T.Compose([
    T.Resize(256),
    T.CenterCrop(224),
    T.ToDtype(torch.float32, scale=True),   # ✅ 自动 /255
    T.Normalize(IMAGENET_MEAN, IMAGENET_STD),
])
3️⃣ 方案 C:加一个“预处理一致性自检”脚本(强烈推荐)
代码语言:python
代码运行次数:0
运行
复制
import torch
from torchvision.utils import make_grid, save_image

def check_batch_stats(loader, name):
    x, y = next(iter(loader))
    print(f"[{name}] shape={tuple(x.shape)}, "
          f"min={x.min():.3f}, max={x.max():.3f}, mean={x.mean():.3f}, std={x.std():.3f}")

def visualize_batch(loader, out_png, denorm=None):
    x, _ = next(iter(loader))
    if denorm is not None:
        x = denorm(x)
    grid = make_grid(x[:16], nrow=8)
    save_image(grid, out_png)
    print(f"saved -> {out_png}")

# 用法示例:
# check_batch_stats(train_loader, "train"); check_batch_stats(val_loader, "val")
# visualize_batch(train_loader, "train.png", lambda t: denorm(t, IMAGENET_MEAN, IMAGENET_STD))
# visualize_batch(val_loader, "val.png",   lambda t: denorm(t, IMAGENET_MEAN, IMAGENET_STD))

训练/验证循环里也要统一正确姿势

代码语言:python
代码运行次数:0
运行
复制
model.train()
for imgs, labels in train_loader:
    # ... forward/backward/step ...
    pass

model.eval()
with torch.inference_mode():          
    for imgs, labels in val_loader:
        # ... 验证 ...
        pass

✅ 修复结果

  • 在 CIFAR-10 上(ResNet18,bs=128,lr=0.1,cosine),修复后:
    • 训练 acc:↑ 95%+
    • 验证 acc:稳定在 92%±
  • 之前版本:验证 acc 经常掉到 50% 甚至 10%。

结语

对初学者来说,这类“看起来像训练不稳定、实际是 数据前处理不一致 的问题”非常常见。遇到验证集表现异常,第一反应不是调参,而是核对数据流:读图 → 缩放/裁剪 → ToTensor → Normalize。 养成“先看 min/max/mean/std、再看可视化”的习惯,能帮你少掉很多不必要的头发。祝训练顺利!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 图像分类精度拉胯复盘:train/val 预处理不一致(Normalize 按 0–255 写错)三步定位与修复
    • ❓ Bug 现象
    • 📽️ 场景重现
    • 排查步骤(真实过程)
    • 解决方案(附代码)
    • 训练/验证循环里也要统一正确姿势
    • ✅ 修复结果
    • 结语
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档