Pycorrector實(shí)現(xiàn)文本糾錯
代碼: https://github.com/shibing624/pycorrector
基于規(guī)則
基于Kenlm語言模型的錯字糾正主要分為2個步驟:
- 錯誤糾正: 用候選集中的字詞替換錯誤位置的字詞后削樊,通過語言模型計算混淆度绽族,排序得到最優(yōu)的糾正詞
- 錯誤檢測: 找出句子中疑似錯誤的字詞和他們的位置
下面詳細(xì)介紹每個步驟勒魔。
錯誤檢測
主要檢測4個方面的錯誤:
自定義混淆集
用戶自定義的混淆集金句,若自定義的錯誤字詞在問句中磷醋,將錯誤字詞及其在問句中的位置加入疑似錯誤詞典南捂。
專名錯誤
對問句進(jìn)行切分(原始代碼以"字"為單位進(jìn)行切分)礼饱,再得到切分字詞的ngram (n=1,2,3,4)哨查,對ngram進(jìn)行去重和過濾;遍歷每個ngram和存儲的專名詞弦疮,計算它們之間詞的相似度夹攒,相似度取字形相似度和拼音相似度的最大值;若詞相似度大于閾值且ngram與專名詞不完全一致胁塞,將該ngram加入疑似錯誤詞典咏尝。
字形相似度
- 兩個詞完全一致,相似度=1啸罢;兩個詞長度不想等编检,相似度=0
- 兩個詞長度相等,遍歷每個位置的字扰才,計算兩個字的字形相似度允懂;若有一對字的字形相似度小于閾值,則認(rèn)為詞的字形相似度為0衩匣,若所有字對的相似度都大于閾值累驮,則取所有字相似度的平均作為詞的字形相似度
字的字形相似度: 一個字為中文字符另一個不是酣倾,相似度=0;兩個不是中文的相等字符谤专,相似度=1躁锡;對于兩個中文字符,得到它們的五筆輸入序列(如"料"=nphspnnnhs, "枓"=hspnnnhs)置侍,相似度計算基于這兩個序列的編輯距離映之。
score = 1.0 - edit_distance(char_stroke1, char_stroke2) # score: 字的字形相似度
##############################################################################
def edit_distance(str1, str2):
try:
# very fast
# http://stackoverflow.com/questions/14260126/how-python-levenshtein-ratio-is-computed
import Levenshtein
d = Levenshtein.distance(str1, str2) / float(max(len(str1), len(str2)))
except:
# https://docs.python.org/2/library/difflib.html
import difflib
d = 1.0 - difflib.SequenceMatcher(lambda x: x == " ", str1, str2).ratio()
return d
拼音相似度
- 兩個詞完全一致,相似度=1蜡坊;兩個詞長度不想等杠输,相似度=0
- 兩個詞長度相等,遍歷每個位置的字秕衙,判斷兩個字是否為臨近拼音蠢甲;若有一對字不為臨近拼音,則認(rèn)為詞的拼音相似度為0据忘,若所有字對的拼音都為臨近拼音鹦牛,則取所有字的拼音相似度的平均作為詞的拼音相似度。
判斷是否為臨近拼音:兩個字的拼音長度相等勇吊,看作臨近拼音曼追;定義一個拼音混淆集,若兩個字的拼音經(jīng)過混淆集替換后相等汉规,看作臨近拼音礼殊;其他情況都不為臨近拼音
confuse_dict = {
"l": "n", "zh": "z", "ch": "c", "sh": "s", "eng": "en", "ing": "in"}
for k, v in confuse_dict.items():
if char_pinyin1.replace(k, v) == char_pinyin2.replace(k, v):
return True
字的拼音相似度: 與<u>字的字形相似度</u>計算方法類似,只不過將五筆輸入序列替換為拼音
詞錯誤
對問句進(jìn)行分詞针史,過濾分詞結(jié)果中的空格晶伦、數(shù)字、字母啄枕、和其他不是中文的字符婚陪,若剩下的分詞結(jié)果有不在vocabulary中的(未登錄詞),則將這些詞加入疑似錯誤詞典射亏。
字錯誤
基于語言模型檢測疑似錯字近忙。遍歷ngram竭业,這里n=2,3智润。對于2gram,得到句子中的所有2gram和對應(yīng)的ngram分?jǐn)?shù) (由語言模型計算得出)未辆,形成ngram分?jǐn)?shù)列表窟绷,移動窗口補(bǔ)全得分(開頭插入首個分?jǐn)?shù),結(jié)尾加入最后一個分?jǐn)?shù)咐柜,3gram進(jìn)行兩遍這樣的操作)兼蜈,再對每2個ngram分?jǐn)?shù)取平均攘残,得到2gram的得分身辨;同理可得3gram的得分酬蹋,再對每個位置2gram和3gram的得分取平均赊窥,得到最終的句子得分需频。
sentence = "x_1x_2x_3 ... x_m" # 句長=m
# 對于2gram, n=2
two_grams = ["x_1x_2", "x_2x_3", ... , "x_m-1x_m"]
two_gram_scores = [s_1, s_2, s_3, ..., s_k] # k=m-n+1
# 移動窗口補(bǔ)全得分
two_gram_scores = [s_1, s_1, s_2, s_3, ..., s_k-1, s_k, s_k]
# 對每n=2個分?jǐn)?shù)取平均猪落,使得len(two_gram_avg_scores)=m
two_gram_avg_scores = [(s_1+s_1)/2, (s_1+s_2)/2, (s_2+s_3)/2, ..., (s_k-1+s_k)/2, (s_k+s_k)/2]
# 同理可得three_gram_avg_scores, 長度也等于m
# 取每個位置2gram和3gram得分的平均蚌铜,作為句子的最終得分; len(sent_scores)=m
sent_scores = (two_gram_avg_scores+three_gram_avg_scores)/2
得到句子得分后处面,根據(jù)平均絕對離差 (MAD median absolute deviation)得到句子中所有疑似錯字的index
median = np.median(sent_scores, axis=0) # get median of all scores
margin_median = np.abs(sent_scores - median).flatten() # deviation from the median
med_abs_deviation = np.median(margin_median) # median absolute deviation
# 源代碼中ratio=0.6745钳降,指正態(tài)分布表參數(shù)
y_score = ratio * margin_median / med_abs_deviation
sent_scores = sent_scores.flatten()
# 源代碼中threshold=2漾根,閾值越小泰涂,得到疑似錯別字越多
maybe_error_indices = np.where((y_score > threshold) & (sent_scores < median))
若疑似錯字是中文且不為停用詞,則將其加入疑似錯誤詞典辐怕。
錯誤糾正
對于自定義混淆集和專名錯誤逼蒙,對應(yīng)的正確字詞已經(jīng)確定;而對于字錯誤和詞錯誤寄疏,需要先找出所有可能正確的詞是牢。
字詞錯誤候選召回
- 相同拼音召回
先對錯誤的詞進(jìn)行置換和替換,舉例:
對于疑似錯誤詞="因該"赁还,得到置換后的詞"該因" (transpose)妖泄,并詞中的每個字符,分別替換 (replace) 為vocabulary中的任意字符艘策,{c}+"該"和因+{c}集合(c指vocabulary中的任意字符)蹈胡;再取集合中的常用詞作為候選。若候選詞中和錯誤詞的拼音相同朋蔫,將這些候選詞加入正確詞候選集合中罚渐。 - 自定義混淆召回
若錯誤的詞在自定義混淆集中,將對應(yīng)的正確詞加入到正確詞候選結(jié)合中驯妄。 - 相似字召回
- 若錯詞為單字荷并,得到該字的混淆字集合(與當(dāng)前字具有相同拼音和相同字形的集合),加入正確詞候選集合青扔。
- 若錯詞有兩個字源织,先得到第一個字的混淆字集合,與錯詞的第二個字組合微猖;再得到第二個字的混淆字集合谈息,與錯詞的第一個字組合;最后將第一個字的混淆字集合和第二個字的混淆字結(jié)合兩兩組合凛剥。將上面的所有組合詞加入正確詞候選集合侠仇。
- 若錯詞有多個字(大于兩個),得到第二個字的混淆字集合,替換掉原始錯字的第二個字逻炊;對錯詞中的第1到倒數(shù)第二個字做<u>相同拼音召回</u>互亮,得到一個相同拼音集合,并與原始錯詞中的最后一個字做拼接余素;對錯詞中的第2到最后一個字做<u>相同拼音召回</u>豹休,得到一個集合,并與原始錯詞中的第一個字做拼接桨吊。將上面的所有組合詞加入正確詞候選集合慕爬。
得到正確詞候選集合后,先過濾掉其中不為中文的詞屏积,再根據(jù)詞頻對這些詞排序医窿。
語言模型糾錯
用正確詞候選集合中的正確詞替換掉句子中的錯詞,并用語言模型計算新句子的混淆度 (perplexity)炊林,根據(jù)所有新句子的混淆度進(jìn)行排序姥卢。
統(tǒng)計語言模型訓(xùn)練&糾錯
kenlm的安裝與使用
參考: https://blog.csdn.net/qq_33424313/article/details/120726582
https://blog.csdn.net/jclian91/article/details/119085472
數(shù)據(jù)預(yù)處理
數(shù)據(jù)采用的是客服機(jī)器人買家聊天語料,主要的問句預(yù)處理操作包括: 去除空格渣聚、字母轉(zhuǎn)小寫独榴、去除表情符號、全角轉(zhuǎn)半角等奕枝。
訓(xùn)練模型
- 語料
以char為單位: 每個句子中的char以空格隔開 -> text_chars.txt
太 寬 大 了 都 沒 法 穿
好 吧
看錯 了
以word為單位: 先 (用jieba_fast) 對句子進(jìn)行分詞棺榔,每個詞以空格隔開 -> text_words.txt
太 寬大 了 都 沒法 穿
好 吧
看錯 了
同時,統(tǒng)計詞頻隘道、生成vocabulary詞匯表症歇。
- 訓(xùn)練
cd kenlm/build/
# 以char為單位
bin/lmplz -o 3 --verbose_header --text text_chars.txt --arpa ./results/text_chars.arps -S 4G
# 以word為單位
bin/lmplz -o 3 --verbose_header --text text_words.txt --arpa ./results/text_words.arps -S 4G
# 參數(shù)-o表示ngram的數(shù)量# 模型壓縮
# 以char為單位
bin/build_binary ./results/text_chars.arps ./results/text_chars.klm
# 以word為單位
bin/build_binary ./results/text_words.arps ./results/text_words.klm
規(guī)則糾錯
用自己訓(xùn)練好的模型替換掉原來的模型,vocabulary (common_char_set.txt 數(shù)量>=10的字)和word_freq.txt (詞頻>=10的詞) 也用我們自己的谭梗。proper_name.txt也是自定義的忘晤。
一些細(xì)節(jié)/改動
- 原始代碼中jieba分詞加載的詞表是word_freq.txt,這里去掉這個邏輯激捏,直接用jieba本身的詞表设塔,不然會有很多錯誤分詞,因?yàn)橛脕碛?xùn)練的線上語料不是完全正確的远舅,所以word_freq表中會有一些不太合理的詞
- 專名錯誤檢測中的閾值原本是固定的闰蛔,這里給一些專名詞特定的閾值,沒有特別設(shè)置就用默認(rèn)閾值 (這里設(shè)的0.9)图柏;原始代碼中給ngram分詞時是以"char"為單位序六,這里改成以"word"為單位,因?yàn)橐?char"為單位的ngram會造成很多誤糾
- 用text_chars.klm作為語言模型時爆办,計算perplexity時要以"char"為單位分詞难咕;而用text_words.klm作為語言模型時课梳,計算perplexity以"word"為單位進(jìn)行分詞
基于預(yù)訓(xùn)練語言模型: MacBERT4CSC
參考: https://github.com/shibing624/pycorrector/tree/master/pycorrector/macbert
訓(xùn)練語料
首先對數(shù)據(jù)進(jìn)行預(yù)處理(參考【基于規(guī)則】中的文本預(yù)處理)距辆,利用同音字替換生成錯字訓(xùn)練語料余佃,并處理成和代碼中相同的格式。
注意:
1)使用工具生成錯誤樣本后跨算,有可能輸入和輸出的句長不一致爆土,要剔除這些樣本,只保留前后句長一致的樣本诸蚕,否則模型訓(xùn)練時會報錯
2)使用工具生成的錯誤樣本步势,在模型訓(xùn)練語料中為original_text,而用來生成錯誤樣本的原始樣本在模型訓(xùn)練語料中為correct_text背犯,不要弄反了
生成訓(xùn)練語料放到對應(yīng)文件夾下就可以開始訓(xùn)練了坏瘩,其他步驟都可參考上面的鏈接。