機器學習(二)——貝葉斯算法

數學原理: 貝葉斯定理

 


貝葉斯定理由英國數學家貝葉斯 ( Thomas Bayes 1702-1761 ) 發展,用來描述兩個條件概率之間的關係。 
所謂”拼寫檢查”,就是在發生輸入錯誤B的情況下,試圖推斷出A。從概率論的角度看,就是已知輸入錯誤B,然後在若干個備選方案中,找出可能性最大的那個輸入正確A,也就是求下面這個式子的最大值。 
(比如lates應該被更正爲late或者latest?),我們用概率決定把哪一個作爲建議。我們從跟原始詞相關的所有可能的正確拼寫中找到可能性最大的那個拼寫建議。 
貝葉斯定理

事件A:要猜測輸入正確事件的概率 
事件B:現實已發生輸入錯誤事件的概率

對於每一個A來說,輸入錯誤B的概率相同,所以最大值可轉換爲

P(B|A)*P(A)

其中

P(A|B)是在拼寫錯誤的情況下推斷出拼寫正確的情況

P(A)的含義是某個正確的詞的出現”概率”,它可以用”頻率”代替。如果我們有一個足夠大的文本庫,那麼這個文本庫中每個單詞的出現頻率,就相當於它的發生概率。某個詞的出現頻率越高,P(A)就越大。

P(B|A)的含義是,在試圖拼寫正確A的情況下,出現拼寫錯誤B的概率。這需要統計數據的支持,但是爲了簡化問題,我們假設兩個單詞在組成上越接近,就有越可能拼錯,P(B|A)就越大。舉例來說,相差一個字母的拼法,就比相差兩個字母的拼法,發生概率更高。你想拼寫單詞hello,那麼錯誤拼成hallo(相差一個字母)的可能性,就比拼成haallo高(相差兩個字母)。

因此

我們只要找到與輸入單詞在組成上最相近的那些詞,再在其中挑出出現頻率最高的一個,就能實現 P(B|A) * P(A) 的最大值。

正因貝葉斯公式可用於事件發生概率的推測,因此它廣泛應用於計算機領域如:垃圾郵件的過濾,中文分詞,機器翻譯,拼寫檢查等等。

實例:拼寫檢查器

算法實現

  1. 建立一個足夠大的文本庫。讀取一個包含了一百萬個單詞的很大的文本文件big.txt。這個文件由Project Gutenberg中幾個公共領域的書串聯而成。
  2. 取出文本庫的每一個單詞,統計它們的出現頻率。
  3. 根據用戶輸入的單詞,得到其所有可能的拼寫相近的形式。 
    所謂”拼寫相近”,指的是兩個單詞之間的”編輯距離”(edit distance)不超過2。也就是說,兩個詞只相差1到2個字母,只通過—-刪除、交換、更改和插入—-這四種操作中的一種,就可以讓一個詞變成另一個詞。
  4. 比較所有拼寫相近的詞在文本庫的出現頻率。頻率最高的那個詞,就是正確的拼法。

代碼實現

1、 加載Python的正則語言模塊(re)和collections模塊

import re, collections
  •  

2、 定義words()函數,用來取出文本庫的每一個詞。 
正則表達式

def words(text):
    return re.findall('[a-z]+', text.lower())#將文本中的單詞分離開 返回一個列表
  •  

lower()將所有詞都轉成小寫,避免因爲大小寫不同,而被算作兩個詞,所以“the”和“The”一樣定義爲同一個單詞。 
3、定義一個train()函數,用來建立一個”字典”結構。文本庫的每一個詞,都是這個”字典”的鍵;它們所對應的值,就是這個詞在文本庫的出現頻率。

def train(features):
    model = collections.defaultdict(lambda: 1)
    for f in features:
        model[f] += 1
    return model
  • 如果有一個單詞在我們訓練的數據中沒有出現怎麼辦?最簡單的方法是把這些單詞看作出現了一次。這個處理叫做平滑處理,因爲我們將那些概率分佈可能爲0的地方平滑化,將他們設置爲最小的概率值1,以後每出現一次,頻率就增加1。 這個類像一個所有鍵的值都默認爲1的Python的字典(在C語言中,叫做哈希表)。 
  • collections.defaultdict 
    4、使用words()和train()函數,生成上一步的”詞頻字典”,放入變量NWORDS。
NWORDS = train(words(open('big.txt').read()))
  •  

5、定義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)
  •  
def edits1(word):
    n = len(word)
    return set([word[0:i]+word[i+1:] for i in range(n)] +                     # deletion
               [word[0:i]+word[i+1]+word[i]+word[i+2:] for i in range(n-1)] + # transposition
               [word[0:i]+c+word[i+1:] for i in range(n) for c in alphabet] + # replaces
               [word[0:i]+c+word[i:] for i in range(n+1) for c in alphabet])  # insertion
  •  

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

(1)splits:將word依次按照每一位分割成前後兩半。比如,'abc'會被分割成 [('', 'abc'), ('a', 
'bc'), ('ab', 'c'), ('abc', '')] 。 
(2)deletes:依次刪除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的單詞,將會有n種刪除,n-1種交換,26*n種修改,26*(n+1)種插入。會返回54n+25個詞。 
6、定義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)

7、定義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)

max(iterable, key, default) 求迭代器的最大值,其中iterable 爲迭代器,max會for i in … 遍歷一遍這個迭代器,然後將迭代器的每一個返回值當做參數傳給key=func 中的func(一般用lambda表達式定義) ,然後將func的執行結果傳給key,然後以key爲標準進行大小的判斷。

完整代碼

import re, collections
def words(text):return re.findall('[a-z]+', text.lower())#將文本中的單詞分離開 返回一個列表
def train(features):
    model = collections.defaultdict(lambda: 1)
    for f in features:
        model[f] += 1
    return model
NWORDS = train(words(open('big.txt').read()))
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)
def known_edits2(word):return set(e2 for e1 in edits1(word) for e2 in edits1(e1) if e2 in NWORDS)
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)文本庫必須有很高的精確性,不能包含拼寫錯誤的詞。 
如果用戶輸入一個錯誤的拼法,文本庫恰好包含了這種拼法,它就會被當成正確的拼法。 
(2)對於不包含在文本庫中的新詞,沒有提出解決辦法。 
如果用戶輸入一個新詞,這個詞不在文本庫之中,就會被當作正確的詞。 
(3)程序返回的是”編輯距離”爲1的詞,但某些情況下,正確的詞的”編輯距離”爲2。 
比如,用戶輸入reciet,會被糾正爲recite(編輯距離爲1),但用戶真正想要輸入的詞是receipt(編輯距離爲2)。也就是說,”編輯距離”越短越正確的規則,並非所有情況下都成立。 
(4)有些常見拼寫錯誤的”編輯距離”大於2。 
這樣的錯誤,程序無法發現。 
(5)用戶輸入的詞的拼寫正確,但是其實想輸入的是另一個詞。 
比如,用戶輸入是where,這個詞拼寫正確,程序不會糾正。但是,用戶真正想輸入的其實是were,不小心多打了一個h。 
(6)程序返回的是出現頻率最高的詞,但用戶真正想輸入的是另一個詞。 
比如,用戶輸入ther,程序會返回the,因爲它的出現頻率最高。但是,用戶真正想輸入的其實是their,少打了一個i。也就是說,出現頻率最高的詞,不一定就是用戶想輸入的詞。 
(7)某些詞有不同的拼法,程序無法辨別。 
比如,英國英語和美國英語的拼法不一致。英國用戶輸入’humur’,應該被糾正爲’humour’;美國用戶輸入’humur’,應該被糾正爲’humor’。但是,我們的程序會統一糾正爲’humor’。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章