樸素貝葉斯模型是機器學習中常常提到的概念。可是相信不少朋友都是知其然而不知其因此然。本文將盡可能使用易懂的方式介紹樸素貝葉斯模型原理,而且經過具體應用場景和源碼來幫助你們深刻理解這個概念。html
已知m個樣本 (x1,y1), ...... (xm,ym),x是特徵變量,y是對應的類別。要求得一個模型函數或者映射規則h,對於新的樣本 xt,可以儘可能準確的預測出 yt = h(xt)。java
咱們也能夠從機率的角度來考慮一下上述問題。假設y有m個類別,即 y1,......yn ∈ {C1,......Cm},對於樣本 xt,若是能計算出每一個類別的條件機率 P(C1|xt),......P(Cm|xt),那麼能夠認爲機率最大的那個類別就是 xt 所屬的類別。算法
h叫作分類器。分類算法的任務就是構造分類器h。機器學習
樸素貝葉斯(Naive Bayes)算法理論基礎是基於貝葉斯定理和條件獨立性假設的一種分類方法。樸素的意思是假設各個特徵之間相互條件獨立的。函數
貝葉斯分類器的基本方法:在統計資料的基礎上,依據找到的一些特徵屬性,來計算各個類別的機率,找到機率最大的類,從而實現分類。即貝葉斯分類器經過預測一個對象屬於某個類別的機率,再預測其類別。學習
找到一個已知分類的待分類項集合,這個集合叫作訓練樣本集。google
找出最大機率的那個類。spa
前文呼延灼的方法是:
求解問題(A): 呼延灼想知道本身是不是公明哥哥的心腹,用A來表明"你是大哥的心腹"。
已知結果(B): 大哥對你下拜。記做事件B。
推理結果 P(A|B): 想經過大哥對你下拜這個事件,來判斷大哥視你爲心腹的機率。.net
因而有:code
P(A|B) = P(B|A)P(A)/P(B) P(A|B) 也就是在B事件"大哥下拜"發生以後,對A事件"大哥視你爲心腹"機率的從新評估。
其實上述公式也隱含着:經過貝葉斯公式可以把人分紅兩類:大哥的心腹 / 普通下屬。
因此貝葉斯公式能夠用類別的思路從新解讀。
咱們把 B 理解成「具備某特徵」,把A理解成「類別標籤」。在最簡單的二分類問題(是與否斷定)下,咱們將A 理解成「屬於某類」的標籤。
P(類別|特徵)=P(特徵|類別)P(類別)/P(特徵)
以前只假設A只有B一個條件, 但在實際應用中,不多有一件事只受一個特徵影響的狀況,每每影響一件事的因素有多個。假設,影響 B 的因素有 n 個,分別是 b1,b2,…,bn。
則 P(A|B) 能夠寫爲:
P(A|b1,b2,...,bn) = P(A) P(b1,b2,...,bn|A) / P(b1,b2,...,bn)
由於假設從 b1 到 bn 這些特徵之間,在機率分佈上是條件獨立的,也就是說每一個特徵 bi與其餘特徵都不相關。因此能夠作以下轉換
P(b1,b2,...,bn|A) = P(b1|A)P(b2|A)...P(bn|A)
這個轉換其實就是 獨立變量的聯合分佈 = 各變量先驗分佈的乘積。只不過這裏是條件機率,可是由於變換先後都有一樣的條件 A,從樣本空間 A 的角度看,其實就是聯合分佈轉換成先驗分佈的乘積。
因此貝葉斯定理能夠作以下推導
P(A|b1,b2,...,bn) = P(A) [P(b1|A)P(b2|A)...P(bn|A)] / P(b1,b2,...,bn)
話說在前文[白話解析] 深刻淺出貝葉斯定理中,呼延灼經過貝葉斯定理,推出了本身不是公明哥哥心腹的結論。雖然有些氣悶,可是也好奇於貝葉斯定理的威力,因而他就決定用樸素貝葉斯模型對馬軍頭領和步軍頭領進行分類。
目前有一個極簡版樸素貝葉斯分類模型,能區分出兩個類(A1, A2),用來分類的特徵也有兩個(B1, B2)。
因此公式爲:
P(A|B1,B2) = P(A) [P(B1|A)P(B2|A)] / P(B1,B2)
這個就是分類器:
P(A|B1,B2) = P(A) [P(B1|A)P(B2|A)] / P(B1,B2) = P(A) [P(B1|A)P(B2|A)] / [P(B1) P(B2)] b1,b2表示特徵變量,Ai表示分類,p(Ai|b1,b2)表示在特徵爲b1,b2的狀況下分入類別Ai的機率
再重溫下樸素貝葉斯分類器,經過預測一個對象屬於某個類別的機率,再預測其類別。
找到一個已知分類的待分類項集合,這個集合叫作訓練樣本集。
找出最大機率的那個類。
樣本是10位馬軍頭領, 10位步兵頭領,如今設定以下:
已知有兩個分類: A1=馬軍頭領 A2=步軍頭領 兩個用來分類的特徵: F1=紋身 F2=鬧事 特徵能夠以下取值: f11 = 有紋身 f12 = 無紋身 f21 = 愛鬧事 f22 = 不愛鬧事
有了分類器模型和預製條件,下面就看如何推導出分類器模型參數了。
如下是根據已知數據統計得來。就是由實際數值訓練出來的 分類器參數。
假定 馬軍頭領中,2位有紋身,1位愛鬧事,步兵頭領中,7位有紋身,6位愛鬧事。因此獲得統計數據以下: P(有紋身) = P(f11) = (7+2)/20 = 9/20 = 0.45 P(無紋身) = P(f12) = 11/20 = 0.55 P(愛鬧事) = P(f21) = 7/20 = 0.35 P(不愛鬧事) = P(f22) = 13/20 = 0.65 P(F1=f11|A=A1) = P(有紋身|馬軍頭領) = 2/20 = 0.1 P(F1=f12|A=A1) = P(無紋身|馬軍頭領) = 8/20 = 0.4 P(F1=f11|A=A2) = P(有紋身|步兵頭領) = 7/20 = 0.35 P(F1=f12|A=A2) = P(無紋身|步兵頭領) = 3/20 = 0.15 P(F2=f21|A=A1) = P(愛鬧事|馬軍頭領) = 1/20 = 0.05 P(F2=f22|A=A1) = P(不愛鬧事|馬軍頭領) = 9/20 = 0.45 P(F2=f21|A=A2) = P(愛鬧事|步兵頭領) = 6/20 = 0.3 P(F2=f22|A=A2) = P(不愛鬧事|步兵頭領) = 4/20 = 0.2
這樣就訓練(統計)出來了一個分類器模型的參數。
能夠結合以前的分類器
P(A|F1,F2) = P(A) [P(F1|A)P(F2|A)] / P(F1,F2) = P(A) [P(F1|A)P(F2|A)] / [P(F1) P(F2)]
來對 "待分類數據" 作處理了。
若是有某位頭領 x:不紋身,不鬧事。進行鍼對兩個分類(馬軍頭領,步兵頭領)進行兩次運算,得出兩個數值。
(不紋身,不鬧事)是馬軍頭領的可能性
P(馬軍頭領|不紋身,不鬧事) = P(馬軍頭領) [P(無紋身|馬軍頭領) P(不鬧事|馬軍頭領) ] / [P(無紋身)P(不鬧事)] P(A=A1|x) = p(A=A1) P(F1=f12|A=A1)p(F2=f22|A=A1) / [P(f12)P(f22)] = 0.5 * 0.4 * 0.45 / [0.55 * 0.65] = 0.18 / [0.55 * 0.65] = 0.25
(不紋身,不鬧事)是步兵頭領的可能性
P(步兵頭領|不紋身,不鬧事) = P(步兵頭領) [P(無紋身|步兵頭領) P(不鬧事|步兵頭領) ] / [P(無紋身)P(不鬧事)] P(A=A2|x) = p(A=A2) P(F1=f12|A=A2)p(F2=f22|A=A2) / [P(f12)P(f22)] = 0.5 * 0.15 * 0.2 / [0.55 * 0.65] = 0.03 / [0.55 * 0.65] = 0.04
因此x是馬軍的可能性更大。
貝葉斯定理最大的好處是能夠用已知的頻率去計算未知的機率,咱們 簡單地將頻率當成了機率。
咱們能夠經過snowNLP的源碼來對樸素貝葉斯模型再進一步理解。
在bayes對象中,有兩個屬性d和total,d是一個數據字典,total存儲全部分類的總詞數,通過train方法訓練數據集後,d中存儲的是每一個分類標籤的數據key爲分類標籤,value是一個AddOneProb對象。
這裏的代碼就是簡單地將頻率當成了機率。訓練就是統計各個分類標籤(key)所對應的個數。
#訓練數據集 def train(self, data): #遍歷數據集,data 中既包含正樣本,也包含負樣本 for d in data: # data中是list # d[0]:分詞的結果,list # d[1]:標籤-->分類類別,正/負樣本的標記 c = d[1] #判斷數據字典中是否有當前的標籤 if c not in self.d: #若是沒有該標籤,加入標籤,值是一個AddOneProb對象 self.d[c] = AddOneProb() # 類的初始化 #d[0]是評論的分詞list,遍歷分詞list for word in d[0]: #調用AddOneProb中的add方法,添加單詞 self.d[c].add(word, 1) #計算總詞數,是正類和負類之和 self.total = sum(map(lambda x: self.d[x].getsum(), self.d.keys())) # # 取得全部的d中的sum之和 class AddOneProb(BaseProb): def __init__(self): self.d = {} self.total = 0.0 self.none = 1 #添加單詞 def add(self, key, value): #更新該類別下的單詞總數 self.total += value #若是單詞未出現過,需新建key if not self.exists(key): #將單詞加入對應標籤的數據字典中,value設爲1 self.d[key] = 1 #更新總詞數 self.total += 1 #若是單詞出現過,對該單詞的value值加1 self.d[key] += value
具體分類則是計算各個分類標籤的機率
#貝葉斯分類 def classify(self, x): tmp = {} #遍歷每一個分類標籤 for k in self.d: # 正類和負類 #獲取每一個分類標籤下的總詞數和全部標籤總詞數,求對數差至關於log(某標籤下的總詞數/全部標籤總詞數) tmp[k] = log(self.d[k].getsum()) - log(self.total) # 正類/負類的和的log函數-全部之和的log函數 for word in x: #獲取每一個單詞出現的頻率,log[(某標籤下的總詞數/全部標籤總詞數)*單詞出現頻率] tmp[k] += log(self.d[k].freq(word)) #計算機率 ret, prob = 0, 0 for k in self.d: now = 0 try: for otherk in self.d: now += exp(tmp[otherk]-tmp[k]) now = 1/now except OverflowError: now = 0 if now > prob: ret, prob = k, now return (ret, prob)
對於有兩個類別c1,c1的分類問題來講,其特徵爲w1,⋯,wn,特徵之間是相互獨立的,屬於類別c1的貝葉斯模型的基本過程爲:
P(c1∣w1,⋯,wn)=P(w1,⋯,wn∣c1)⋅P(c1) / P(w1,⋯,wn) 若是作句子分類,能夠認爲是出現了w1, w2, ..., wn這些詞以後,該句子被概括到c1類的機率。
其中:
P(w1,⋯,wn)=P(w1,⋯,wn∣c1)⋅P(c1) + P(w1,⋯,wn∣c2)⋅P(c2)
預測的過程使用到了上述的公式,即:
\[ P(c1∣w1,⋯,wn)=\frac{P(w1,⋯,wn∣c1)⋅P(c1)}{P(w1,⋯,wn∣c1)⋅P(c1)+P(w1,⋯,wn∣c2)⋅P(c2)} \]
對上述的公式簡化:
\[ P(c1∣w1,⋯,wn)=\frac{P(w1,⋯,wn∣c1)⋅P(c1)}{P(w1,⋯,wn∣c1)⋅P(c1)+P(w1,⋯,wn∣c2)⋅P(c2)} \]
\[ =\frac{1}{1+\frac{P(w1,⋯,wn∣c2)⋅P(c2)}{P(w1,⋯,wn∣c1)⋅P(c1)}} \]
\[ =\frac{1}{1+exp[log(\frac{P(w1,⋯,wn∣c2)⋅P(c2)}{P(w1,⋯,wn∣c1)⋅P(c1)})]} \]
\[ =\frac{1}{1+exp[log(P(w1,⋯,wn∣c2)⋅P(c2))−log(P(w1,⋯,wn∣c1)⋅P(c1))]} \]
其中,分母中的1能夠改寫爲:
\[ 1=exp[log(P(w1,⋯,wn∣c1)⋅P(c1))−log(P(w1,⋯,wn∣c1)⋅P(c1))] \]
根據上面的公式,針對c1, c2,咱們須要
\[ P(w1,⋯,wn∣c1)⋅P(c1) \]
\[ P(c1) \]
結合代碼
p(Ck) = k這類詞出現的機率 = self.d[k].getsum() / self.total p(w1|Ck) = w1這個詞在Ck類出現的機率 = self.d[k].freq(word) k = 1,2
\[ log(P(w1,⋯,wn∣c1)⋅P(c1)) \]
這個公式就是
\[ log(P(w1|c1)...p(wn∣c1)⋅P(c1)) \]
這個公式的結果就是:
\[ log(sum_{p(w1|C1)...p(wn|C1)}) + log(P(c1)) \]
最後展開:
\[ log(sum_{p(w1|C1)...p(wn|C1)}) + log(self.d[1].getsum()) - log(self.total)) \]
這個就是下面的 tmp[k]。其中,第一個for循環中的tmp[k]對應了公式中的log(P(ck)),第二個for循環中的tmp[k]對應了公式中的log(P(w1,⋯,wn∣ck)⋅P(ck))。兩個for循環的結果就是最終的tmp[k]。
def classify(self, x): tmp = {} for k in self.d: # 正類和負類 tmp[k] = log(self.d[k].getsum()) - log(self.total) # 正類/負類的和的log函數-全部之和的log函數 for word in x: tmp[k] += log(self.d[k].freq(word)) # 詞頻,不存在就爲0 ret, prob = 0, 0 for k in self.d: now = 0 try: for otherk in self.d: now += exp(tmp[otherk]-tmp[k]) # for循環中有一個結果是0, exp(0)就是1.就是上面分母中的1 now = 1/now except OverflowError: now = 0 if now > prob: ret, prob = k, now return (ret, prob)