训练图片少时,可以通过变换增加输入的变化,但“看起来像新图片”不等于“标签仍然正确”。翻转猫的照片通常不改变类别,翻转文字、交通标志或医学图像却可能改变任务含义。选增强策略之前,先写清哪些变化应该被模型忽略。
前置阅读:建议先读 CNN 详解、过拟合与正则化、OpenCV 基础。
基础盘:几何 + 光度,两类先分清
几何变换(动像素位置):翻转、旋转、缩放、裁剪、平移。核心问题:变换后标签还成立吗——水平翻转猫还是猫,但翻转「6」就成了「9」;OCR 场景翻转文字是灾难。增强必须尊重任务的语义不变性,这是第一条纪律。
光度变换(动像素值):亮度、对比度、饱和度、噪声、模糊。相对安全,但医学影像/工业检测里像素值本身可能承载信息(CT 值、缺陷灰度特征),乱动会把信号洗掉。
import albumentations as A
train_tf = A.Compose([
A.RandomResizedCrop(size=(224, 224), scale=(0.6, 1.0)),
A.HorizontalFlip(p=0.5),
A.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, p=0.5),
A.GaussNoise(std_range=(0.01, 0.05), p=0.3),
A.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
])
为什么用 Albumentations 而不是 torchvision.transforms:快(OpenCV 实现)、检测/分割的标签同步开箱即用(bbox/keypoint/mask 跟着图像一起变)。
样本级增强:MixUp 与 CutMix
这两个是近十年最有影响力的增强思想——不增强单张图,而是「混合」两张图:
MixUp:像素级线性叠加,标签也线性混合:img = λ·A + (1−λ)·B, label = λ·yA + (1−λ)·yB。模型学到的决策边界更平滑,泛化和对抗鲁棒性都提升。
CutMix:把 B 的一块矩形区域剪切贴到 A 上,标签按面积比例混合。比 MixUp 更符合视觉习惯(局部区域完整保留)。
import numpy as np
def cutmix(img_a, img_b, label_a, label_b, alpha=1.0):
lam = np.random.beta(alpha, alpha)
H, W = img_a.shape[:2]
# 按 lam 算裁剪框
rh, rw = int(H * np.sqrt(1 - lam)), int(W * np.sqrt(1 - lam))
cy, cx = np.random.randint(H), np.random.randint(W)
y1, y2 = max(cy - rh//2, 0), min(cy + rh//2, H)
x1, x2 = max(cx - rw//2, 0), min(cx + rw//2, W)
img_a[y1:y2, x1:x2] = img_b[y1:y2, x1:x2]
lam_adj = 1 - (y2-y1)*(x2-x1) / (H*W) # 按实际面积修正
return img_a, lam_adj * label_a + (1 - lam_adj) * label_b
注意标签变成软标签后,损失函数要用 BCE 或软标签交叉熵,不能用硬标签的 sparse CE。训练技巧:MixUp/CutMix 通常在最后一个 epoch 关掉(fine-tune 干净数据),收敛更稳。
检测与分割:标签必须跟着变
分类增强只动图,检测/分割的增强要动标签——bbox 坐标随裁剪平移变换、mask 同步形变。这是 Albumentations 的主场:
det_tf = A.Compose([
A.RandomResizedCrop(size=(640, 640), scale=(0.7, 1.0)),
A.HorizontalFlip(p=0.5),
A.RandomBrightnessContrast(p=0.4),
], bbox_params=A.BboxParams(format="yolo", label_fields=["class_labels"],
min_visibility=0.3)) # 裁剪后可见度<30%的框丢弃
out = det_tf(image=img, bboxes=bboxes, class_labels=labels)
min_visibility 是关键细节:裁剪把目标切得只剩一个角时,这个框该丢——否则模型被迫学「一个角 → 整个物体」的伪标签。YOLO 系列的训练管线(Mosaic 四图拼接)也是样本级增强的思想,YOLO 篇有展开。
增强强度的调优:不是越强越好
增强是正则,同样遵循 偏差-方差权衡:
- 数据少 → 强增强(2000 张:MixUp + RandAugment 全套)
- 数据多 → 弱增强(100 万张:翻转 + 轻裁剪即可,过强反而拖慢收敛)
- 分布对齐原则:增强生成的样本别跑出真实部署分布——线上图都是白天拍的,狂加黑夜增强是浪费容量
- 自动搜索:RandAugment(随机抽 N 种操作统一强度)和 TrivialAugment(每次随机一种随机强度)已经替代手工配方成为默认起点,AutoAugment 的强化学习搜索成本太高不值得复现
我的默认配方(中等数据量分类任务):RandomResizedCrop + Flip + ColorJitter + RandAugment(n=2) + 最后几 epoch 关强增强。从小往强调,过拟合消失就停。
踩坑排查
| 现象 | 原因 | 解法 |
|---|---|---|
| 加增强后训练准确率暴跌 | 强度太大/语义被破(如文字翻转) | 逐种加,每加一种看曲线 |
| 检测训练 loss 异常 | 裁剪后 bbox 出界/丢空 | min_visibility 过滤 + 空标签样本处理 |
| MixUp 后不收敛 | 用了硬标签损失 | 换软标签 BCE/KL |
| 验证集也被增强了 | transform 写错位置 | 训练/验证两套 transform,验证只做 Resize+Normalize |
| 增强后比不增强还差 | 增强分布偏离真实分布 | 对齐部署场景,砍掉无关变换 |
练习
- 在 CIFAR-10 子集(2000 张)上对比:无增强 / 基础增强 / 基础+CutMix 三条训练曲线。
- 用 Albumentations 实现一个检测增强管道,可视化 8 个增强后的样本和框,检查 min_visibility 的作用。
- 实验「增强强度」:RandAugment 的 n 从 1 到 4 各训一次,画「n vs 验证准确率」曲线。
- 故意把验证集也加上随机增强,观察验证指标的噪声——理解为什么验证集必须确定性。
面试常问
Q:MixUp 为什么能提升泛化? 三个解释:① 决策边界平滑——两张图之间的线性插值迫使模型在样本间隙也表现良好(vicinal risk 最小化);② 软标签自带 标签平滑效果,抑制过自信;③ 破坏了「死记硬背单一样本」的可能。代价:训练收敛变慢,校准性变差(概率不再锐利),所以最后几 epoch 常关掉。
Q:检测任务增强的特殊考虑? 标签几何同步(bbox/mask 随图像变换)、目标完整性(min_visibility 丢弃残缺框)、小目标风险(RandomResizedCrop 可能把小目标裁没——小目标多的场景慎用)、Mosaic 等多图拼接类增强的批量统计量偏移(BN 在拼接图上的分布和单图不同)。
Q:RandAugment 相对手工配方的优势? 把「选哪几种操作、各多强」的超参搜索压缩成两个数:N(每图随机 N 种)+ M(统一强度)。论文证明这个简化空间就够摸到手工精调的水平,且跨数据集迁移性好。TrivialAugment 更进一步——连 M 都随机,零超参。工程结论:别再手调增强配方,从 TrivialAugment 起步。
Q:测试时增强(TTA)值得用吗? 推理时对同一图做多增强版本(翻转/多尺度),预测取平均——白捡 0.5~1 个点。代价:推理时间 ×N。竞赛里标配,生产里看延迟预算。注意 TTA 要和训练增强分布一致,别引入训练没见过的变换。
Q:生成模型(Diffusion)合成数据算增强吗? 算「数据合成」的新范式:用扩散模型生成稀有类别的样本补充训练。2024 后实证有效(尤其长尾类),但注意:合成数据是模型分布的近似,喂太多会让模型「自食其尾」(model collapse 风险);合成:真实 比例控制在 1:1 以内,且合成图别进验证集。
相关阅读
- 过拟合与正则化——增强是「数据侧正则」的完整版图
- CNN 详解——增强服务的主战场
- 目标检测实战:YOLO——Mosaic 与检测增强
- 自监督学习入门——增强定义「不变性」也是对比学习的根
- 图像生成:GAN 与 Diffusion——合成数据的下一步
数据增强的哲学:把你对问题的先验知识(什么变换不改语义)免费地注入模型。它是少数「理解业务就能提升指标」的技术,和特征工程一样,是领域知识的变现渠道。
💬 评论
这套方案在哪一步卡住了?也欢迎补充你的实现方式或踩坑经验。