初涉机器学习3 -- 生成学习算法之朴素贝叶斯分类器

723 阅读5分钟
分类问题和线性的问题有些区别,它的预测结果是离散的,比如判断一封邮件是否是垃圾邮件,它结果只可能是yes,no,今天带来的是基于概率来预测分类结果。而本篇文章使用的算法叫做朴素贝叶斯分类器,是基于朴素贝叶斯对于多特征参数的分类问题。
贝叶斯定理就不细说了,大学概率论一般都应该学过,引用下 WIKI:

贝叶斯定理

1.引入内容:

简单地说,假设要求预测一个 未知邮件 是否是垃圾邮件,则我们需要一些已知的邮件,得到训练集来进行一些概率的统计,假定邮件里的文字特征是一些特定词汇 X (特征数量可能很多),那么结果是判断是否是垃圾邮件 Y,根据贝叶斯有:

P(Y|X) = \frac{P(X|Y) * P(Y)}{P(X)}

但是如果有很多特征的时候(X种类很多),这个公式似乎就不好用了,因为特征之间可能存在概率关联,比如 X_2 可能是基于 X_1 的触发的概率下所触发的, 这个时候 X 的概率公式是:

P(X_1,X_2,X_3...X_n|Y) = P(X_1|Y)P(X_2|Y,X_1)...P(X_n|Y,X_1...X_{n-1})

这个时候基于贝叶斯定理提出了一个叫做 朴素贝叶斯 的理论,理论里假设特征之间的概率是相互独立的,即上面的 X_1X_2相互独立,所以这样求多特征的概率也就相互独立了,所以特征 X概率

P(X|Y) = \Pi_{i=1}^n P(X_i|Y)

所以所有特征的联合后验概率(预测结果为j,比如 Y = 1是垃圾邮件,Y = 0 是正常邮件):

P(Y=j|X) = \frac{\Pi_{i=1}^n P(X_i|Y=j) * P(Y)}{P(X_1,X_2...X_n)};(n 是特征的数量)

那么对于一个 Y 是一个多结果的分类,需要取得这几个类别中最大的后验概率 (PS:因为只要比较概率大小,所以计算的时候计算分母中的先验概率 P(X) 可以不计算的),即:

arg\; \max_Y \;P(Y|X)

就能给出一封邮件是否是垃圾邮件的结果。

那么计算 P(Y|X) 就得需要知道 P(X|Y)\;P(Y)\;P(X)的概率,这个应该比较简单,简单的概率计算就能得到,我们定义下参数, i 为特征类别,比如邮件中特定词的数量,发件人,发件时间;k 为特征具体内容,如 特定词数量为1,发件人是admin,发件时间为晚上7点;j为结果,如 j = 1是垃圾邮件,j = 0 是正常邮件; z 是训练集数量

P(X_i=k|Y=j) = \frac{\sum_{m=1}^{z} 1\{{x_i^m = k\; and\;Y=j}\}}{\sum_{m=1}^{z}Y^m = j}
P(Y=j) =  \frac{\sum_{m=1}^{z} 1\{Y^m = j\}}{z}

其中方括号里的表示and,只有两者都ture才进入求和, 式子比较复杂,举个例子说明: 邮件里,发件时间在19:00为垃圾邮件的概率为: P(X_{发件时间} = 19:00|Y=垃圾邮件) = \frac{发件时间在7点并且是是垃圾邮件的数量}{所有垃圾邮件的数量}

特别地,如果要计算分母P(X),需要用到概率公式:

所以求P(Y=j|X)就可以变成:

P(Y=j|X) \;= \frac{\Pi_{i=1}^n P(Xi|Y=j) * P(Y=j)}{ P(X)} \\
= \frac{\Pi_{i=1}^n P(Xi|Y=j) * P(Y=j)}{ P(X|Y=1) * P(Y=j) + P(X|(Y=1)^c) * P((Y=j)^c)} \\
= \frac{\Pi_{i=1}^n P(Xi|Y=j) * P(Y=j)}{\sum_{j}(\Pi_{i=1}^n P(Xi|Y=j) * P(Y=j))}

最后的式子,分母求和的J相当于Y结果的数量。

2.实践

那么结合理论,就找一个数据集来进行一般分类预测,在具体实践前需要对数据集进行分类处理,即需要一个 训练集 一个 验证集, 本文采用的是 自助法,理论就不多说了,直接截取 周志华的《机器学习》里的描述:

自助法

数据集来源 Kagglewww.kaggle.com/uciml/mushr… ,是一个关于蘑菇的数据集,里面含有23种大特征,然后区别了是否可食用,该例子就是通过特征来预测一个蘑菇是否是可食用。

2.1 Coding
2.1.1 取数据
import pandas as pd
# import matplotlib.pyplot as plt  # 绘图库

import numpy.matlib 
import numpy as np

import os

# data from https://www.kaggle.com/uciml/mushroom-classification
df = pd.read_csv("./../../LogisticRegression/20191018/input/mushrooms.csv")

print('size:', df.shape)

#拆解训练集 与 验证集
#训练集
train_df = []
#验证集
validation_df = []

m = len(df)

print('total:', m)
2.1.2 区别测试集和验证集
#使用自组法 获取训练集和验证集 (行数)
validation_list = np.random.choice(m, m) #取长度 0 ~ m-1 取m次 
total_list = np.arange(m)
train_list = np.setdiff1d(total_list, validation_list)
print(train_list.shape)

其中 np.setdiff1d 方法是将 两个np.array 取差集作为训练集

2.1.3 格式化数据
#从df 中取出特征以及分类结果
df = df[['class', 'cap-shape', 'cap-color', 'bruises', 'odor', 'gill-attachment', 'gill-spacing']];
#取key做下标区别
key = ['cap_shape_', 'cap_color_', 'bruises_', 'odor_', 'gill-attachment_', 'gill-spacing_']

df = np.array(df)
#获取训练集
for i in range(len(train_list)):
    train_df.append(df[train_list[i]])   
train_df = np.array(train_df)
#获取验证集
for i in range(len(validation_list)):
    validation_df.append(df[validation_list[i]])   
validation_df = np.array(validation_df)
print(validation_df.shape)

字段class是表示是否是可使用数据里用的是 e(可食用)和p(有毒的)。

2.1.4 计算训练集里各个特征的概率
#计算各个参数的概率

#概率list
p = {}
total_p = {}
train_df = train_df.tolist()
validation_df = validation_df.tolist()
for i in range(len(train_df)):
    for j in range(len(key)):
        key1 = key[j] + str(train_df[i][j+1])
        if key1 in p:
            if train_df[i][0] in p[key1]:
                p[key1][train_df[i][0]] = p[key1][train_df[i][0]] + 1
            else :
                p[key1][train_df[i][0]] = 1
        else :
            p[key1] = {train_df[i][0] : 1}
    if train_df[i][0] in total_p:
        total_p[train_df[i][0]] = total_p[train_df[i][0]] + 1
    else :
        total_p[train_df[i][0]] = 1
#加上拉普拉斯平滑
for i in p:
    for j in p[i]:
        p[i][j] = (p[i][j] + 1) / (total_p[j] + len(train_df))

for i in total_p:
    total_p[i] = total_p[i] / m

print('train set p:', p)
print('total set p:', total_p)

就是上面的求各个特征的概率P(X_i|Y) \;P(Y=j)方法。接下来就是使用验证集合验证了。

2.1.5 验证结果
right_num = 0
error_num = 0
validation_num = len(validation_df)
print('start validat.....\n')
for i in range(validation_num):
    data = validation_df[i]
    #各个后验概率的字典
    validation_p = {}
    #p(x)的多元概率和
    total_validation_p = 0
    for j in total_p:
        #分类器可能不止2种
        _p_x_i = 1
        #计算特征p(xi|y)的概率
        for z in range(len(key)):
            key1 =  key[z] + str(data[z + 1])
            if key1 not in p:
                 #加上拉普拉斯平滑
                _p_x_i = _p_x_i * (1 / (len(train_df) + len(key)))
            else :
                if j not in p[key1]:
                    #加上拉普拉斯平滑
                    _p_x_i = _p_x_i * (1 / (len(train_df) + len(key)))
                else :
                    _p_x_i = _p_x_i * p[key1][j]
        _validation_p = total_p[j] * _p_x_i
        validation_p[j] = _validation_p
        total_validation_p = total_validation_p + _validation_p
    _max = 0
    _validation_result = ''
    #这一步应该可以省略 用来计算分母p(x)的
    for j in total_p:
        validation_p[j] = validation_p[j] / total_validation_p
    #判断最大后验概率
    for j in validation_p:
        if validation_p[j] > _max:
            _max = validation_p[j]
            _validation_result = j
    print('validat data :', data)
    print('out of test result :', _validation_result)
    # data[0]是验证集合的class的位置
    if data[0] == _validation_result:
        print('result correct')
        right_num = right_num + 1
    else :
        print('result fail')
        print('validation_p is :', validation_p)
        error_num = error_num + 1
print('run over...')
print('validation num:', validation_num)
print('validation right num', right_num)
print("validation right rate %s" %str(right_num / validation_num * 100))
print('validation error num', error_num)
print("validation error rate %s" %str(error_num / validation_num * 100))
2.1.6 查看结果

跑一下代码,查看最后输出结果:

正确率在98%左右,其中有1.5%的数据错误,取了一条错误的查看输出,发现
e的概率是接近1的原因应该是这条新数据(在训练集里没有的特征),不过也可能是因为是关联特征(蘑菇的几个形态关联性比较大),因为使用了拉普拉斯平滑所以一些没有的数据(概率为0)概率会大于0,不然会导致代码报错。

到此,本人的机器学习的第三个算法记录就到这里结束了~

最后,cs229讲义牛逼!

以上。

代码git地址: 地址