来自:布尔NLPer
进NLP群—>加入NLP交流群
引言
在工业界要想用深度学习训练出一个优秀的算法模型往往需要一个全面高质量的人工标注数据集,但是在实际的业务中想要获取大量高质量的训练数据需要投入很多的标注人力成本,除此之外还有可能有的标签数据在业务数据的出现比例就很小,很难真正的获取非常全面的数据集,这时,对于数据的扩增也就是数据增强工作就尤为重要了。和大多数算法工程师一样,我在公司的实际业务中也遇到了这个瓶颈,在做客服领域的意图识别过程中,其中某一个类别数据本事出现的概率就小,导致标注的该类别数据量也很少。今天我在这里和大家分享一下两种简单的数据增强方法,分别是EDA和比他更简单的AEDA技术,后面我们将详细的介绍这两种方案。
一 EDA数据增强
1 EDA介绍
EDA是一种简单但是非常有效的文本数据增强方法,是由美国Protago实验室发表于 EMNLP-IJCNLP 2019 会议。
方法参考paper:
https://arxiv.org/pdf/1901.11196.pdf
论文核心思想:提出了四种数据增强技术方案,具体包括同义词替换,随机插入,随机交换,随机删除。并在深度学习模型RNN和CNN上用五个数据集做了文本分类实验的对比研究,实验中,作者根据数据集大小将训练集分为3种规模,用于比较EDA技术在训练数据集规模上的影响。实验也表明了,EDA提升了文本分类的效果。
2 增强方法
(1)同义词替换(Synonym Replacement, SR)
从文本中随机选择n个不在停用词表中的单词,针对每个单词随机地从其同义词词集中选择一个,并将其替换。
不考虑 stopwords,在句子中随机抽取n个词,然后从同义词词典中随机抽取同义词,并进行替换。关于同义词可以使用开源同义词表+领域自定义词表来建立。
def synonym_replacement(words, n):
new_words = words.copy()
random_word_list = list(set([word for word in words if word not in stop_words]))
random.shuffle(random_word_list)
num_replaced = 0
for random_word in random_word_list:
# get_synonyms 获取某个单词的同义词列表
synonyms = get_synonyms(random_word)
if len(synonyms) >= 1:
synonym = random.choice(list(synonyms))
new_words = [synonym if word == random_word else word for word in new_words]
num_replaced += 1
if num_replaced >= n:
break
sentence = ' '.join(new_words)
new_words = sentence.split(' ')
return new_words
(2)随机插入(Random Insertion, RI)
从文本中随机选择一个不在停用词表中的词,从它的同义词词集中随机选择一个词,插入到句子中的随机位置,并将该步骤重复n次。
句子中的每个词,以概率p随机删除。随机插入的字符通过同义词获取。
def random_insertion(words, n):
new_words = words.copy()
for _ in range(n):
add_word(new_words)
return new_words
def add_word(new_words):
synonyms = []
counter = 0
while len(synonyms) < 1:
random_word = new_words[random.randint(0, len(new_words) - 1)]
synonyms = get_synonyms(random_word)
counter += 1
if counter >= 10:
return
random_synonym = synonyms[0]
random_idx = random.randint(0, len(new_words) - 1)
new_words.insert(random_idx, random_synonym)
(3)随机交换(Random Swap, RS)
从文本中随机选择两个单词,并且交换他们的位置,并将该步骤重复n次。
句子中,随机选择两个词,位置交换。该过程可以重复n次。 (swap_word 函数中随机产生两个序列下标,如果相同最多重新生成三次。)
def random_swap(words, n):
new_words = words.copy()
for _ in range(n):
new_words = swap_word(new_words)
return new_words
def swap_word(new_words):
random_idx_1 = random.randint(0, len(new_words) - 1)
random_idx_2 = random_idx_1
counter = 0
while random_idx_2 == random_idx_1:
random_idx_2 = random.randint(0, len(new_words) - 1)
counter += 1
if counter > 3:
return new_words
new_words[random_idx_1], new_words[random_idx_2] = new_words[random_idx_2], new_words[random_idx_1]
return new_words
(4)随机删除(Random Deletion, RD)
用概率p随机删除文本中的单词。
如果句子中只有一个单词,则直接返回
如果句子中所有单词都被删掉,则随机返回一个单词。(这个策略可以根据自己项目进行自定义。)
def random_deletion(words, p):
# obviously, if there's only one word, don't delete it
if len(words) == 1:
return words
# randomly delete words with probability p
new_words = []
for word in words:
r = random.uniform(0, 1)
if r > p:
new_words.append(word)
# if you end up deleting all words, just return a random word
if len(new_words) == 0:
rand_int = random.randint(0, len(words) - 1)
return [words[rand_int]]
return new_words
这些方法里,只有SR曾经被人研究过,其他三种方法都是本文作者首次提出。
值得一提的是,长句子相对于短句子,存在一个特性:长句比短句有更多的单词,因此长句在保持原有的类别标签的情况下,能吸收更多的噪声。为了充分利用这个特性,作者提出一个方法:基于句子长度来变化改变的单词数,换句话说,就是不同的句长,因增强而改变的单词数可能不同。
3 实验结果
数据集:SST-2,CR,SUBJ,TREC,PC
训练集规模:500、2000、5000、全部
模型:CNN、RNN
在规模小的训练数据集上,EDA方法的Accuracy提升效果越明显,2000和5000 的数据量上Accuracy提高0.8%和0.9%, 在500的数据量上面提高3%。
在五个数据集上取平均性能对比有无EDA,以及使用不同数据量时的效果对比:
结果分析:论文作者称小数据集上训练容易过拟合,所以作者对比了一下普通数据训练和带EDA增强的数据训练在不同数据量上的对比。可以看到最后一行是5个数据集的平均acc的结果对比,带EDA的用100%的数据就可以达到88.6%,不带EDA的100%的训练集才能达到87.8%。
4 问题总结
(1)若句子中有多个单词被改变了,那么句子的原始标签类别是否还会有效?
为了验证通过EDA方法产生的数据是否原数据特征一致,作者在Pro-Con数据集上进行数据的对比分析。
具体方法:首先,使用RNN在一未使用EDA过的数据集上进行训练;然后,对测试集进行EDA扩增,每个原始句子扩增出9个增强的句子,将这些句子作为测试集输入到RNN中;最后,从最后一个全连接层取出输出向量。应用t-SNE技术,将这些向量以二维的形式表示出来。如下图所示。下图中大三角和大圆圈都是原来的句子,小三角和小圆圈表示使用EDA技术进行数据增强的句子,可以看出来绝大多数原数据和EDA增强数据保持一致,即没有发生语义偏移,故而文中提出的4种数据增强技术不会影响文本的原始标签。
(2)对于EDA中的每个方法,单独提升的效果如何?
为了确定性能的提升到底是由四种数据增强方式中哪一种,或哪几种方式起到的作用,以及哪种方式起到的作用比较大,作者做了消融研究——分别单独使用其中一种数据增强方式进行实验研究。并得到如下实验结果。
四种数据增强方法消融实验对比
上图中,参数α表示四种数据增强方式里被改变的单词数量占原文本长度的比例,实验中取α={0.05,0.1,0.2,0.3,0.4,0.5}。
对于同义词替换(SR),当α较小时,实验性能提升明显,但是α变大时性能有所下降,可能是因为替换过多单词时改变了原文本的含义;
对于随机插入(RI),α在上述范围内的取值使得实验性能保持相对稳定,可能是因为随机插入的方法使得原文本中的单词顺序保持相对稳定;
对于随机交换(RS),当α≤0.2时实验性能提升明显,当α≥0.3时性能有所下降,因为过多的单词位置交换打乱了原文本的整体顺序,改变了文本含义;
对于随机删除(RD),当α较小时能够使得实验性能达到最高,但是α变大时能严重降低实验性能,因为删除过多单词时,句子难以理解,导致文本丢失语义信息。
消融实验得出的结论是,对于每个方法在小数据集上取得的效果更明显。α如果太大的话,甚至会降低模型表现效果,α=0.1似乎是最佳值。
(3)如何选取合适的增强语句个数?
在较小的数据集上,模型容易过拟合,因此生成多一点的语料能取得较好的效果。对于较大的数据集,每句话生成超过4个句子对于模型的效果提升就没有太大帮助。因此,作者推荐实际使用中的一些参数选取如下表所示
(4)EDA提高文本分类的效果的原理是什么?
生成类似于原始数据的增强数据会引入一定程度的噪声,有助于防止过拟合;
使用EDA可以通过同义词替换和随机插入操作引入新的词汇,允许模型泛化到那些在测试集中但不在训练集中的单词;
(5)为什么使用EDA而不使用语境增强、噪声、GAN和反向翻译?
上述的其它增强技术作者都希望你使用,它们确实在一些情况下取得比EDA较好的性能,但是,由于需要一个深度学习模型,这些技术往往在其取得的效果面前,付出的实现代价更高。而EDA的目标在于,使用简单方便的技术就能取得相接近的结果。
(6)EDA是否有可能会降低模型的性能?
有可能。原因在于,EDA有可能在增强的过程中,改变了句子的意思,但其仍保留原始的类别标签,从而产生了标签错误的句子。
5 EDA数据增强代码实现
代码实现中是需要jieba分词,停用词,以及一个提供同义词的包(Synonyms)
固定格式数据增强
需要将待增强的数据处理成如下格式,然后再书写一个py脚本,在命令行运行即可。
label sentence
其中数据的标签在前,文本在后,标签和文本之间使用\t分割。
使用方式如下,python 文件为example.py
from eda import SimpleEDAEnhance
sed = SimpleEDAEnhance()
sed.simple_eda_enhance()
然后在控制台中输入相关参数即可,控制台输入样例如下:
python example.py --input train.txt --output eda_train.txt --num_aug 2 --alpha 0.2
相关参数说明:
--input: 原始数据的输入文件, must
--output: 增强数据后的输出文件, optional,如果未填写,会在input目录下生成一个eda开头的结果
--num_aug: 一条数据增强几条数据,optional, default 9
--alpha: 每条语句中将会被改变的单词数占比, optional, default 0.1
自定义格式数据增强
有时数据格式相对复杂,这时需要我们将增强的方法嵌入到数据处理的程序中,这时可以参考如下方法, 案例代码如下,这也是实例化EDA这个类。
eda = EDA(num_aug=num_aug,stop_words=stop_words, stop_words_type="hit")
enhance_result = []
with open(input, 'r', encoding='utf8') as reader:
for index, line in enumerate(reader):
line = line.strip()
if not line:
continue
parts = line.split('\t')
label = parts[0]
sentence = parts[1]
aug_sentences = eda.eda(sentence, alpha_sr=alpha, alpha_ri=alpha, alpha_rs=alpha,
p_rd=alpha)
for aug_sentence in aug_sentences:
enhance_result.append(f"{label}\t{aug_sentence}")
其中,EDA初始化类的参数如下:
num_aug: 一条数据增强到多少条,optional, default 9
stop_words: 增强过程使用的停用词, optional, default use hit提供的停用词 stop_words_type: 停用词类型,optional, select scope: ["hit", "cn", "baidu", "scu"]
EDA类中的数据增强方法的分数如下:
sentence: must, 待增强的语句;
alpha_sr: default=0.1,近义词替换词语的比例
alpha_ri: default=0.1,随机插入词语个数占语句词语数据的比例
alpha_rs: default=0.1,随机交换词语个数占语句词语数据的比例
p_rd: default=0.1,随机删除词语个数占语句词语数据的比例
一个简单的测试如下:
from ChineseTextEDA.eda import EDA
eda = EDA()
res = eda.eda("我们就像蒲公英,我也祈祷着能和你飞去同一片土地")
print(res)
结果如下:
['我们 就 像 蒲公英 , 我 也 天主 着 能 和 你 飞去 同 一片 土地', '我们 就 像 蒲公英 , 我 也 祈祷 着 能 和 你 飞去 同 一片 土地', '我们 就 像 蒲公英 , 我 也 祈祷 和 能 着 你 飞去 同 一片 土地', '我们 就 像 蒲公英 , 我 也 祈祷 着 聚花 能 和 你 飞去 同 一片 土地', '我们 就 像 蒲公英 , 我 也 祈祷 着 能 和 飞去 你 同 一片 土地', '我们 就 像 蒲公英 , 我 也 祈祷 能 和 你 飞去 同 一片 土地', '我们 就 像 , 我 也 祈祷 着 能 和 你 飞去 同 一片 土地', '我们 就 像 蒲公英 , 我 也 祈祷 着 能 和 你 飞去 同 假如 一片 土地', '我们 就 像 蒲公英 , 我 也 祈祷 着 能 和 你 直奔 同 一片 土地', '我们 就 像 蒲公英 , 我 也 祈祷 着 能 和 你 飞去 同 一片 土地']
二 AEDA数据增强
1 AEDA介绍
AEDA是一种比EDA更为简单的文本数据增强方法,论文由意大利帕尔马大学的IMP实验室发表于 EMNLP 2021 会议。主要是在原始文本中随机插入一些标点符号,属于增加噪声的一种,在深度学习模型RNN和CNN上用五个数据集做了文本分类实验的对比研究,同时也和EDA做了对比实验。对比结果,相对于EDA数据增强方法,该方法更简单,效果更好。
paper地址:
https://arxiv.org/pdf/2108.13230.pdf
论文核心思想:作者认为,EDA方法,如论是同义词替换,还是随机替换、随机插入、随机删除,都改变了原始文本的序列信息;而AEDA方法,只是插入标点符号,对于原始数据的序列信息修改不明显。在原始文本中随机插入标点符号,文中给出可选的标点符号有六个:{".", “;”, “?”, “:”, “!”, “,”}。
2 增强方法
具体的增强方法很简单就是在原始文本中随机插入标点符号,文中给出可选的标点符号有六个:{".", “;”, “?”, “:”, “!”, “,”}。
插入方式:首先从1~ n*/3(句长的三分之一)随机选取一个数字k,则向原始文本中随机插入k个标点符号。(文中表示,在保证有标点符号插入的同时,又不能插入过多的标点符号,避免产生过多的噪声数据进而对实验性能产生负面影响,所以提出了如此选择数字k的方法。)。
AEDA数据增强例子
论文中给出几点说明:
(1)插入标点的数目:从1到三分之一句子长度中,随机选择一个数,作为插入标点符号的个数。
(2)为什么是1到三分之一句长:作者表示,既想每个句子中有标点符号插入,增加句子的复杂性;又不想加入太多标点符号,过于干扰句子的语义信息,并且太多噪声对模型可能有负面影响。
(3)插入标点的位置:随机插入。
(4)标点符号共包含:主要有6种,“.”、“;”、“?”、“:”、“!”、“,”。
代码如下:
PUNCTUATIONS = ['.', ',', '!', '?', ';', ':']
PUNC_RATIO = 0.3
def insert_punctuation_marks(sentence, punc_ratio=PUNC_RATIO):
words = sentence.split(' ')
new_line = []
q = random.randint(1, int(punc_ratio * len(words) + 1))
qs = random.sample(range(0, len(words)), q)
for j, word in enumerate(words):
if j in qs:
new_line.append(PUNCTUATIONS[random.randint(0, len(PUNCTUATIONS)-1)])
new_line.append(word)
else:
new_line.append(word)
new_line = ' '.join(new_line)
return new_line
3 实验结果
数据集:SST-2,CR,SUBJ,TREC,PC
训练集规模:500、2000、5000、全部
模型:CNN、RNN、BERT
从下表可以看出,在RNN模型和CNN模型上,AEDA方法均好于EDA方法和原始训练方法。并且EDA方法,在数据集比较小时,效果有所提升;数据集增大后,效果有所下降。而AEDA方法,如论数据集大小,效果均匀提升,不过数据集越小,提升的越明显。
AEDA方法与EDA方法在不同数据比例上效果对比
AEDA方法与EDA方法在不同数据集上的效果分布图
在深度模型(BERT)中,该方法依然有效。如下表所示,在BERT模型上,AEDA方法依然有效,只不过提升不明显。而EDA方法却使效果降低。
BERT模型上效果对比
如下图所示,当数据集较小时,数据增强倍数越大,效果提升的越明显;但是当数据量较大时,数据增强倍数越大,效果提升将会下降。
数据增强比例的影响对比
4 问题总结
AEDA确实是一种超简单的数据增强方法,但是也被很多学术界所质疑,因为方法的过于简单被认为这篇论文很水,我也看了很多人都有这样的评价,当然我自己也在我的数据集上做了实验,确实效果没有提升。但是这种简单的方法也给自然语言处理领域的数据增强技术发展提供了新的思路可以尝试。
三 结束语
本文主要介绍了两种非常简单的数据增强技术EDA和AEDA,通常在样本数量较少、样本不均衡或者需要提高模型的鲁棒性的场景下,我们可以使用样本增强技术很好的提升模型的泛化能力。这也是我在实际公司业务里面遇到的问题,所以就调研做了实际的尝试,分别在我自己业务的数据集上做了文本分类的实验,在我公司业务数据集上使用EDA做的数据增强效果提升了两个点不到,但是AEDA并没有在效果上有提升,我使用的模型是TextCNN文本分类算法。
TextCNN没有使用数据增强
TextCNN使用EDA数据增强
TextCNN使用AEDA数据增强
文章参考:
https://mp.weixin.qq.com/s/x8h1j61_YdfPrGh_8WpPKA
https://zhuanlan.zhihu.com/p/408774734
最后给大家推荐一下最近小编从最新的斯坦福NLP的公开课都放到了bilibili上了,都已做了中英翻译,大部分已经更新完毕了,给需要的小伙伴~
是最新的呦~
目录
词向量
神经分类器
反向传播和神经网络
句法结构
RNN
LSTM
机器翻译、Seq2Seq和注意力机制
自注意力和Transformer
Transformers和预训练
问答
自然语言生成
指代消解
T5和大型预训练模型
待更...
点击阅读原文直达b站~
进NLP群—>加入NLP交流群