朴素贝叶斯分类器


写在前面

无聊的时候总要找点事情做,今天就搞了搞朴素贝叶斯分类,训练样本集为playtennis,此案例是我大学期间在一本名为《数据挖掘》的书中看到的,比较简单,当时是用matlab做的,根据算法原理写了好多for循环,今天用python试了试,原本以为sklearn里面会有对应的方法,实际上也确实有,但测试集包含的数据量太少,训练得到的模型精度非常低,所以根据朴素贝叶斯分类的原理写了代码,在此记录一下。

算法介绍

该算法主要依据贝叶斯公式,是一种有监督的机器学习,算法思想真的很朴素!具体参考下面链接。


  1. 带你理解朴素贝叶斯【知乎】
  2. 朴素贝叶斯分类器【百度百科】

数据集

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

写在后面

折腾来折腾去算是搞掂了,但是我还要继续折腾,生命不息折腾不止!看知乎的文章似乎还需要拉普拉斯平滑,其实我也不知道这是个什么东西,回头我瞅瞅。


拉普拉斯平滑的代码已更新,背后的数学原理还请自行学习。