文本基础增强:五招搞定小样本困境

做 NLP 的朋友应该都有体会——标注数据永远不够用。我入行那会儿,接过一个情感分析项目,甲方只给了 500 条标注数据。500 条啊,连 BERT 都喂不饱。后来怎么解决的?就是靠今天要讲的这五招文本增强技巧。

说白了,文本增强就是「无中生有」。在不改变语义的前提下,把一条数据变成多条。你想想看,这比重新标注划算太多了。

核心思路:通过微小的文本扰动,生成语义不变的新样本。既扩充了数据量,又提升了模型的鲁棒性。

文本基础增强五法 原始文本 1条 → N条 同义词替换 随机插入 随机交换 随机删除 回译 五种方法各有侧重,建议组合使用效果更佳

1. 同义词替换:最基础也最稳妥

这个方法最简单。找到句子中的非停用词,用同义词替换掉。比如「这家餐厅的菜品很好」→「这家餐厅的菜肴很棒」。

我在项目中遇到过一个问题:直接用 WordNet 替换,结果把「苹果」替换成了「公司名」那个意思。嗯,这里要注意——一定要考虑词义消歧

我的经验:替换比例控制在 20%-30% 比较安全。替换太多,句子会变得不自然。我一般每句话只替换 1-2 个词。

import nltk
from nltk.corpus import wordnet
import random

def synonym_replacement(text, replace_ratio=0.2):
    words = text.split()
    # 只替换非停用词
    stop_words = set(nltk.corpus.stopwords.words('chinese'))
    candidate_indices = [i for i, w in enumerate(words) if w not in stop_words]
    
    n_replace = max(1, int(len(candidate_indices) * replace_ratio))
    selected = random.sample(candidate_indices, n_replace)
    
    for i in selected:
        synonyms = wordnet.synsets(words[i], lang='cmn')
        if synonyms:
            # 取第一个同义词
            new_word = synonyms[0].lemmas()[0].name()
            words[i] = new_word
    
    return ' '.join(words)

# 示例
text = "这家餐厅的菜品味道很好"
print(synonym_replacement(text))
# 可能输出:这家餐厅的菜肴味道很好

2. 随机插入:给模型加点「噪音」

随机插入就是在句子中随机位置插入一个词。这个词可以是同义词,也可以是随机词。目的是让模型学会忽略无关信息。

说白了,就是训练模型「抓重点」的能力。你想想看,真实场景中用户输入经常带一些废话,模型得能扛得住。

我曾经踩过的坑:插入太多无关词,导致语义完全变了。比如「今天天气真好」插成「今天天气真好苹果」——这就离谱了。建议插入的词和上下文相关,或者干脆用停用词填充。

def random_insertion(text, insert_ratio=0.1):
    words = text.split()
    n_insert = max(1, int(len(words) * insert_ratio))
    
    for _ in range(n_insert):
        # 随机选一个位置插入
        pos = random.randint(0, len(words))
        # 从原文中随机选一个词插入
        insert_word = random.choice(words)
        words.insert(pos, insert_word)
    
    return ' '.join(words)

# 示例
text = "这部电影非常精彩"
print(random_insertion(text))
# 可能输出:这部电影非常非常精彩

3. 随机交换:打乱局部顺序

随机交换就是随机挑两个词,交换它们的位置。注意,不是全局打乱,而是局部微调。

我习惯只交换相邻的词,或者间隔不超过 2 个位置的词。为什么?因为交换太远,句子就读不通了。比如「我爱北京天安门」交换成「天安门爱北京我」——这谁看得懂?

交换策略 示例(原句:我喜欢在周末去公园跑步) 效果评价
相邻交换 我喜欢在周末去跑步公园 ✅ 可接受,语义基本保留
间隔 2 词交换 我喜欢公园周末去在跑步 ⚠️ 有点别扭,但还能理解
全局随机交换 跑步周末去公园在我喜欢 ❌ 语义完全混乱
def random_swap(text, swap_ratio=0.1):
    words = text.split()
    n_swap = max(1, int(len(words) * swap_ratio))
    
    for _ in range(n_swap):
        i, j = random.sample(range(len(words)), 2)
        # 只交换距离不超过 3 的词
        if abs(i - j) <= 3:
            words[i], words[j] = words[j], words[i]
    
    return ' '.join(words)

# 示例
text = "我每天早上都会喝一杯咖啡"
print(random_swap(text))
# 可能输出:我每天早上都会喝咖啡一杯

4. 随机删除:教模型「脑补」

这个方法很暴力——随机删掉句子中的一些词。目的是让模型学会根据上下文推断缺失的信息。

我在做文本分类时发现,随机删除对提升模型泛化能力特别有效。尤其是当你的数据中有很多「废话」时,模型学会了抓关键词。

参数建议:删除比例控制在 10%-15%。我试过 30% 的删除率,结果模型直接懵了——输入「我 吃饭」它都不知道该输出什么。

def random_deletion(text, delete_ratio=0.1):
    words = text.split()
    # 至少保留一个词
    if len(words) == 1:
        return text
    
    remaining = [w for w in words if random.random() > delete_ratio]
    # 防止删光
    if not remaining:
        return random.choice(words)
    
    return ' '.join(remaining)

# 示例
text = "今天天气非常好适合出去散步"
print(random_deletion(text))
# 可能输出:今天天气适合出去散步

5. 回译:最「高级」的增强方法

回译是我个人最喜欢的方法。流程很简单:中文 → 英文 → 中文。因为翻译过程会引入自然的语序变化和同义表达,效果比前几种方法都好。

举个例子:「我很喜欢这部电影」→ 英译「I really like this movie」→ 回译「我真的很喜欢这部电影」。你看,多了个「真的」,语气更强了,但语义完全没变。

我曾经用回译给一个医疗问答项目做数据增强,效果立竿见影——F1 分数直接提升了 3 个点。不过要注意,回译依赖翻译 API,有成本问题。

# 使用 transformers 库做回译
from transformers import pipeline

# 加载翻译 pipeline
zh_to_en = pipeline("translation", model="Helsinki-NLP/opus-mt-zh-en")
en_to_zh = pipeline("translation", model="Helsinki-NLP/opus-mt-en-zh")

def back_translation(text):
    # 中文 → 英文
    en_text = zh_to_en(text)[0]['translation_text']
    # 英文 → 中文
    zh_text = en_to_zh(en_text)[0]['translation_text']
    return zh_text

# 示例
text = "这家酒店的性价比很高"
print(back_translation(text))
# 可能输出:这家酒店性价比很高

实战建议:这五种方法不要单独用。我一般会组合使用:先做同义词替换,再做随机删除,最后用回译。这样一条数据能变出 5-10 条,数据量瞬间就上来了。

好了,文本增强的基础方法就这些。记住一个原则:增强后的数据一定要人工抽检。我见过太多人一股脑跑完代码,结果生成的数据里全是乱码——那还不如不用。

专注资料整理