朴素贝叶斯的理解
一、背景
朴素贝叶斯是基于贝叶斯定理与特征条件独立假设的分类方法。对于给定的训练数据集,首先基于特征条件独立假设学习输入输出的联合概率分布,然后基于模型,对给定的输入x,利用贝叶斯定理求出后验概率的最大的输出y。
二、贝叶斯定理
2.1、贝叶斯定理公式
具体的推导可以参考网上的博文,这里不再进行叙述。
其中P(A)称之为先验概率,我们希望求得的P(A|B)称之为后验概率。
单纯的看这个公式很难理解贝叶斯的含义,这里用周志华西瓜书中例子来进行更好的理解。
2.2、西瓜的例子
假设我们手里有了一个西瓜,它有一系列的特征,那么我们现在需要根据这些特征来判断这个是好瓜还是坏瓜呢?这也就变成了一个数理统计里面称为条件概率的东西,也就是在某个条件存在的基础上,我们来求某一个事件发生的概率,比如事件B发生的条件下求事件A发生的概率,我们可以写成P(A|B).
那我们西瓜的例子来说,事件B是什么?当我是我们可以观察到的一系列的这个瓜的特征值了。假设我们用加粗的X来表示,因为特征很多,加粗表示这是一个特征向量,X=x1,x2,...,Xn。那么我们要求的就是基于这个条件下这个瓜是好瓜或者是坏瓜的事件的概率。就是求P("好瓜"|X)或者P("坏瓜"|X)。那这个怎么求呢?当然是使用上面的贝叶斯公式了。
最终我们可以写出
来比较这两个哪个的概率大,那么我们就认为我们的这个瓜是好瓜还是坏瓜。
三、朴素贝叶斯
3.1、计算困难
既然已经有了可以求概率的公式,那我们可以着手进行计算了,首先是先验概率P(Ci)(这里换成字母C表示类别以及下标i表示第i类,当然在西瓜的例子里面只有两个类别,那就是“好瓜”和“坏瓜”),这个很好计算,只用统计出“好瓜”和“坏瓜”各有多少个,然后除以全部的个数就可以得出相应的概率了。
这边先看分母,因为在计算中我们用到的特征数据都是一样的,所以分母完全可以当成一个常数,也就是我们的公式可以简化成:
P(Ci)可以容易求出,但是P(X/Ci)就很困难了。因为把这个展开后为:
理论上这个可以利用我们的数据集来进行估计的,但是现实情况是,n的值往往非常大(属性非常多),而我们的数据集往往不能保证我们的样本包含了属性值的所有可能组合。那么很多p(X|ci)我们估计得到的值就是0。然而这些样本很可能仅仅是我们的数据集中没包含到,即“未被观测到”,但不代表它们现实中“出现概率为0”。
3.2、引入朴素贝叶斯
朴素贝叶斯对条件概率分布作了条件独立性的假设,由于这是一个较强的假设,朴素贝叶斯由此得名。有了这个假设,我们就可以这样计算P(X/Ci):
P(x1/ci)P(x2/ci)...P(xn/ci)
没错,就是把每个特征独立的拆出来写成连乘的形式来计算这个概率。
四、几个细节处理
4.1、连乘操作
引入连乘操作后可能导致一个问题,那就是数据量大了之后,进行多次的连乘操作可能导致结果下溢,也就是最后算出的概率为0了,所以把连乘操作改为取对数操作,即logP(X/ci),展开后把每个概率取对数后进行相加。
4.2、拉普拉斯平滑
由于我们验证的西瓜中有些特征属性可能数据集中不会出现,导致最终算出的概率为0,但现实中这种瓜是存在的,所以引入拉普拉斯平滑来进行处理。也就是计算公式是修改为:
N表示训练集D中可能的类别树,Ni表示第i个属性可能的取值数
4.3、离散数据和连续型数据的处理
对于离散数据只需要把对应特征的属性个数加起来除以总数即可,而连续型数据则需要借助概率密度函数,此处假设数据服从高斯分布,用高斯密度函数来计算连续型数据的概率。
Python例子
此处用Python实现西瓜书上151页的例子,数据集是西瓜数据集3.0。
整体的思路:使用两个全局变量来存储好瓜和坏瓜在数据集中的索引,遍历待分类数据的数据,拿出待分类的特征属性来进行概率计算,,每次计算都需要算出特征属性值在所有好瓜或者坏瓜上的概率,计算概率时要区分离散数据以及连续型数据,加入拉普拉斯平滑和取对数运算,最终比较各自大小,得出分类结果。
from collections import Counter
import numpy as np
import pandas as pd
import math
# 全局变量 存放 好瓜的索引位置
good_melon_List = []
# 全局变量 存放 坏瓜的索引位置
bad_melon_List = []
# 全局变量 存放 基于好瓜的后验概率
the_good_melon_probability = []
# 全局变量 存放 基于坏瓜的后验概率
the_bad_melon_probability = []
# 全局变量 记录每列有多少个不同属性
kindsOfAttribute={}
# 计算先验概率
def Cal_Prior_probability(y_train):
cnt = Counter(y_train)
prior = {}
for key in cnt.keys():
# 加入拉普拉斯平滑
prior[key] = round((cnt[key]+1) / (len(y_train)+2),3)
return prior
# 获取数据集以及处理
def create_data():
# 读取 csv文件
dataset = pd.read_csv('watermelon_3.csv', delimiter=",")
del dataset['编号']
# 获取全部数据
X = dataset.values[:, :-1]
# 获取数据的行数和列数
m, n = np.shape(X)
# 对数据进行规范,保留三位小数
for i in range(m):
X[i, n - 1] = round(X[i, n - 1], 3)
X[i, n - 2] = round(X[i, n - 2], 3)
# 获取最后一列数据 即 西瓜所属分类(好瓜 坏瓜)
y = dataset.values[:, -1]
for i in range(n):
kindsOfAttribute[i] = len(set(X[:, i]))
# 获取好瓜 坏瓜的位置
for i in range(len(y)):
if y[i] == '是':
good_melon_List.append(i)
else:
bad_melon_List.append(i)
return X,y
# 计算概率
def Cal_probability(X,attribute,col_id,melon_class,cur_List,the_input_data):
"""
:param X: 训练数据
:param attribute: 待分类中的特征属性
:param col_id: 列id
:param melon_class: 瓜的类别(好 坏)
:param cur_List:(当前处理的数据列表(里面全是好瓜或者全是坏瓜))
:param the_input_data:(输入待预测的数据)
:return:
"""
# 初始化概率
the_probability = 0
# 根据西瓜数据特点 列id在6之后的数据为连续型数据 所以需要概率密度函数来处理
if col_id >= 6:
# 当前处理的列数据(需要区分好瓜还是坏瓜)
cur_data = X[cur_List,col_id]
# 求均值
mean = cur_data.mean()
# 求标准差
std = cur_data.std()
# 把待分类数据转换为浮点数 用以后续的计算
the_input_data = float(the_input_data)
# 计算概率密度函数
exponent = math.exp(-(math.pow(the_input_data - mean, 2) / (2 * math.pow(std, 2))))
the_probability = (1 / (math.sqrt(2 * math.pi) * std)) * exponent
# 针对离散数据的处理
else:
# 遍历当前处理的所有好瓜或者坏瓜数据
for i in cur_List:
# 出现相同属性则对应概率+1
if(X[i,col_id]) == attribute:
the_probability += 1
the_probability = (the_probability+1) / (len(cur_List) + kindsOfAttribute[col_id])
return the_probability
# 训练数据
def fit(X_train,y_train,attribute,ColID,X_test):
"""
:param X_train: 训练数据
:param y_train: 训练数据
:param attribute: 待分类属性
:param ColID: 列id
:param X_test: 测试数据
:return:
"""
# 获取类别列表(用set去除重复类)
labels = list(set(y_train))
# 构建每个类别对应的数据 用字典的形式
# 键为类别 值为对应的数据
# 初始数据格式 {0.0: [], 1.0: []}
data = {label: [] for label in labels}
cur_List = []
for label in labels:
# 计算好瓜的各个特征概率
if label == '是':
cur_List = good_melon_List
prob = Cal_probability(X=X_train,attribute=attribute,col_id=ColID,melon_class=label,cur_List=cur_List,the_input_data=X_test[ColID])
data[label].append(round(prob,3))
# 计算坏瓜的各个特征概率
if label == '否':
cur_List = bad_melon_List
prob = Cal_probability(X=X_train,attribute=attribute,col_id=ColID,melon_class=label,cur_List=cur_List,the_input_data=X_test[ColID])
data[label].append(round(prob,3))
# 分别把概率添加到全局变量中
the_good_melon_probability.append(*data['是'])
the_bad_melon_probability.append(*data['否'])
# 最终的预测
def prediction(y_train):
# 计算概率时为避免连乘操作,使用log操作把概率转换成相加的形式
good = 0
bad = 0
prior = Cal_Prior_probability(y_train=y)
for i in the_good_melon_probability:
good = math.log(i) + good
good = math.log(prior['是']) + good
for j in the_bad_melon_probability:
bad = math.log(j) + bad
bad = math.log(prior['否']) + bad
if good > bad:
return '好'
else:
return '否'
X,y = create_data()
the_test = np.array(
['青绿','蜷缩','浊响','清晰','凹陷','硬滑',0.697,0.460]
)
for i in range(len(the_test)):
fit(X_train=X,y_train=y,attribute=the_test[i],ColID=i,X_test=the_test)
Cal_Prior_probability(y_train=y)
print(prediction(y_train=y))