Skip to content

文本纠错工具包(Text Correct, CSC), 支持中文文本纠错(拼写纠错/标点符号纠错)(CSC, Chinese Spelling Correct / Check; Punct), CSC支持各领域数据的中文文本纠错(包括古文), 模型在大规模、各领域的、现代/当代语料上训练而得, 泛化性强.重点是错别字检测纠正.

License

Notifications You must be signed in to change notification settings

yongzhuo/macro-correct

Repository files navigation

PyPI Build Status PyPI_downloads Stars Forks Join the chat at https://gitter.im/yongzhuo/macro-correct

macro-correct, 文本纠错工具包(Text Correct), 支持中文拼写纠错/标点符号纠错(CSC, Chinese Spelling Correct / Check), CSC支持各领域数据(包括古文), 模型在大规模、各领域的、现代/当代语料上训练而得, 泛化性强.

macro-correct是一个只依赖pytorch、transformers、numpy、opencc的文本纠错(CSC, 中文拼写纠错; Punct, 中文标点纠错)工具包,专注于中文文本纠错的极简自然语言处理工具包。 使用大部分市面上的开源数据集构建生成的混淆集,使用人民日报语料&学习强国语料等生成1000万+训练数据集来训练模型; 支持MDCSpell、Macbert、ReLM、SoftBERT、BertCRF等多种经典模型; 支持中文拼写纠错、中文标点符号纠错、中文语法纠错(待续)、独立的检测模型/识别模型(待续); 具有依赖轻量、代码简洁、注释详细、调试清晰、配置灵活、拓展方便、适配NLP等特性。

目录

安装

pip install macro-correct

# 清华镜像源
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple macro-correct

# 如果不行, 则不带依赖安装, 之后缺什么包再补充什么
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple macro-correct --no-dependencies

调用

更多样例sample详情见/tet目录

  • 使用example详见/tet/tet目录, 中文拼写纠错代码为tet_csc_token_zh.py, 中文标点符号纠错代码为tet_csc_punct_zh.py, CSC也可以直接用tet_csc_flag_transformers.py
  • 训练代码详见/tet/train目录, 可配置本地预训练模型地址和各种参数等;

体验

2.调用-文本纠错

2.1 CSC 使用 macro-bert

# !/usr/bin/python
# -*- coding: utf-8 -*-
# @time    : 2021/2/29 21:41
# @author  : Mo
# @function: 文本纠错, 使用macro-correct


import os
os.environ["MACRO_CORRECT_FLAG_CSC_TOKEN"] = "1"
from macro_correct import correct
### 默认纠错(list输入)
text_list = ["真麻烦你了。希望你们好好的跳无",
             "少先队员因该为老人让坐",
             "机七学习是人工智能领遇最能体现智能的一个分知",
             "一只小鱼船浮在平净的河面上"
             ]
text_csc = correct(text_list)
print("默认纠错(list输入):")
for res_i in text_csc:
    print(res_i)
print("#" * 128)

"""
默认纠错(list输入):
{'index': 0, 'source': '真麻烦你了。希望你们好好的跳无', 'target': '真麻烦你了。希望你们好好地跳舞', 'errors': [['的', '地', 12, 0.6584], ['无', '舞', 14, 1.0]]}
{'index': 1, 'source': '少先队员因该为老人让坐', 'target': '少先队员应该为老人让坐', 'errors': [['因', '应', 4, 0.995]]}
{'index': 2, 'source': '机七学习是人工智能领遇最能体现智能的一个分知', 'target': '机器学习是人工智能领域最能体现智能的一个分支', 'errors': [['七', '器', 1, 0.9998], ['遇', '域', 10, 0.9999], ['知', '支', 21, 1.0]]}
{'index': 3, 'source': '一只小鱼船浮在平净的河面上', 'target': '一只小鱼船浮在平静的河面上', 'errors': [['净', '静', 8, 0.9961]]}
"""

2.2 CSC 使用 transformers

# !/usr/bin/python
# -*- coding: utf-8 -*-
# @time    : 2021/2/29 21:41
# @author  : Mo
# @function: transformers直接加载bert类模型测试


import traceback
import operator
import time
import sys
import os
os.environ["USE_TORCH"] = "1"
from transformers import BertConfig, BertTokenizer, BertForMaskedLM
import torch


# pretrained_model_name_or_path = "../../macro_correct/output/text_correction/macbert4mdcspell_v1"
# pretrained_model_name_or_path = "../../macro_correct/output/text_correction/macbert4csc_v1"
# pretrained_model_name_or_path = "../../macro_correct/output/text_correction/macbert4csc_v2"
# pretrained_model_name_or_path = "../../macro_correct/output/text_correction/bert4csc_v1"
# pretrained_model_name_or_path = "shibing624/macbert4csc-base-chinese"
# pretrained_model_name_or_path = "Macropodus/macbert4mdcspell_v1"
pretrained_model_name_or_path = "Macropodus/macbert4mdcspell_v2"
# pretrained_model_name_or_path = "Macropodus/macbert4csc_v1"
# pretrained_model_name_or_path = "Macropodus/macbert4csc_v2"
# pretrained_model_name_or_path = "Macropodus/bert4csc_v1"
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
max_len = 128

print("load model, please wait a few minute!")
tokenizer = BertTokenizer.from_pretrained(pretrained_model_name_or_path)
bert_config = BertConfig.from_pretrained(pretrained_model_name_or_path)
model = BertForMaskedLM.from_pretrained(pretrained_model_name_or_path)
model.to(device)
vocab = tokenizer.vocab
print("load model success!")

texts = [
    "机七学习是人工智能领遇最能体现智能的一个分知",
    "我是练习时长两念半的鸽仁练习生蔡徐坤",
    "真麻烦你了。希望你们好好的跳无",
    "他法语说的很好,的语也不错",
    "遇到一位很棒的奴生跟我疗天",
    "我们为这个目标努力不解",
]
len_mid = min(max_len, max([len(t)+2 for t in texts]))

with torch.no_grad():
    outputs = model(**tokenizer(texts, padding=True, max_length=len_mid,
                                return_tensors="pt").to(device))


def flag_total_chinese(text):
    """
    judge is total chinese or not, 判断是不是全是中文
    Args:
        text: str, eg. "macadam, 碎石路"
    Returns:
        bool, True or False
    """
    for word in text:
        if not "\u4e00" <= word <= "\u9fa5":
            return False
    return True

def get_errors_from_diff_length(corrected_text, origin_text, unk_tokens=[], know_tokens=[]):
    """Get errors between corrected text and origin text
    code from:  https://github.com/shibing624/pycorrector
    """
    new_corrected_text = ""
    errors = []
    i, j = 0, 0
    unk_tokens = unk_tokens or [' ', '', '', '', '', '', '\n', '', '', '\t', '', '']
    while i < len(origin_text) and j < len(corrected_text):
        if origin_text[i] in unk_tokens or origin_text[i] not in know_tokens:
            new_corrected_text += origin_text[i]
            i += 1
        elif corrected_text[j] in unk_tokens:
            new_corrected_text += corrected_text[j]
            j += 1
        # Deal with Chinese characters
        elif flag_total_chinese(origin_text[i]) and flag_total_chinese(corrected_text[j]):
            # If the two characters are the same, then the two pointers move forward together
            if origin_text[i] == corrected_text[j]:
                new_corrected_text += corrected_text[j]
                i += 1
                j += 1
            else:
                # Check for insertion errors
                if j + 1 < len(corrected_text) and origin_text[i] == corrected_text[j + 1]:
                    errors.append(('', corrected_text[j], j))
                    new_corrected_text += corrected_text[j]
                    j += 1
                # Check for deletion errors
                elif i + 1 < len(origin_text) and origin_text[i + 1] == corrected_text[j]:
                    errors.append((origin_text[i], '', i))
                    i += 1
                # Check for replacement errors
                else:
                    errors.append((origin_text[i], corrected_text[j], i))
                    new_corrected_text += corrected_text[j]
                    i += 1
                    j += 1
        else:
            new_corrected_text += origin_text[i]
            if origin_text[i] == corrected_text[j]:
                j += 1
            i += 1
    errors = sorted(errors, key=operator.itemgetter(2))
    return new_corrected_text, errors

def get_errors_from_same_length(corrected_text, origin_text, unk_tokens=[], know_tokens=[]):
        """Get new corrected text and errors between corrected text and origin text
        code from:  https://github.com/shibing624/pycorrector
        """
        errors = []
        unk_tokens = unk_tokens or [' ', '', '', '', '', '', '\n', '', '', '\t', '', '', '']

        for i, ori_char in enumerate(origin_text):
            if i >= len(corrected_text):
                continue
            if ori_char in unk_tokens or ori_char not in know_tokens:
                # deal with unk word
                corrected_text = corrected_text[:i] + ori_char + corrected_text[i + 1:]
                continue
            if ori_char != corrected_text[i]:
                if not flag_total_chinese(ori_char):
                    # pass not chinese char
                    corrected_text = corrected_text[:i] + ori_char + corrected_text[i + 1:]
                    continue
                if not flag_total_chinese(corrected_text[i]):
                    corrected_text = corrected_text[:i] + corrected_text[i + 1:]
                    continue
                errors.append([ori_char, corrected_text[i], i])
        errors = sorted(errors, key=operator.itemgetter(2))
        return corrected_text, errors

def get_errors(text, probs):
    """   获取错误信息   """
    _text = tokenizer.decode(torch.argmax(probs, dim=-1), skip_special_tokens=True).replace(' ', '')
    corrected_text = _text[:len(text)]
    if len(corrected_text) == len(text):
        corrected_text, details = get_errors_from_same_length(corrected_text, text, know_tokens=vocab)
    else:
        corrected_text, details = get_errors_from_diff_length(corrected_text, text, know_tokens=vocab)
    print(text, ' => ', corrected_text, details)
    return details


for probs, source in zip(outputs.logits, texts):
    errors = get_errors(source, probs)

"""
机七学习是人工智能领遇最能体现智能的一个分知  =>  机器学习是人工智能领域最能体现智能的一个分支 [['七', '器', 1], ['遇', '域', 10], ['知', '支', 21]]
我是练习时长两念半的鸽仁练习生蔡徐坤  =>  我是练习时长两年半的个人练习生蔡徐坤 [['念', '年', 7], ['鸽', '个', 10], ['仁', '人', 11]]
真麻烦你了。希望你们好好的跳无  =>  真麻烦你了。希望你们好好地跳舞 [['的', '地', 12], ['无', '舞', 14]]
他法语说的很好,的语也不错  =>  他法语说得很好,德语也不错 [['的', '得', 4], ['的', '德', 8]]
遇到一位很棒的奴生跟我疗天  =>  遇到一位很棒的女生跟我聊天 [['奴', '女', 7], ['疗', '聊', 11]]
我们为这个目标努力不解  =>  我们为这个目标努力不懈 [['解', '懈', 10]]
"""

3.调用-标点纠错

import os
os.environ["MACRO_CORRECT_FLAG_CSC_PUNCT"] = "1"
from macro_correct import correct_punct


### 1.默认标点纠错(list输入)
text_list = ["山不在高有仙则名。",
             "水不在深,有龙则灵",
             "斯是陋室惟吾德馨",
             "苔痕上阶绿草,色入帘青。"
             ]
text_csc = correct_punct(text_list)
print("默认标点纠错(list输入):")
for res_i in text_csc:
    print(res_i)
print("#" * 128)

"""
默认标点纠错(list输入):
{'index': 0, 'source': '山不在高有仙则名。', 'target': '山不在高,有仙则名。', 'score': 0.9917, 'errors': [['', ',', 4, 0.9917]]}
{'index': 1, 'source': '水不在深,有龙则灵', 'target': '水不在深,有龙则灵。', 'score': 0.9995, 'errors': [['', '。', 9, 0.9995]]}
{'index': 2, 'source': '斯是陋室惟吾德馨', 'target': '斯是陋室,惟吾德馨。', 'score': 0.9999, 'errors': [['', ',', 4, 0.9999], ['', '。', 8, 0.9998]]}
{'index': 3, 'source': '苔痕上阶绿草,色入帘青。', 'target': '苔痕上阶绿,草色入帘青。', 'score': 0.9998, 'errors': [['', ',', 5, 0.9998]]}
"""

词典

默认混淆词典地址

  • macro_correct/output/confusion_dict.json

操作混淆词典

## 自定义混淆词典
# !/usr/bin/python
# -*- coding: utf-8 -*-
# @time    : 2021/2/29 21:41
# @author  : Mo
# @function: tet csc of token confusion dict, 混淆词典


import os
os.environ["MACRO_CORRECT_FLAG_CSC_TOKEN"] = "1"

from macro_correct.pytorch_textcorrection.tcTrie import ConfusionCorrect
from macro_correct import MODEL_CSC_TOKEN
from macro_correct import correct


### 默认使用混淆词典
user_dict = {
    "乐而往返": "乐而忘返",
    "金钢钻": "金刚钻",
    "藤罗蔓": "藤萝蔓",
}
text_list = [
    "为什么乐而往返?",
    "没有金钢钻就不揽瓷活!",
    "你喜欢藤罗蔓吗?",
    "三周年祭日在哪举行?"
]
text_csc = correct(text_list, flag_confusion=False)
print("默认纠错(不带混淆词典):")
for res_i in text_csc:
    print(res_i)
print("#" * 128)



text_csc = correct(text_list, flag_confusion=True)
print("默认纠错(-带混淆词典-默认):")
for res_i in text_csc:
    print(res_i)
print("#" * 128)


# ---混淆词典---
### 只新增, 新增用户词典(默认混淆词典也使用)
MODEL_CSC_TOKEN.model_csc.model_confusion = ConfusionCorrect(user_dict=user_dict)
text_csc = correct(text_list, flag_confusion=True)
print("默认纠错(-带混淆词典-新增):")
for res_i in text_csc:
    print(res_i)
print("#" * 128)
### 全覆盖, 只使用用户词典(默认混淆词典废弃)
MODEL_CSC_TOKEN.model_csc.model_confusion = ConfusionCorrect(confusion_dict=user_dict)
text_csc = correct(text_list, flag_confusion=True)
print("默认纠错(-带混淆词典-全覆盖):")
for res_i in text_csc:
    print(res_i)
print("#" * 128)


# ---混淆词典文件---
### 只新增, 新增用户词典(默认混淆词典也使用), path不为空即可; json文件, {混淆词语:正确词语} key-value; 详见macro-correct/tet/tet/tet_csc_token_confusion.py
path_user = "./user_confusion_dict.json"
MODEL_CSC_TOKEN.model_csc.model_confusion = ConfusionCorrect(path="1", path_user=path_user)
text_csc = correct(text_list, flag_confusion=True)
print("默认纠错(-带混淆词典文件-新增):")
for res_i in text_csc:
    print(res_i)
print("#" * 128)
### 全覆盖, 只使用用户词典(默认混淆词典废弃); path必须传空字符串
MODEL_CSC_TOKEN.model_csc.model_confusion = ConfusionCorrect(path="", path_user=path_user)
text_csc = correct(text_list, flag_confusion=True)
print("默认纠错(-带混淆词典文件-全覆盖):")
for res_i in text_csc:
    print(res_i)
print("#" * 128)

"""
默认纠错(不带混淆词典):
{'index': 0, 'source': '为什么乐而往返?', 'target': '为什么乐而往返?', 'errors': []}
{'index': 1, 'source': '没有金钢钻就不揽瓷活!', 'target': '没有金刚钻就不揽瓷活!', 'errors': [['钢', '刚', 3, 0.6587]]}
{'index': 2, 'source': '你喜欢藤罗蔓吗?', 'target': '你喜欢藤萝蔓吗?', 'errors': [['罗', '萝', 4, 0.8582]]}
{'index': 3, 'source': '三周年祭日在哪举行?', 'target': '三周年祭日在哪举行?', 'errors': []}
################################################################################################################################
默认纠错(-带混淆词典-默认):
{'index': 0, 'source': '为什么乐而往返?', 'target': '为什么乐而往返?', 'errors': []}
{'index': 1, 'source': '没有金钢钻就不揽瓷活!', 'target': '没有金刚钻就不揽瓷活!', 'errors': [['钢', '刚', 3, 1.0]]}
{'index': 2, 'source': '你喜欢藤罗蔓吗?', 'target': '你喜欢藤萝蔓吗?', 'errors': [['罗', '萝', 4, 0.8582]]}
{'index': 3, 'source': '三周年祭日在哪举行?', 'target': '三周年忌日在哪举行?', 'errors': [['祭', '忌', 3, 1.0]]}
################################################################################################################################
默认纠错(-带混淆词典-新增):
{'index': 0, 'source': '为什么乐而往返?', 'target': '为什么乐而忘返?', 'errors': [['往', '忘', 5, 1.0]]}
{'index': 1, 'source': '没有金钢钻就不揽瓷活!', 'target': '没有金刚钻就不揽瓷活!', 'errors': [['钢', '刚', 3, 1.0]]}
{'index': 2, 'source': '你喜欢藤罗蔓吗?', 'target': '你喜欢藤萝蔓吗?', 'errors': [['罗', '萝', 4, 1.0]]}
{'index': 3, 'source': '三周年祭日在哪举行?', 'target': '三周年忌日在哪举行?', 'errors': [['祭', '忌', 3, 1.0]]}
################################################################################################################################
默认纠错(-带混淆词典-全覆盖):
{'index': 0, 'source': '为什么乐而往返?', 'target': '为什么乐而忘返?', 'errors': [['往', '忘', 5, 1.0]]}
{'index': 1, 'source': '没有金钢钻就不揽瓷活!', 'target': '没有金刚钻就不揽瓷活!', 'errors': [['钢', '刚', 3, 1.0]]}
{'index': 2, 'source': '你喜欢藤罗蔓吗?', 'target': '你喜欢藤萝蔓吗?', 'errors': [['罗', '萝', 4, 1.0]]}
{'index': 3, 'source': '三周年祭日在哪举行?', 'target': '三周年祭日在哪举行?', 'errors': []}
################################################################################################################################
默认纠错(-带混淆词典文件-新增):
{'index': 0, 'source': '为什么乐而往返?', 'target': '为什么乐而忘返?', 'errors': [['往', '忘', 5, 1.0]]}
{'index': 1, 'source': '没有金钢钻就不揽瓷活!', 'target': '没有金刚钻就不揽瓷活!', 'errors': [['钢', '刚', 3, 1.0]]}
{'index': 2, 'source': '你喜欢藤罗蔓吗?', 'target': '你喜欢藤萝蔓吗?', 'errors': [['罗', '萝', 4, 1.0]]}
{'index': 3, 'source': '三周年祭日在哪举行?', 'target': '三周年忌日在哪举行?', 'errors': [['祭', '忌', 3, 1.0]]}
################################################################################################################################
默认纠错(-带混淆词典文件-全覆盖):
{'index': 0, 'source': '为什么乐而往返?', 'target': '为什么乐而忘返?', 'errors': [['往', '忘', 5, 1.0]]}
{'index': 1, 'source': '没有金钢钻就不揽瓷活!', 'target': '没有金刚钻就不揽瓷活!', 'errors': [['钢', '刚', 3, 1.0]]}
{'index': 2, 'source': '你喜欢藤罗蔓吗?', 'target': '你喜欢藤萝蔓吗?', 'errors': [['罗', '萝', 4, 1.0]]}
{'index': 3, 'source': '三周年祭日在哪举行?', 'target': '三周年祭日在哪举行?', 'errors': []}
################################################################################################################################
"""

详情

CSC调用(超参数说明)

import os
os.environ["MACRO_CORRECT_FLAG_CSC_TOKEN"] = "1"
from macro_correct import correct
### 默认纠错(list输入)
text_list = ["真麻烦你了。希望你们好好的跳无",
             "少先队员因该为老人让坐",
             "机七学习是人工智能领遇最能体现智能的一个分知",
             "一只小鱼船浮在平净的河面上"
             ]
### 默认纠错(list输入, 参数配置)
params = {
    "threshold": 0.55,  # token阈值过滤
    "batch_size": 32,  # 批大小
    "max_len": 128,  # 自定义的长度, 如果截断了, 则截断部分不参与纠错, 后续直接一模一样的补回来
    "rounded": 4,  # 保存4位小数
    "flag_confusion": True,  # 是否使用默认的混淆词典
    "flag_prob": True,  # 是否返回纠错token处的概率
    "flag_cut": False,  # 是否切分句子, 长句, False会只处理前max_len长度的文本; True会按照标点切分(在超出就按照maxlen切分)
}
text_csc = correct(text_list, **params)
print("默认纠错(list输入, 参数配置):")
for res_i in text_csc:
    print(res_i)
print("#" * 128)


"""
默认纠错(list输入):
{'index': 0, 'source': '真麻烦你了。希望你们好好的跳无', 'target': '真麻烦你了。希望你们好好地跳舞', 'errors': [['的', '地', 12, 0.6584], ['无', '舞', 14, 1.0]]}
{'index': 1, 'source': '少先队员因该为老人让坐', 'target': '少先队员应该为老人让坐', 'errors': [['因', '应', 4, 0.995]]}
{'index': 2, 'source': '机七学习是人工智能领遇最能体现智能的一个分知', 'target': '机器学习是人工智能领域最能体现智能的一个分支', 'errors': [['七', '器', 1, 0.9998], ['遇', '域', 10, 0.9999], ['知', '支', 21, 1.0]]}
{'index': 3, 'source': '一只小鱼船浮在平净的河面上', 'target': '一只小鱼船浮在平静的河面上', 'errors': [['净', '静', 8, 0.9961]]}
"""

PUNCT调用(超参数说明)

import os
os.environ["MACRO_CORRECT_FLAG_CSC_PUNCT"] = "1"
from macro_correct import correct_punct


### 1.默认标点纠错(list输入)
text_list = ["山不在高有仙则名。",
             "水不在深,有龙则灵",
             "斯是陋室惟吾德馨",
             "苔痕上阶绿草,色入帘青。"
             ]
### 2.默认标点纠错(list输入, 参数配置详情)
params = {
        "limit_num_errors": 4,  # 一句话最多的错别字, 多的就剔除
        "limit_len_char": 4,   # 一句话的最小字符数
        "threshold_zh": 0.5,  # 句子阈值, 中文字符占比的最低值
        "threshold": 0.55,  # token阈值过滤
        "batch_size": 32,  # 批大小
        "max_len": 128,  # 自定义的长度, 如果截断了, 则截断部分不参与纠错, 后续直接一模一样的补回来
        "rounded": 4,  # 保存4位小数
        "flag_prob": True,  # 是否返回纠错token处的概率
    }
text_csc = correct_punct(text_list, **params)
print("默认标点纠错(list输入):")
for res_i in text_csc:
    print(res_i)
print("#" * 128)

"""
默认标点纠错(list输入):
{'index': 0, 'source': '山不在高有仙则名。', 'target': '山不在高,有仙则名。', 'score': 0.9917, 'errors': [['', ',', 4, 0.9917]]}
{'index': 1, 'source': '水不在深,有龙则灵', 'target': '水不在深,有龙则灵。', 'score': 0.9995, 'errors': [['', '。', 9, 0.9995]]}
{'index': 2, 'source': '斯是陋室惟吾德馨', 'target': '斯是陋室,惟吾德馨。', 'score': 0.9999, 'errors': [['', ',', 4, 0.9999], ['', '。', 8, 0.9998]]}
{'index': 3, 'source': '苔痕上阶绿草,色入帘青。', 'target': '苔痕上阶绿,草色入帘青。', 'score': 0.9998, 'errors': [['', ',', 5, 0.9998]]}
"""

CSC调用繁体

import os
os.environ["MACRO_CORRECT_FLAG_CSC_TOKEN"] = "1"

from macro_correct import correct_tradition


### 默认纠错(list输入)
text_list = ["一個分知,陌光回聚,莪受打去,禰愛帶餘",
            "余額還有100w",
            "放在陌光下",
            "真麻煩你了。希望你們好好的跳舞",
            "少先隊員因該爲老人讓坐",
            "機七學習是人工智能領遇最能體現智能的一個分知",
            "一只小魚船浮在平淨的河面上",
            "這一條次,我選擇了一條與往常不同的路線。",
            "春節發貨部"
             ]
### 默认纠错(list输入, 参数配置)
params = {
    "flag_confusion": True,  # 是否使用默认的混淆词典
    "flag_prob": True,  # 是否返回纠错token处的概率
    "flag_cut": True,  # 是否切分句子, 长句, False会只处理前max_len长度的文本; True会按照标点切分(在超出就按照maxlen切分)
    "limit_nums_errors": 8,  # 一句话最多的错别字, 多的就剔除(全不纠错)
    "num_rethink": 0,  # 多次预测, think-twice
    "batch_size": 32,  # 批大小
    "threshold": 0.01,  # token阈值过滤
    "max_len": 128,  # 自定义的长度, 如果截断了, 则截断部分不参与纠错, 后续直接一模一样的补回来
    "rounded": 4,  # 保存4位小数
}
text_csc = correct_tradition(text_list, **params)
print("默认纠错(list输入, 参数配置):")
for res_i in text_csc:
    print(res_i)
print("#" * 128)

"""
默认纠错(list输入, 参数配置):
{'index': 0, 'source': '一個分知,陌光回聚,莪受打去,禰愛帶餘', 'target': '一個分支,陽光回聚,莪受打擊,禰愛帶餘', 'errors': [['知', '支', 3, 1.0], ['陌', '陽', 5, 1.0], ['去', '擊', 13, 1.0]]}
{'index': 1, 'source': '余額還有100w', 'target': '餘額還有100w', 'errors': [['余', '餘', 0, 1]]}
{'index': 2, 'source': '放在陌光下', 'target': '放在陽光下', 'errors': [['陌', '陽', 2, 1.0]]}
{'index': 3, 'source': '真麻煩你了。希望你們好好的跳舞', 'target': '真麻煩你了。希望你們好好地跳舞', 'errors': [['的', '地', 12, 0.8873]]}
{'index': 4, 'source': '少先隊員因該爲老人讓坐', 'target': '少先隊員應該爲老人讓坐', 'errors': [['因', '應', 4, 0.9933]]}
{'index': 5, 'source': '機七學習是人工智能領遇最能體現智能的一個分知', 'target': '機器學習是人工智能領域最能體現智能的一個分支', 'errors': [['七', '器', 1, 0.9999], ['遇', '域', 10, 1.0], ['知', '支', 21, 1.0]]}
{'index': 6, 'source': '一只小魚船浮在平淨的河面上', 'target': '一隻小魚船浮在平靜的河面上', 'errors': [['只', '隻', 1, 1], ['淨', '靜', 8, 0.9849]]}
{'index': 7, 'source': '這一條次,我選擇了一條與往常不同的路線。', 'target': '這一條次,我選擇了一條與往常不同的路線。', 'errors': []}
{'index': 8, 'source': '春節發貨部', 'target': '春節發貨不', 'errors': [['部', '不', 4, 0.3544]]}
################################################################################################################################
"""

训练

CSC任务

目录地址

  • macbert4mdcspell: macro_correct/pytorch_user_models/csc/macbert4mdcspell/train_yield.py
  • macbert4csc: macro_correct/pytorch_user_models/csc/macbert4csc/train_yield.py
  • relm: macro_correct/pytorch_user_models/csc/relm/train_yield.py

数据准备

  • espell: list的json文件结构, 带"original_text"和"correct_text"就好, 参考macro_correct/corpus/text_correction/espell
[
    {
        "original_text": "遇到逆竟时,我们必须勇于面对,而且要愈挫愈勇,这样我们才能朝著成功之路前进。",
        "correct_text": "遇到逆境时,我们必须勇于面对,而且要愈挫愈勇,这样我们才能朝著成功之路前进。",
    }
]
  • sighan: list的json文件结构, 带"source"和"target"就好, 参考macro_correct/corpus/text_correction/sighan
[
    {
        "source": "若被告人正在劳动教养,则可以通过劳动教养单位转交",
        "target": "若被告人正在劳动教养,则可以通过劳动教养单位转交",
    }
]

配置-训练-验证-预测

配置

配置好数据地址和超参, 参考macro_correct/pytorch_user_models/csc/macbert4mdcspell/config.py

训练-验证-预测

训练
nohup python train_yield.py > tc.train_yield.py.log 2>&1 &
tail -n 1000  -f tc.train_yield.py.log
验证
python eval_std.py
预测
python predict.py

PUNCT任务

目录地址

  • PUNCT: macro_correct/pytorch_sequencelabeling/slRun.py

数据准备

  • SPAN格式: NER任务, 默认用span格式(jsonl), 参考macro_correct/corpus/sequence_labeling/chinese_symbol的chinese_symbol.dev.span文件
{'label': [{'type': '0', 'ent': '下', 'pos': [7, 7]}, {'type': '1', 'ent': '林', 'pos': [14, 14]}], 'text': '#桂林山水甲天下阳朔山水甲桂林'}
{'label': [{'type': '11', 'ent': 'o', 'pos': [5, 5]}, {'type': '0', 'ent': 't', 'pos': [12, 12]}, {'type': '1', 'ent': '包', 'pos': [19, 19]}], 'text': '#macrocorrect文本纠错工具包'}
  • CONLL格式: 生成SPAN格式后, 用macro_correct/tet/corpus/pos_to_conll.py转换一下就好
神 O
秘 O
宝 O
藏 B-1
在 O
旅 O
途 O
中 B-0
他 O

配置-训练-验证-预测

配置

配置好数据地址和超参, 参考macro_correct/pytorch_user_models/csc/macbert4mdcspell/config.py

训练-验证-预测

训练
nohup python train_yield.py > tc.train_yield.py.log 2>&1 &
tail -n 1000  -f tc.train_yield.py.log
验证
python eval_std.py
预测
python predict.py

测评

说明

3.1 测评数据

1.gen_de3.json(5545): '的地得'纠错, 由人民日报/学习强国/chinese-poetry等高质量数据人工生成;
2.lemon_v2.tet.json(1053): relm论文提出的数据, 多领域拼写纠错数据集(7个领域), ; 包括game(GAM), encyclopedia (ENC), contract (COT), medical care(MEC), car (CAR), novel (NOV), and news (NEW)等领域;
3.acc_rmrb.tet.json(4636): 来自NER-199801(人民日报高质量语料);
4.acc_xxqg.tet.json(5000): 来自学习强国网站的高质量语料;
5.gen_passage.tet.json(10000): 源数据为qwen生成的好词好句, 由几乎所有的开源数据汇总的混淆词典生成;
6.textproof.tet.json(1447): NLP竞赛数据, TextProofreadingCompetition;
7.gen_xxqg.tet.json(5000): 源数据为学习强国网站的高质量语料, 由几乎所有的开源数据汇总的混淆词典生成;
8.faspell.dev.json(1000): 视频字幕通过OCR后获取的数据集; 来自爱奇艺的论文faspell;
9.lomo_tet.json(5000): 主要为音似中文拼写纠错数据集; 来自腾讯; 人工标注的数据集CSCD-NS;
10.mcsc_tet.5000.json(5000): 医学拼写纠错; 来自腾讯医典APP的真实历史日志; 注意论文说该数据集只关注医学实体的纠错, 常用字等的纠错并不关注;
11.ecspell.dev.json(1500): 来自ECSpell论文, 包括(law/med/gov)等三个领域;
12.sighan2013.dev.json(1000): 来自sighan13会议;
13.sighan2014.dev.json(1062): 来自sighan14会议;
14.sighan2015.dev.json(1100): 来自sighan15会议;

3.2 测评再说明

1.数据预处理, 测评数据都经过 全角转半角,繁简转化,标点符号标准化等操作;
2.指标带common的极为宽松指标, 同开源项目pycorrector的评估指标;
3.指标带strict的极为严格指标, 同开源项目[wangwang110/CSC](https://github.com/wangwang110/CSC);
4.macbert4mdcspell_v1/v2模型为训练使用mdcspell架构+bert的mlm-loss, 但是推理的时候只用bert-mlm;
5.acc_rmrb/acc_xxqg数据集没有错误, 用于评估模型的误纠率(过度纠错);
6.qwen25_1-5b_pycorrector的模型为shibing624/chinese-text-correction-1.5b, 其训练数据包括了lemon_v2/mcsc_tet/ecspell的验证集和测试集, 其他的bert类模型的训练不包括验证集和测试集;
7.xxx_rethink1是指再多预测1次, xxx_thr30表示阈值为0.3;

3.3 测评结果

3.3.1 F1(common_cor_f1)

model/common_cor_f1 avg gen_de3 lemon_v2 gen_passage text_proof gen_xxqg faspell lomo_tet mcsc_tet ecspell sighan2013 sighan2014 sighan2015
macbert4csc_pycorrector 45.8 42.44 42.89 31.49 46.31 26.06 32.7 44.83 27.93 55.51 70.89 61.72 66.81
qwen25_1-5b_pycorrector 45.11 27.29 89.48 14.61 83.9 13.84 18.2 36.71 96.29 88.2 36.41 15.64 20.73
relm_v1 54.12 89.86 51.79 38.4 63.74 30.6 31.95 49.82 64.7 73.57 66.4 39.87 48.8
bert4csc_v1 62.28 93.73 61.99 44.79 68.0 35.03 48.28 61.8 64.41 79.11 77.66 51.01 61.54
macbert4csc_v1 68.55 96.67 65.63 48.4 75.65 38.43 51.76 70.11 80.63 85.55 81.38 57.63 70.7
macbert4csc_v2 68.6 96.74 66.02 48.26 75.78 38.84 51.91 70.17 80.71 85.61 80.97 58.22 69.95
macbert4mdcspell_v1 71.1 96.42 70.06 52.55 79.61 43.37 53.85 70.9 82.38 87.46 84.2 61.08 71.32
macbert4mdcspell_v2 71.23 96.42 65.8 52.35 75.94 43.5 53.82 72.66 82.28 88.69 82.51 65.59 75.26
macbert4mdcspell_v1_rethink2 69.64 92.4 67.99 57.69 77.49 50.38 53.96 69.35 84.65 88.26 70.96 56.05 66.54
macbert4mdcspell_v2_rethink2 72.54 95.59 65.54 58.01 75.86 49.67 55.56 72.78 84.65 90.78 80.93 65.74 75.39

3.3.2 acc(common_cor_acc)

model/common_cor_acc avg gen_de3 lemon_v2 gen_passage text_proof gen_xxqg faspell lomo_tet mcsc_tet ecspell sighan2013 sighan2014 sighan2015
macbert4csc_pycorrector 48.26 26.96 28.68 34.16 55.29 28.38 22.2 60.96 57.16 67.73 55.9 68.93 72.73
qwen25_1-5b_pycorrector 46.09 15.82 81.29 22.96 82.17 19.04 12.8 50.2 96.4 89.13 22.8 27.87 32.55
relm_v1 51.9 81.71 36.18 37.04 63.99 29.34 22.9 51.98 74.1 76.0 50.3 45.76 53.45
bert4csc_v1 60.76 88.21 45.96 43.13 68.97 35.0 34.0 65.86 73.26 81.8 64.5 61.11 67.27
macbert4csc_v1 65.34 93.56 49.76 44.98 74.64 36.1 37.0 73.0 83.6 86.87 69.2 62.62 72.73
macbert4csc_v2 65.22 93.69 50.14 44.92 74.64 36.26 37.0 72.72 83.66 86.93 68.5 62.43 71.73
macbert4mdcspell_v1 67.15 93.09 54.8 47.71 78.09 39.52 38.8 71.92 84.78 88.27 73.2 63.28 72.36
macbert4mdcspell_v2 68.31 93.09 50.05 48.72 75.74 40.52 38.9 76.9 84.8 89.73 71.0 71.94 78.36
macbert4mdcspell_v1_rethink2 65.04 85.88 52.42 51.69 76.23 44.52 38.9 70.78 86.48 88.93 55.8 59.98 68.91
macbert4mdcspell_v2_rethink2 69.14 91.56 49.76 53.01 75.67 44.84 40.5 76.98 86.56 91.47 68.8 72.03 78.45

3.3.3 acc(acc_true, thr=0.75)

model/acc avg acc_rmrb acc_xxqg
macbert4csc_pycorrector 99.24 99.22 99.26
qwen25_1-5b_pycorrector 82.0 77.14 86.86
relm_v1 93.47 90.21 96.74
bert4csc_v1 98.71 98.36 99.06
macbert4csc_v1 97.72 96.72 98.72
macbert4csc_v2 97.89 96.98 98.8
macbert4mdcspell_v1 97.75 96.51 98.98
macbert4mdcspell_v2 99.54 99.22 99.86
macbert4mdcspell_v1_rethink2 92.78 88.31 97.24
macbert4mdcspell_v2_rethink2 98.15 96.72 99.58

3.3.4 结论(Conclusion)

1.macbert4csc_v1/macbert4csc_v2/macbert4mdcspell_v1等模型使用多种领域数据训练, 比较均衡, 也适合作为第一步的预训练模型, 可用于专有领域数据的继续微调;
2.比较macbert4csc_pycorrector/bertbase4csc_v1/macbert4csc_v2/macbert4mdcspell_v1, 观察表2.3, 可以发现训练数据越多, 准确率提升的同时, 误纠率也会稍微高一些;
3.MFT(Mask-Correct)依旧有效, 不过对于数据量足够的情形提升不明显, 可能也是误纠率升高的一个重要原因;
4.训练数据中也存在文言文数据, 训练好的模型也支持文言文纠错;
5.训练好的模型对"地得的"等高频错误具有较高的识别率和纠错率;
6.macbert4mdcspell_v2的MFT只70%的时间no-error-mask(0.15), 15%的时间target-to-target, 15%的时间不mask;
7.对于macbert4mdcspell, rethink能更好处理存在多个错误的句子(适合错误多的情况gen_passage/gen_xxqg这些错误数多的数据集都提升了4%, 多预测3次效果最佳, 考虑性能问题可以多预测1次), 同时rethink对过度纠错的影响不大;
8.对于macbert4mdcspell, thr阈值设置能更好地避免过度纠错, 除非是特别需要精确率的情况, 否则不建议超过0.5(0.5往后对效果的影响就比较大了); 

日志

1. v20240129, 完成csc_punct模块;
2. v20241001, 完成csc_token模块;
3. v20250117, 完成csc_eval模块;
4. v20250501, 完成macbert4mdcspell_v2

参考

This library is inspired by and references following frameworks and papers.

论文

中文拼写纠错(CSC, Chinese Spelling Correction)

Cite

For citing this work, you can refer to the present GitHub project. For example, with BibTeX:

@software{macro-correct,
    url = {https://github.com/yongzhuo/macro-correct},
    author = {Yongzhuo Mo},
    title = {macro-correct},
    year = {2025}

About

文本纠错工具包(Text Correct, CSC), 支持中文文本纠错(拼写纠错/标点符号纠错)(CSC, Chinese Spelling Correct / Check; Punct), CSC支持各领域数据的中文文本纠错(包括古文), 模型在大规模、各领域的、现代/当代语料上训练而得, 泛化性强.重点是错别字检测纠正.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •  

Languages