引言

在上一个项目ERNIE for CSC:【的、地、得】傻傻分不清?救星来了!中,我们介绍了如何从0开始训练一个文本纠错模型。

文章的最后,提到了PaddleNLP的Taskflow。本文就基于文本纠错的Taskflow,展开一个趣味项目:如何让AI做改错别字的作业。

错别字纠正历来是语文考试的“保留曲目”,想必下面这种形式的习题大家再熟悉不过了吧?

本文研究的就是如何基于PaddleNLP的Taskflow,实现AI自动答题,并检测下它的“语文水平”。https://ai-studio-static-online.cdn.bcebos.com/d500f483e55544a5b929abad59de208f180c068cc81648009fab60a0b6d9bda2

PaddleNLP Taskflow介绍

paddlenlp.Taskflow是旨在提供开箱即用的NLP预置任务,覆盖自然语言理解与自然语言生成两大核心应用,在中文场景上提供产业级的效果与极致的预测性能。

任务清单

当前paddlenlp.Taskflow支持以下任务,本文用到的文本纠错是其中一种。

自然语言理解任务自然语言生成任务
中文分词生成式问答
词性标注智能写诗
命名实体识别文本翻译(TODO)
文本纠错开放域对话(TODO)
句法分析自动对联(TODO)
情感分析

随着版本迭代paddlenlp.Taskflow会持续开放更多的应用场景。

用法

接着我们来看看它的用法。

from paddlenlp import Taskflow

corrector = Taskflow("text_correction")
corrector('遇到逆竟时,我们必须勇于面对,而且要愈挫愈勇,这样我们才能朝著成功之路前进。')
>>> [{'source': '遇到逆竟时,我们必须勇于面对,而且要愈挫愈勇,这样我们才能朝著成功之路前进。', 'target': '遇到逆境时,我们必须勇于面对,而且要愈挫愈勇,这样我们才能朝著成功之路前进。', 'errors': [{'position': 3, 'correction': {'竟': '境'}}]}]

corrector(['遇到逆竟时,我们必须勇于面对,而且要愈挫愈勇,这样我们才能朝著成功之路前进。',
                '人生就是如此,经过磨练才能让自己更加拙壮,才能使自己更加乐观。'])
>>> [{'source': '遇到逆竟时,我们必须勇于面对,而且要愈挫愈勇,这样我们才能朝著成功之路前进。', 'target': '遇到逆境时,我们必须勇于面对,而且要愈挫愈勇,这样我们才能朝著成功之路前进。', 'errors': [{'position': 3, 'correction': {'竟': '境'}}]}, {'source': '人生就是如此,经过磨练才能让自己更加拙壮,才能使自己更加乐观。', 'target': '人生就是如此,经过磨练才能让自己更加茁壮,才能使自己更加乐观。', 'errors': [{'position': 18, 'correction': {'拙': '茁'}}]}]

如果看到这个示例还存在疑问,不妨想想PaddleHub,其实两者非常类似,对吧?其实就是Paddle训练好了模型,提供非常简单的方式供用户调用。

至于……为什么有了PaddleHub,又造了paddlenlp.Taskflow这个轮子……我也不知道https://ai-studio-static-online.cdn.bcebos.com/d500f483e55544a5b929abad59de208f180c068cc81648009fab60a0b6d9bda2

作为用户,关注它们好不好用就行了吧https://ai-studio-static-online.cdn.bcebos.com/d500f483e55544a5b929abad59de208f180c068cc81648009fab60a0b6d9bda2

环境准备

# 本项目paddlenlp的版本要升到2.1
!pip install -U paddlenlp
!pip install pypinyin --upgrade

错别字检测与结果封装

from paddlenlp import Taskflow
text_correction = Taskflow("text_correction")
text = '若成长是一篇著作,那么烦恼便是藏在段落深处的措别字;如果成长是一张白纸,那么烦恼便是附在背面的一个瑕疵'
result = text_correction(text)
[2021-11-04 23:43:35,039] [    INFO] - Already cached /home/aistudio/.paddlenlp/models/ernie-1.0/vocab.txt
# 查看检测到错字的位置
result[0]['errors'][0]['position']
# 查看更正信息
result[0]['errors'][0]['correction']
{'措': '错'}
# 做个后处理,拼接纠错效果
postprocess= text[:result[0]['errors'][0]['position']] + str(result[0]['errors'][0]['correction']) + text[result[0]['errors'][0]['position']+1:] 
text[:result[0]['errors'][0]['position']] + list(result[0]['errors'][0]['correction'].keys())[0] + '(' + list(result[0]['errors'][0]['correction'].values())[0] + ')' + text[result[0]['errors'][0]['position']+1:] 
'若成长是一篇著作,那么烦恼便是藏在段落深处的措(错)别字;如果成长是一张白纸,那么烦恼便是附在背面的一个瑕疵'

在上面的操作中,我们通过比较“暴力”的封装,对测试语句进行了纠错,组合成类似于答题需要的效果。

但是,很显然这个过程还可用优化一番,比如说,不是通过“暴力”拼接括号,而是通过替换的方式,至少看起来会比较合理些;同时,这种做法也为同时处理存在多个错别字的句子,埋下了伏笔。

# 字符串替换模型,将字符串中,指定索引位的文字替换成另一个
def replace_char(string, char, index):
     string = list(string)
     string[index] = char
     return ''.join(string)
replace_char(text, (list(result[0]['errors'][0]['correction'].keys())[0] + '(' + list(result[0]['errors'][0]['correction'].values())[0] + ')'), result[0]['errors'][0]['position'])
'若成长是一篇著作,那么烦恼便是藏在段落深处的措(错)别字;如果成长是一张白纸,那么烦恼便是附在背面的一个瑕疵'

中文断句的处理

前文我们实现的错别字更正,只能更正一个句子。如果语句长度太长,我们就会看到模型罢工了……

很显然,受限于当前算力和模型结果,让NLP处理太长的语句,也没什么太大的意义。

因此,如果我们要让这个文本纠错模型,处理一整个大的段落,就需要对其进行中文分句。

import re
def cut_sent(para):
    para = re.sub('([。!?\?])([^”’])', r"\1\n\2", para)  # 单字符断句符
    para = re.sub('(\.{6})([^”’])', r"\1\n\2", para)  # 英文省略号
    para = re.sub('(\…{2})([^”’])', r"\1\n\2", para)  # 中文省略号
    para = re.sub('([。!?\?][”’])([^,。!?\?])', r'\1\n\2', para)
    # 如果双引号前有终止符,那么双引号才是句子的终点,把分句符\n放到双引号后,注意前面的几句都小心保留了双引号
    para = para.rstrip()  # 段尾如果有多余的\n就去掉它
    # 很多规则中会考虑分号;,但是这里我把它忽略不计,破折号、英文双引号等同样忽略,需要的再做些简单调整即可。
    return para.split("\n")

实现段落分句可以有很多形式——极端点的,参考分词训练个模型也可以。不过本文使用的是基于正则表达式的方法,显然效率高了不少,在大部分情况下也比较够用。

参考链接:
用python进行精细中文分句(基于正则表达式)

接下来,我们用下面这道精心设计的段落,检测下文本纠错模型逐句检测改正的效果。

para = "初一那年的一个周末,劳禄的母亲第一次来到穿流不息的县城,到学校来看我。见到母亲,我迫不急待地说,“妈,带了什么好吃的没有?” 母亲兴奋地塞给我一盒包装得美仑美奂的营养液。我惊讶地问母亲:“咱家那么困难,买它干什么?”母亲笑容可鞠地说:“顾名思意,营养液嘛,补脑子,喝了它准能考上大学。”我摸挲着那盒营养液,嘟囔着:“那么贵,大慨又借钱了吧?” 母亲一笑:“没有!是用手镯换的”。那知漂亮得银手镯是外祖母传给母亲的,是贫穷的母亲最贵重得东西了,多年来一直舍不得戴,压在箱底。"
sents = cut_sent(para)
print("\n".join(sents))
results = text_correction(cut_sent(para))
for idx, item in enumerate(sents):    
    res = text_correction(item)
    if (len(res[0]['errors'])) > 0:
        for i, error in enumerate(res[0]['errors']):
            if i == 0:
                item = replace_char(item, (list(res[0]['errors'][i]['correction'].keys())[0] + '(' + list(res[0]['errors'][i]['correction'].values())[0] + ')'), res[0]['errors'][i]['position'])
            else:
                # 如果句子中有多处错字,那么每替换前面一个字,后面的错字索引往后移动3位:即括号+字=3位
                p = res[0]['errors'][i]['position'] + i * 3
                item = replace_char(item, (list(res[0]['errors'][i]['correction'].keys())[0] + '(' + list(res[0]['errors'][i]['correction'].values())[0] + ')'), p)
        print(item)
    else:
        print(item)
初一那年的一个周末,劳禄的母亲第一次来到穿流不息的县城,到学校来看我。
见到母亲,我迫不急(及)待地说,“妈,带了什么好吃的没有?”
 母亲兴奋地塞给我一盒包装得仑(轮)仑美奂的营养液。
我惊讶地问母亲:“咱家那么困难,买它干什么?”
母亲笑容可鞠地说:“顾名思意,营养液嘛,补脑子,喝了它准能考上大学。”
我摸挲(叩)着那盒营养液,嘟囔着:“那么贵,大慨又借钱了吧?”
 母亲一笑:“没有!是用手镯换的”。
那知(只)漂亮得(的)银手镯是外祖母传给母亲的,是贫穷的母亲最贵重得(的)东西了,多年来一直舍不得戴,压在箱底。

就这样,一个AI错别字检测答题器基本诞生了!

其实,这个段落中存在很多个错别字,使用paddlenlp.Taskflow只找到了其中一部分,如果去考试,很可能挂科啊。https://ai-studio-static-online.cdn.bcebos.com/d500f483e55544a5b929abad59de208f180c068cc81648009fab60a0b6d9bda2

因此,如果是在工业场景,需要在预训练模型的基础上,更加丰富数据集,以获得更好的效果。本文稍后会给出一些补充数据集的思路。

Word文档纠错

接下来,我们继续丰富在不同场景下的paddlenlp.Taskflow文本纠错实现。word文档是我们工作中非常常用的一种形式,为省去一点复制粘贴的时间,现在我们研究下如何直接进行整个word文档的智能纠错

我们知道,虽然word文档自带了语法识别功能,但显然也不是完美的……比如下面的例子:
某点小说错别字盘点(一)

这里面大部分的错字问题都逃过了word的语法识别——如果一早就能识别到,作者可能码字的时候早就改过来了……

那么,我们就来看看,paddlenlp.Taskflow内置模型对于网络小说的错别字识别效果如何?同最后,我们会把检测结果保存到一个新的word文档中。

# 安装依赖库
!pip install python-docx
from docx import Document
from docx.shared import Inches
def get_paragraphs_text(path):
    """
    获取所有段落的文本
    :param path: word路径
    :return: list类型,如:
        ['Test', 'hello world', ...]
    """
    document = Document(path) 
    # 提取正文文本
    paragraphs_text = ""
    for paragraph in document.paragraphs:
        # 拼接一个list,包括段落的结构和内容
        paragraphs_text += paragraph.text + "\n"
    return paragraphs_text
text = get_paragraphs_text('csc-qd.docx')
print("\n".join(cut_sent(text)))
csc_result = text_correction(cut_sent(text))
for idx, item in enumerate(cut_sent(text)):
    if item is not '': 
        res = text_correction(item)
        if (len(res[0]['errors'])) > 0:
            for i, error in enumerate(res[0]['errors']):
                if i == 0:
                    item = replace_char(item, (list(res[0]['errors'][i]['correction'].keys())[0] + '(' + list(res[0]['errors'][i]['correction'].values())[0] + ')'), res[0]['errors'][i]['position'])
                else:
                    p = res[0]['errors'][i]['position'] + i * 3
                    item = replace_char(item, (list(res[0]['errors'][i]['correction'].keys())[0] + '(' + list(res[0]['errors'][i]['correction'].values())[0] + ')'), p)
            print(item)
        else:
            print(item)
终于领着一家大小浩浩荡荡的(地)从苍(沧)山里杀了出来
只是那初春的风儿惹的(得)众女满脸陶醉
照的(得)那些云朵丝丝发光,看上去十分震撼
只听得见前面的马蹄声和马儿打响鼻地(的)声音
一个个活的挺滋润
然后又去靖王府拜见那位相熟地(的)王爷
了了这两椿来往
所以等范(反)闲入太学就职地时候
这位以十七稚龄,便官至五品地朝中大红人
这些学生们的隔膜感渐渐退祛(去)
但是他地诗名毕竟早已流传在外
也曾随着上司四处查看举子入京后地(的)状况
想到五竹叔在澹(澳)州讲过地(的)故事
既然不是市(重)恩之举
范闲(雅)略略一惊,清亮的眸子里马上回(恢)复了平静
一丝欣慰之外,更多是的对范闲(畴)似乎安于仕途,而产生某种放心。
后几日,首先领着婉儿回了相府。
“查自然是要查的,但不是你这般莽撞的(地)查下去,”李长寿道
敖乙很轻松的就被转移了注意力。
李长寿此刻也是听的(得)颇为感慨。
两条灵鱼,姜思儿吃的最是欢快
敖乙与姜思儿依依不舍的告别。
李长寿带着灵娥对守门仙人做了个道揖
李长寿淡定的(地)点点头
海神教主庙难得在白天清冷(冷清)了一次
或许是因为这些人被朝廷压榨的(得)太狠
我很担心老卢会不顾一切的继续朝建奴(女)发起进攻。
我不认为那些带着大军去了河南的宦(赛)官会好心的(地)把自己的弹药分给老卢。
你说,他怎么就得罪了杨嗣昌,高起(启)潜这些恶人的?
洪承畴悠悠的(地)道:
老老实实的做槛(地)车养足精神
不,我尽心竭力的(地)挽救这棵枯树,直到枯树寿终正寝。
钱多多翘着脚躺在自己的床上,大脚趾上还挂着一只鞋子不停的晃啊晃的,却怎么都不愿意从脚上掉下里(礼)。
何常氏忧心忡忡的看着钱多多道:
何常氏无奈的走到门口
司离人转头无语的撇了李神坛一眼
毕竟这种清理师门的戏码,就应该打的(得)特别激烈
这人惊恐的一屁股坐在地上,还手忙脚乱的(地)往后退去。
所有人超(抄)那边看去
李神坛无奈的低头看了一眼
罗岚慢慢的(地)向后退去
天地间浮现出一条又一条大道秩链,它们在嘎嘣嘎嘣的断裂
那位无上生灵咳出一口血,霍的仰头望去。
不让让那离去的真身观照此世!
一定会不顾一切的(地)回来
又像是不可抹名状的生物被磨灭后的碎屑!

应该说,paddlenlp.Taskflow的文本纠错模型现在在【的、地、得】区分上有一定造诣——大概是数据集的关系。

虽然检错纠错准确性效果还是有待提高,但看起来比回答精心设计的考题时好了不少?

new_document = Document()
for idx, item in enumerate(cut_sent(text)):
    if item is not '': 
        res = text_correction(item)
        if (len(res[0]['errors'])) > 0:
            for i, error in enumerate(res[0]['errors']):
                if i == 0:
                    item = replace_char(item, (list(res[0]['errors'][i]['correction'].keys())[0] + '(' + list(res[0]['errors'][i]['correction'].values())[0] + ')'), res[0]['errors'][i]['position'])
                else:
                    p = res[0]['errors'][i]['position'] + i * 3
                    item = replace_char(item, (list(res[0]['errors'][i]['correction'].keys())[0] + '(' + list(res[0]['errors'][i]['correction'].values())[0] + ')'), p)
            new_document.add_paragraph(item)
        else:
            new_document.add_paragraph(item)
# 结果保存到新文档    
new_document.save('csc-result.docx')

PDF作业答题

我们再让AI来做道题,这次面对的文本格式是PDF。

!pip install pdfplumber
import pdfplumber
import pandas as pd

with pdfplumber.open("12345.pdf") as pdf:
    page = pdf.pages[0]   # 第一页的信息
    text = page.extract_text()
    print(text)
基础知识·汉字
6、下面的句子中没有错别字的是( )。
A.书藉是人类进步的阶梯。
B.老师经常利用课余时间哺导我们学习。
C.初中学习生活即将结束,同学们都格外珍惜这段时光。
D.梦园世界杯,中国队从西安起飞。
for idx, item in enumerate(cut_sent(text)):
    if item is not '': 
        res = text_correction(item)
        if (len(res[0]['errors'])) > 0:
            for i, error in enumerate(res[0]['errors']):
                if i == 0:
                    item = replace_char(item, (list(res[0]['errors'][i]['correction'].keys())[0] + '(' + list(res[0]['errors'][i]['correction'].values())[0] + ')'), res[0]['errors'][i]['position'])
                else:
                    p = res[0]['errors'][i]['position'] + i * 3
                    item = replace_char(item, (list(res[0]['errors'][i]['correction'].keys())[0] + '(' + list(res[0]['errors'][i]['correction'].values())[0] + ')'), p)
            print(item)
        else:
            print(item)
基础知识·汉字
6、下面的句子中没有错别字的是( )。
A.书藉(戒)是人类进步的阶梯。
B.老师经常利用课余时间哺(陪)导我们学习。
C.初中学习生活即将结束,同学们都格外珍惜这段时光。
D.梦园(圆)世界杯,中国队从西安起飞。

在A选项中,其实模型已经定位到了错别字,只不过改得有点离谱……总体来说,这题虽然答得磕磕碰碰,好歹AI是把正确答案选出来了。不过,处理PDF时,换行问题还是比较让人头疼的,比如同一个题目,如果C选项换行了并且没有处理好,答题结果就变了。

PDF换行问题

with pdfplumber.open("14540300364943.pdf") as pdf:
    page = pdf.pages[0]   # 第一页的信息
    text = page.extract_text()
    print(text)
基础知识·汉字
6、下面的句子中没有错别字的一项是( )。
A.书藉是人类进步的阶梯。
B.老师经常利用课余时间哺导我们学习。
C.初中学习生活即将结束,同学们都格外珍惜这
段时光。
D.梦园世界杯,中国队从西安起飞。
for idx, item in enumerate(cut_sent(text)):
    if item is not '': 
        res = text_correction(item)
        if (len(res[0]['errors'])) > 0:
            for i, error in enumerate(res[0]['errors']):
                if i == 0:
                    item = replace_char(item, (list(res[0]['errors'][i]['correction'].keys())[0] + '(' + list(res[0]['errors'][i]['correction'].values())[0] + ')'), res[0]['errors'][i]['position'])
                else:
                    p = res[0]['errors'][i]['position'] + i * 3
                    item = replace_char(item, (list(res[0]['errors'][i]['correction'].keys())[0] + '(' + list(res[0]['errors'][i]['correction'].values())[0] + ')'), p)
            print(item)
        else:
    print(item)
        else:
            print(item)
基础知识·汉字
6、下面的句子中没有错别字的一项(像)是( )。
A.书藉(戒)是人类进步的阶梯。
B.老师经常利用课余时间哺(陪)导我们学习。
C.初中学习生活即将结束,同学们都格外珍惜这
段(短)时光。
D.梦园(圆)世界杯,中国队从西安起飞。

因为换行后的语句直接被送入模型预测,显然发生了误解。https://ai-studio-static-online.cdn.bcebos.com/d500f483e55544a5b929abad59de208f180c068cc81648009fab60a0b6d9bda2

小结与思考

paddlenlp.Taskflow前面做的这些题目看,如果直接参加错别字检测考试,估计也就拿个50-60分,显然不太合格。

不过,众所周知,深度学习模型是数据“喂”出来的,相信该模型要是“见过”《五年模拟三年高考》《黄冈密卷》等等等,跑出来成绩应该能更上一层楼吧?

这里给出补充数据集的几个思路:

  • 百度文档中有关【错别字专项】的全系列
  • 《咬文嚼字》整理出的最常见错别字
  • 更多为中文文本纠错任务自动生成的数据

数据集的标注方式也有多种,比如sgml格式数据可以这样标注:

考虑到语文考试内容就那些,个人感觉,如果补充了针对性数据集,让AI又好又快地做起作业来还是比较有希望的。https://ai-studio-static-online.cdn.bcebos.com/d500f483e55544a5b929abad59de208f180c068cc81648009fab60a0b6d9bda2

Logo

学大模型,用大模型上飞桨星河社区!每天8点V100G算力免费领!免费领取ERNIE 4.0 100w Token >>>

更多推荐