-
Notifications
You must be signed in to change notification settings - Fork 403
数据增强 说明文档
冬日新雨 edited this page Jul 25, 2022
·
18 revisions
- 文本数据增强的两个前提:
- 1、不干扰模型标签:文本增强后的语义不干扰模型训练,不会导致样本标签失效;如“这个小吃真好吃。=>正面情绪”增强为“这个小吃真不好吃。=>正面情绪”,随机加字影响到了标签的正确性。
- 2、人可理解:增强后文本,依然保持可读性,达到人可以理解文本的含义;如“这个小吃真好吃。”增强为“斯口吃真好吃。”,其中“这个”替换为“斯”,“小吃”替换为“口吃”,已经完全令人无法理解,模型训练也已偏离。此问题在 同义词替换上非常频繁与普遍。
方法 | 任务类型 | 效果 |
---|---|---|
回译 | 文本分类、序列标注、匹配、文本生成 | 基于机翻效果决定,目前对新闻通用领域效果较好,专项领域视语料决定。长短文本均适合 |
邻近汉字换位 | 文本分类、匹配 | 汉字换位会影响具体实体的含义,在实体含义并不影响整体语义情况下适用。换位汉字占比不宜过大 |
同音词替换 | 文本分类、匹配 | 同音词替换会对局部语义产生影响,造成误差,但对整体语义理解并无干扰。替换词汇的占比不宜过大 |
随机增删符号 | 文本分类、匹配 | 在文本中随机增删不影响语义的额外非中文符号。增加比例不宜过大,若某类字符(数字、字母)对语义有影响,则应该规避此类字符 |
NER实体替换 | 文本分类、匹配、序列标注 | 在文本中随机替换不影响语义的实体。如人名、地名、机构等实体 |
同义词替换 | - | 造成语言连贯性差,语义完全被曲解的概率非常大。此种方法作废,本工具包不支持。具体解释见jio.random_add_delete.__doc__
|
语言模型预测 | 分类、匹配、文本生成 | 利用大型的语言模型如 bert 等,预测句子中空缺的词汇。此种方法依赖大型的语言模型参数,本工具暂不支持 |
给定一段文本,利用各类大厂公开的免费 api,对文本数据做增强。用户可在各大厂的云平台上自行申请密钥,填在接口的参数中。 各厂申请 API 地址如下:
- 百度BaiduApi
- 有道YoudaoApi
- 腾讯TecentApi
- 讯飞XunfeiApi
- 请注意:样例中的 api 参数并非全都可用,请注册账号在各个厂官网申请自己的 api 参数
>>> import jionlp as jio
>>> xunfei_api = jio.XunfeiApi(
[{"appid": "5f5846b1",
"api_key": "52465bb3de9a258379e6909c4b1f2b4b",
"secret": "b21fdc62a7ed0e287f31cdc4bf4ab9a3"}])
>>> tencent_api = jio.TencentApi(
[{"project_id": "0",
"secret_id": "AKID5zGGuInJwmLehbyKyYXGS3NXOXYLE96o",
"secret_key": "buwiGXXifLt888rKQLwGH3dsfsdmeCX"}, # 错误的 api
{"project_id": "0",
"secret_id": "AKID5zGGuInJwmLehbyKyYXGS3NXOXYLE",
"secret_key": "buwiGXXifLt888rKQLwGH3asuhFbmeCX"}]) # 错误的 api
>>> youdao_free_api = jio.YoudaoFreeApi()
>>> youdao_api = jio.YoudaoApi(
[{'appid': '39856bd56b482cfc',
'app_secret': '87XpTE63nBVnrR0b6Hy0aTDWlkoq2l4A'}])
>>> google_api = jio.GoogleApi()
>>> baidu_api = jio.BaiduApi(
[{'appid': '20200618000498778',
'secretKey': 'raHalLakgYitNuzGOoB2'}, # 错误的密钥
{'appid': '20200618000498778',
'secretKey': 'raHalLakgYitNuzGdsoB2'}, # 错误的密钥
{'appid': '20200618000498778',
'secretKey': 'raHalLakgYitNuzGOoBZ'}], gap_time=0.5)
>>> print(baidu_api.__doc__) # 查看接口说明
>>> apis = [baidu_api, youdao_api, google_api,
youdao_free_api, tencent_api, xunfei_api]
>>> back_trans = jio.BackTranslation(mt_apis=apis)
>>> text = '饿了么凌晨发文将推出新功能,用户可选择是否愿意多等外卖员 5 分钟,你愿意多等这 5 分钟吗?'
>>> print(youdao_api(text)) # 使用接口做单次调用
>>> result = back_trans(text)
>>> print(result)
# ['饿了么将在凌晨推出一项新功能。用户可以选择是否愿意额外等待外卖人员5分钟。您想多等5分钟吗?',
# '《饿了么》将在凌晨推出一档新节目。用户可以选择是否愿意等待餐饮人员多花5分钟。您愿意再等五分钟吗?',
# 'Ele.me将在早晨的最初几个小时启动一个新的功能。用户可以选择是否准备好再等5分钟。你不想再等五分钟吗?',
# 'Eleme将在清晨推出新的功能。用户可以选择是否愿意再等5分钟工作人员。你想再等五分钟吗?']
- 原理简述:利用公开的大厂 API 对文本数据做回译增强,即完成从 中文->外文->中文 的翻译过程。
- 该框架考虑了对各 API 的语言种类支持问题;两次调用之间的等待时间问题;等待超时问题;支持在 API 接口中输入多个密钥(appkey_obj)。
- 每一个 API 类提供了初始化
lang_pool
参数,用于指定翻译的语种。基于此种考虑:某些小语种的模型效果并不如英语理想,如上例“饿了么”句子的翻译,小语种的翻译质量不如英汉互译。 - 该接口框架包括了常用的若干 API(BaiduApi、XunfeiApi、GoogleApi、TecentApi、YoudaoApi、YoudaoFreeApi),也支持自定义训练的模型 API 接口。具体见下。
- 自定义 API 接口接收一个 str 格式文本输入,输出对应的 str 格式翻译文本;
- 自定义 API 须指定文本的源语言和目标翻译语言,如(zh, en) 和 (en, zh);
- 自定义 API 在请求调用报错后需要提供
raise Exeption
语句的异常抛出。 - 自定义 API 接口可参考代码中的写法。
- API 接口支持多个密钥,即申请若干个某一厂商的 API,混合在一起调用。框架接口自动选择可用密钥,忽略掉无效密钥。如上例中腾讯和百度的多个密钥,以列表形式传入。
- 您可自己登录对应大厂的云平台,机器翻译服务页面,申请属于自己的 API 的密钥。使用更高效。
- 若某些 API 接口效果不理想,可以随意选定若干或指定某个厂商的 API。
- 各厂机翻评价(个人使用体会,不完全客观):
厂名 | 翻译质量 | 可免费调用数量 |
---|---|---|
百度 | 中上 | 大 |
腾讯 | 较优 | 小 |
有道 | 中上 | 大 |
讯飞 | 中下 | 小 |
谷歌 | 中上 | 无穷多但有ip反爬限制 |
随机交换相邻近字符的位置,用以增强文本数据,理论依据为相邻近汉字顺序变动不影响人的阅读理解。 如“民盟发言人:昂季素山目前情况良好”,“研表究明,汉字的序顺并不定一能影阅响读”。
>>> import jionlp as jio
>>> res = jio.swap_char_position('民盟发言人:昂山素季目前情况良好')
>>> print(res)
# ['民盟发言人:昂季素山目前情况良好',
# '民盟发言人:昂山季素目前情况良好',
# '民盟发言人:素山昂季目前情况良好']
- 随机交换相近字符的位置,且交换位置的距离以正态分布得到,scale 参数为1,默认比例为相邻字符交换占 76.4%,中间隔1个字符占比 21.8%,中间隔两个字符占比为 1.8%
-
augmentation_num(int)
参数控制返回几条增强后的数据 -
swap_ratio(float)
参数控制对每一个汉字的调整其位置概率 - 其余参数参考
jio.swap_char_position.__doc__
采用同音词汇进行原文替换,达到数据增强的目的。汉语输入法中,拼音输入法为目前使用最广泛的一种打字法,使用率占比约 97%。 在实际使用中,常常出现同音词的打字错误,例如:原句为
# 原句:“人口危机如果无法得到及时解决,80后、90后们将受到巨大的冲击”
# 拼输:“人口危机如果无法得到即时解决,80后、90后门将受到巨大的冲击”。
从输入的错误来看,完全不影响人的阅读理解。
>>> import jionlp as jio
>>> res = jio.homophone_substitution('中国驻英记者一向恪守新闻职业道德,为增进中英两国人民之间的了解和沟通发挥了积极作用。')
>>> print(res)
# ['中国驻英记者一向刻手信问职业道德,为增进中英两国人民之间的了解和沟通发挥了积极作用。',
# '中国驻英记者一向恪守新闻职业道德,为增进中英两国人民指尖的了解和沟通发挥了积极作用。',
# '中国驻英记者一向恪守新闻职业道德,为增进中英两国人民之间的了解和沟通发挥了积积作用。']
- 不考虑拼音声调,考虑常见方言读音误读,如 zh 与 z 不分,eng 与 en 不分,f 与 h 不分,l 与 n 不分等情况
- 替换时,优先使用常用词汇(依据词频而定)
-
augmentation_num(int)
参数控制返回几条增强后的数据 -
homo_ratio(float)
参数控制对每一个汉字的调整其位置概率 -
allow_mispronounce(bool)
控制是否允许方言读音误读,如 zh 与 z 卷舌不分,默认为 True,允许词汇错音 - 其余参数参考
jio.homophone_substitution.__doc__
随机在文本中增加、删除某个字符。不影响原意的字符,对文本语义不造成影响。例如:
# 原句:“23日,山东省监狱管理局原副局长王文杰等5人玩忽职守”
# 增删:"2日,山东监狱 管理局、原副局长文杰等5人玩忽职守.."
随机增加的字符的选择,依据对海量文本统计字符分布规律的 char_distribution.json
文件得到,取其中的非中文字符进行添加。
>>> import jionlp as jio
>>> res = jio.random_add_delete('孙俪晒11年对比照庆领证纪念日,邓超被指沧桑。')
>>> print(res)
# ['孙俪晒11年对比照庆领证纪念日,邓超被指沧。',
# '孙+俪晒11年对比照庆领证纪念日,邓超被指沧桑。',
# '孙俪晒 11年对比照庆领证纪念日,邓超被指沧/桑。']
- 对于某些 NLP 任务,如抽取其中时间词汇,则以上方法很容易干扰关键时间信息,故方法失效。待后续优化,
- 替换时,优先使用常用词汇(依据词频而定)
-
augmentation_num(int)
参数控制返回几条增强后的数据 -
add_ratio(float)
对每一个位置随机增加字符概率,默认为 0.02 -
delete_ratio(float)
对每一个汉字随机做删除的概率,默认为 0.02 - 其余参数参考
jio.random_add_delete.__doc__
根据实体词典,随机在文本中替换某个实体,对语义不造成影响。例如:
# 原句:“坦桑尼亚现任总统马古富力病逝”
# 增删:"柬埔寨现任总统张达美病逝"
该方法不仅仅用于实体识别数据增强,也可用于其他相似序列标注任务(如要素抽取等),也可用于文本分类、匹配等任务。
实体词典的获得,可用jio.ner.collect_dataset_entities
工具使用。
>>> import jionlp as jio
>>> # 从标注语料中获取实体词典
>>> dataset_y = [[{'type': 'Person', 'text': '马成宇', 'offset': (0, 3)},
{'type': 'Company', 'text': '百度', 'offset': (10, 12)},
{'type': 'Company', 'text': '百度', 'offset': (20, 22)}],
[{'type': 'Company', 'text': '国力教育公司', 'offset': (2, 8)}],
[{'type': 'Organization', 'text': '延平区人民法院', 'offset': (0, 7)},
{'type': 'Company', 'text': '百度', 'offset': (10, 12)},
{'type': 'Company', 'text': '百度', 'offset': (20, 22)}]]
>>> entity_dict = jio.ner.collect_dataset_entities(dataset_y)
>>> print(entity_dict)
>>> replace_entity = jio.ReplaceEntity(entity_dict)
>>> text = '腾讯致力于游戏,阿里巴巴致力于电商。小马会玩。'
>>> entities = [{'type': 'Company', 'text': '腾讯', 'offset': (0, 2)},
{'type': 'Company', 'text': '阿里巴巴', 'offset': (8, 12)},
{'type': 'Person', 'text': '小马', 'offset': (18, 20)}]
>>> aug_texts, aug_entities = replace_entity(text, entities)
>>> print(aug_texts, aug_entities)
# entity_dict:
# {
# "Person":{
# "马成宇":1
# },
# "Company":{
# "百度":4,
# "国力教育公司":1
# },
# "Organization":{
# "延平区人民法院":1
# }
# }
#
# aug_texts:
# ['腾讯致力于解决冲突,国力教育公司致力于玩。小马爱玩。',
# '百度致力于解决冲突,阿里巴巴致力于玩。小马爱玩。',
# '腾讯致力于解决冲突,阿里巴巴致力于玩。马成宇爱玩。']
# aug_entities:
# [[{'type': 'Company', 'text': '腾讯', 'offset': (0, 2)},
# {'text': '国力教育公司', 'type': 'Company', 'offset': [10, 16]},
# {'text': '小马', 'type': 'Person', 'offset': (21, 23)}],
# [{'text': '百度', 'type': 'Company', 'offset': [0, 2]},
# {'text': '阿里巴巴', 'type': 'Company', 'offset': (10, 14)},
# {'text': '小马', 'type': 'Person', 'offset': (19, 21)}],
# [{'type': 'Company', 'text': '腾讯', 'offset': (0, 2)},
# {'type': 'Company', 'text': '阿里巴巴', 'offset': (10, 14)},
# {'text': '马成宇', 'type': 'Person', 'offset': [19, 22]}]])
- 由此可以看到,该方法不仅仅可以用于序列标注的数据增强,同时可以用于文本分类:使用前须将文本做实体识别、序列标注,将相应的实体词典准备好,进行替换。
-
augmentation_num(int)
参数控制返回几条增强后的数据 -
replace_ratio(float)
对每一个实体做替换的概率,默认为 0.1