在上一篇博客中,我们介绍了朴素贝叶斯方法以及详细推导。在这篇博客中,我们将介绍朴素贝叶斯的python3实现代码。
这里,我们将算法复述如下:
(1) 先验概率及条件概率
P ( Y = c k ) = ∑ i = 1 N I ( y i = c k ) N P(Y=c_k)=\frac{\sum_{i=1}^NI(y_i=c_k)}{N} P(Y=ck)=N∑i=1NI(yi=ck)
P ( X ( j ) = a j l ∣ Y = c k ) = ∑ i = 1 N I ( x i ( j ) = a j l , y i = c k ) ∑ i = 1 N I ( y i = c k ) P(X^{(j)}=a_{jl}|Y=c_k)=\frac{\sum_{i=1}^NI(x_i^{(j)}=a_{jl}, y_i=c_k)}{\sum_{i=1}^NI(y_i=c_k)} P(X(j)=ajl∣Y=ck)=∑i=1NI(yi=ck)∑i=1NI(xi(j)=ajl,yi=ck)
(2) 对于给定的 x = ( x ( 1 ) , x ( 2 ) , . . . , x ( n ) ) x=(x^{(1)}, x^{(2)}, ..., x^{(n)}) x=(x(1),x(2),...,x(n)),计算
P ( Y = c k ) Π j = 1 n P ( X ( j ) = x ( j ) ∣ Y = c k ) P(Y=c_k)\Pi_{j=1}^{n} P(X^{(j)}=x^{(j)}|Y=c_k) P(Y=ck)Πj=1nP(X(j)=x(j)∣Y=ck)
(3) 确定实例 x x x 的类
y = arg max c k P ( Y = c k ) Π j = 1 n P ( X ( j ) = x ( j ) ∣ Y = c k ) y=\argmax_{c_k}P(Y=c_k)\Pi_{j=1}^{n} P(X^{(j)}=x^{(j)}|Y=c_k) y=ckargmaxP(Y=ck)Πj=1nP(X(j)=x(j)∣Y=ck)
从上述算法的步骤(1)和步骤(2),可以看到,计算概率的分子分母可能为0。比如,计算先验概率的时候,假设类别有100种,但是我们只有50个数据,此时,必然出现某些类别出现的概率为0;再比如,计算条件概率时,相亲对象数据特征有 高矮 和 胖瘦 这两个维度,如果我们只取了高个子数据,就会导致 P ( x i ( 1 ) = 矮 子 , y i = c k ) P(x_i^{(1)}=矮子, y_i=c_k) P(xi(1)=矮子,yi=ck) 恒为0。
为了避免上述的计算概率为0的情况,我们在分子分母上分别加上一个正数 λ \lambda λ:
P ( Y = c k ) = ∑ i = 1 N I ( y i = c k ) + λ N + K ⋅ λ P(Y=c_k)=\frac{\sum_{i=1}^NI(y_i=c_k)+\lambda}{N+K\cdot \lambda} P(Y=ck)=N+K⋅λ∑i=1NI(yi=ck)+λ
P ( X ( j ) = a j l ∣ Y = c k ) = ∑ i = 1 N I ( x i ( j ) = a j l , y i = c k ) + λ ∑ i = 1 N I ( y i = c k ) + S j ⋅ λ P(X^{(j)}=a_{jl}|Y=c_k)=\frac{\sum_{i=1}^NI(x_i^{(j)}=a_{jl}, y_i=c_k)+\lambda}{\sum_{i=1}^NI(y_i=c_k)+S_j\cdot \lambda} P(X(j)=ajl∣Y=ck)=∑i=1NI(yi=ck)+Sj⋅λ∑i=1NI(xi(j)=ajl,yi=ck)+λ
可以简单验证,上面的表达式依然是概率。
注意:此处可以参考@miangangzhen的代码。这里,我们将用非常简单的代码来实现该问题的预测功能。
import pandas as pd
import numpy as np
# 载入数据
def load_data(file):
data = pd.read_csv(file, header=None, names=['data1', 'data2', 'label'], sep=' ')
return data
x_y_data = load_data('D:/pycharm/myproject/data.txt')
# 数据数量
N, _ = x_y_data.shape
##############################
# 类别及其数量
categories = x_y_data['label'].value_counts()
print('类别及其数量为\n', categories)
# 先验概率
pre_possibility = categories / N
print('先验概率为\n', pre_possibility)
##############################
# 同时限定某维度的值和类别时,数据的数量counts以及条件概率
# 维度1
dim1_cate = x_y_data.groupby(['data1', 'label']).count()
dim1_cate = dim1_cate.reset_index().rename(columns={'data2': 'counts'})
dim1_cate['possibilities'] = dim1_cate.apply(lambda x: x.counts / categories[x.label], axis=1)
print('维度1+类别+条件概率:\n', dim1_cate)
# 维度2
dim2_cate = x_y_data.groupby(['data2', 'label']).count()
dim2_cate = dim2_cate.reset_index().rename(columns={'data1': 'counts'})
dim2_cate['possibilities'] = dim2_cate.apply(lambda x: x.counts / categories[x.label], axis=1)
print('维度2+类别+条件概率:\n', dim2_cate)
##############################
# 计算 x=(2, S)的类标记
x = (2, 'S')
res = []
for label in list(categories.index): # 遍历类别
#print('label=', label)
# 计算该类别下的后验概率
post_possibility = pre_possibility[label]
# 遍历特征
dim1_con_pos = dim1_cate[(dim1_cate['label'] == label) & (dim1_cate['data1'] == x[0])].iloc[0, 3]
#print(dim1_con_pos)
dim2_con_pos = dim2_cate[(dim2_cate['label'] == label) & (dim2_cate['data2'] == x[1])].iloc[0, 3]
#print(dim2_con_pos)
post_possibility *= dim1_con_pos * dim2_con_pos
res.append((label, post_possibility))
#print('label=', label, ' post=', post_possibility)
print('类标记及其对应后验概率为')
print(res)
结果为
类别及其数量为
1 9
-1 6
Name: label, dtype: int64
先验概率为
1 0.6
-1 0.4
Name: label, dtype: float64
维度1+类别+条件概率:
data1 label counts possibilities
0 1 -1 3 0.500000
1 1 1 2 0.222222
2 2 -1 2 0.333333
3 2 1 3 0.333333
4 3 -1 1 0.166667
5 3 1 4 0.444444
维度2+类别+条件概率:
data2 label counts possibilities
0 L -1 1 0.166667
1 L 1 4 0.444444
2 M -1 2 0.333333
3 M 1 4 0.444444
4 S -1 3 0.500000
5 S 1 1 0.111111
类标记及其对应后验概率为
[(1, 0.02222222222222222), (-1, 0.06666666666666667)]
可见,我们应该选取-1作为 x x x的类标记。
在下一篇博客中,我们将介绍决策树算法。
数据:数据来源为《统计学习方法》第二版的例4.1。复制保存为.txt文件。
1 S -1
1 M -1
1 M 1
1 S 1
1 S -1
2 S -1
2 M -1
2 M 1
2 L 1
2 L 1
3 L 1
3 M 1
3 M 1
3 L 1
3 L -1