Skip to content

数据增强 说明文档

冬日新雨 edited this page Jul 25, 2022 · 18 revisions

数据增强方法对比

  • 文本数据增强的两个前提:
  • 1、不干扰模型标签:文本增强后的语义不干扰模型训练,不会导致样本标签失效;如“这个小吃真好吃。=>正面情绪”增强为“这个小吃真不好吃。=>正面情绪”,随机加字影响到了标签的正确性。
  • 2、人可理解:增强后文本,依然保持可读性,达到人可以理解文本的含义;如“这个小吃真好吃。”增强为“斯口吃真好吃。”,其中“这个”替换为“斯”,“小吃”替换为“口吃”,已经完全令人无法理解,模型训练也已偏离。此问题在 同义词替换上非常频繁与普遍。
方法 任务类型 效果
回译 文本分类、序列标注、匹配、文本生成 基于机翻效果决定,目前对新闻通用领域效果较好,专项领域视语料决定。长短文本均适合
邻近汉字换位 文本分类、匹配 汉字换位会影响具体实体的含义,在实体含义并不影响整体语义情况下适用。换位汉字占比不宜过大
同音词替换 文本分类、匹配 同音词替换会对局部语义产生影响,造成误差,但对整体语义理解并无干扰。替换词汇的占比不宜过大
随机增删符号 文本分类、匹配 在文本中随机增删不影响语义的额外非中文符号。增加比例不宜过大,若某类字符(数字、字母)对语义有影响,则应该规避此类字符
NER实体替换 文本分类、匹配、序列标注 在文本中随机替换不影响语义的实体。如人名、地名、机构等实体
同义词替换 - 造成语言连贯性差,语义完全被曲解的概率非常大。此种方法作废,本工具包不支持。具体解释见jio.random_add_delete.__doc__
语言模型预测 分类、匹配、文本生成 利用大型的语言模型如 bert 等,预测句子中空缺的词汇。此种方法依赖大型的语言模型参数,本工具暂不支持

回译数据增强

BackTranslation、BaiduApi、XunfeiApi、GoogleApi、TecentApi、YoudaoApi、YoudaoFreeApi

给定一段文本,利用各类大厂公开的免费 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反爬限制

邻近汉字换位

swap_char_position

随机交换相邻近字符的位置,用以增强文本数据,理论依据为相邻近汉字顺序变动不影响人的阅读理解。 如“民盟发言人:昂季素山目前情况良好”,“研表究明,汉字的序顺并不定一能影阅响读”。

>>> 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__

同音词替换

homophone_substitution

采用同音词汇进行原文替换,达到数据增强的目的。汉语输入法中,拼音输入法为目前使用最广泛的一种打字法,使用率占比约 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__

随机增删字符

random_add_delete

随机在文本中增加、删除某个字符。不影响原意的字符,对文本语义不造成影响。例如:

# 原句:“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__

NER实体替换

ReplaceEntity

根据实体词典,随机在文本中替换某个实体,对语义不造成影响。例如:

# 原句:“坦桑尼亚现任总统马古富力病逝”
# 增删:"柬埔寨现任总统张达美病逝"

该方法不仅仅用于实体识别数据增强,也可用于其他相似序列标注任务(如要素抽取等),也可用于文本分类、匹配等任务。 实体词典的获得,可用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