数据增强:图像、文本、音频数据增强方法

数据增强这玩意儿,说白了就是「无中生有」—— 在有限的数据集上变出更多样本来。你想想看,深度学习模型再强,数据不够照样过拟合。我自己的经验是,一套好的增强策略,能让模型泛化能力提升 5-10 个点,而且几乎零成本。

但这里有个误区:不是所有增强都适合你的任务。图像翻转、文本同义替换、音频加噪,看起来都是在「扩增」,但对不同模态的数据,坑完全不一样。我从图像聊起,这是大家最熟悉的。

图像数据增强:从基础到策略

图像增强我做得最多,也踩过不少坑。常用的方法可以分为几何变换、颜色变换和噪声注入三类。

核心原则: 增强后的样本必须保持「语义不变」。比如数字 6 旋转 180° 变成 9,这就属于语义改变,不能用。

我个人习惯用以下组合做基线:

  • 翻转与旋转: 水平翻转几乎无损,垂直翻转要谨慎(比如人脸、文字)。旋转角度控制在 ±15° 以内。
  • 随机裁剪与缩放: 模拟目标在图像中的位置变化,尺度变化。
  • 颜色抖动: 亮度、对比度、饱和度微调。别调太狠,否则图像像加了滤镜一样失真。
  • 高斯噪声 / 模糊: 提升模型对噪声的鲁棒性,但噪声太大会盖掉特征。
# 我用 torchvision 常用的增强组合
from torchvision import transforms

train_transform = transforms.Compose([
    transforms.RandomHorizontalFlip(p=0.5),
    transforms.RandomRotation(degrees=10),
    transforms.RandomResizedCrop(size=224, scale=(0.8, 1.0)),
    transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406],
                         std=[0.229, 0.224, 0.225])
])
我曾经踩过的坑: 用 RandomRotation 做旋转时忘了设置 fill 参数,结果图像边角出现黑色区域,模型学到的特征是「边角黑块 = 特定类别」。后来我加上了 fill=(114,114,114) 填充,问题解决。

还有一个进阶玩法:MixUp 和 CutMix。前者是把两张图按比例混合,后者是随机裁剪一块贴到另一张图上。别小看这些,MIX 家族能大幅提升鲁棒性。

# MixUp 的核心实现
def mixup_data(x, y, alpha=1.0):
    lam = np.random.beta(alpha, alpha)
    batch_size = x.size()[0]
    index = torch.randperm(batch_size)
    mixed_x = lam * x + (1 - lam) * x[index, :]
    mixed_y = (lam, y[index])  # 标签也按比例混合
    return mixed_x, mixed_y

文本数据增强:别把语义搞丢了

文本增强就有点不一样了。说白了,图像怎么翻都行,但文本翻错了语义就跑了。我个人习惯从以下几个方向入手:

方法 描述 风险点
同义词替换 用 WordNet / 同义词词典替换部分词 替换后语义可能偏移(比如「开心」换成「快乐」没问题,但「苹果」换成「水果」就太宽泛)
随机插入 / 删除 随机插入一个近义词或删除一个词 删除关键实体词会导致标注错误
回译 英文 → 法文 → 英文,利用翻译噪声 长句子容易丢失细节,生成句子不通顺
对抗性增强 基于梯度或掩码替换重要词 需要训练一个辅助模型,计算成本高

我最常用的是 回译,性价比极高。写一个简单 pipeline:

# 回译增强示例(用 transformers 实现)
from transformers import pipeline

back_translate = pipeline(
    "translation_en_to_fr",
    model="Helsinki-NLP/opus-mt-en-fr"
)
fr_to_en = pipeline(
    "translation_fr_to_en",
    model="Helsinki-NLP/opus-mt-fr-en"
)

def back_translate_augment(text):
    fr_text = back_translate(text)[0]['translation_text']
    en_text = fr_to_en(fr_text)[0]['translation_text']
    return en_text
小技巧: 回译时保留下原始标签,只增强输入文本。标签是「Positive / Negative」的,回译后情绪大概率不变,但实体识别任务就不好说了。

嗯,这里要注意:文本增强对短文本(比如一个句子)效果明显,但对长文本(段落级)容易引入无关信息。我做情感分析时试过,回译后句子结构变了但极性没变,效果很不错。

音频数据增强:时间和频率的艺术

音频增强算是个小众区域,但做语音识别、说话人识别时绕不开。我最初做语音唤醒时,音频数据不够,硬性增强后模型在真实场景掉点严重,后来才发现问题出在采样率不一致上。

常见方法可以分两类:时域增强 和 频域增强。

时域操作

  • 时间拉伸: 改变语速,不改变音调。librosa.effects.time_stretch
  • 音高偏移: 改变音调,不改变语速。librosa.effects.pitch_shift
  • 加性噪声: 叠加背景噪声(风声、街道声)。关键是要控制信噪比(SNR)在 10-20 dB 之间
  • 随机裁剪: 从长音频中随机截取片段,模拟不同起始位置

频域操作

  • SpecAugment: 在频谱图上随机遮盖时间频段或频率频段
  • 频率掩码: 随机遮盖一段频率带,模拟部分频段缺失
  • 时间掩码: 随机遮盖一段时间,模拟丢包或断连
# SpecAugment 风格增强示例
import torch
import torchaudio

def spec_augment(spec, time_mask=10, freq_mask=5):
    spec = spec.clone()
    # 频率掩码
    _, freq_len, time_len = spec.shape
    f_start = np.random.randint(0, freq_len - freq_mask)
    spec[:, f_start:f_start+freq_mask, :] = 0
    # 时间掩码
    t_start = np.random.randint(0, time_len - time_mask)
    spec[:, :, t_start:t_start+time_mask] = 0
    return spec

# 与 torchaudio 配合
waveform, sr = torchaudio.load("audio.wav")
spec = torchaudio.transforms.MelSpectrogram()(waveform)
aug_spec = spec_augment(spec)
避坑指南: 我曾经直接在原始波形上加高斯噪声,但忘记归一化,结果音量变得极小。正确的做法是先计算 RMS,按信噪比缩放噪声再叠加。

音频增强有个关键点:保持标签有效。比如做情感识别,时间拉伸太多可能会改变语气;做说话人识别,音高偏移太大可能会让语音特征偏离原说话人。

# 保持音调不变的时间拉伸示例
import librosa

y, sr = librosa.load("audio.wav", sr=16000)
y_stretch = librosa.effects.time_stretch(y, rate=1.2)  # 语速变快 20%

# 同时做音高偏移 + 时间缩放,模拟「声音变尖且语速变快」
y_fast_high = librosa.effects.time_stretch(
    y, rate=1.1
)
y_fast_high = librosa.effects.pitch_shift(
    y_fast_high, sr=sr, n_steps=2  # 升两个半音
)

多模态增强的共通原则

说到底,不管图像、文本还是音频,核心就一条:增强不能破坏数据-标签的对应关系。你想想看,如果增强后的样本标签错了,模型学到的就是错误的映射。

我自己的检查清单:
1. 增强后人工随机抽 50 条样本,看标签是否合理
2. 做一个消融实验:只加增强 vs 不加增强,看验证集指标
3. 如果增强导致指标下降,先降低增强强度,再检查是否引入噪声

最后,用一张图总结今天的内容逻辑:

数据增强:图像·文本·音频方法体系 数据增强方法体系 原始数据 图像增强 翻转 / 旋转 裁剪 / 缩放 颜色抖动 噪声 / MixUp 文本增强 同义词替换 随机插入/删除 回译 音频增强 时间拉伸 音高偏移 SpecAugment 核心原则:增强不破坏语义 / 标签

你看这张图就很清楚:从原始数据出发,分三路展开,每个模态都有特定的增强方法。但无论选哪条路,最后都得回到那个核心原则:不破坏语义和标签。

数据增强没有银弹,也没有万能参数。我通常的做法是:先开一个全量增强的 baseline,然后按模态逐个降低强度,验证哪个环节在掉点。说白了,增强不是越多越好,而是刚刚好。