From 81f6498c603ea7b0f5ea66eb4bf7e83769b90fe1 Mon Sep 17 00:00:00 2001 From: shibing624 Date: Mon, 28 Oct 2024 17:44:34 +0800 Subject: [PATCH] update mucgec model. --- README.md | 67 ++++------ examples/mucgec_bart/README.md | 54 +++++++++ examples/mucgec_bart/demo.py | 29 +++++ pycorrector/__init__.py | 2 - pycorrector/mucgec_bart/monkey_pack.py | 114 +++++++++--------- .../mucgec_bart/mucgec_bart_corrector.py | 47 ++++---- .../nasgec_bart/nasgec_bart_corrector.py | 45 +++---- pycorrector/utils/sentence_utils.py | 16 +-- requirements-dev.txt | 5 +- requirements.txt | 3 - tests/test_mucgec_bart.py | 6 +- tests/test_nasgec_bart.py | 6 +- 12 files changed, 220 insertions(+), 174 deletions(-) create mode 100644 examples/mucgec_bart/README.md create mode 100644 examples/mucgec_bart/demo.py diff --git a/README.md b/README.md index 371d6afb..3cf826f4 100644 --- a/README.md +++ b/README.md @@ -56,9 +56,10 @@ * [T5模型](https://github.com/shibing624/pycorrector/tree/master/examples/t5):本项目基于PyTorch实现了用于中文文本纠错的T5模型,使用Langboat/mengzi-t5-base的预训练模型finetune中文纠错数据集,模型改造的潜力较大,效果好 * [ERNIE_CSC模型](https://github.com/shibing624/pycorrector/tree/master/examples/ernie_csc):本项目基于PaddlePaddle实现了用于中文文本纠错的ERNIE_CSC模型,模型在ERNIE-1.0上finetune,模型结构适配了中文拼写纠错任务,效果好 * [MacBERT模型](https://github.com/shibing624/pycorrector/tree/master/examples/macbert)【推荐】:本项目基于PyTorch实现了用于中文文本纠错的MacBERT4CSC模型,模型加入了错误检测和纠正网络,适配中文拼写纠错任务,效果好 -* [GPT模型](https://github.com/shibing624/pycorrector/tree/master/examples/gpt):本项目基于PyTorch实现了用于中文文本纠错的ChatGLM/LLaMA模型,模型在中文CSC和语法纠错数据集上finetune,适配中文文本纠错任务,效果好 -* [MuCGECBart模型](https://modelscope.cn/models/iic/nlp_bart_text-error-correction_chinese/summary):本项目直接使用该开源模型,临时修复批量推理问题,支持自动处理长篇文章,同时可以自定义后处理逻辑修改推理结果,方便使用。该模型中文文本纠错效果非常好, 但是推理速度较慢,需要GPU推理 -* [NaSGECBart](https://github.com/HillZhang1999/NaSGEC): MuCGECBart的同作者模型, 无需modelscope依赖, 使用方法类似, 模型作者提供, 在不同领域微调的五个模型 +* [MuCGECBart模型](https://modelscope.cn/models/iic/nlp_bart_text-error-correction_chinese/summary):本项目基于ModelScope实现了用于文本纠错的Seq2Seq方法的MuCGECBart模型,该模型中文文本纠错效果较好 +* [NaSGECBart模型](https://github.com/HillZhang1999/NaSGEC): MuCGECBart的同作者模型,无需modelscope依赖,基于中文母语纠错数据集NaSGEC在Bart模型上微调训练得到,效果好 +* [GPT模型](https://github.com/shibing624/pycorrector/tree/master/examples/gpt):本项目基于PyTorch实现了用于中文文本纠错的ChatGLM/LLaMA模型,模型在中文CSC和语法纠错数据集上finetune,适配中文文本纠错任务,效果很好 + - 延展阅读:[中文文本纠错实践和原理解读](https://github.com/shibing624/pycorrector/blob/master/docs/correction_solution.md) @@ -70,7 +71,7 @@ - HuggingFace demo: https://huggingface.co/spaces/shibing624/pycorrector -![](docs/hf.png) +![](https://github.com/shibing624/pycorrector/blob/master/docs/hf.png) run example: [examples/macbert/gradio_demo.py](https://github.com/shibing624/pycorrector/blob/master/examples/macbert/gradio_demo.py) to see the demo: ```shell @@ -458,58 +459,36 @@ output: ### MuCGECBart模型 -模型在第一次运行时,会自动下载到"~/.cache/modelscope/hub/"子目录 -注意该模型在python=3.8.19环境下通过测试, 其它依赖包版本可能会有问题 +模型在第一次运行时,会自动下载到"~/.cache/modelscope/hub/"子目录。 +注意该模型在python=3.8.19环境下通过测试,其它依赖包版本可能会有问题。 ```python -from pycorrector import MuCGECBartCorrector -from pycorrector.utils.sentence_utils import is_not_chinese_error +from pycorrector.mucgec_bart.mucgec_bart_corrector import MuCGECBartCorrector if __name__ == "__main__": - bc = MuCGECBartCorrector() - result = bc.correct_batch(['这洋的话,下一年的福气来到自己身上。', '在拥挤时间,为了让人们尊守交通规律,派至少两个警察或者交通管理者。', '随着中国经济突飞猛近,建造工业与日俱增']+["北京是中国的都。", "他说:”我最爱的运动是打蓝球“", "我每天大约喝5次水左右。", "今天,我非常开开心。"]) - print(result) - long_text = "在一个充满生活热闹和忙碌的城市中,有一个年轻人名叫李华。他生活在北京,这座充满着现代化建筑和繁忙街道的都市。每天,他都要穿行在拥挤的人群中,追逐着自己的梦想和生活节奏。\n\n李华从小就听祖辈讲述关于福气和努力的故事。他相信,“这洋的话,下一年的福气来到自己身上”。因此,尽管每天都很忙碌,他总是尽力保持乐观和积极。\n\n某天早晨,李华骑着自行车准备去上班。北京的交通总是非常繁忙,尤其是在早高峰时段。他经过一个交通路口,看到至少两个交警正在维持交通秩序。这些交警穿着整齐的制服,手势有序而又果断,让整个路口的车辆有条不紊地行驶着。这让李华想起了他父亲曾经告诫过他的话:“在拥挤的时间里,为了让人们遵守交通规则,至少要派两个警察或者交通管理者。”\n\n李华心中感慨万千,他想要在自己的生活中也如此积极地影响他人。他虽然只是一名普通的白领,却希望能够通过自己的努力和行动,为这座城市的安全与和谐贡献一份力量。\n\n随着时间的推移,中国的经济不断发展,北京的建设也日益繁荣。李华所在的公司也因为他的努力和创新精神而蓬勃发展。他喜欢打篮球,每周都会和朋友们一起去运动场,放松身心。他也十分重视健康,每天都保持适量的饮水量,大约喝五次左右。\n\n今天,李华觉得格外开心。他意识到,自己虽然只是一个普通人,却通过日复一日的努力,终于在生活中找到了属于自己的那份福气。他明白了祖辈们口中的那句话的含义——“这洋的话,下一年的福气来到自己身上”,并且深信不疑。\n\n在这个充满希望和机遇的时代里,李华将继续努力工作,为自己的梦想而奋斗,也希望能够在这座城市中留下自己的一份足迹,为他人带来更多的希望和正能量。\n\n这就是李华的故事,一个在现代城市中追寻梦想和福气的普通青年。" - result = bc.correct(long_text) - print(result) - # 模型结果后处理 - result = bc.correct(long_text, ignore_function=is_not_chinese_error) + m = MuCGECBartCorrector() + result = m.correct_batch(['这洋的话,下一年的福气来到自己身上。', + '在拥挤时间,为了让人们尊守交通规律,派至少两个警察或者交通管理者。', + '随着中国经济突飞猛近,建造工业与日俱增', + "北京是中国的都。", + "他说:”我最爱的运动是打蓝球“", + "我每天大约喝5次水左右。", + "今天,我非常开开心。"]) print(result) ``` output: ```shell -[{'source': '这洋的话,下一年的福气来到自己身上。', 'target': '这样的话,下一年的福气就会来到自己身上。', 'errors': [('洋', '样', 1), ('', '就会', 11)]}, {'source': '在拥挤时间,为了让人们尊守交通规律,派至少两个警察或者交通管理者。', 'target': '在拥挤时间,为了让人们遵守交通规则,应该派至少两个警察或者交通管理者。', 'errors': [('尊', '遵', 11), ('律', '则', 16), ('', '应该', 18)]}, {'source': '随着中国经济突飞猛近,建造工业与日俱增', 'target': '随着中国经济突飞猛进,建造工业与日俱增', 'errors': [('近', '进', 9)]}, {'source': '北京是中国的都。', 'target': '北京是中国的首都。', 'errors': [('', '首', 6)]}, {'source': '他说:”我最爱的运动是打蓝球“', 'target': '他说:“我最爱的运动是打篮球”', 'errors': [('”', '“', 3), ('蓝', '篮', 12), ('“', '”', 14)]}, {'source': '我每天大约喝5次水左右。', 'target': '我每天大约喝5杯水左右。', 'errors': [('次', '杯', 7)]}, {'source': '今天,我非常开开心。', 'target': '今天,我非常开心。', 'errors': [('开', '', 7)]}] - -{'source': '在一个充满生活热闹和忙碌的城市中,有一个年轻人名叫李华。他生活在北京,这座充满着现代化建筑和繁忙街道的都市。每天,他都要穿行在拥挤的人群中,追逐着自己的梦想和生活节奏。\n\n李华从小就听祖辈讲述关于福气和努力的故事。他相信,“这洋的话,下一年的福气来到自己身上”。因此,尽管每天都很忙碌,他总是尽力保持乐观和积极。\n\n某天早晨,李华骑着自行车准备去上班。北京的交通总是非常繁忙,尤其是在早高峰时段。他经过一个交通路口,看到至少两个交警正在维持交通秩序。这些交警穿着整齐的制服,手势有序而又果断,让整个路口的车辆有条不紊地行驶着。这让李华想起了他父亲曾经告诫过他的话:“在拥挤的时间里,为了让人们遵守交通规则,至少要派两个警察或者交通管理者。”\n\n李华心中感慨万千,他想要在自己的生活中也如此积极地影响他人。他虽然只是一名普通的白领,却希望能够通过自己的努力和行动,为这座城市的安全与和谐贡献一份力量。\n\n随着时间的推移,中国的经济不断发展,北京的建设也日益繁荣。李华所在的公司也因为他的努力和创新精神而蓬勃发展。他喜欢打篮球,每周都会和朋友们一起去运动场,放松身心。他也十分重视健康,每天都保持适量的饮水量,大约喝五次左右。\n\n今天,李华觉得格外开心。他意识到,自己虽然只是一个普通人,却通过日复一日的努力,终于在生活中找到了属于自己的那份福气。他明白了祖辈们口中的那句话的含义——“这洋的话,下一年的福气来到自己身上”,并且深信不疑。\n\n在这个充满希望和机遇的时代里,李华将继续努力工作,为自己的梦想而奋斗,也希望能够在这座城市中留下自己的一份足迹,为他人带来更多的希望和正能量。\n\n这就是李华的故事,一个在现代城市中追寻梦想和福气的普通青年。', 'target': '在一个充满生活热闹和忙碌的城市中,有一个年轻人名叫李华,他生活在北京,这座充满着现代化建筑和繁忙街道的都市,每天,他都要穿行在拥挤的人群中,追逐着自己的梦想和生活节奏。李华从小就听祖辈讲述关于福气和努力的故事。他相信,“这样的话,下一年的福气会来到自己身上”,因此,尽管每天都很忙碌,他总是尽力保持乐观和积极。某天早晨,李华骑着自行车准备去上班,北京的交通总是非常繁忙,尤其是在早高峰时段,他经过一个交通路口,看到至少两个交警正在维持交通秩序。这些交警穿着整齐的制服,手势有序而又果断,让整个路口的车辆有条不紊地行驶着,这让李华想起了他父亲曾经告诫过他的话:“在拥挤的时间里,为了让人们遵守交通规则,至少要派两个警察或者交通管理者。”李华心中感慨万千,他想要在自己的生活中也如此积极地影响他人。他虽然只是一名普通的白领,却希望能够通过自己的努力和行动,为这座城市的安全与和谐贡献一份力量。随着时间的推移,中国的经济不断发展,北京的建设也日益繁荣,李华所在的公司也因为他的努力和创新精神而蓬勃发展。他喜欢打篮球,每周都会和朋友们一起去运动场,放松身心;他也十分重视健康,每天都保持适量的饮水量,大约喝五次左右。今天,李华觉得格外开心,他意识到,自己虽然只是一个普通人,却通过日复一日的努力,终于在生活中找到了属于自己的那份福气。他明白了祖辈们口中的那句话的含义——“这样的话,下一年的福气会来到自己身上”,并且深信不疑,在这个充满希望和机遇的时代里,李华将继续努力工作,为自己的梦想而奋斗,也希望能够在这座城市中留下自己的一份足迹,为他人带来更多的希望和正能量。这就是李华的故事,一个在现代城市中追寻梦想和幸福的普通青年。', 'errors': [('。', ',', 27), ('。', ',', 53), ('\n\n', '', 84), ('洋', '样', 113), ('', '会', 123), ('。', ',', 130), ('\n\n', '', 156), ('。', ',', 175), ('。', ',', 197), ('。', ',', 262), ('\n\n', '', 320), ('\n\n', '', 399), ('。', ',', 429), ('。', ';', 481), ('\n\n', '', 511), ('。', ',', 524), ('洋', '样', 592), ('', '会', 602), ('。\n\n', ',', 616), ('\n\n', '', 690), ('', '幸', 714), ('气', '', 715)]} - - -{'source': '在一个充满生活热闹和忙碌的城市中,有一个年轻人名叫李华。他生活在北京,这座充满着现代化建筑和繁忙街道的都市。每天,他都要穿行在拥挤的人群中,追逐着自己的梦想和生活节奏。\n\n李华从小就听祖辈讲述关于福气和努力的故事。他相信,“这洋的话,下一年的福气来到自己身上”。因此,尽管每天都很忙碌,他总是尽力保持乐观和积极。\n\n某天早晨,李华骑着自行车准备去上班。北京的交通总是非常繁忙,尤其是在早高峰时段。他经过一个交通路口,看到至少两个交警正在维持交通秩序。这些交警穿着整齐的制服,手势有序而又果断,让整个路口的车辆有条不紊地行驶着。这让李华想起了他父亲曾经告诫过他的话:“在拥挤的时间里,为了让人们遵守交通规则,至少要派两个警察或者交通管理者。”\n\n李华心中感慨万千,他想要在自己的生活中也如此积极地影响他人。他虽然只是一名普通的白领,却希望能够通过自己的努力和行动,为这座城市的安全与和谐贡献一份力量。\n\n随着时间的推移,中国的经济不断发展,北京的建设也日益繁荣。李华所在的公司也因为他的努力和创新精神而蓬勃发展。他喜欢打篮球,每周都会和朋友们一起去运动场,放松身心。他也十分重视健康,每天都保持适量的饮水量,大约喝五次左右。\n\n今天,李华觉得格外开心。他意识到,自己虽然只是一个普通人,却通过日复一日的努力,终于在生活中找到了属于自己的那份福气。他明白了祖辈们口中的那句话的含义——“这洋的话,下一年的福气来到自己身上”,并且深信不疑。\n\n在这个充满希望和机遇的时代里,李华将继续努力工作,为自己的梦想而奋斗,也希望能够在这座城市中留下自己的一份足迹,为他人带来更多的希望和正能量。\n\n这就是李华的故事,一个在现代城市中追寻梦想和福气的普通青年。', 'target': '在一个充满生活热闹和忙碌的城市中,有一个年轻人名叫李华。他生活在北京,这座充满着现代化建筑和繁忙街道的都市。每天,他都要穿行在拥挤的人群中,追逐着自己的梦想和生活节奏。\n\n李华从小就听祖辈讲述关于福气和努力的故事。他相信,“这样的话,下一年的福气来到自己身上”,。此,尽管每天都很忙碌,他总是尽力保持乐观和积极。某\n\n天早晨,李华骑着自行车准备去上。,北京的交通总是非常繁忙,尤其是在早高峰时。,他经过一个交通路口,看到至少两个交警正在维持交通秩序。这些交警穿着整齐的制服,手势有序而又果断,让整个路口的车辆有条不紊地行驶着。这让李华想起了他父亲曾经告诫过他的话:“在拥挤的时间里,为了让人们遵守交通规则,至少要派两个警察或者交通管理者。”\n\n李华心中感慨万千,他想要在自己的生活中也如此积极地影响他人。他虽然只是一名普通的白领,却希望能够通过自己的努力和行动,为这座城市的安全与和谐贡献一份力量。\n\n随着时间的推移,中国的经济不断发展,北京的建设也日益。荣,李华所在的公司也因为他的努力和创新精神而蓬勃发展。他喜欢打篮球,每周都会和朋友们一起去运动场,放松身心。他也十分重视健康,每天都保持适量的饮水量,大约喝五次左右。\n\n今天,李华觉得格外。心,他意识到,自己虽然只是一个普通人,却通过日复一日的努力,终于在生活中找到了属于自己的那份福气。他明白了祖辈们口中的那句话的含义——“这样的话,下一年的福气来到自己身上”,并且深信不疑,。\n\n这个充满希望和机遇的时代里,李华将继续努力工作,为自己的梦想而奋斗,也希望能够在这座城市中留下自己的一份足迹,为他人带来更多的希望和正能量\n\n。这就是李华的故事,一个在现代城市中追寻梦想和福的普通青年。', 'errors': [('洋', '样', 113), ('洋', '样', 592), ('气', '', 715)]} -``` - - - -### NaSGECBart模型 - -相比MuCGECBart, 使用方法类似 - -``` -from pycorrector import NaSGECBartCorrector -from pycorrector.utils.sentence_utils import is_not_chinese_error - - -if __name__ == "__main__": - bc = NaSGECBartCorrector() - result = bc.correct_batch(['这洋的话,下一年的福气来到自己身上。', '在拥挤时间,为了让人们尊守交通规律,派至少两个警察或者交通管理者。', '随着中国经济突飞猛近,建造工业与日俱增']+["北京是中国的都。", "他说:”我最爱的运动是打蓝球“", "我每天大约喝5次水左右。", "今天,我非常开开心。"]) - print(result) +# [{'source': '这洋的话,下一年的福气来到自己身上。', 'target': '这样的话,下一年的福气就会来到自己身上。', 'errors': [('洋', '样', 1), ('', '就会', 11)]}, +# {'source': '在拥挤时间,为了让人们尊守交通规律,派至少两个警察或者交通管理者。', 'target': '在拥挤时间,为了让人们遵守交通规则,应该派至少两个警察或者交通管理者。', 'errors': [('尊', '遵', 11), ('律', '则', 16), ('', '应该', 18)]}, +# {'source': '随着中国经济突飞猛近,建造工业与日俱增', 'target': '随着中国经济突飞猛进,建造工业与日俱增', 'errors': [('近', '进', 9)]}, +# {'source': '北京是中国的都。', 'target': '北京是中国的首都。', 'errors': [('', '首', 6)]}, +# {'source': '他说:”我最爱的运动是打蓝球“', 'target': '他说:“我最爱的运动是打篮球”', 'errors': [('”', '“', 3), ('蓝', '篮', 12), ('“', '”', 14)]}, +# {'source': '我每天大约喝5次水左右。', 'target': '我每天大约喝5杯水左右。', 'errors': [('次', '杯', 7)]}, +# {'source': '今天,我非常开开心。', 'target': '今天,我非常开心。', 'errors': [('开', '', 7)]}] ``` -output: -```shell -['这样的话,下一年的福气会来到自己身', '在拥挤时间,为了让人们遵守交通规则', '随着中国经济突飞猛进,建造工业与日', '北京是中国的首都。', '他说:“我最爱的运动是打篮球”', '我每天大约喝5次水左右。', '今天,我非常开心。'] -[{'source': '这洋的话,下一年的福气来到自己身上。', 'target': '这样的话,下一年的福气会来到自己身', 'errors': [('洋', '样', 1), ('', '会', 11), ('上。', '', 16)]}, {'source': '在拥挤时间,为了让人们尊守交通规律,派至少两个警察或者交通管理者。', 'target': '在拥挤时间,为了让人们遵守交通规则', 'errors': [('尊', '遵', 11), ('律,派至少两个警察或者交通管理者。', '则', 16)]}, {'source': '随着中国经济突飞猛近,建造工业与日俱增', 'target': '随着中国经济突飞猛进,建造工业与日', 'errors': [('近', '进', 9), ('俱增', '', 17)]}, {'source': '北京是中国的都。', 'target': '北京是中国的首都。', 'errors': [('', '首', 6)]}, {'source': '他说:”我最爱的运动是打蓝球“', 'target': '他说:“我最爱的运动是打篮球”', 'errors': [('”', '“', 3), ('蓝', '篮', 12), ('“', '”', 14)]}, {'source': '我每天大约喝5次水左右。', 'target': '我每天大约喝5次水左右。', 'errors': []}, {'source': '今天,我非常开开心。', 'target': '今天,我非常开心。', 'errors': [('开', '', 7)]}] -``` ## Dataset diff --git a/examples/mucgec_bart/README.md b/examples/mucgec_bart/README.md new file mode 100644 index 00000000..37f222c9 --- /dev/null +++ b/examples/mucgec_bart/README.md @@ -0,0 +1,54 @@ +# BART文本纠错-中文-通用领域-large + + +#### 中文文本纠错模型介绍 +输入一句中文文本,文本纠错技术对句子中存在拼写、语法、语义等错误进行自动纠正,输出纠正后的文本。主流的方法为seq2seq和seq2edits,常用的中文纠错数据集包括NLPCC18和CGED等,我们最新的工作提供了高质量、多答案的测试集MuCGEC。 + +我们采用基于transformer的seq2seq方法建模文本纠错任务。模型训练上,我们使用中文BART作为预训练模型,然后在Lang8和HSK训练数据上进行finetune。不引入额外资源的情况下,本模型在NLPCC18测试集上达到了SOTA。 + +模型效果如下: +输入:这洋的话,下一年的福气来到自己身上。 +输出:这样的话,下一年的福气就会来到自己身上。 + +#### 期望模型使用方式以及适用范围 +本模型主要用于对中文文本进行错误诊断,输出符合拼写、语法要求的文本。该纠错模型是一个句子级别的模型,模型效果会受到文本长度、分句粒度的影响,建议是每次输入一句话。具体调用方式请参考代码示例。 + + + + + +## Usage +#### 安装依赖 +```shell +pip install pycorrector difflib modelscope==1.16.0 fairseq==0.12.2 +``` +#### pycorrector快速预测 + +example: [examples/mucgec_bart/demo.py](https://github.com/shibing624/pycorrector/blob/master/examples/mucgec_bart/demo.py) +```python +from pycorrector.mucgec_bart.mucgec_bart_corrector import MuCGECBartCorrector + + +if __name__ == "__main__": + m = MuCGECBartCorrector() + result = m.correct_batch(['这洋的话,下一年的福气来到自己身上。', + '在拥挤时间,为了让人们尊守交通规律,派至少两个警察或者交通管理者。', + '随着中国经济突飞猛近,建造工业与日俱增', + "北京是中国的都。", + "他说:”我最爱的运动是打蓝球“", + "我每天大约喝5次水左右。", + "今天,我非常开开心。"]) + print(result) +``` + +output: +```shell +[{'source': '今天新情很好', 'target': '今天心情很好', 'errors': [('新', '心', 2)]}, +{'source': '你找到你最喜欢的工作,我也很高心。', 'target': '你找到你最喜欢的工作,我也很高兴。', 'errors': [('心', '兴', 15)]}] +``` + +## Reference +- https://modelscope.cn/models/iic/nlp_bart_text-error-correction_chinese/summary +- 苏大:Tang et al. Chinese grammatical error correction enhanced by data augmentation from word and character levels. 2021. +- 北大 & MSRA & CUHK:Sun et al. A Unified Strategy for Multilingual Grammatical Error Correction with Pre-trained Cross-Lingual Language Model. 2021. +- Ours:Zhang et al. MuCGEC: a Multi-Reference Multi-Source Evaluation Dataset for Chinese Grammatical Error Correction. 2022. \ No newline at end of file diff --git a/examples/mucgec_bart/demo.py b/examples/mucgec_bart/demo.py new file mode 100644 index 00000000..c6bd5177 --- /dev/null +++ b/examples/mucgec_bart/demo.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +""" +@author:XuMing(xuming624@qq.com) +@description: +""" +import sys + +sys.path.append('../..') + +from pycorrector.mucgec_bart.mucgec_bart_corrector import MuCGECBartCorrector + +if __name__ == "__main__": + m = MuCGECBartCorrector() + result = m.correct_batch(['这洋的话,下一年的福气来到自己身上。', + '在拥挤时间,为了让人们尊守交通规律,派至少两个警察或者交通管理者。', + '随着中国经济突飞猛近,建造工业与日俱增', + "北京是中国的都。", + "他说:”我最爱的运动是打蓝球“", + "我每天大约喝5次水左右。", + "今天,我非常开开心。"]) + print(result) + +# [{'source': '这洋的话,下一年的福气来到自己身上。', 'target': '这样的话,下一年的福气就会来到自己身上。', 'errors': [('洋', '样', 1), ('', '就会', 11)]}, +# {'source': '在拥挤时间,为了让人们尊守交通规律,派至少两个警察或者交通管理者。', 'target': '在拥挤时间,为了让人们遵守交通规则,应该派至少两个警察或者交通管理者。', 'errors': [('尊', '遵', 11), ('律', '则', 16), ('', '应该', 18)]}, +# {'source': '随着中国经济突飞猛近,建造工业与日俱增', 'target': '随着中国经济突飞猛进,建造工业与日俱增', 'errors': [('近', '进', 9)]}, +# {'source': '北京是中国的都。', 'target': '北京是中国的首都。', 'errors': [('', '首', 6)]}, +# {'source': '他说:”我最爱的运动是打蓝球“', 'target': '他说:“我最爱的运动是打篮球”', 'errors': [('”', '“', 3), ('蓝', '篮', 12), ('“', '”', 14)]}, +# {'source': '我每天大约喝5次水左右。', 'target': '我每天大约喝5杯水左右。', 'errors': [('次', '杯', 7)]}, +# {'source': '今天,我非常开开心。', 'target': '今天,我非常开心。', 'errors': [('开', '', 7)]}] diff --git a/pycorrector/__init__.py b/pycorrector/__init__.py index 54d00340..feb90243 100644 --- a/pycorrector/__init__.py +++ b/pycorrector/__init__.py @@ -16,8 +16,6 @@ from pycorrector.proper_corrector import ProperCorrector from pycorrector.seq2seq.conv_seq2seq_corrector import ConvSeq2SeqCorrector from pycorrector.t5.t5_corrector import T5Corrector -from pycorrector.mucgec_bart.mucgec_bart_corrector import MuCGECBartCorrector -from pycorrector.nasgec_bart.nasgec_bart_corrector import NaSGECBartCorrector from pycorrector.utils import text_utils, tokenizer, io_utils, math_utils, evaluate_utils from pycorrector.utils.evaluate_utils import eval_model_batch from pycorrector.utils.get_file import get_file diff --git a/pycorrector/mucgec_bart/monkey_pack.py b/pycorrector/mucgec_bart/monkey_pack.py index c49d0c07..e185d95e 100644 --- a/pycorrector/mucgec_bart/monkey_pack.py +++ b/pycorrector/mucgec_bart/monkey_pack.py @@ -1,70 +1,68 @@ +from typing import Any, Dict, List import torch from modelscope.pipelines import Pipeline -from typing import Any, Dict, List from modelscope.utils.constant import Frameworks from modelscope.utils.device import device_placement -# 批量推理问题 -def _process_batch(self, input: List, batch_size, - **kwargs) -> Dict[str, Any]: - preprocess_params = kwargs.get('preprocess_params') - forward_params = kwargs.get('forward_params') - postprocess_params = kwargs.get('postprocess_params') - # batch data - output_list = [] - for i in range(0, len(input), batch_size): - end = min(i + batch_size, len(input)) - real_batch_size = end - i - preprocessed_list = [ - self.preprocess(i, **preprocess_params) for i in input[i:end] - ] +def _process_batch(self, input: List, batch_size, **kwargs): + preprocess_params = kwargs.get('preprocess_params') + forward_params = kwargs.get('forward_params') + postprocess_params = kwargs.get('postprocess_params') - with device_placement(self.framework, self.device_name): - if self.framework == Frameworks.torch: - with torch.no_grad(): - batched_out = self._batch(preprocessed_list) - if self._auto_collate: - batched_out = self._collate_fn(batched_out) - batched_out = self.forward(batched_out, - **forward_params) - else: + # batch data + output_list = [] + for i in range(0, len(input), batch_size): + end = min(i + batch_size, len(input)) + real_batch_size = end - i + preprocessed_list = [ + self.preprocess(i, **preprocess_params) for i in input[i:end] + ] + + with device_placement(self.framework, self.device_name): + if self.framework == Frameworks.torch: + with torch.no_grad(): batched_out = self._batch(preprocessed_list) - batched_out = self.forward(batched_out, **forward_params) - model_name = kwargs.get("model_name") - # print("model_name", model_name) - if model_name=="batch_correct": - for batch_idx in range(real_batch_size): - out = {} - for k, element in batched_out.items(): - if element is not None: - if isinstance(element, (tuple, list)): - out[k] = element[batch_idx] - else: - out[k] = element[batch_idx:batch_idx + 1] - out = self.postprocess(out, **postprocess_params) - self._check_output(out) - output_list.append(out) + if self._auto_collate: + batched_out = self._collate_fn(batched_out) + batched_out = self.forward(batched_out, + **forward_params) else: - for batch_idx in range(real_batch_size): - out = {} - for k, element in batched_out.items(): - if element is not None: - if isinstance(element, (tuple, list)): - if isinstance(element[0], torch.Tensor): - out[k] = type(element)( - e[batch_idx:batch_idx + 1] - for e in element) - else: - # Compatible with traditional pipelines - out[k] = element[batch_idx] + batched_out = self._batch(preprocessed_list) + batched_out = self.forward(batched_out, **forward_params) + model_name = kwargs.get("model_name") + # print("model_name", model_name) + if model_name == "batch_correct": + for batch_idx in range(real_batch_size): + out = {} + for k, element in batched_out.items(): + if element is not None: + if isinstance(element, (tuple, list)): + out[k] = element[batch_idx] + else: + out[k] = element[batch_idx:batch_idx + 1] + out = self.postprocess(out, **postprocess_params) + self._check_output(out) + output_list.append(out) + else: + for batch_idx in range(real_batch_size): + out = {} + for k, element in batched_out.items(): + if element is not None: + if isinstance(element, (tuple, list)): + if isinstance(element[0], torch.Tensor): + out[k] = type(element)( + e[batch_idx:batch_idx + 1] + for e in element) else: - out[k] = element[batch_idx:batch_idx + 1] - out = self.postprocess(out, **postprocess_params) - self._check_output(out) - output_list.append(out) - - return output_list + # Compatible with traditional pipelines + out[k] = element[batch_idx] + else: + out[k] = element[batch_idx:batch_idx + 1] + out = self.postprocess(out, **postprocess_params) + self._check_output(out) + output_list.append(out) + return output_list -Pipeline._process_batch = _process_batch \ No newline at end of file +Pipeline._process_batch = _process_batch diff --git a/pycorrector/mucgec_bart/mucgec_bart_corrector.py b/pycorrector/mucgec_bart/mucgec_bart_corrector.py index b5b6ff66..69067269 100644 --- a/pycorrector/mucgec_bart/mucgec_bart_corrector.py +++ b/pycorrector/mucgec_bart/mucgec_bart_corrector.py @@ -5,19 +5,16 @@ import torch from loguru import logger -from tqdm import tqdm - -device = torch.device("cuda" if torch.cuda.is_available() else "cpu") -os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE" -import sys -sys.path.append('../..') from modelscope.pipelines import pipeline from modelscope.utils.constant import Tasks -from pycorrector.mucgec_bart.monkey_pack import Pipeline from pycorrector.utils.sentence_utils import long_sentence_split import difflib +device = torch.device("mps" if hasattr(torch.backends, 'mps') and torch.backends.mps.is_available() + else "cuda" if torch.cuda.is_available() else "cpu") +os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE" + class MuCGECBartCorrector: def __init__(self, model_name_or_path: str = "damo/nlp_bart_text-error-correction_chinese"): @@ -28,9 +25,9 @@ def __init__(self, model_name_or_path: str = "damo/nlp_bart_text-error-correctio def _predict(self, sentences, batch_size=32, max_length=128, silent=True): raise NotImplementedError - - - def correct_batch(self, sentences: List[str], max_length: int = 128, batch_size: int = 32, silent: bool = True, ignore_function=None): + + def correct_batch(self, sentences: List[str], max_length: int = 128, batch_size: int = 32, silent: bool = True, + ignore_function=None): """ 批量句子纠错 :param sentences: list[str], sentence list @@ -47,29 +44,37 @@ def correct_batch(self, sentences: List[str], max_length: int = 128, batch_size: result = [r["output"] for r in result] for i in range(n): a, b = sentences[i], result[i] - if len(a)==0 or len(b)==0 or a=="\n": + if len(a) == 0 or len(b) == 0 or a == "\n": start_idx += len(a) return s = difflib.SequenceMatcher(None, a, b) errors = [] offset = 0 for tag, i1, i2, j1, j2 in s.get_opcodes(): - if tag!="equal": - e = [a[i1:i2], b[j1+offset:j2+offset], i1] + if tag != "equal": + e = [a[i1:i2], b[j1 + offset:j2 + offset], i1] if ignore_function and ignore_function(e): # 因为不认为是错误, 所以改回原来的偏移值 b = b[:j1] + a[i1:i2] + b[j2:] - offset += i2-i1-j2+j1 + offset += i2 - i1 - j2 + j1 continue - + errors.append(tuple(e)) data.append({"source": a, "target": b, "errors": errors}) return data - def correct(self, sentence: str, **kwargs): - """长句改为短句, 可直接调用长文本""" - sentences = long_sentence_split(sentence, max_length=kwargs.pop("max_length", 128), period=kwargs.pop("period", None), comma=kwargs.pop("comma", None)) + """ + 长句改为短句, 可直接调用长文本 + Args: + sentence: + **kwargs: + + Returns: + dict + """ + sentences = long_sentence_split(sentence, max_length=kwargs.pop("max_length", 128), + period=kwargs.pop("period", None), comma=kwargs.pop("comma", None)) batch_results = self.correct_batch(sentences, **kwargs) source, target, errors = "", "", [] for sr in batch_results: @@ -83,9 +88,3 @@ def correct(self, sentence: str, **kwargs): e[2] += ll errors.append(tuple(e)) return {"source": source, "target": target, "errors": errors, "sentences": batch_results} - - - - - - diff --git a/pycorrector/nasgec_bart/nasgec_bart_corrector.py b/pycorrector/nasgec_bart/nasgec_bart_corrector.py index f97cd41c..2db215c5 100644 --- a/pycorrector/nasgec_bart/nasgec_bart_corrector.py +++ b/pycorrector/nasgec_bart/nasgec_bart_corrector.py @@ -2,20 +2,16 @@ import os import time from typing import List - import torch from loguru import logger -from tqdm import tqdm - - -device = torch.device("cuda" if torch.cuda.is_available() else "cpu") -os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE" -import sys -sys.path.append('../..') -from transformers import BertTokenizer, BartForConditionalGeneration, Text2TextGenerationPipeline +from transformers import BertTokenizer, BartForConditionalGeneration from pycorrector.utils.sentence_utils import long_sentence_split import difflib +device = torch.device("mps" if hasattr(torch.backends, 'mps') and torch.backends.mps.is_available() + else "cuda" if torch.cuda.is_available() else "cpu") +os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE" + class NaSGECBartCorrector: def __init__(self, model_name_or_path: str = "HillZhang/real_learner_bart_CGEC"): @@ -28,9 +24,9 @@ def __init__(self, model_name_or_path: str = "HillZhang/real_learner_bart_CGEC") def _predict(self, sentences, batch_size=32, max_length=128, silent=True): raise NotImplementedError - - - def correct_batch(self, sentences: List[str], max_length: int = 128, batch_size: int = 32, silent: bool = True, ignore_function=None): + + def correct_batch(self, sentences: List[str], max_length: int = 128, batch_size: int = 32, silent: bool = True, + ignore_function=None): """ 批量句子纠错 :param sentences: list[str], sentence list @@ -52,29 +48,32 @@ def correct_batch(self, sentences: List[str], max_length: int = 128, batch_size: print(result) for i in range(n): a, b = sentences[i], result[i] - if len(a)==0 or len(b)==0 or a=="\n": + if len(a) == 0 or len(b) == 0 or a == "\n": start_idx += len(a) return s = difflib.SequenceMatcher(None, a, b) errors = [] offset = 0 for tag, i1, i2, j1, j2 in s.get_opcodes(): - if tag!="equal": - e = [a[i1:i2], b[j1+offset:j2+offset], i1] + if tag != "equal": + e = [a[i1:i2], b[j1 + offset:j2 + offset], i1] if ignore_function and ignore_function(e): # 因为不认为是错误, 所以改回原来的偏移值 b = b[:j1] + a[i1:i2] + b[j2:] - offset += i2-i1-j2+j1 + offset += i2 - i1 - j2 + j1 continue - + errors.append(tuple(e)) data.append({"source": a, "target": b, "errors": errors}) return data - def correct(self, sentence: str, **kwargs): - """长句改为短句, 可直接调用长文本""" - sentences = long_sentence_split(sentence, max_length=kwargs.pop("max_length", 128), period=kwargs.pop("period", None), comma=kwargs.pop("comma", None)) + sentences = long_sentence_split( + sentence, + max_length=kwargs.pop("max_length", 128), + period=kwargs.pop("period", None), + comma=kwargs.pop("comma", None) + ) batch_results = self.correct_batch(sentences, **kwargs) source, target, errors = "", "", [] for sr in batch_results: @@ -88,9 +87,3 @@ def correct(self, sentence: str, **kwargs): e[2] += ll errors.append(tuple(e)) return {"source": source, "target": target, "errors": errors, "sentences": batch_results} - - - - - - diff --git a/pycorrector/utils/sentence_utils.py b/pycorrector/utils/sentence_utils.py index 11ffbc82..75af2784 100644 --- a/pycorrector/utils/sentence_utils.py +++ b/pycorrector/utils/sentence_utils.py @@ -1,13 +1,13 @@ import re -default_period = set(["。", "……", "!", "?", "?", "\n",]) -default_comma = set([",", ","]) +default_period = {"。", "……", "!", "?", "?", "\n"} +default_comma = {",", ","} def is_not_chinese_error(e): """不是全中文的情况, 忽略这类错误""" text = e[0] - if len(text)==0: + if len(text) == 0: return True for char in text: chinese_char_pattern = re.compile(r'[\u4e00-\u9fff]') @@ -24,7 +24,7 @@ def long_sentence_split(text, max_length=128, period=None, comma=None): period = default_period if comma is None: comma = default_comma - + def same_split(text, max_length=128): """ 等长切分 @@ -47,7 +47,7 @@ def get_sentences_by_punc(text, punc, max_length): if last < n: sentences.extend(same_split(text[last:], max_length=max_length)) return sentences - + sentences = [] n, last = len(text), 0 @@ -61,10 +61,10 @@ def get_sentences_by_punc(text, punc, max_length): new = [] cur = "" for s in sentences: - if len(cur)+len(s)>max_length: + if len(cur) + len(s) > max_length: new.append(cur) cur = "" cur += s - if len(cur)>0: + if len(cur) > 0: new.append(cur) - return new \ No newline at end of file + return new diff --git a/requirements-dev.txt b/requirements-dev.txt index 52eae707..3fc6f5aa 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -15,4 +15,7 @@ tensorboardX paddlenlp paddlepaddle pytest -kenlm \ No newline at end of file +kenlm +difflib +modelscope==1.16.0 +fairseq==0.12.2 diff --git a/requirements.txt b/requirements.txt index 19fcd3a9..5ac3a55d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,6 +7,3 @@ pandas six loguru pyahocorasick -difflib -modelscope==1.16.0 -fairseq==0.12.2 diff --git a/tests/test_mucgec_bart.py b/tests/test_mucgec_bart.py index 0eda2d37..7a0dd877 100644 --- a/tests/test_mucgec_bart.py +++ b/tests/test_mucgec_bart.py @@ -2,10 +2,9 @@ import unittest sys.path.append('..') -from pycorrector import MuCGECBartCorrector +from pycorrector.mucgec_bart.mucgec_bart_corrector import MuCGECBartCorrector from pycorrector.utils.sentence_utils import is_not_chinese_error - m = MuCGECBartCorrector() @@ -19,7 +18,6 @@ def test1(self): self.assertEqual(res[2]['target'], '我每天大约喝5杯水左右。') self.assertEqual(res[3]['target'], '今天,我非常开心。') - def test2(self): long_text = "在一个充满生活热闹和忙碌的城市中,有一个年轻人名叫李华。他生活在北京,这座充满着现代化建筑和繁忙街道的都市。每天,他都要穿行在拥挤的人群中,追逐着自己的梦想和生活节奏。\n\n李华从小就听祖辈讲述关于福气和努力的故事。他相信,“这洋的话,下一年的福气来到自己身上”。因此,尽管每天都很忙碌,他总是尽力保持乐观和积极。\n\n某天早晨,李华骑着自行车准备去上班。北京的交通总是非常繁忙,尤其是在早高峰时段。他经过一个交通路口,看到至少两个交警正在维持交通秩序。这些交警穿着整齐的制服,手势有序而又果断,让整个路口的车辆有条不紊地行驶着。这让李华想起了他父亲曾经告诫过他的话:“在拥挤的时间里,为了让人们遵守交通规则,至少要派两个警察或者交通管理者。”\n\n李华心中感慨万千,他想要在自己的生活中也如此积极地影响他人。他虽然只是一名普通的白领,却希望能够通过自己的努力和行动,为这座城市的安全与和谐贡献一份力量。\n\n随着时间的推移,中国的经济不断发展,北京的建设也日益繁荣。李华所在的公司也因为他的努力和创新精神而蓬勃发展。他喜欢打篮球,每周都会和朋友们一起去运动场,放松身心。他也十分重视健康,每天都保持适量的饮水量,大约喝五次左右。\n\n今天,李华觉得格外开心。他意识到,自己虽然只是一个普通人,却通过日复一日的努力,终于在生活中找到了属于自己的那份福气。他明白了祖辈们口中的那句话的含义——“这洋的话,下一年的福气来到自己身上”,并且深信不疑。\n\n在这个充满希望和机遇的时代里,李华将继续努力工作,为自己的梦想而奋斗,也希望能够在这座城市中留下自己的一份足迹,为他人带来更多的希望和正能量。\n\n这就是李华的故事,一个在现代城市中追寻梦想和福气的普通青年。" result = m.correct(long_text, ignore_function=is_not_chinese_error) @@ -28,4 +26,4 @@ def test2(self): if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/test_nasgec_bart.py b/tests/test_nasgec_bart.py index 7719e1a4..74a4fc4c 100644 --- a/tests/test_nasgec_bart.py +++ b/tests/test_nasgec_bart.py @@ -2,10 +2,9 @@ import unittest sys.path.append('..') -from pycorrector import NaSGECBartCorrector +from pycorrector.nasgec_bart.nasgec_bart_corrector import NaSGECBartCorrector from pycorrector.utils.sentence_utils import is_not_chinese_error - m = NaSGECBartCorrector() @@ -19,7 +18,6 @@ def test1(self): self.assertEqual(res[2]['target'], '我每天大约喝5次水左右。') self.assertEqual(res[3]['target'], '今天,我非常开心。') - def test2(self): long_text = "在一个充满生活热闹和忙碌的城市中,有一个年轻人名叫李华。他生活在北京,这座充满着现代化建筑和繁忙街道的都市。每天,他都要穿行在拥挤的人群中,追逐着自己的梦想和生活节奏。\n\n李华从小就听祖辈讲述关于福气和努力的故事。他相信,“这洋的话,下一年的福气来到自己身上”。因此,尽管每天都很忙碌,他总是尽力保持乐观和积极。\n\n某天早晨,李华骑着自行车准备去上班。北京的交通总是非常繁忙,尤其是在早高峰时段。他经过一个交通路口,看到至少两个交警正在维持交通秩序。这些交警穿着整齐的制服,手势有序而又果断,让整个路口的车辆有条不紊地行驶着。这让李华想起了他父亲曾经告诫过他的话:“在拥挤的时间里,为了让人们遵守交通规则,至少要派两个警察或者交通管理者。”\n\n李华心中感慨万千,他想要在自己的生活中也如此积极地影响他人。他虽然只是一名普通的白领,却希望能够通过自己的努力和行动,为这座城市的安全与和谐贡献一份力量。\n\n随着时间的推移,中国的经济不断发展,北京的建设也日益繁荣。李华所在的公司也因为他的努力和创新精神而蓬勃发展。他喜欢打篮球,每周都会和朋友们一起去运动场,放松身心。他也十分重视健康,每天都保持适量的饮水量,大约喝五次左右。\n\n今天,李华觉得格外开心。他意识到,自己虽然只是一个普通人,却通过日复一日的努力,终于在生活中找到了属于自己的那份福气。他明白了祖辈们口中的那句话的含义——“这洋的话,下一年的福气来到自己身上”,并且深信不疑。\n\n在这个充满希望和机遇的时代里,李华将继续努力工作,为自己的梦想而奋斗,也希望能够在这座城市中留下自己的一份足迹,为他人带来更多的希望和正能量。\n\n这就是李华的故事,一个在现代城市中追寻梦想和福气的普通青年。" result = m.correct(long_text, ignore_function=is_not_chinese_error) @@ -28,4 +26,4 @@ def test2(self): if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main()