深度学习常常以黑盒模型著称,在训练效果差劲时也很难发现是数据问题还是模型问题,经常会出现训练集上 loss 下降、acc 还能凑合,但验证集 acc 时好时坏、甚至比随机还差。以本人在最初学习时经常踩到的坑为例,写一篇关于模型训练debug的文章。
loss
缓慢下降,acc
能到 70% 左右。acc
大幅波动,40%~60% 之间随机跳;有时直接 10% 左右(≈随机猜)。在这个训练过程中,也尝试了许多其他的方法,但是依然旧不回来,例如一些超参数的调整,正则化的加入等等,都于事无补。但是在采用 torchvision.models.resnet18(weights=IMAGENET1K_V1)
预训练模型也救不回来后,我大概知道了应该是数据上的问题。
问题 1:Normalize 的 mean/std 写成了 0–255 标度(常见于直接复制某些检测/分割配置)。 问题 2:验证集没做 Normalize(或与训练不同)。
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:直接看“进模型前的张量统计”
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())
2️⃣ Step 2:可视化“反归一化后的图片”
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)
3️⃣ Step 3:把 train/val 的 transforms 打印出来逐行核对
适合大多数用预训练模型(ResNet/ViT)的入门实验。
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, # ✅ 与训练一致
])
如 torchvision.io.read_ima
ge)这种函数返回
uint8
(0–255),不能直接 Normalize。需要先转浮点并缩放。
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),
])
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))
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
对初学者来说,这类“看起来像训练不稳定、实际是 数据前处理不一致 的问题”非常常见。遇到验证集表现异常,第一反应不是调参,而是核对数据流:读图 → 缩放/裁剪 → ToTensor → Normalize。 养成“先看 min/max/mean/std、再看可视化”的习惯,能帮你少掉很多不必要的头发。祝训练顺利!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。