理论基础
朴素贝叶斯是机器学习里的一种分类模型。本节将介绍朴素贝叶斯相关的理论基础知识。
相关知识
贝叶斯学派的思想:
在这里举个例子帮助大家有个简单的理解:投掷一枚硬币100次,20次正面朝上,求这枚硬币正面朝上的概率。
- 频率学派:
- 贝叶斯派:
- 先验概率:这枚硬币是从银行拿出来的,我认为它正面朝上的概率是0.5,并假设之前它已经被投掷1000次:500次正,500次负。
- 数据分布:本次的投掷结果:20次正,80次负。
- 后验概率:
贝叶斯派被频率学派所诟病的就是所谓的先验概率。一般来说先验概率就是我们对于数据所在领域的历史经验,但是这个经验常常难以量化或者模型化,于是贝叶斯派大胆的假设先验分布的模型,比如正态分布,beta分布等。这个假设一般没有特定的依据,因此一直被频率学派认为很荒谬。
模型特点
- 朴素贝叶斯的模型能力:分类
给定样本x的特征,判断样本的标签
。
- 朴素贝叶斯的学习方法:有监督学习
需要有训练样本对模型进行训练。 - 朴素贝叶斯的模型类型:生成式模型
通过训练样本计算特征和标签
的联合分布
。联合分布
可以看做是朴素贝叶斯的模型,里面的每个概率都是模型的参数。
- 朴素贝叶斯的特点:朴素
各维特征对标签的影响相互独立,
数学基础
先看条件独立公式,如果和
相互独立,
根据条件概率公式:
可以推出贝叶斯公式:
其中,表示类别标签的先验分布,
表示在已知类别标签
的情况下,特征
取值的条件概率。
表示特征的分布,所有类别下
均是相同的。
对于二分类问题,通过比较和
,就可以实现分类。
模型推导
假设我们有如下m个样本,每个样本有n维特征:
训练过程:根据训练样本建立联合分布。
第一步:通过训练样本,可以计算训练样本中标签的分布,通过最大似然估计,我们可以认为样本中类别标签的分布就是类别标签的先验分布
。最大似然估计:通俗来说就是已知样本分布的情况,得到能够导致这种情况(似然)最大概率发生的参数。假设所有标签为
的训练样本个数共
个,所有标签为
的训练样本个数共
个。则
。
第二步:针对每种标签下的样本,统计每一维特征的分布,得到条件分布
。例如针对所有标签为
的训练样本,统计第
维特征
的取值,假设有两种取值0和1,取值为0共
个,则
。则条件概率
。
第三步:根据标签的先验分布和每维特征基于标签的条件分布
,建立联合分布
。例如
。注:这里联合分布
就可以看成是模型训练的参数。
预测过程:根据联合分布和特征
,判断
与
的大小。
第一步:根据预测样本的每维特征和联合分布
,都可以计算
和
。
第二步:根据特征对标签的影响相互独立,我们有:
第三步:比较和
,确定标签。
实例讲解
接下来,我会有一个简单的例子,帮助大家充分理解上述讲的过程。
假设我们拿到了10个男生的信息,并让一位女生根据这些信息进行判断是否考虑嫁给他们(下述随机举例,不代表真实情况)。
| 特征 | 高 | 富 | 帅 | 温柔 | 女生嫁不嫁 |
|---|---|---|---|---|---|
| 男生1 | 高 | 富 | 帅 | 温柔 | 嫁 |
| 男生2 | 高 | 穷 | 搓 | 温柔 | 不嫁 |
| 男生3 | 矮 | 穷 | 帅 | 不温柔 | 不嫁 |
| 男生4 | 矮 | 穷 | 搓 | 温柔 | 不嫁 |
| 男生5 | 矮 | 富 | 帅 | 不温柔 | 不嫁 |
| 男生6 | 高 | 穷 | 帅 | 温柔 | 嫁 |
| 男生7 | 矮 | 穷 | 帅 | 温柔 | 不嫁 |
| 男生8 | 矮 | 富 | 搓 | 温柔 | 嫁 |
| 男生9 | 高 | 穷 | 搓 | 不温柔 | 不嫁 |
| 男生10 | 高 | 富 | 帅 | 不温柔 | 嫁 |
现在,有一个男生x(样本),他高、富、搓、温柔(特征),请问这个女生是否会选择嫁给他(分类)。这就是个典型的分类问题。 根据贝叶斯公式:?P(嫁|高、富、搓、温柔) = \frac{P(嫁)P(高、富、搓、温柔|嫁)}{P(高、富、搓、温柔)}?
第一步:我们需要考虑女生嫁人的主观意愿,也就是标签的先验分布。
就是无论对方男生条件怎么样,这个女生自己到底有多想嫁人。根据上述的10个样本,我们可以知道,这个女生想嫁人的概率,这个女生不想嫁人的概率
。
这里有一点需要说明:先验分布我们是不知道的,也不是能算的(女生的心思你不懂)。但是现实的结果是10个男生里,女生想嫁4个人。所以我们可以假定在某种先验分布的情况下,让这种现实结果出现的概率最大。所以就得到了先验分布。
我们想象一个极端的例子,如果10个男生里,女生的选择全是不嫁,我们可以推断出这个女生可能在当前阶段是个独身主义者,完全不考虑嫁给任何人(即使样本不能代表任何人,比如王校长),即。即使样本是王校长,朴素贝叶斯模型也会认为女生选择不嫁。
第二步:我们知道了女生嫁人的主观意愿,我们还需要知道,如果女生选择嫁的给男生x,他的高、富、搓对嫁这个结果分别有多大的影响,也就是条件概率。
接下来就要用到朴素贝叶斯的“朴素”。朴素贝叶斯认为,男生的高、富、搓(特征)对嫁(标签)这个结果的影响是相互独立的,也就是。
这里有一点需要说明:为什么要假设特征对标签的影响是相互独立的呢?因为如果不独立,特征空间就是1个三维空间,特征总共2*2*2=8,如果独立呢,特征空间就是3个一维空间,2+2+2=6。一个累乘,一个累加。在现实生活中,往往有非常多的特征,每维特征的取值也是非常之多,而“朴素”正是以牺牲准确率为代价来大幅降低计算强度。
我们选择重新整理下样本,只看选择嫁的这4个男生:
| 特征 | 高 | 富 | 帅 | 温柔 | 女生嫁不嫁 |
|---|---|---|---|---|---|
| 男生1 | 高 | 富 | 帅 | 温柔 | 嫁 |
| 男生6 | 高 | 穷 | 帅 | 温柔 | 嫁 |
| 男生8 | 矮 | 富 | 搓 | 温柔 | 嫁 |
| 男生10 | 高 | 富 | 帅 | 不温柔 | 嫁 |
的计算方式:在所有选择嫁的男生里,高的男生占几个。可以得到
。同理可以得到
,
,
。
对于同一个样本而言,都是一样的,故只需要比较
贝叶斯公式的分子
和
即可。
我们可以计算得到:
可以得到:
朴素贝叶斯模型就可以告诉这个女生,对于男生x:嫁 !
进阶优化
由于朴素贝叶斯模型里假设特征相互独立,当计算联合概率时,就可能用到各维特征空间中概率的累乘。累乘存在一个致命的缺点:一旦其中一项为0,整项就为0。
当特征很多时,非常容易出现稀疏情况,,导致
,直接导致最后的结果
。显然不够合理。为了避免这种情况发生,可以采取平滑策略。
最常见的平滑策略就是拉普拉斯平滑,即在统计计算概率时,默认某类样本中,在各维空间中各个取值至少出现了一次。如果训练样本集数量充分大时,并不会对结果产生影响,并且解决了上述概率为0的尴尬局面。
优缺点
优点
- 思路简单。
- 空间复杂度低。(联合概率
只需要二维存储)
- 时间复杂度低。(
可以从联合分布
中直接计算)
缺点
- 假设了各维对结果的影响相互独立,现实中不成立。(比如富就会对帅有影响,有钱更容易打扮变帅。)当特征之间影响较大,朴素贝叶斯的性能就不太好。
工程实现
代码中NB_classify为朴素贝叶斯模型,具体代码如下。
代码
# -*- coding: utf-8 -*-
"""
Created on Thu Aug 23 19:52:02 2018
@author: huangzhaolong
"""
#利用sklearn自带的鸢尾花数据集
from sklearn.datasets import load_iris
import random
def load_data():
#加载鸢尾花数据集
iris = load_iris()
X = iris.data
Y = iris.target
#特征离散化
X = X.round()
data_num = X.shape[0]
index_list = [random.random() >= 0.2 for _ in range(data_num)]
train_index_list = []
test_index_list = []
for i in range(len(index_list)):
if index_list[i]:
train_index_list.append(i)
else:
test_index_list.append(i)
X_train = X[train_index_list]
X_test = X[test_index_list]
Y_train = Y[train_index_list]
Y_test = Y[test_index_list]
return X_train, X_test, Y_train, Y_test
class NB_classify(object):
def NB_train(self, X_train, Y_train):
'''
在已知X和Y的情况下,构建联合分布P(X,Y),即参数计算,计算方式为统计
根据贝叶斯公式,计算P(X,Y)=P(X|Y)*P(Y)
'''
p_xy = []
#对于每个类别
p_y = {}
for k in set(Y_train):
total_xi_y = list(Y_train).count(k)
print("对于类别为",k,"的样本共有",total_xi_y,"个")
#计算P(Y)
p_y.setdefault(k,list(Y_train).count(k)/Y_train.shape[0])
p_x_y_sub = []
#对每个维度的特征,计算P(x|Y)
for i in range(X_train.shape[1]):
#对于每一维特征,统计当前类别下样本个数,得到P(x|Y)
p_x_sub_y_sub = {}
print("对于类别为",k,"的样本,第",i,"维的离散特征共有",len(set([(round(item)) for item in X_train[[l for l in range(len(Y_train)) if Y_train[l] == k],i]])),"种")
#对每个维度下特征的取值,计算p(x=xi|Y)
for j in set(X_train[[l for l in range(len(Y_train)) if Y_train[l] == k],i]):
#统计每个类别下,每个特征占该类别样本个数的比例,并做贝叶斯平滑
print("对于类别为",k,"的样本,第",i,"维特征为",j,"的样本共有",list(X_train[[l for l in range(len(Y_train)) if Y_train[l] == k],i]).count(j), "个")
#计算p(y)*p(x|y)
p_x_sub_y_sub.setdefault(j,p_y[k]*(list(X_train[[l for l in range(len(Y_train)) if Y_train[l] == k],i]).count(j)+1)/(total_xi_y+len(set(X_train[[l for l in range(len(Y_train)) if Y_train[l] == k],i]))))
p_x_y_sub.append(p_x_sub_y_sub)
'''
最后得到P(X|Y)的模型参数,即矩阵M(xy),其中矩阵M为m行n列,m为类别个数,n为特征维数。每个元素是一个字典,字典的key是该维特征的取值,字典的value是概率
例如矩阵M中的元素M_ij,M_ij是一个字典。
M_ij的key是第j维特征的所有取值,例如k_j
M_ij的value是第j维特征取值为k_j时,类别为i的概率P(xj=k_j|i),也就是所有类别为i的样本下,第j维特征为k_j的概率v_ij。
v_ij = 所有类别为i的样本下,第j维特征为k_j的样本数数/所有类别为i的样本数
'''
p_xy.append(p_x_y_sub)
print("P(Y)的先验概率:\n",p_y)
print("P(X,Y)的联合概率:\n",p_xy)
self.p_xy = p_xy
def NB_test(self, X_test):
p_xy = self.p_xy
pred = []
#对于每个样本
for z in range(len(X_test)):
#对于每个类别
pj = [1]*3
for j in range(3):
#对于每个特征,通过联合概率分布,计算每个特征下得到的类别P(yj|xi)
for i in range(len(X_test[z])):
if X_test[z][i] in p_xy[j][i]:
p_xi_yj = p_xy[j][i][X_test[z][i]]
else:
#如果训练集没有出现过该特征,则说明构建的P(X,Y)联合分布并不准确,生成式模型需要训练样本足够大,足够全
p_xi_yj = 0.0000001
#计算全部特征到每个类别的概率P(yj|X),这里是认为各特征对类别是独立的,可以连乘
pj[j] = pj[j]*p_xi_yj
#根据概率最大计算最可能的类别,即 argmax P(yj|X)
pred.append(pj.index(max(pj)))
return pred
def cal_accuracy(pred, Y_test):
acc = list(pred-Y_test).count(0)/len(pred-Y_test)
return acc
if __name__=="__main__":
acc = 0
for i in range(1):
X_train, X_test, Y_train, Y_test = load_data()
nb = NB_classify()
nb.NB_train(X_train, Y_train)
pred = nb.NB_test(X_test)
acc += cal_accuracy(pred,Y_test)
acc = acc/1
print("acc:",acc)
结果
对于类别为 0 的样本共有 42 个
对于类别为 0 的样本,第 0 维的离散特征共有 3 种
对于类别为 0 的样本,第 0 维特征为 4.0 的样本共有 4 个
对于类别为 0 的样本,第 0 维特征为 5.0 的样本共有 34 个
对于类别为 0 的样本,第 0 维特征为 6.0 的样本共有 4 个
对于类别为 0 的样本,第 1 维的离散特征共有 2 种
对于类别为 0 的样本,第 1 维特征为 3.0 的样本共有 25 个
对于类别为 0 的样本,第 1 维特征为 4.0 的样本共有 17 个
对于类别为 0 的样本,第 2 维的离散特征共有 2 种
对于类别为 0 的样本,第 2 维特征为 1.0 的样本共有 17 个
对于类别为 0 的样本,第 2 维特征为 2.0 的样本共有 25 个
对于类别为 0 的样本,第 3 维的离散特征共有 2 种
对于类别为 0 的样本,第 3 维特征为 0.0 的样本共有 41 个
对于类别为 0 的样本,第 3 维特征为 1.0 的样本共有 1 个
对于类别为 1 的样本共有 40 个
对于类别为 1 的样本,第 0 维的离散特征共有 3 种
对于类别为 1 的样本,第 0 维特征为 5.0 的样本共有 5 个
对于类别为 1 的样本,第 0 维特征为 6.0 的样本共有 27 个
对于类别为 1 的样本,第 0 维特征为 7.0 的样本共有 8 个
对于类别为 1 的样本,第 1 维的离散特征共有 2 种
对于类别为 1 的样本,第 1 维特征为 2.0 的样本共有 12 个
对于类别为 1 的样本,第 1 维特征为 3.0 的样本共有 28 个
对于类别为 1 的样本,第 2 维的离散特征共有 3 种
对于类别为 1 的样本,第 2 维特征为 3.0 的样本共有 3 个
对于类别为 1 的样本,第 2 维特征为 4.0 的样本共有 24 个
对于类别为 1 的样本,第 2 维特征为 5.0 的样本共有 13 个
对于类别为 1 的样本,第 3 维的离散特征共有 2 种
对于类别为 1 的样本,第 3 维特征为 1.0 的样本共有 29 个
对于类别为 1 的样本,第 3 维特征为 2.0 的样本共有 11 个
对于类别为 2 的样本共有 41 个
对于类别为 2 的样本,第 0 维的离散特征共有 4 种
对于类别为 2 的样本,第 0 维特征为 8.0 的样本共有 5 个
对于类别为 2 的样本,第 0 维特征为 5.0 的样本共有 1 个
对于类别为 2 的样本,第 0 维特征为 6.0 的样本共有 23 个
对于类别为 2 的样本,第 0 维特征为 7.0 的样本共有 12 个
对于类别为 2 的样本,第 1 维的离散特征共有 3 种
对于类别为 2 的样本,第 1 维特征为 2.0 的样本共有 4 个
对于类别为 2 的样本,第 1 维特征为 3.0 的样本共有 34 个
对于类别为 2 的样本,第 1 维特征为 4.0 的样本共有 3 个
对于类别为 2 的样本,第 2 维的离散特征共有 4 种
对于类别为 2 的样本,第 2 维特征为 4.0 的样本共有 1 个
对于类别为 2 的样本,第 2 维特征为 5.0 的样本共有 17 个
对于类别为 2 的样本,第 2 维特征为 6.0 的样本共有 20 个
对于类别为 2 的样本,第 2 维特征为 7.0 的样本共有 3 个
对于类别为 2 的样本,第 3 维的离散特征共有 2 种
对于类别为 2 的样本,第 3 维特征为 1.0 的样本共有 1 个
对于类别为 2 的样本,第 3 维特征为 2.0 的样本共有 40 个
P(Y)的先验概率:
{0: 0.34146341463414637, 1: 0.3252032520325203, 2: 0.3333333333333333}
P(X,Y)的联合概率:
[[{4.0: 0.03794037940379404, 5.0: 0.2655826558265583, 6.0: 0.03794037940379404}, {3.0: 0.2017738359201774, 4.0: 0.13968957871396895}, {1.0: 0.13968957871396895, 2.0: 0.2017738359201774}, {0.0: 0.3259423503325942, 1.0: 0.015521064301552107}], [{5.0: 0.04537719795802609, 6.0: 0.21176025713745508, 7.0: 0.06806579693703914}, {2.0: 0.10065814943863724, 3.0: 0.22454510259388305}, {3.0: 0.030251465305350726, 4.0: 0.189071658158442, 5.0: 0.10588012856872754}, {1.0: 0.23228803716608595, 2.0: 0.09291521486643438}], [{8.0: 0.044444444444444446, 5.0: 0.014814814814814814, 6.0: 0.17777777777777778, 7.0: 0.0962962962962963}, {2.0: 0.03787878787878787, 3.0: 0.26515151515151514, 4.0: 0.0303030303030303}, {4.0: 0.014814814814814814, 5.0: 0.13333333333333333, 6.0: 0.15555555555555556, 7.0: 0.029629629629629627}, {1.0: 0.015503875968992248, 2.0: 0.3178294573643411}]]
acc: 0.9629629629629629