【機器學習】算法原理詳細推導與實現(三):朴素貝葉斯
在上一篇算法中,邏輯回歸作為一種二分類的分類器,一般的回歸模型也是是判別模型,也就根據特征值來求結果概率。形式化表示為 \(p(y|x;\theta)\),在參數 \(\theta\) 確定的情況下,求解條件概率 \(p(y|x)\) 。通俗的解釋為:在給定特定特征后預測結果出現的概率。邏輯回歸的 \(y\) 是離散型,取值為 \(\{0,1\}\) 。這里將要介紹另一個分類算法 朴素貝葉斯,用以解決 \(x\) 是離散型的數據,這是判別模型,也是一個生成學習算法。
貝葉斯定理
定理推導
朴素貝葉斯是基於貝葉斯原理得到的。假設A和B為兩個不相互獨立的事件:
由上圖可以看出,在事件B已經發生的情況下,事件A發生的概率為事件A和事件B的交集除以事件B:
同理,在事件A已經發生的情況下,事件B發生的概率為事件A和事件B的交集除以事件A:
結合這兩個方程式,我們可以得到:
轉換后貝葉斯定理的公式定義為:
總的來說,貝葉斯定理可以總結為:
- 貝葉斯定理是將先驗概率做一次更新,得到后驗概率
- 朴素貝葉斯是輸入先驗概率,找到后驗概率,最后找到最大后驗概率,作為最終的分類結果,以及分類概率
實際問題
假設我們有兩個裝滿了餅干的碗,第一個碗里有10個巧克力餅干和30個普通餅干,第二個碗里兩種餅干都有20個。我們隨機挑一個碗,再在碗里隨機挑餅干。那么我們挑到的普通餅干來自一號碗的概率有多少?
解決方案
我們用 x1 代表一號碗,x2 代表二號碗。在x1中取到普通餅干的概率是 \(P(y|x1)=\frac{30}{10+30}\times\frac{1}{2}\),即抽到x1的概率是 \(\frac{1}{2}\) ,再在x1中抽到普通餅干的概率是 \(\frac{30}{10+30}=\frac{3}{4}\) ,同理可得 \(P(y|x2)=\frac{20}{20+20}\times\frac{1}{2}\) 。而問題中挑到挑到的普通餅干來自一號碗,已知挑到普通餅干,那么這個普通餅干來自一號碗的概率為:
根據 全概率公式 可知,其中拿到普通餅干的概率為: \(P(y)=P(y|x1)P(x1)+ P(y|x2)P(x2)\)
計算為:
朴素貝葉斯
例如如果想實現一個垃圾郵件分類器,用郵件作為輸入,確定郵件是否為垃圾郵件作為輸出,即 \(y\in \{0,1\}\),1表示是垃圾郵件0表示不是垃圾郵件,那么問題來了:給你一封郵件,怎么將郵件轉化為特征向量 \(\vec x\) 來表示這個郵件,以及怎樣區分這封郵件是否是垃圾郵件。
電子郵件僅僅是一段文本,就像是一個詞列表,因此利用詞來構建特征向量 \(\vec x\) 。首先遍歷詞典,並且得到一個詞典中詞的列表,假設詞典中此的列表如下所示:
詞 |
---|
word_1 |
word_2 |
word_3 |
... |
word_n |
假設郵件中存在字典中的詞,那么特征向量 \(\vec x\) 就就記為1,不存在就記為0。例如郵件中假設存在詞 \([word_1,word_2,...,word_n]\) ,則該郵件的特征向量 \(\vec x\) 表示為:
則 $x\in {0,1}^n $ ,假設詞典的長度為50000,那么 \(x\) 就可能有 \(2^{50000}\) 種向量。如果需要簡歷多項式用回歸模型進行建模分類,那么可能需要 \(2^{50000-1}\) 個參數 \(\theta\),很明顯看出需要的參數太多了,如果使用梯度下降那么收斂將會非常慢,因此利用朴素貝葉斯算法是一個很好的選擇。
算法推導
在朴素貝葉斯算法中,我們會對 \(p(x|y)\) 做一個假設,假設給定 \(y\) 的時候,其中\(x \in \{0,1\}^{50000}\), \(x_i\) 是條件獨立的,根據鏈式法則可以得到:
為了擬合出模型的參數,符號假設為,其中 \(y=1\) 是垃圾郵件, \(y=0\) 是正常郵件:
假設存在 \(m\) 個樣本,那么 \(y=1\) 和 \(y=0\) 的組合起來的似然估計表示為:
假設訓練樣本為 \(m\) 封郵件,\(x_j=1\)表示包含關鍵詞 \(j\),\(x_j=0\)表示不包含關鍵詞 \(j\),則垃圾郵件 \(y=1\) 里面包含的單詞 \(j\) 的極大似然估計為:
上述公式中,分子的含義是從 1到 \(m\) 遍歷垃圾郵件內容,對於標簽\(x_j=1\)的郵件計算其中詞語 \(j\) 出現的郵件數目之和。換句話說就是,遍歷所有垃圾郵件,統計這些垃圾郵件中包含詞語 \(j\) 的郵件數目。分母是對 \(i\) 從1到 \(m\) 求和,最后得到垃圾郵件的總數,即分母就是垃圾郵件的數目。
同理,正常郵件 \(y=0\) 里面包含的單詞 \(j\) 的極大似然估計為:
垃圾郵件 \(y=1\) 的極大似然估計為:
假設 \(m\) 封郵件里面的詞向量 \(\vec x\) 和標識 \(y\) 如下所示:
所以當垃圾郵件分類器開始訓練時,假設訓練垃圾郵件中包含某些詞 \(x\) 的概率 \(p(y=1|x)\) :
上式中分母是由 全概率 計算出詞 \(p(x)\) 的概率,即假設 \(word_3\) 在垃圾郵件中沒有出現,那么可以得到:
這就意味着如果 \(word_3\) 在垃圾郵件中沒有出現,那么概率為0,這樣子很明顯是不合理的,無論在數學上會導致無法繼續計算,還是從概率的角度來說直接排除 \(word_3\) 的可能性,其實最好的是 \(word_3\) 沒出現,但是還是會有概率,只是概率很低很低。舉個例子來說,如果某個人投籃球,連續5次都是沒投中,那么是不是投中的概率為0了,沒投中的概率是1了?
為了修正這個方法,這里最好是在分子分母加上一個極小數,防止數學上的無效計算和實際中的絕對不可能發生。
拉普拉斯平滑(Laplace smoothing)
繼續上面投籃球的例子,假設沒投中的概率記為 \(p(y=0)\) ,投中的概率記為 \(p(y=1)\) ,原來的概率為:
如果給每一項都平滑一個極小數1,代表投中籃球和沒投中籃球在事先都已經發生過一次了,那么上述式子變成:
那么同理可以知道, \(m\) 封郵件中,\(x_j=1\)表示包含關鍵詞 \(j\),\(x_j=0\)表示不包含關鍵詞 \(j\),則垃圾郵件 \(y=1\) 里面包含的單詞 \(j\) 的極大似然估計為:
正常郵件 \(y=0\) 里面包含的單詞 \(j\) 的極大似然估計為:
因為 \(y\) 只有兩種可能,我們這里也假設事先存在一封垃圾郵件和一封正常郵件,所以分子只需要+1,分母只需要+2。
總結
總的來說,朴素貝葉斯訓練階段為,給定一組已知的訓練樣本 \((\vec{x_1},y_1),(\vec{x_2},y_2),...,(\vec{x_n},y_n)\),可以得到垃圾郵件中,每一個單詞出現的概率:
而在 預測階段 ,給定一封郵件的單詞向量 \(\vec x\),求這個郵件是否是垃圾郵件,那么問題就轉化為:已知單詞\(\vec x\)已經發生,求解是否垃圾郵件p(y|x):
上述中,\(x\) 的取值只能是 \(x \in \{0,1 \}\),\(n\)的長度應該等於詞典中詞的數目。
實例
朴素貝葉斯是一個非常優秀的文本分類器,現在大部分垃圾郵件過濾的底層也是基於貝葉斯思想。作者收集了 25
封垃圾郵件, 25
封正常郵件,取 40
封郵件做訓練,10
封郵件做測試。
加載數據:
# 打開數據集,獲取郵件內容,
# spam為垃圾郵件,ham為正常郵件
def loadData():
# 選取一部分郵件作為測試集
testIndex = random.sample(range(1, 25), 5)
dict_word_temp = []
testList = []
trainList = []
testLabel = []
trainLabel = []
for i in range(1, 26):
wordListSpam = textParse(open('./email/spam/%d.txt' % i, 'r').read())
wordListHam = textParse(open('./email/ham/%d.txt' % i, 'r').read())
dict_word_temp = dict_word_temp + wordListSpam + wordListHam
if i in testIndex:
testList.append(wordListSpam)
# 用1表示垃圾郵件
testLabel.append(1)
testList.append(wordListHam)
# 用0表示正常郵件
testLabel.append(0)
else:
trainList.append(wordListSpam)
# 用1表示垃圾郵件
trainLabel.append(1)
trainList.append(wordListHam)
# 用0表示正常郵件
trainLabel.append(0)
# 去重得到詞字典
dict_word = list(set(dict_word_temp))
trainData = tranWordVec(dict_word, trainList)
testData = tranWordVec(dict_word, testList)
return trainData, trainLabel, testData, testLabel
訓練函數為:
# 訓練函數
def train(trainData, trainLabel):
trainMatrix = np.array(trainData)
# 計算訓練的文檔數目
trainNum = len(trainMatrix)
# 計算每篇文檔的詞條數
wordNum = len(trainMatrix[0])
# 文檔屬於垃圾郵件類的概率
ori_auc = sum(trainLabel) / float(trainNum)
# 拉普拉斯平滑
# 分子+1
HamNum = np.ones(wordNum)
SpamNum = np.ones(wordNum)
# 分母+2
HamDenom = 2.0
SpamDenom = 2.0
for i in range(trainNum):
# 統計屬於垃圾郵件的條件概率所需的數據,即P(x0|y=1),P(x1|y=1),P(x2|y=1)···
if trainLabel[i] == 1:
SpamNum += trainMatrix[i]
SpamDenom += sum(trainMatrix[i])
else:
# 統計屬於正常郵件的條件概率所需的數據,即P(x0|y=0),P(x1|y=0),P(x2|y=0)···
HamNum += trainMatrix[i]
HamDenom += sum(trainMatrix[i])
# 取對數,防止下溢出
SpamVec = np.log(SpamNum / SpamDenom)
HamVec = np.log(HamNum / HamDenom)
# 返回屬於正常郵件類的條件概率數組,屬於垃圾郵件類的條件概率數組,文檔屬於垃圾郵件類的概率
return HamVec, SpamVec, ori_auc
預測函數:
# 預測函數
def predict(testDataVec, HamVec, SpamVec, ori_auc):
predictToSpam = sum(testDataVec * SpamVec) + np.log(ori_auc)
predictToHam = sum(testDataVec * HamVec) + np.log(1.0 - ori_auc)
if predictToSpam > predictToHam:
return 1
else:
return 0
預測錯誤一個,錯誤率 10%
,正確率 90%
:
數據和代碼下載請關注公眾號【 機器學習和大數據挖掘 】,后台回復【 機器學習 】即可獲取