天天看點

貝葉斯推斷及其網際網路應用(三):拼寫檢查

使用google的時候,如果你拼錯一個單詞,它會提醒你正确的拼法。

比如,你不小心輸入了seperate。

貝葉斯推斷及其網際網路應用(三):拼寫檢查
貝葉斯推斷及其網際網路應用(三):拼寫檢查

下面我們就來看看,怎麼利用貝葉斯推斷,實作"拼寫檢查"。其實很簡單,一小段代碼就夠了。

一、原理

使用者輸入了一個單詞。這時分成兩種情況:拼寫正确,或者拼寫不正确。我們把拼寫正确的情況記做c(代表correct),拼寫錯誤的情況記做w(代表wrong)。

所謂"拼寫檢查",就是在發生w的情況下,試圖推斷出c。從機率論的角度看,就是已知w,然後在若幹個備選方案中,找出可能性最大的那個c,也就是求下面這個式子的最大值。

  p(c|w)

根據貝葉斯定理:

  p(c|w) = p(w|c) * p(c) / p(w)

對于所有備選的c來說,對應的都是同一個w,是以它們的p(w)是相同的,是以我們求的其實是

  p(w|c) * p(c)

的最大值。

p(c)的含義是,某個正确的詞的出現"機率",它可以用"頻率"代替。如果我們有一個足夠大的文本庫,那麼這個文本庫中每個單詞的出現頻率,就相當于它的發生機率。某個詞的出現頻率越高,p(c)就越大。

p(w|c)的含義是,在試圖拼寫c的情況下,出現拼寫錯誤w的機率。這需要統計資料的支援,但是為了簡化問題,我們假設兩個單詞在字形上越接近,就有越可能拼錯,p(w|c)就越大。舉例來說,相差一個字母的拼法,就比相差兩個字母的拼法,發生機率更高。你想拼寫單詞hello,那麼錯誤拼成hallo(相差一個字母)的可能性,就比拼成haallo高(相差兩個字母)。

是以,我們隻要找到與輸入單詞在字形上最相近的那些詞,再在其中挑出出現頻率最高的一個,就能實作 p(w|c) * p(c) 的最大值。

二、算法

最簡單的算法,隻需要四步就夠了。

第一步,建立一個足夠大的文本庫。

第二步,取出文本庫的每一個單詞,統計它們的出現頻率。

第三步,根據使用者輸入的單詞,得到其所有可能的拼寫相近的形式。

所謂"拼寫相近",指的是兩個單詞之間的"編輯距離"(edit distance)不超過2。也就是說,兩個詞隻相差1到2個字母,隻通過----删除、交換、更改和插入----這四種操作中的一種,就可以讓一個詞變成另一個詞。

第四步,比較所有拼寫相近的詞在文本庫的出現頻率。頻率最高的那個詞,就是正确的拼法。

根據peter norvig的驗證,這種算法的精确度大約為60%-70%(10個拼寫錯誤能夠檢查出6個。)雖然不令人滿意,但是能夠接受。畢竟它足夠簡單,計算速度極快。(本文的最後部分,将詳細讨論這種算法的缺陷在哪裡。)

三、代碼

我們使用python語言,實作上一節的算法。

第二步,加載python的正則語言子產品(re)和collections子產品,後面要用到。

  import re, collections

第三步,定義words()函數,用來取出文本庫的每一個詞。

  def words(text): return re.findall('[a-z]+', text.lower())

lower()将所有詞都轉成小寫,避免因為大小寫不同,而被算作兩個詞。

第四步,定義一個train()函數,用來建立一個"字典"結構。文本庫的每一個詞,都是這個"字典"的鍵;它們所對應的值,就是這個詞在文本庫的出現頻率。

  def train(features):     model = collections.defaultdict(lambda: 1)     for f in features:       model[f] += 1     return model

collections.defaultdict(lambda: 1)的意思是,每一個詞的預設出現頻率為1。這是針對那些沒有出現在文本庫的詞。如果一個詞沒有在文本庫出現,我們并不能認定它就是一個不存在的詞,是以将每個詞出現的預設頻率設為1。以後每出現一次,頻率就增加1。

第五步,使用words()和train()函數,生成上一步的"詞頻字典",放入變量nwords。

  nwords = train(words(file('big.txt').read()))

第六步,定義edits1()函數,用來生成所有與輸入參數word的"編輯距離"為1的詞。

  alphabet = 'abcdefghijklmnopqrstuvwxyz'   def edits1(word):     splits = [(word[:i], word[i:]) for i in range(len(word) + 1)]     deletes = [a + b[1:] for a, b in splits if b]     transposes = [a + b[1] + b[0] + b[2:] for a, b in splits if len(b)>1]     replaces = [a + c + b[1:] for a, b in splits for c in alphabet if b]     inserts = [a + c + b for a, b in splits for c in alphabet]     return set(deletes + transposes + replaces + inserts)

edit1()函數中的幾個變量的含義如下:

  (1)splits:将word依次按照每一位分割成前後兩半。比如,'abc'會被分割成 [('', 'abc'), ('a', 'bc'), ('ab', 'c'), ('abc', '')] 。   (2)beletes:依次删除word的每一位後、所形成的所有新詞。比如,'abc'對應的deletes就是 ['bc', 'ac', 'ab'] 。   (3)transposes:依次交換word的鄰近兩位,所形成的所有新詞。比如,'abc'對應的transposes就是 ['bac', 'acb'] 。   (4)replaces:将word的每一位依次替換成其他25個字母,所形成的所有新詞。比如,'abc'對應的replaces就是 ['abc', 'bbc', 'cbc', ... , 'abx', ' aby', 'abz' ] ,一共包含78個詞(26 × 3)。   (5)inserts:在word的鄰近兩位之間依次插入一個字母,所形成的所有新詞。比如,'abc' 對應的inserts就是['aabc', 'babc', 'cabc', ..., 'abcx', 'abcy', 'abcz'],一共包含104個詞(26 × 4)。

最後,edit1()傳回deletes、transposes、replaces、inserts的合集,這就是與word"編輯距離"等于1的所有詞。對于一個n位的詞,會傳回54n+25個詞。

第七步,定義edit2()函數,用來生成所有與word的"編輯距離"為2的詞語。

  def edits2(word):     return set(e2 for e1 in edits1(word) for e2 in edits1(e1))

但是這樣的話,會傳回一個 (54n+25) * (54n+25) 的數組,實在是太大了。是以,我們将edit2()改為known_edits2()函數,将傳回的詞限定為在文本庫中出現過的詞。

  def known_edits2(word):     return set(e2 for e1 in edits1(word) for e2 in edits1(e1) if e2 in nwords)

第八步,定義correct()函數,用來從所有備選的詞中,選出使用者最可能想要拼寫的詞。

  def known(words): return set(w for w in words if w in nwords)   def correct(word):     candidates = known([word]) or known(edits1(word)) or known_edits2(word) or [word]     return max(candidates, key=nwords.get)

我們采用的規則為:

  (1)如果word是文本庫現有的詞,說明該詞拼寫正确,直接傳回這個詞;   (2)如果word不是現有的詞,則傳回"編輯距離"為1的詞之中,在文本庫出現頻率最高的那個詞;   (3)如果"編輯距離"為1的詞,都不是文本庫現有的詞,則傳回"編輯距離"為2的詞中,出現頻率最高的那個詞;   (4)如果上述三條規則,都無法得到結果,則直接傳回word。

使用方法如下:

  >>> correct('speling')   'spelling'   >>> correct('korrecter')   'corrector'

四、缺陷

我們使用的這種算法,有一些缺陷,如果投入生産環境,必須在這些方面加入改進。

(1)文本庫必須有很高的精确性,不能包含拼寫錯誤的詞。

如果使用者輸入一個錯誤的拼法,文本庫恰好包含了這種拼法,它就會被當成正确的拼法。

(2)對于不包含在文本庫中的新詞,沒有提出解決辦法。

如果使用者輸入一個新詞,這個詞不在文本庫之中,就會被當作錯誤的拼寫進行糾正。

(3)程式傳回的是"編輯距離"為1的詞,但某些情況下,正确的詞的"編輯距離"為2。

比如,使用者輸入reciet,會被糾正為recite(編輯距離為1),但使用者真正想要輸入的詞是receipt(編輯距離為2)。也就是說,"編輯距離"越短越正确的規則,并非所有情況下都成立。

(4)有些常見拼寫錯誤的"編輯距離"大于2。

這樣的錯誤,程式無法發現。下面就是一些例子,每一行前面那個詞是正确的拼法,後面那個則是常見的錯誤拼法。

purple perpul curtains courtens minutes muinets successful sucssuful inefficient ineffiect availability avaiblity dissension desention unnecessarily unessasarily necessary nessasary unnecessary unessessay night nite assessing accesing necessitates nessisitates

(5)使用者輸入的詞的拼寫正确,但是其實想輸入的是另一個詞。

比如,使用者輸入是where,這個詞拼寫正确,程式不會糾正。但是,使用者真正想輸入的其實是were,不小心多打了一個h。

(6)程式傳回的是出現頻率最高的詞,但使用者真正想輸入的是另一個詞。

比如,使用者輸入ther,程式會傳回the,因為它的出現頻率最高。但是,使用者真正想輸入的其實是their,少打了一個i。也就是說,出現頻率最高的詞,不一定就是使用者想輸入的詞。

(7)某些詞有不同的拼法,程式無法辨識。

比如,英國英語和美國英語的拼法不一緻。英國使用者輸入'humur',應該被糾正為'humour';美國使用者輸入'humur',應該被糾正為'humor'。但是,我們的程式會統一糾正為'humor'。

(完)

繼續閱讀