数据增强:图像、文本、音频数据增强方法
数据增强这玩意儿,说白了就是「无中生有」—— 在有限的数据集上变出更多样本来。你想想看,深度学习模型再强,数据不够照样过拟合。我自己的经验是,一套好的增强策略,能让模型泛化能力提升 5-10 个点,而且几乎零成本。
但这里有个误区:不是所有增强都适合你的任务。图像翻转、文本同义替换、音频加噪,看起来都是在「扩增」,但对不同模态的数据,坑完全不一样。我从图像聊起,这是大家最熟悉的。
图像数据增强:从基础到策略
图像增强我做得最多,也踩过不少坑。常用的方法可以分为几何变换、颜色变换和噪声注入三类。
我个人习惯用以下组合做基线:
- 翻转与旋转: 水平翻转几乎无损,垂直翻转要谨慎(比如人脸、文字)。旋转角度控制在 ±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])
])
还有一个进阶玩法: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
嗯,这里要注意:文本增强对短文本(比如一个句子)效果明显,但对长文本(段落级)容易引入无关信息。我做情感分析时试过,回译后句子结构变了但极性没变,效果很不错。
音频数据增强:时间和频率的艺术
音频增强算是个小众区域,但做语音识别、说话人识别时绕不开。我最初做语音唤醒时,音频数据不够,硬性增强后模型在真实场景掉点严重,后来才发现问题出在采样率不一致上。
常见方法可以分两类:时域增强 和 频域增强。
时域操作
- 时间拉伸: 改变语速,不改变音调。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)
音频增强有个关键点:保持标签有效。比如做情感识别,时间拉伸太多可能会改变语气;做说话人识别,音高偏移太大可能会让语音特征偏离原说话人。
# 保持音调不变的时间拉伸示例
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. 如果增强导致指标下降,先降低增强强度,再检查是否引入噪声
最后,用一张图总结今天的内容逻辑:
你看这张图就很清楚:从原始数据出发,分三路展开,每个模态都有特定的增强方法。但无论选哪条路,最后都得回到那个核心原则:不破坏语义和标签。
数据增强没有银弹,也没有万能参数。我通常的做法是:先开一个全量增强的 baseline,然后按模态逐个降低强度,验证哪个环节在掉点。说白了,增强不是越多越好,而是刚刚好。