在上一篇算法中,邏輯迴歸做爲一種二分類的分類器,通常的迴歸模型也是是判別模型,也就根據特徵值來求結果機率。形式化表示爲 \(p(y|x;\theta)\),在參數 \(\theta\) 肯定的狀況下,求解條件機率 \(p(y|x)\) 。通俗的解釋爲:在給定特定特徵後預測結果出現的機率。邏輯迴歸的 \(y\) 是離散型,取值爲 \(\{0,1\}\) 。這裏將要介紹另外一個分類算法 樸素貝葉斯,用以解決 \(x\) 是離散型的數據,這是判別模型,也是一個生成學習算法。算法
樸素貝葉斯是基於貝葉斯原理獲得的。假設A和B爲兩個不相互獨立的事件:數組
由上圖能夠看出,在事件B已經發生的狀況下,事件A發生的機率爲事件A和事件B的交集除以事件B:app
\[ p(A|B)=\frac{p(A\bigcap B)}{p(B)} \]dom
同理,在事件A已經發生的狀況下,事件B發生的機率爲事件A和事件B的交集除以事件A:機器學習
\[ p(B|A)=\frac{p(B\bigcap A)}{p(A)} \]函數
結合這兩個方程式,咱們能夠獲得:學習
\[ p(A|B)p(B)=p(A\bigcap B)=p(B|A)p(A) \]測試
轉換後貝葉斯定理的公式定義爲:大數據
\[ P(A|B) = \frac{P(B|A)P(A)}{P(B)} \]spa
總的來講,貝葉斯定理能夠總結爲:
- 貝葉斯定理是將先驗機率作一次更新,獲得後驗機率
- 樸素貝葉斯是輸入先驗機率,找到後驗機率,最後找到最大後驗機率,做爲最終的分類結果,以及分類機率
假設咱們有兩個裝滿了餅乾的碗,第一個碗裏有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(x1|y) = \frac{P(y|x1)P(x1)}{P(y)} \]
根據 全機率公式 可知,其中拿到普通餅乾的機率爲: \(P(y)=P(y|x1)P(x1)+ P(y|x2)P(x2)\)
計算爲:
\[ \begin{split} P(x1|y)&=\frac{P(y|x1)P(x1)}{P(y)} \\ &=\frac{P(y|x1)P(x1)}{P(y|x1)P(x1)+ P(y|x2)P(x2)} \\ &= \frac{0.75\times0.5}{0.75\times0.5+0.5\times0.5} \\ &=0.6 \end{split} \]
例如若是想實現一個垃圾郵件分類器,用郵件做爲輸入,肯定郵件是否爲垃圾郵件做爲輸出,即 \(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= \begin{bmatrix} 1 \\ 1 \\ 0 \\ ... \\ 1 \end{bmatrix} \]
則 $x\in {0,1}^n $ ,假設詞典的長度爲50000,那麼 \(x\) 就可能有 \(2^{50000}\) 種向量。若是須要簡歷多項式用迴歸模型進行建模分類,那麼可能須要 \(2^{50000-1}\) 個參數 \(\theta\),很明顯看出須要的參數太多了,若是使用梯度降低那麼收斂將會很是慢,所以利用樸素貝葉斯算法是一個很好的選擇。
在樸素貝葉斯算法中,咱們會對 \(p(x|y)\) 作一個假設,假設給定 \(y\) 的時候,其中\(x \in \{0,1\}^{50000}\), \(x_i\) 是條件獨立的,根據鏈式法則能夠獲得:
\[ \begin{split} p(x_1,x_2,...,x_{50000}|y)&=p(x_1|y)p(x_1|y,x_1)p(x_2|y,x_1,x_2)...p(x_50000|y,x_1,x_2,...,x_{49999}) \\ &=p(x_1|y)p(x_2|y)...p(x_{50000}|y) \\ &=\prod_{i=1}^n p(x_i|y) \end{split} \]
爲了擬合出模型的參數,符號假設爲,其中 \(y=1\) 是垃圾郵件, \(y=0\) 是正常郵件:
\[ \phi_{i|y=1}=p(x_i=1|y=1) \phi_{i|y=0}=p(x_i=1|y=0) \phi_y=p(y=1) \]
假設存在 \(m\) 個樣本,那麼 \(y=1\) 和 \(y=0\) 的組合起來的似然估計表示爲:
\[ L(\phi_y,\phi_{i|y=0},\phi_{i|y=1})=\prod_{i=1}^m p(x^{(i)}|y^{(i)}) \]
假設訓練樣本爲 \(m\) 封郵件,\(x_j=1\)表示包含關鍵詞 \(j\),\(x_j=0\)表示不包含關鍵詞 \(j\),則垃圾郵件 \(y=1\) 裏面包含的單詞 \(j\) 的極大似然估計爲:
\[ \phi_{j|y=1}=\frac{\sum_{i=1}^ml\{x_j^{(i)}\bigcap y^{(i)}=1\}}{\sum_{i=1}^ml\{y^{(i)}=1\}} \]
上述公式中,分子的含義是從 1到 \(m\) 遍歷垃圾郵件內容,對於標籤\(x_j=1\)的郵件計算其中詞語 \(j\) 出現的郵件數目之和。換句話說就是,遍歷全部垃圾郵件,統計這些垃圾郵件中包含詞語 \(j\) 的郵件數目。分母是對 \(i\) 從1到 \(m\) 求和,最後獲得垃圾郵件的總數,即分母就是垃圾郵件的數目。
同理,正常郵件 \(y=0\) 裏面包含的單詞 \(j\) 的極大似然估計爲:
\[ \phi_{j|y=0}=\frac{\sum_{i=1}^ml\{x_j^{(i)}=1\bigcap y^{(i)}=0\}}{\sum_{i=1}^ml\{y^{(i)}=0\}} \]
垃圾郵件 \(y=1\) 的極大似然估計爲:
\[ \phi_{j|y=1}=\frac{\sum_{i=1}^ml\{x_j^{(i)}=1\bigcap y^{(i)}=1\}}{\sum_{i=1}^ml\{y^{(i)}=1\}} \]
假設 \(m\) 封郵件裏面的詞向量 \(\vec x\) 和標識 \(y\) 以下所示:
\[ (x^{(1)},y^{(1)}),(x^{(2)},y^{(2)}),...,(x^{(m)},y^{(m)}) \]
因此當垃圾郵件分類器開始訓練時,假設訓練垃圾郵件中包含某些詞 \(x\) 的機率 \(p(y=1|x)\) :
\[ \begin{split} p(y=1|x)&=\frac{p(x|y=1)p(y=1)}{p(x)} \\ &=\frac{(\prod_{i=1}^n p(x_i|y=1))p(y=1)}{(\prod_{i=1}^n p(x_i|y=1))p(y=1)+(\prod_{i=0}^n p(x_i|y=0))p(y=0)} \end{split} \]
上式中分母是由 全機率 計算出詞 \(p(x)\) 的機率,即假設 \(word_3\) 在垃圾郵件中沒有出現,那麼能夠獲得:
\[ \begin{split} p(y=1|x)&=\frac{p(x|y=1)p(y=1)}{p(x)} \\ &=\frac{(\prod_{i=1}^{50000} p(x_3|y=1))p(y=1)}{(\prod_{i=1}^{50000} p(x_3|y=1))p(y=1)+(\prod_{i=0}^n p(x_i|y=0))p(y=0)} \\ &=\frac{0}{0+0} \end{split} \]
這就意味着若是 \(word_3\) 在垃圾郵件中沒有出現,那麼機率爲0,這樣子很明顯是不合理的,不管在數學上會致使沒法繼續計算,仍是從機率的角度來講直接排除 \(word_3\) 的可能性,其實最好的是 \(word_3\) 沒出現,可是仍是會有機率,只是機率很低很低。舉個例子來講,若是某我的投籃球,連續5次都是沒投中,那麼是否是投中的機率爲0了,沒投中的機率是1了?
爲了修正這個方法,這裏最好是在分子分母加上一個極小數,防止數學上的無效計算和實際中的絕對不可能發生。
繼續上面投籃球的例子,假設沒投中的機率記爲 \(p(y=0)\) ,投中的機率記爲 \(p(y=1)\) ,原來的機率爲:
\[ \begin{split} p(y=0)&=\frac{沒投中的次數}{投籃球的總次數} \\ &=\frac{沒投中的次數}{投中的次數+沒投中的次數} \\ &=\frac{0}{5+0} \end{split} \]
若是給每一項都平滑一個極小數1,表明投中籃球和沒投中籃球在事先都已經發生過一次了,那麼上述式子變成:
\[ \begin{split} p(y=0)&=\frac{0+1}{(5+1)+(0+1)} \\ &=\frac{1}{7} \end{split} \]
那麼同理能夠知道, \(m\) 封郵件中,\(x_j=1\)表示包含關鍵詞 \(j\),\(x_j=0\)表示不包含關鍵詞 \(j\),則垃圾郵件 \(y=1\) 裏面包含的單詞 \(j\) 的極大似然估計爲:
\[ \phi_{j|y=1}=\frac{\sum_{i=1}^ml\{x_j^{(i)}\bigcap y^{(i)}=1\}+1}{\sum_{i=1}^ml\{y^{(i)=1}\}+2} \]
正常郵件 \(y=0\) 裏面包含的單詞 \(j\) 的極大似然估計爲:
\[ \phi_{j|y=0}=\frac{\sum_{i=1}^ml\{x_j^{(i)}=1\bigcap y^{(i)}=0\}+1}{\sum_{i=1}^ml\{y^{(i)}=0\}+2} \]
由於 \(y\) 只有兩種可能,咱們這裏也假設事先存在一封垃圾郵件和一封正常郵件,因此分子只須要+1,分母只須要+2。
總的來講,樸素貝葉斯訓練階段爲,給定一組已知的訓練樣本 \((\vec{x_1},y_1),(\vec{x_2},y_2),...,(\vec{x_n},y_n)\),能夠獲得垃圾郵件中,每個單詞出現的機率:
\[ p(x|y)=(\prod_{i=1}^{n}p(x_i|y_i) \]
而在 預測階段 ,給定一封郵件的單詞向量 \(\vec x\),求這個郵件是不是垃圾郵件,那麼問題就轉化爲:已知單詞\(\vec x\)已經發生,求解是否垃圾郵件p(y|x):
\[ argmax_yp(y|x)=argmax_y \frac{p(x|y)p(y)}{p(x)}argmax_y p(x|y)p(y) \]
上述中,\(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%
:
數據和代碼下載請關注公衆號【 機器學習和大數據挖掘 】,後臺回覆【 機器學習 】便可獲取