写在前面
无聊的时候总要找点事情做,今天就搞了搞朴素贝叶斯分类,训练样本集为playtennis,此案例是我大学期间在一本名为《数据挖掘》的书中看到的,比较简单,当时是用matlab做的,根据算法原理写了好多for循环,今天用python试了试,原本以为sklearn里面会有对应的方法,实际上也确实有,但测试集包含的数据量太少,训练得到的模型精度非常低,所以根据朴素贝叶斯分类的原理写了代码,在此记录一下。
算法介绍
该算法主要依据贝叶斯公式,是一种有监督的机器学习,算法思想真的很朴素!具体参考下面链接。
数据集
Outlook | Temperature | Humidity | Wind | PlayTennis |
---|---|---|---|---|
Sunny | Hot | High | Weak | No |
Sunny | Hot | High | Strong | No |
Overcast | Hot | High | Weak | Yes |
Rain | Mild | High | Weak | Yes |
Rain | Cool | Normal | Weak | Yes |
Rain | Cool | Normal | Strong | No |
Overcast | Cool | Normal | Strong | Yes |
Sunny | Mild | High | Weak | No |
Sunny | Cool | Normal | Weak | Yes |
Rain | Mild | Normal | Weak | Yes |
Sunny | Mild | Normal | Strong | Yes |
Overcast | Mild | High | Strong | Yes |
Overcast | Hot | Normal | Weak | Yes |
Rain | Mild | High | Strong | No |
用到的包
#表格处理模块
import pandas as pd
#用于统计各分类指标出现的次数
from collections import Counter
清洗数据
即从样本集中读取分类结果对应分类指标的频数
#df是一个pandas对象,读取了训练样本集
def Get_Info(df):
#获取df索引
index = df.columns
#计算各分类结果(df的最后一列)的频数,这返回的是一个Counter字典
Category = Counter(df[index[-1]])
#预保存数据的空字典
content = {}
#统计每一个分类结果,key1代表某个分类结果
for key1 in Category.keys():
#字典的次级仍然是字典,在本例中通过字典读取数据比较方便
Cat_dict = {}
#key2代表df的某个索引,但不包括最后一列
for key2 in index[0:-1]:
#从df中筛选出结果为key1的切片,并找到索引为key2的列,统计其频数并转为普通字典
Cat_dict[key2] = dict(Counter(df[key2][df[index[-1]] == key1]))
#为字典添加一个键值对,键为分类结果,值为分类结果对应的各分类指标的频数
content[key1] = Cat_dict
#返回统计完成的数据和分类结果
return content,Category
返回的content字典是这样的:
{'No': {'Outlook': {'Sunny': 3, 'Rain': 2}, 'Temperature': {'Hot': 2, 'Cool': 1, 'Mild': 2}, 'Humidity': {'High': 4, 'Normal': 1}, 'Wind': {'Weak': 2, 'Strong': 3}}, 'Yes': {'Outlook': {'Overcast': 4, 'Rain': 3, 'Sunny': 2}, 'Temperature': {'Hot': 2, 'Mild': 4, 'Cool': 3}, 'Humidity': {'High': 3, 'Normal': 6}, 'Wind': {'Weak': 6, 'Strong': 3}}}
各分类概率
#test为预分类的数据
def classify(test,content,Category,df):
index = df.columns
#训练样本的数量
total_columns = len(df)
#用于保存各分类结果对应的概率
result = {}
#计算每个分类结果的概率
for key1 in content.keys():
#该分类结果占样本总数的比重
p_key1 = Category[key1] / total_columns
#t是z对应的一个指标,根据上面的content来说,z是Outlook,t就可能是Sunny,以此类推
for t,z in zip(test,index[0:-1]):
#如果z中包含t,则计算t在该分类结果下的概率,否则设为0
if t in content[key1][z].keys():
p_t = content[key1][z][t] / Category[key1]
else:
p_t = 0
#概率相乘
p_key1 *= p_t
#保存一个‘结果’-‘概率’键值对
result[key1] = p_key1
#返回包含所有概率的字典
return result
计算误判率
def Err_Probability(df):
#误判的数量,初始设为0
err = 0
content,Category = Get_Info(df)
#对每一个样本进行检验
for i in range(len(df)):
#获取当前循环中的样本
test = df.loc[i]
#调用前述方法获取分类结果
result = classify(test[0:-1],content,Category,df)
#最大概率对应的结果
pre = max(result,key = result.get)
#如果预测与实际不符,则误判数量加1
if pre != test[-1]:
err += 1
#print('预测:',pre,'实际:',test[-1])
#输出误判率
print('误判率: %.4f' % (err / len(df)))
单样本预测
这里着实没什么好说的。。
def predict(test,df):
content,Category = Get_Info(df)
result = classify(test,content,Category,df)
print('{}分类结果:{}'.format(test,max(result,key = result.get)))
入口函数
if __name__ == '__main__':
#从Excel中读取数据
df = pd.read_excel('playtennis.xlsx')
#预分类数据
test = ['Rain', 'Mild', 'High', 'Strong']
#误判率
Err_Probability(df)
#预测结果
predict(test, df)
最后输出是这样的:
误判率: 0.0714
['Rain', 'Mild', 'High', 'Strong']分类结果:No
拉普拉斯平滑
修改classify函数(为每个类别下的所有划分加1):
def Laplace(test,content,Category,df):
index = df.columns
total_columns = len(df)
result = {}
for key1 in content.keys():
p_key1 = (Category[key1] + 1) / (total_columns + len(Category))
for t,z in zip(test,index[0:-1]):
if t in content[key1][z].keys():
p_t = (content[key1][z][t] + 1) / (Category[key1] + len(content[key1][z]))
elif t in df[z].tolist():
p_t = 1 / (Category[key1] + len(content[key1][z]))
else:
p_t = 1 / (Category[key1] + len(content[key1][z]) + 1)
p_key1 *= p_t
result[key1] = p_key1
return result
Matlab代码
感觉这都不能称之为代码,需要手动把分类数据转换为数组,再看看朴素贝叶斯分类的原理,怎么都能做出来的。
clc;
clear;
n=0;y=0;
a1=0;a2=0;
b1=0;b2=0;
c1=0;c2=0;
d1=0;d2=0;
data=[
1,1,1,1,2;
1,1,1,2,2;
2,1,1,1,1;
3,2,1,1,1;
3,3,2,1,1;
3,3,2,2,2;
2,3,2,2,1;
1,2,1,1,2;
1,3,2,1,1;
3,2,2,1,1;
1,2,2,2,1;
2,2,1,2,1;
2,1,2,1,1;
3,2,1,2,2
];
[e,f]=size(data);
test=[1,1,1,2];
for i=1:e
if data(i,5)==1
y=y+1;
else
n=n+1;
end
end
for i=1:e
if data(i,1)==test(1)&&data(i,f)==1
a1=a1+1;
elseif data(i,1)==test(1)&&data(i,f)==2
a2=a2+1;
end
end
for i=1:e
if data(i,2)==test(2)&&data(i,f)==1
b1=b1+1;
elseif data(i,2)==test(2)&&data(i,f)==2
b2=b2+1;
end
end
for i=1:e
if data(i,3)==test(3)&&data(i,f)==1
c1=c1+1;
elseif data(i,3)==test(3)&&data(i,f)==2
c2=c2+1;
end
end
for i=1:e
if data(i,4)==test(4)&&data(i,f)==1
d1=d1+1;
elseif data(i,4)==test(4)&&data(i,f)==2
d2=d2+1;
end
end
Y=(y/e)*(a1/y)*(b1/y)*(c1/y)*(d1/y);
N=(n/e)*(a2/n)*(b2/n)*(c2/n)*(d2/n);
if Y>N
disp('Playtennis=YES!');
else
disp('Playtennis=NO!');
end
写在后面
折腾来折腾去算是搞掂了,但是我还要继续折腾,生命不息折腾不止!看知乎的文章似乎还需要拉普拉斯平滑,其实我也不知道这是个什么东西,回头我瞅瞅。
拉普拉斯平滑的代码已更新,背后的数学原理还请自行学习。