和前面介紹到的kNN,決策樹一樣,貝葉斯分類法也是機器學習中常用的分類方法。貝葉斯分類法主要以概率論中貝葉斯定理為分類依據,具有很廣泛的應用。本文通過一個完整的例子,來介紹如何用朴素貝葉斯分類法實現分類。主要內容有下:
1、條件概率與貝葉斯定理介紹
2、數據集選擇及處理
3、朴素貝葉斯分類器實現
4、測試分類效果
5、寫在后面的話
一 、條件概率與貝葉斯定理介紹
條件概率:
條件概率是指事件A在另外一個事件B已經發生條件下的發生概率,用P(A|B)表示。通常,事件A在事件B(發生)的條件下的概率,與事件B在事件A的條件下的概率是不一樣的。但是,這兩者是有確定的關系,貝葉斯定理 就是這種關系的陳述。
貝葉斯公式:
其中:P(c | x) 為在事件 x 已經發生的前提下事件 c 發生的概率,P(c),P(x)分別為事件 c 和事件 x 發生的概率。
舉個例子:
如下圖有6次搶火車票記錄,現在小明條件是:一般熟練 | 不用工具 | 網速適中,問他能搶到票的概率是多少?
電腦熟練程度 | 是否使用搶票軟件 | 網速 | 能夠搶到票 |
超級熟練 | 不用工具 | 網速快 | 搶到 |
超級熟練 | 不用工具 | 網速慢 | 沒搶到 |
一般熟練 | 用工具 | 網速適中 | 搶到 |
一般熟練 | 不用工具 | 網速快 | 搶到 |
生疏 | 不用工具 | 網速適中 | 沒搶到 |
生疏 | 用工具 | 網速快 | 搶到 |
根據貝葉斯定律,小明搶到票的概率為:
P(搶到 | 一般熟練 ✕ 不用工具 ✕ 網速適中 )
= P(一般熟練 ✕ 不用工具 ✕ 網速適中 | 搶到 ) * P( 搶到 ) / P( 一般熟練 ✕ 不用工具 ✕ 網速適中 )
這里用的是朴素貝葉斯方法,之所以被稱為“朴素”,是因為 這里假設所有特征之間相互獨立,如電腦熟練程度 和 是否使用搶票軟件等無關。
則上面式子可展開為:
P(搶到 | 一般熟練 ✕ 不用工具 ✕ 網速適中 )
= P(一般熟練 | 搶到 ) * P(不用工具 | 搶到 ) * P(網速適中 | 搶到 ) * P( 搶到 ) / P( 一般熟練 ) / P( 不用工具 )/ P( 網速適中 )
= 0.5 * 0.5 * 0.25 * 0.6667 / 0.3333/0.6667/0.3333 = 0.5625
我們可以大概算出 小明搶到票的概率為 56.25%
二 、數據集選擇及處理
Kaggle 是一個提供機器學習競賽、托管數據庫、編寫和分享代碼的平台,上面有一些用戶分享的開放數據集,本文選取Kory Becker提供的男女聲音特征分類數據集 https://www.kaggle.com/primaryobjects/voicegender
該數據集樣例如下:
這里的數據集是針對男女聲音使用聲波和調諧器包進行預處理,是基於聲音和語音的聲學特性而創建的,用來識別男性或女性的聲音。我們不必過於糾結20個比較專業的的特征名稱,這里把其當做一般的連續性數值特征就可以了。這里只展示了前兩條數據,前20列(meanfreq,sd,median......dfrange,modindx)為聲音的各種特征 ,最后一列 label 為聲音的分類。我們可以看到 mode 和 dfrange 等列都有為0 的值,說明數據可能有缺失,我們在處理數據集時要考慮到數據缺失的問題.
處理缺失數據:
我們將值為 0 的 特征值 視為缺失,彌補缺失的辦法是用該特征的平均值填充。
1 # 求每一個特征的平均值 2 data_mat = np.array(data_mat).astype(float) 3 count_vector = np.count_nonzero(data_mat, axis=0) 4 sum_vector = np.sum(data_mat, axis=0) 5 mean_vector = sum_vector / count_vector 6 7 # 數據缺失的地方 用 平均值填充 8 for row in range(len(data_mat)): 9 for col in range(num): 10 if data_mat[row][col] == 0.0: 11 data_mat[row][col] = mean_vector[col]
數據離散化:
貝葉斯公式 適合標稱型數據,這里每個特征都是連續的浮點數值,我們需要將特征值離散化。
離散的方法如下:
max min分別為該特征的最大值和最小值,n為離散的程度,Floor()為向下取整
本文中 n 取 10 ,每個特征的值離散為 [0,1,2,3,4,5,6,7,8,9] 中的一個,離散化后的數據樣例如下:
實現如下:
1 # 將數據連續型的特征值離散化處理 2 min_vector = data_mat.min(axis=0) 3 max_vector = data_mat.max(axis=0) 4 diff_vector = max_vector - min_vector 5 diff_vector /= 9 6 7 new_data_set = [] 8 for i in range(len(data_mat)): 9 line = np.array((data_mat[i] - min_vector) / diff_vector).astype(int) 10 new_data_set.append(line)
切分訓練、測試數據集
本數據集公有3168條數據,為了讓男性聲音樣本和女性聲音樣本隨機地分布到訓練和測試數據集,這里使用隨機算法隨機選取2000條作為訓練數據集,其余的1168條作為測試數據集:
1 # 隨機划分數據集為訓練集 和 測試集 2 test_set = list(range(len(new_data_set))) 3 train_set = [] 4 for i in range(2000): 5 random_index = int(np.random.uniform(0, len(test_set))) 6 train_set.append(test_set[random_index]) 7 del test_set[random_index] 8 9 # 訓練數據集 10 train_mat = [] 11 train_classes = [] 12 for index in train_set: 13 train_mat.append(new_data_set[index]) 14 train_classes.append(list_class[index]) 15 16 # 測試數據集 17 test_mat = [] 18 test_classes = [] 19 for index in test_set: 20 test_mat.append(new_data_set[index]) 21 test_classes.append(list_class[index])
三 、朴素貝葉斯分類器實現
我們假設20個特征值分類為F1,F2 ,F3 ......,F20
F1i1表示 特征F1 取 第i1 個 分類
則一個測試樣本向量可以表示如下:
test_vector = [F1i1 , F2i2 , F3i3 ..... F19i19 , F20i20]
則 其是男性的概率為:
P(男 | F1i1 ✕ F2i2 ✕ F3i3 ..... F19i19 ✕ F20i20)
= P(F1i1 ✕ F2i2 ✕ F3i3 ..... F19i19 ✕ F20i20 | 男) * P(男) / P(F1i1 ✕ F2i2 ✕ F3i3 ..... F19i19 ✕ F20i20)
實現為:
1 def native_bayes(train_matrix, list_classes): 2 """ 3 :param train_matrix: 訓練樣本矩陣 4 :param list_classes: 訓練樣本分類向量 5 :return:p_1_class 任一樣本分類為1的概率 p_feature,p_1_feature 分別為給定類別的情況下所以特征所有取值的概率 6 """ 7 8 # 訓練樣本個數 9 num_train_data = len(train_matrix) 10 num_feature = len(train_matrix[0]) 11 # 分類為1的樣本占比 12 p_1_class = sum(list_classes) / float(num_train_data) 13 14 n = 10 15 list_classes_1 = [] 16 train_data_1 = [] 17 18 for i in list(range(num_train_data)): 19 if list_classes[i] == 1: 20 list_classes_1.append(i) 21 train_data_1.append(train_matrix[i]) 22 23 # 分類為1 情況下的各特征的概率 24 train_data_1 = np.matrix(train_data_1) 25 p_1_feature = {} 26 for i in list(range(num_feature)): 27 feature_values = np.array(train_data_1[:, i]).flatten() 28 # 避免某些特征值概率為0 影響總體概率,每個特征值最少個數為1 29 feature_values = feature_values.tolist() + list(range(n)) 30 p = {} 31 count = len(feature_values) 32 for value in set(feature_values): 33 p[value] = np.log(feature_values.count(value) / float(count)) 34 p_1_feature[i] = p 35 36 # 所有分類下的各特征的概率 37 p_feature = {} 38 train_matrix = np.matrix(train_matrix) 39 for i in list(range(num_feature)): 40 feature_values = np.array(train_matrix[:, i]).flatten() 41 feature_values = feature_values.tolist() + list(range(n)) 42 p = {} 43 count = len(feature_values) 44 for value in set(feature_values): 45 p[value] = np.log(feature_values.count(value) / float(count)) 46 p_feature[i] = p 47 48 return p_feature, p_1_feature, p_1_class
問題1:
因為 計算中有 P(F1i1 ✕ F2i2 ✕ F3i3 ..... F19i19 ✕ F20i20)= P(F1i1 )*P( F2i2 ) ..... P(F20i20)
需要多個概率相乘,但如果某個概率為0,則總的概率就會為0,這樣計算就會出現錯誤,所以這里給每個分類至少添加一條數據,如上圖第29行和第41行
問題2:
因為本文的數據集有20個特征,多個特征的概率相乘,乘積的結果將會非常小,可能會在四舍五入過程中被舍棄掉,從而影響實驗結果。所以這里統一取對數,如上圖第33行和45行
四 、測試分類效果
按照貝葉斯公式,計算測試樣本在特定條件下 為男性的概率 和 為女性的概率,選取概率大的分類為最終的分類:
1 def classify_bayes(test_vector, p_feature, p_1_feature, p_1_class): 2 """ 3 :param test_vector: 要分類的測試向量 4 :param p_feature: 所有分類的情況下特征所有取值的概率 5 :param p_1_feature: 類別為1的情況下所有特征所有取值的概率 6 :param p_1_class: 任一樣本分類為1的概率 7 :return: 1 表示男性 0 表示女性 8 """ 9 # 計算每個分類的概率(概率相乘取對數 = 概率各自對數相加) 10 sum = 0.0 11 for i in list(range(len(test_vector))): 12 sum += p_1_feature[i][test_vector[i]] 13 sum -= p_feature[i][test_vector[i]] 14 p1 = sum + np.log(p_1_class) 15 p0 = 1 - p1 16 if p1 > p0: 17 return 1 18 else: 19 return 0
由於程序每次執行都是隨機選取2000個樣本作為訓練樣本,所以每次執行的訓練集也是不同的,因此得到的正確率也是波動的。
測試了幾下,識別正確率分別如下:0.88955 | 0.8998 | 0.8844 | 0.8904 | 0.8981
可以看到正確率基本在0.89 左右
五 、寫在后面的話
本文的完整代碼已上傳碼雲倉庫 https://gitee.com/beiyan/machine_learning/tree/master/naive_bayes
朴素貝葉斯分類器的原理和實現都比較簡單,這是基於各個特征都是相對獨立的情況。為了提高識別率,還可以通過比如 特征加權、增加特征值離散程度等改進辦法來實現。
假如各個特征不是相互獨立,例如身高特征和體重特征有一定關系等,在這種情況下,就需要基於有向圖的決策模:貝葉斯網絡。