【NLP實(shí)戰(zhàn)】文本糾錯: pycorrector

Pycorrector實(shí)現(xiàn)文本糾錯

代碼: https://github.com/shibing624/pycorrector

基于規(guī)則

基于Kenlm語言模型的錯字糾正主要分為2個步驟:

  1. 錯誤糾正: 用候選集中的字詞替換錯誤位置的字詞后削樊,通過語言模型計算混淆度绽族,排序得到最優(yōu)的糾正詞
  2. 錯誤檢測: 找出句子中疑似錯誤的字詞和他們的位置

下面詳細(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é)/改動

  1. 原始代碼中jieba分詞加載的詞表是word_freq.txt,這里去掉這個邏輯激捏,直接用jieba本身的詞表设塔,不然會有很多錯誤分詞,因?yàn)橛脕碛?xùn)練的線上語料不是完全正確的远舅,所以word_freq表中會有一些不太合理的詞
  2. 專名錯誤檢測中的閾值原本是固定的闰蛔,這里給一些專名詞特定的閾值,沒有特別設(shè)置就用默認(rèn)閾值 (這里設(shè)的0.9)图柏;原始代碼中給ngram分詞時是以"char"為單位序六,這里改成以"word"為單位,因?yàn)橐?char"為單位的ngram會造成很多誤糾
  3. 用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)練語料余佃,并處理成和代碼中相同的格式。

同音字替換工具: https://github.com/dongrixinyu/JioNLP/wiki/%E6%95%B0%E6%8D%AE%E5%A2%9E%E5%BC%BA-%E8%AF%B4%E6%98%8E%E6%96%87%E6%A1%A3#%E5%90%8C%E9%9F%B3%E8%AF%8D%E6%9B%BF%E6%8D%A2

注意:
1)使用工具生成錯誤樣本后跨算,有可能輸入和輸出的句長不一致爆土,要剔除這些樣本,只保留前后句長一致的樣本诸蚕,否則模型訓(xùn)練時會報錯
2)使用工具生成的錯誤樣本步势,在模型訓(xùn)練語料中為original_text,而用來生成錯誤樣本的原始樣本在模型訓(xùn)練語料中為correct_text背犯,不要弄反了

生成訓(xùn)練語料放到對應(yīng)文件夾下就可以開始訓(xùn)練了坏瘩,其他步驟都可參考上面的鏈接。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末漠魏,一起剝皮案震驚了整個濱河市倔矾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌柱锹,老刑警劉巖哪自,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異禁熏,居然都是意外死亡壤巷,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進(jìn)店門瞧毙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胧华,“玉大人,你說我怎么就攤上這事宙彪〕湃幔” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵您访,是天一觀的道長铅忿。 經(jīng)常有香客問我,道長灵汪,這世上最難降的妖魔是什么檀训? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮享言,結(jié)果婚禮上峻凫,老公的妹妹穿的比我還像新娘。我一直安慰自己览露,他們只是感情好荧琼,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般命锄。 火紅的嫁衣襯著肌膚如雪堰乔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天脐恩,我揣著相機(jī)與錄音镐侯,去河邊找鬼。 笑死驶冒,一個胖子當(dāng)著我的面吹牛苟翻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播骗污,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼崇猫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了需忿?” 一聲冷哼從身側(cè)響起邓尤,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎贴谎,沒想到半個月后汞扎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡擅这,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年澈魄,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片仲翎。...
    茶點(diǎn)故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡痹扇,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出溯香,到底是詐尸還是另有隱情鲫构,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布玫坛,位于F島的核電站结笨,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏湿镀。R本人自食惡果不足惜炕吸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望勉痴。 院中可真熱鬧赫模,春花似錦、人聲如沸蒸矛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至斩祭,卻和暖如春劣像,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背停忿。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蚊伞,地道東北人席赂。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像时迫,于是被迫代替她去往敵國和親颅停。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容