贝叶斯分类器
贝叶斯分类器是一种基于贝叶斯决策理论的分类器。
贝叶斯分类器是各种分类器中分类错误概率最小或者在预先给定代价的情况下平均风险最小的分类器。它的设计方法是一种最基本的统计分类方法。其分类原理是通过某对象的先验概率,利用贝叶斯公式计算出其后验概率,即该对象属于某一类的概率,选择具有最大后验概率的类作为该对象所属的类。
贝叶斯决策论
在实际应用中,会出现很多案例与类标签之间的关系是不确定的。也就是说,测试记录的样本与某些训练样例相同,但是也不能正确地预测它的类标签。产生这种现象的原因可能是噪声,或者是存在潜在影响因素。例如,考虑根据一个人的饮食和锻炼频率预测他是否患有心脏病的危险。这个问题是具有不确定性的,因为虽然饮食健康且经常锻炼的人患心脏病的概率很小,但是可能存在遗传、过量吸烟等原因而患病。贝叶斯定理(Bayes theorem)就是将类先验知识与从数据中收集的新证据相结合的统计原理。
朴素贝叶斯分类
朴素贝叶斯分类器的基本概念包括以下几点:
1. 理论基础:朴素贝叶斯是建立在贝叶斯定理的基础上,使用概率统计知识对样本数据集进行分类的方法。
2. 核心思想:朴素贝叶斯的核心是通过考虑特征概率来预测分类。对于给出的待分类样本,算法会求解在此样本出现的条件下各个类别出现的概率,并判断属于概率最大的那个类别。
3. 独立性假设:朴素贝叶斯假设各特征之间相互独立,即某个特征对于给定类别的影响独立于其他特征。这一假设简化了计算过程,但可能在实际中并不总是成立。
4. 数学表达:朴素贝叶斯算法涉及到的数学概念主要包括条件概率和联合概率。条件概率是指在某事件已经发生的条件下另一事件发生的概率。而朴素贝叶斯通过贝叶斯定理将难以直接计算的条件概率转换为更容易确定的其他概率值的计算。
贝叶斯公式
P(cj)代表还没有训练模型之前,根据历史数据/经验估算cj 拥有的初始概率。P(cj)常被称为cj的先验概率(prior probability) , 它反映了cj的概率分布,该分布独立于样本。
通常可以用样例中属于cj的样例数|cj|比上总样例数|D|来
近似,即:
全概率公式:
条件概率公式:
寻找一个判定准则最小化所有样本的条件风险总和,因此就有了贝叶斯判定准则(Bayes decision rule):为最小化总体风险,只需在每个样本上选择那个使得条件风险最小的类标。
若损失函数λ取0-1损失,则有:
P(A) 称为先验概率(prior probability),即在B事件发生之前,我们对A事件概率的一个判断;
P ( A ∣ B ) 称为后验概率(posterior probability),即在B事件发生之后,我们对A事件概率的重新评估;
P ( B ∣ A ) /P(B) 称为可能性函数(Likely hood),这是一个调整因子,使得预估概率更接近真实概率。
先验概率:根据以往经验分析得到的概率。
后验概率:某件事发生是由某个因素引起的可能性大小。
联合概率:联合概率是指在多元的概率分布中多个随机变量分别满足各自条件的概率。假设X和Y都服从正态分布,那么P{X<4,Y<0}就是一个联合概率,表示X<4,Y<0两个条件同时成立的概率。表示两个事件共同发生的概率。A与B的联合概率表示为 P(AB) 或者P(A,B),或者P(A∩B)。
全概率:全概率公式为概率论中的重要公式,它将对一复杂事件A的概率求解问题转化为了在不同情况下发生的简单事件的概率的求和问题。如果事件B1、B2、B3…Bn 构成一个完备事件组,即它们两两互不相容,其和为全集;并且P(Bi)大于0,则对任一事件A有P(A)=P(A|B1)P(B1) + P(A|B2)P(B2) + ... + P(A|Bn)P(Bn)。或者:p(A)=P(AB1)+P(AB2)+...+P(ABn)),其中A与Bn的关系为交)。
所以条件概率可以理解为:后验概率 = 先验概率 × 调整因子
如果"可能性函数">1,意味着"先验概率"被增强,事件A的发生的可能性变大;
如果"可能性函数"=1,意味着B事件无助于判断事件A的可能性;
如果"可能性函数"<1,意味着"先验概率"被削弱,事件A的可能性变小。
朴素贝叶斯分类器实例
西瓜实例:
1.定义数据集:
dataset = np.array([
# 色泽,根蒂,敲击,纹理,脐部,触感, 好坏
['青绿', '蜷缩', '浊响', '清晰', '凹陷', '硬滑', '好瓜'],
# ...
['青绿', '蜷缩', '沉闷', '稍糊', '稍凹', '硬滑', '坏瓜']
])
features = {
'色泽': ['青绿', '乌黑', '浅白'],
'根蒂': ['蜷缩', '稍蜷', '硬挺'],
'敲击': ['浊响', '沉闷', '清脆'],
'纹理': ['清晰', '稍糊', '模糊'],
'脐部': ['凹陷', '稍凹', '平坦'],
'触感': ['硬滑', '软粘']
}
labels = ["好瓜", "坏瓜"]
2.将特征与标签分开:
X = dataset[:, :6] #前6列为特征
Y = dataset[:, 6] #最后一列为标签
3.现在我们构造一个Bayes类,这个类的实例代表一个朴素贝叶斯分类器,其设计如下:
class Bayes:
def __init__(self, features: dict, labels: list) -> None:
pass # 初始化模型
def bayes_prob(self, arr):
pass # 计算贝叶斯概率
def train(self, X, Y):
pass # 训练模型
def predic(self, X):
pass # 模型推理
def show_params(self) -> str:
pass # 辅助函数,用来观察模型内部
4.首先我们实现一下构造函数,这个函数里要完成模型必要参数的初始化。那么模型需要一些什么参数呢?首先特征的维度、标签的种类肯定需要保存。贝叶斯公式里的核心,先验概率和似然度也需要保存。考虑到方方面面,最后构造函数设计如下:
def __init__(self, features: dict, labels: list) -> None:
self.features = features # 保存特征的维度、取值范围
self.labels = labels # 保存标签的类别
self.feature_classes = list(self.features.keys()) # 为了方便
self.condi_prob = {} # 保存条件概率/似然度的字典
self.prior_prob = {} # 保存先验概率的字典
# 保存计算概率用的训练样本统计信息,具体保存形式如下:
# {
# '好瓜':8,
# '坏瓜':9,
# '坏瓜.敲击.沉闷': 3,
# '好瓜.根蒂.稍蜷': 3,
# ...
# }
self._statistic = {}
self._samples = 0 # 计数
for i in self.labels:
# 先验概率的初始值认为各标签是等概率的
self.prior_prob[i] = 1 / len(self.labels)
# 每个标签初始化一个空的字典储存各标签下的条件概率
self.condi_prob[i] = {}
# 每个标签下的样本数清零
self._statistic[i] = 0
for j in self.feature_classes:
# i标签下j维度的的条件概率初始化
self.condi_prob[i][j] = {}
# 遍历j维度的取值范围
for k in self.features[j]:
# 初始化时假设j维度的每个特征等概率出现
self.condi_prob[i][j][k] = 1 / len(self.features[j])
# 'i.j.k'储存在i标签下,j维度的k取值,出现的次数,初始值为0
self._statistic['.'.join([i, j, k])] = 0
5.为了方便观察内部情况,设置一个辅助函数打印内部参数:
def show_params(self) -> str:
pprint(self.prior_prob)
pprint(self.condi_prob)
# pprint(self._statistic)
6.初始化完成后打印一下可以看到,目前的模型参数:
{'坏瓜': 0.5, '好瓜': 0.5}
{'坏瓜': {'敲击': {'沉闷': 0.3333333333333333,
'浊响': 0.3333333333333333,
'清脆': 0.3333333333333333},
'根蒂': {'硬挺': 0.3333333333333333,
'稍蜷': 0.3333333333333333,
'蜷缩': 0.3333333333333333},
'纹理': {'模糊': 0.3333333333333333,
'清晰': 0.3333333333333333,
'稍糊': 0.3333333333333333},
'脐部': {'凹陷': 0.3333333333333333,
'平坦': 0.3333333333333333,
'稍凹': 0.3333333333333333},
'色泽': {'乌黑': 0.3333333333333333,
'浅白': 0.3333333333333333,
'青绿': 0.3333333333333333},
'触感': {'硬滑': 0.5, '软粘': 0.5}},
'好瓜': {'敲击': {'沉闷': 0.3333333333333333,
'浊响': 0.3333333333333333,
'清脆': 0.3333333333333333},
'根蒂': {'硬挺': 0.3333333333333333,
'稍蜷': 0.3333333333333333,
'蜷缩': 0.3333333333333333},
'纹理': {'模糊': 0.3333333333333333,
'清晰': 0.3333333333333333,
'稍糊': 0.3333333333333333},
'脐部': {'凹陷': 0.3333333333333333,
'平坦': 0.3333333333333333,
'稍凹': 0.3333333333333333},
'色泽': {'乌黑': 0.3333333333333333,
'浅白': 0.3333333333333333,
'青绿': 0.3333333333333333},
'触感': {'硬滑': 0.5, '软粘': 0.5}}}
7.训练函数 :
def train(self, X, Y):
rows, cols = X.shape
for i in range(rows):
# 好瓜、坏瓜计数
self._statistic[Y[i]] += 1
for j in range(cols):
# 这里为Y[i]为好瓜坏瓜
# self.feature_classes[j]为色泽、根蒂等特征维度
# X[i, j]就是上面的某个特征维度的具体取值
# 比如 self._statistic['好瓜.色泽.青绿'] +=1
self._statistic['.'.join(
[Y[i], self.feature_classes[j], X[i, j]])] += 1
# 总的样本数量
self._samples += 1
# 统计完了开始算概率
for i in self.labels:
# 好瓜坏瓜的数量 / 总的样本的数量
# 这里分子加了1,分母加了2 (len(self.labels))是因为拉普拉斯平滑
self.prior_prob[i] = (self._statistic[i] + 1) / (self._samples+len(self.labels))
for j in self.feature_classes:
for k in self.features[j]:
# 分子:好瓜或坏瓜里面色泽、根蒂等特征维度里取值为K的样本的数量
# 分母:好瓜或者坏瓜的数量
# 分子分母额外加的数是因为拉普拉斯平滑
self.condi_prob[i][j][k] = (self._statistic['.'.join([i, j, k])] + 1) / (self._statistic[i] + len(self.features[j]))
8.用西瓜书中的数据集训练一下,再调用打印内部参数的函数可以得到下面的结果:
{'坏瓜': 0.5263157894736842, '好瓜': 0.47368421052631576}
{'坏瓜': {'敲击': {'沉闷': 0.3333333333333333, '浊响': 0.4166666666666667, '清脆': 0.25},
'根蒂': {'硬挺': 0.25, '稍蜷': 0.4166666666666667, '蜷缩': 0.3333333333333333},
'纹理': {'模糊': 0.3333333333333333, '清晰': 0.25, '稍糊': 0.4166666666666667},
'脐部': {'凹陷': 0.25, '平坦': 0.4166666666666667, '稍凹': 0.3333333333333333},
'色泽': {'乌黑': 0.25, '浅白': 0.4166666666666667, '青绿': 0.3333333333333333},
'触感': {'硬滑': 0.6363636363636364, '软粘': 0.36363636363636365}},
'好瓜': {'敲击': {'沉闷': 0.2727272727272727,'浊响': 0.6363636363636364,'清脆': 0.09090909090909091},
'根蒂': {'硬挺': 0.09090909090909091,'稍蜷': 0.36363636363636365,'蜷缩': 0.5454545454545454},
'纹理': {'模糊': 0.09090909090909091,'清晰': 0.7272727272727273,'稍糊': 0.18181818181818182},
'脐部': {'凹陷': 0.5454545454545454,'平坦': 0.09090909090909091,'稍凹': 0.36363636363636365},
'色泽': {'乌黑': 0.45454545454545453,'浅白': 0.18181818181818182,'青绿': 0.36363636363636365},
'触感': {'硬滑': 0.7, '软粘': 0.3}}}
9.接下来是预测函数,前面已经展开了贝叶斯公式,可以看出计算时只要将各个维度的概率相乘即可。而贝叶斯公式分数线下方的内容完全可以忽略不计算。
# 输入一个特征,输出每种标签的概率
def bayes_prob(self, arr):
plist = []
for i in self.labels:
idx = 0
# 先验概率P(A)
p = self.prior_prob[i]
# 各维度的条件概率连乘
for j in self.feature_classes:
p *= self.condi_prob[i][j][arr[idx]]
idx += 1
# 算完一个标签的概率
plist.append(p)
return plist
# 返回预测标签
def predic(self, X):
rows, cols = X.shape
res = []
for i in range(rows):
# 对每个样本计算各标签概率
y = self.bayes_prob(X[i])
res.append(y)
# 找出概率最大的标签的索引值
res = np.argmax(res, axis=1)
# 转换为标签
res = [self.labels[x] for x in res]
return np.array(res)
10.直接那训练的数据去跑预测结果如下:
原始:['好瓜' '好瓜' '好瓜' '好瓜' '好瓜' '好瓜' '好瓜' '好瓜' '坏瓜' '坏瓜' '坏瓜' '坏瓜' '坏瓜' '坏瓜'
'坏瓜' '坏瓜' '坏瓜']
预测:['好瓜' '好瓜' '好瓜' '好瓜' '好瓜' '好瓜' '坏瓜' '好瓜' '坏瓜' '坏瓜' '坏瓜' '坏瓜' '好瓜' '坏瓜'
'好瓜' '坏瓜' '坏瓜']
准确率:0.8235294117647058
完整代码:
from pprint import pprint
import numpy as np
dataset = np.array([
# 色泽,根蒂,敲击,纹理,脐部,触感, 好坏
['青绿', '蜷缩', '浊响', '清晰', '凹陷', '硬滑', '好瓜'],
['乌黑', '蜷缩', '沉闷', '清晰', '凹陷', '硬滑', '好瓜'],
['乌黑', '蜷缩', '浊响', '清晰', '凹陷', '硬滑', '好瓜'],
['青绿', '蜷缩', '沉闷', '清晰', '凹陷', '硬滑', '好瓜'],
['浅白', '蜷缩', '浊响', '清晰', '凹陷', '硬滑', '好瓜'],
['青绿', '稍蜷', '浊响', '清晰', '稍凹', '软粘', '好瓜'],
['乌黑', '稍蜷', '浊响', '稍糊', '稍凹', '软粘', '好瓜'],
['乌黑', '稍蜷', '浊响', '清晰', '稍凹', '硬滑', '好瓜'],
['乌黑', '稍蜷', '沉闷', '稍糊', '稍凹', '硬滑', '坏瓜'],
['青绿', '硬挺', '清脆', '清晰', '平坦', '软粘', '坏瓜'],
['浅白', '硬挺', '清脆', '模糊', '平坦', '硬滑', '坏瓜'],
['浅白', '蜷缩', '浊响', '模糊', '平坦', '软粘', '坏瓜'],
['青绿', '稍蜷', '浊响', '稍糊', '凹陷', '硬滑', '坏瓜'],
['浅白', '稍蜷', '沉闷', '稍糊', '凹陷', '硬滑', '坏瓜'],
['乌黑', '稍蜷', '浊响', '清晰', '稍凹', '软粘', '坏瓜'],
['浅白', '蜷缩', '浊响', '模糊', '平坦', '硬滑', '坏瓜'],
['青绿', '蜷缩', '沉闷', '稍糊', '稍凹', '硬滑', '坏瓜']
])
features = {
'色泽': ['青绿', '乌黑', '浅白'],
'根蒂': ['蜷缩', '稍蜷', '硬挺'],
'敲击': ['浊响', '沉闷', '清脆'],
'纹理': ['清晰', '稍糊', '模糊'],
'脐部': ['凹陷', '稍凹', '平坦'],
'触感': ['硬滑', '软粘']
}
labels = ["好瓜", "坏瓜"]
class Bayes:
def __init__(self, features: dict, labels: list) -> None:
self.features = features
self.labels = labels
self.feature_classes = list(self.features.keys())
self.condi_prob = {}
self.prior_prob = {}
self._statistic = {}
self._samples = 0
for i in self.labels:
self.prior_prob[i] = 1 / len(self.labels)
self.condi_prob[i] = {}
self._statistic[i] = 0
for j in self.feature_classes:
self.condi_prob[i][j] = {}
for k in self.features[j]:
self.condi_prob[i][j][k] = 1 / len(self.features[j])
self._statistic['.'.join([i, j, k])] = 0
def bayes_prob(self, arr):
plist = []
for i in self.labels:
idx = 0
p = self.prior_prob[i]
for j in self.feature_classes:
p *= self.condi_prob[i][j][arr[idx]]
idx += 1
plist.append(p)
return plist
def train(self, X, Y):
rows, cols = X.shape
for i in range(rows):
self._statistic[Y[i]] += 1
for j in range(cols):
self._statistic['.'.join(
[Y[i], self.feature_classes[j], X[i, j]])] += 1
self._samples += 1
for i in self.labels:
self.prior_prob[i] = (self._statistic[i] + 1) / (self._samples+len(self.labels))
for j in self.feature_classes:
for k in self.features[j]:
self.condi_prob[i][j][k] = (self._statistic['.'.join([i, j, k])] + 1) / (self._statistic[i] + len(self.features[j]))
def predic(self, X):
rows, cols = X.shape
res = []
for i in range(rows):
y = self.bayes_prob(X[i])
res.append(y)
res = np.argmax(res, axis=1)
res = [self.labels[x] for x in res]
return np.array(res)
def show_params(self) -> str:
pprint(self.prior_prob)
pprint(self.condi_prob , sort_dicts=False)
# pprint(self._statistic)
if __name__ == "__main__":
X = dataset[:, :6]
Y = dataset[:, 6]
print(X)
print(Y)
clf = Bayes(features, labels)
clf.show_params()
clf.train(X, Y)
clf.show_params()
Yp = clf.predic(X)
print(f"原始:{Y}\n预测:{Yp}\n准确率:{np.sum(Y==Yp)/len(Y)}")
朴素贝叶斯分类器小结
朴素贝叶斯分类器的优点包括:
1. 简单:朴素贝叶斯算法逻辑简单,易于理解和实现。
2. 开销小:在分类过程中,时间和空间的开销较小。
3. 性能良好:在属性相关性较小时,朴素贝叶斯的性能表现最为良好。
4. 适合多分类任务:能够处理多分类任务,适合增量式训练,尤其当数据量超过内存时,可以逐批进行增量训练。
5. 对缺失数据不太敏感:算法对缺失数据有一定的容忍度,因此即使在数据不完整的情况下也能进行有效分类。
朴素贝叶斯分类器的缺点包括:
1. 独立性假设:朴素贝叶斯模型假设属性之间相互独立,这在实际应用中往往是不成立的,特别是在属性个数较多或属性间相关性较大时,分类效果可能不佳。
2. 先验概率:需要知道先验概率,而这些概率很多时候取决于假设,假设的模型可以有很多种,因此在某些时候会由于假设的先验模型原因导致预测效果不佳。
3. 误差率:虽然理论上朴素贝叶斯模型具有最小的误差率,但实际上由于各种因素的影响,分类决策可能会存在一定的错误率。
4. 输入数据敏感:对输入数据的表达形式很敏感,这可能影响分类的准确性。
朴素贝叶斯分类器的应用领域非常广泛,主要包括:
1. 垃圾邮件过滤:通过学习邮件特征来识别垃圾邮件和正常邮件。
2. 情感分析:分析文本数据的情感倾向,如正面或负面。
3. 文档分类:自动将文档归类到不同的类别中。
4. 医疗诊断:根据病人的症状和病史来预测疾病的可能性。
总的来说,朴素贝叶斯分类器是一种简单有效的分类工具,尽管存在一些局限性,但在很多情况下仍然是一个非常有用的选择。