和前面介绍到的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
朴素贝叶斯分类器的原理和实现都比较简单,这是基于各个特征都是相对独立的情况。为了提高识别率,还可以通过比如 特征加权、增加特征值离散程度等改进办法来实现。
假如各个特征不是相互独立,例如身高特征和体重特征有一定关系等,在这种情况下,就需要基于有向图的决策模:贝叶斯网络。