关于贝叶斯分类器的一份介绍
在这篇文章中,我将介绍贝叶斯分类器有关的东西,其中将包括贝叶斯定理、极大似然估计、朴素贝叶斯分类器表达式、贝叶斯网与EM算法等内容。
一、贝叶斯定理
1.1 贝叶斯学派
在参数估计上,统计学界里有两个学派分别提供了不同的解决思路,首先是贝叶斯学派,他们认为:参数是未观察到的随机变量,其本身存在分布,所以可以假定参数服从一个先验分布,然后基于观测到的数据来计算参数的后验分布;然后是频率主义学派,他们认为:参数是未知的,但却客观存在着固定值,因此,就可以通过优化似然函数等准则来确定参数值。
而贝叶斯定理正是贝叶斯学派的基础定理之一,此外稍后将提及的极大似然估计则是频率主义学派提出来的。
1.2 贝叶斯定理推导
定义:
我们假定对于事件A与B的发生的概率分别为:P(A)与P(B)
然后二者同时发生的联合概率为:P(A∩B)
以及条件概率:在A发生的条件下,B也发生的概率为:P(B|A)和在B发生的条件下,A也发生的概率为:P(A|B)
联合概率:
其中,关于联合概率可以表示为:
P(A∩B)=P(A)P(B|A)或P(B∩A)=P(B)P(A|B)
推导:
由上述的联合概率的两种表达方式可知,P(A)P(B|A)=P(B)P(A|B),然后:
而这就是贝叶斯定理的公式了。
二、极大似然估计(MLE)
2.1 基本思想
假设我们有一组独立同分布(IID)的观测数据 x=(x1,x2,…,xn),这些数据来自于某个概率分布,该分布的参数为 θ。我们的目标是估计这个参数 θ。
极大似然估计的基本思想是找到一个参数值 θ^,使得给定这个参数值时,观测到的数据 x 的概率最大。
2.2 似然函数
我们给定似然函数L(θ;x),它是参数 θ 的函数,表示给定 θ 时,观测到数据 x 的概率:
对于独立同分布的数据,似然函数可以表示为各个观测值的概率的乘积:
2.3 极大似然估计定义
极大似然估计的目标是最优化似然函数 L(θ;x),即寻找参数 θ 的值 θ^,使得似然函数 L(θ;x) 达到最大:
2.4 推导
为了便于求解,我们通常对似然函数取对数,得到对数似然函数LL(θ;x):
则2.3中式子变为:
2.5 求解
求解极大似然估计通常需要对对数似然函数求导,并令导数等于零来找到极值点:
然后解这个方程来得到 θ^ 的值。有时候,需要解非线性方程组,这时可能需要用到数值方法来求解。
三、朴素贝叶斯分类器
3.1 介绍
朴素贝叶斯分类器(Naive Bayes Classifier)是一种基于贝叶斯定理的概率分类器,它假设特征之间相互独立。尽管这个假设在现实中很少成立,但朴素贝叶斯分类器在许多情况下仍然表现出良好的性能,尤其是在文本分类、垃圾邮件过滤、情感分析等领域。
其中,在这个名词里“朴素”一次的意思是假设特征之间的条件独立性。
3.2 表达式
因为基于属性条件独立性假设,则原贝叶斯定理可改写为:(其中,我们将A与B用c与x来替换)
其中n为属性数目。
因此,朴素贝叶斯分类器的表达式为:
而其中的先验概率为:
对于离散属性而言,令表示中第i个属性上取值为xi的样本组成的集合,则条件概率P(xi|c)为:
对于连续属性则考虑概率密度函数,假定P(xi|c)~N(,),其中与fe分别表示均值与方差,因此则有:
3.3 朴素贝叶斯分类器变种
朴素贝叶斯分类器有不同的变种,常见的有:
多项式朴素贝叶斯(Multinomial Naive Bayes):适用于特征是离散的多分类问题,如文本分类。
伯努利朴素贝叶斯(Bernoulli Naive Bayes):适用于特征是二进制的情况,如文档分类中的词汇存在与否。
高斯朴素贝叶斯(Gaussian Naive Bayes):适用于特征是连续的情况,假设特征服从高斯分布。
3.4 拉普拉斯修正
拉普拉斯修正(Laplace smoothing)又称加一平滑或加λ平滑,它在处理概率估计时为避免因数据稀疏或0频率而导致的概率为0的情况。
其原理为:通过在概率估计中加入一个小的正数来避免估计值为0的情况。如此是为防止模型在遇到从未见到的样本时错误给出0概率的预测结果,从而使模型更加健壮。
在MLE中,原本为:,当在加入了拉普拉斯修正后则变形为;
其中,α是修正因子,值通常为1;V是特征的取值数量。
3.5 python实现
如下是一个用numpy创建数据集,并用朴素贝叶斯分类器来进行分类的代码:
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import GaussianNB
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score# 创建一个简单的二维数据集
X = np.array([[1.0, 2.0], [2.0, 3.0], [1.5, 2.5], [3.0, 4.0], [3.5, 4.5], [4.0, 5.0]])
y = np.array([0, 0, 0, 1, 1, 1]) # 类标签# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)# 实例化并训练
gnb = GaussianNB()
gnb.fit(X_train, y_train)# 预测测试集
predictions = gnb.predict(X_test)# 输出预测结果
print("Predictions:", predictions)# 评估模型性能
print("Confusion Matrix:\n", confusion_matrix(y_test, predictions))
print("Classification Report:\n", classification_report(y_test, predictions))
print("Accuracy Score:", accuracy_score(y_test, predictions))
此时的代码里没有拉普拉斯修正,因为在sklearn中的GaussianNB中默认是没有的,所以在需要时我们要自己添加,我们可以通过自定义一个继承原有的GaussianNB并带有拉普拉斯的朴素贝叶斯分类器或是手动添加,如下就是手动添加的代码:
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import GaussianNB
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score# 创建一个简单的二维数据集
X = np.array([[1.0, 2.0], [2.0, 3.0], [1.5, 2.5], [3.0, 4.0], [3.5, 4.5], [4.0, 5.0]])
y = np.array([0, 0, 0, 1, 1, 1]) # 类标签# 应用拉普拉斯修正(添加一个小的正值)
epsilon = 0.1
X += epsilon# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)# 实例化并训练
gnb = GaussianNB()
gnb.fit(X_train, y_train)# 预测测试集
predictions = gnb.predict(X_test)# 输出预测结果
print("Predictions:", predictions)# 评估模型性能
print("Confusion Matrix:\n", confusion_matrix(y_test, predictions))
print("Classification Report:\n", classification_report(y_test, predictions))
print("Accuracy Score:", accuracy_score(y_test, predictions))
四、半朴素贝叶斯分类器
在朴素贝叶斯分类器中,我们假设属性间全都不相关的,但在实际的现实中这往往不成立。于是,可以考虑将这个前提假设适当放宽松。所以,可以考虑“独依赖估计”策略(ODE),即假设每个属性在类别之外最多仅依赖一个其他属性,即:
其中为属性所依赖的属性,称为父属性。而建立在这种策略上的学习方法就是半朴素贝叶斯分类器。
其中最直接的做法是假设多有属性都依赖于同一属性,成为“超父”,然后通过交叉验证等方法来确定超父属性,于是形成了SPODE方法。如下图x1就是超父属性:
此外还有TAN,其是在最大带权生成树算法的基础上通过一定步骤来获得如下的树形结构的:
而AODE是 一种基于集成学习机制且更强大的独依赖分类器。它与SPODE不同,它尝试将每个属性都作为超父来构建SPODE,然后将具有足够训练数据支撑的SPODE集成起来作为最终结果。
(上述的两图不够美观,因为我是用graphviz函数库来制作的图,所以我建议大家可以看西瓜书的155页的图像,它更美观。另外关于画出的上述图像的代码如下,不过只有第一幅的,因为两幅几乎一样而只需更改与增减edge()函数即可有第一幅变为第二幅。)
import graphviz
import matplotlib.pyplot as plt
from imageio import imreadax = plt.gca()# 创建一个有向图,并设置节点和边的属性
mygraph = graphviz.Digraph(node_attr={'shape': 'circle'},edge_attr={'labeldistance': '10.5'},format='png',graph_attr={'rankdir': 'LR'}) # LR 表示从左到右排列# 设置 rank 同一等级
with mygraph.subgraph(name='cluster_x') as c:c.attr(rank='same', style='invis')c.node("1", "x1")c.node("2", "xd")c.node("3", "x2")c.node("4", "x3")# 添加 y 节点
mygraph.node("0", "y")# 添加边
mygraph.edge("0", "1")
mygraph.edge("0", "2")
mygraph.edge("0", "3")
mygraph.edge("0", "4")mygraph.edge("1", "2")
mygraph.edge("1", "3")
mygraph.edge("1", "4")# 渲染图形
mygraph.render("SPODE")
ax.imshow(imread("SPODE.png"))
ax.set_axis_off()
plt.show()
五、贝叶斯网
5.1 概念
贝叶斯网(Bayesian Network,简称 BN)又称“信念网”,它借助有向无环图(DAG)来刻画属性间的依赖关系,并使用条件概率表(CPT)来描述属性间的联合概率分布。
5.2 组成
一个贝叶斯网B由结构G与参数θ两部分构成,即B=<G,θ>。G是一个有向无环图,其每个节点都对应一个属性,若两个属性有直接依赖关系则它们由一条边连接起来,而参数θ定量描述这种依赖关系,假设属性在G中的父节点集为,则θ包含了每个属性的条件概率表:
5.3 连接模式
在贝叶斯网中,节点之间的连接模式可以反映出变量之间的不同依赖关系。其中,“同父结构”(Common Parent Structure)和“V型结构”(V-Structure)是两种重要的模式,而“顺序结构”(Serial Connection)则是另一种常见的连接方式。下面分别解释这三种结构:
同父结构:
同父结构是指多个变量共享一个父亲的情况,具体的可如下图:
此时,x2与x3相互独立,而x1为公共父节点,那么就意味着当我们知道了x1的值后,x2与x3间就没有直接的关系了。
V型结构:
V型结构是一种特殊的结构,其中一个节点有两个父节点,这两个父节点之间没有直接连接。如图:
x2有两个父节点 x1 和 x3,但 x1 和 x3 之间没有直接的连接。这种结构通常表示x2 是由两个独立的因素 x1 和 x3 共同导致的结果。所以,一旦给定了x2,那么x1与x3就会变得相关。这是因为x2的观察值会影响我们对于x1与x3的信念。
顺序结构:
顺序结构指的是一个节点影响另一个节点,后者再影响下一个节点,依此类推。如图所示:
这种结构表示了一个因果链,其中每个节点都是前一个节点的后果。这意味着如果知道了x2,那么x1与x3间就没有直接的关系了。
我们观察这个顺序结构的图,我们会发现它与马尔科夫链十分相似,而实际上马尔科夫链确实是一种特殊的顺序结构其中每个状态仅依赖于前一个状态。(具体有关马尔科夫链的内容我之前有介绍过,在此不再多言)
5.4 学习过程
在贝叶斯网中,如果网络结构已知,即属性间的依赖关系已知,则其学习过程相对简单,只要通过对训练样本计数然后估计出每个节点的条件概率即可。但在实际中,我们往往并不知晓网络结构,所以此时的首要任务就是根据训练数据集来找出结构最优的贝叶斯网络来。而“评分搜索”是求解这一问题的常用办法。具体的,我们会先定义一个评分函数,然后以此来估计贝叶斯网络与训练数据的契合程度,最后就基于这个评分函数来寻找最优结构的贝叶斯网络。
此时,我们就需要考虑这个评分函数如何构造了。我们常用的评分函数有:似然函数(Likelihood)、AIC(Akaike Information Criterion)、BIC(Bayesian Information Criterion)、MDL(Minimum Description Length)。
这些常用的评分函数通常是基于信息论准则的,此类准则将数学问题看作一个数据压缩任务,学习目标是找到一个能以最短编码长度描述训练数据的模型,此时编码的长度包括描述模型自身所需的编码位数和使用该模型描述数据所需的编码位数。
我们选择那个综合编码长度最短的贝叶斯网,这就是“最小描述长度”(MDL)准则。
对于给定的数据集D={x1,x2,x3……xm},贝叶斯网B在D上的评分函数可写为:
当f(θ)=1,即每个参数用1编码位描述,则得到AIC评分函数:
当f(θ)=,即每个参数用编码位描述,则得到BIC评分函数:
当f(θ)=0时,即不计算对网络进行编码的长度,则评分函数退化为负对数似然,相应的,学习任务退化为极大似然估计。
对于从所有可能的网络结构空间中搜索最优贝叶斯网结构是一个NP问题,所以无法快速解决。不过有两种常用的策略可以在有限时间内求得近似解,这两种策略分别为:贪心法与通过给网络结构施加约束条件来削弱搜索空间的方法。此外,像动态规划方法虽然也可以用,但它是会用在一些特殊环境下的。
5.5 推断
贝叶斯网训练好之后就能用来回答“查询”,即通过一些属性变量的观测值来推断其他属性变量的取值。这样通过变量观测值来推断待查询变量的过程称为“推断”,已知变量观测值称为“证据”。
如果直接根据贝叶斯网定义的联合概率分布来精确计算后验概率,那么这样的“精确计算”会因为是个NP问题而无法很好地求解。在现实中,贝叶斯网的近似推断常使用吉布斯采样来完成,这是一种随机采样方法。
吉布斯思想为:吉布斯采样利用了条件概率分布来逐步更新每个变量的值。假设我们有一个联合分布 p(x1,x2,…,xn),其中 xi 是随机变量。如果我们能够容易地从每个变量的条件概率分布中抽样,即 p(xi∣x−i),那么就可以通过依次更新每个变量的值来近似联合分布。
实质上,吉布斯采样是在贝叶斯网所有变量的联合状态空间与证据E=e一致的子空间中进行“随机漫步”。每一步依赖于前一步的状态,也就是一个马尔科夫链。
5.6 python实现
如下是一个用pgmpy来实现贝叶斯网络的代码:
import pgmpy.models
from pgmpy.factors.discrete import TabularCPD
from pgmpy.inference import VariableElimination# 实例化
model = pgmpy.models.BayesianNetwork([('Weather', 'Activity')])# Weather CPD
weather_cpd = TabularCPD(variable='Weather', variable_card=3,values=[[0.5], [0.3], [0.2]],evidence=None, evidence_card=None)# Activity CPD
activity_cpd = TabularCPD(variable='Activity', variable_card=2,values=[[0.8, 0.5, 0.2],[0.2, 0.5, 0.8]],evidence=['Weather'], evidence_card=[3])# 添加 CPD 到模型
model.add_cpds(weather_cpd, activity_cpd)# 检查模型是否有效
assert model.check_model()# 创建推理器
infer = VariableElimination(model)# 查询天气为晴天时活动为室外的概率
query_results = infer.query(variables=['Activity'], evidence={'Weather': 0})
print(query_results)# 查询天气为阴天时活动为室外的概率
query_results = infer.query(variables=['Activity'], evidence={'Weather': 1})
print(query_results)# 查询天气为雨天时活动为室外的概率
query_results = infer.query(variables=['Activity'], evidence={'Weather': 2})
print(query_results)
其输出如下:
+-------------+-----------------+
| Activity | phi(Activity) |
+=============+=================+
| Activity(0) | 0.8000 |
+-------------+-----------------+
| Activity(1) | 0.2000 |
+-------------+-----------------+
+-------------+-----------------+
| Activity | phi(Activity) |
+=============+=================+
| Activity(0) | 0.5000 |
+-------------+-----------------+
| Activity(1) | 0.5000 |
+-------------+-----------------+
+-------------+-----------------+
| Activity | phi(Activity) |
+=============+=================+
| Activity(0) | 0.2000 |
+-------------+-----------------+
| Activity(1) | 0.8000 |
+-------------+-----------------+
六、EM算法
在贝叶斯分类器中,我们会遇到隐变量(那些未观测到的变量的学名叫“隐变量”),此时就需要EM算法来帮助我们去估计模型参数,如下是对于EM算法的介绍:
6.1 概念
EM 算法(Expectation-Maximization Algorithm)是一种用于求解含有隐变量的概率模型参数的最大似然估计(Maximum Likelihood Estimation, MLE)或最大后验概率估计(Maximum A Posteriori Estimation, MAP)的迭代算法。EM 算法广泛应用于各种统计模型中,特别是在处理不完整数据的情况下。
6.2 基本思想
EM 算法的核心思想是在给定数据的情况下,通过引入隐变量来简化模型的参数估计问题。算法通过交替执行两个步骤来进行迭代:E 步(Expectation Step)和 M 步(Maximization Step)。随着迭代的进行,算法逐渐逼近全局最优解或者局部最优解。
6.3 步骤
关于EM算法,首先以初始值为起点,对于迭代执行以下步骤直至收敛:
1.基于推断隐变量Z的期望,记为;
2.基于已观测到的变量X和对参数θ做极大似然估计,记为
简单说就是EM算法使用两个步骤交替计算,第一步期望(E)步,利用当前估计的参数值来计算对数似然的期望值;第二步是最大化(M)步,寻找能使E步产生的似然期望最大化的参数值;然后利用得到的参数值用于新的E步中,如此这样反复迭代直至收敛到局部最优解。
6.4 python实现
我们假设我们有一个简单的数据集,其中包含两个类别(Class A 和 Class B),每个类别都由两个高斯分布组成。我们将使用 EM 算法来估计这些高斯分布的参数,并使用这些参数来构建贝叶斯分类器。
那么首先我们来生成模拟数据:
import numpy as np
from sklearn.mixture import GaussianMixture# 生成模拟数据
np.random.seed(0)
n_samples = 1000# 类别 A 的数据
class_a_samples = np.concatenate([np.random.normal(loc=[0, 0], scale=[1, 1], size=(int(n_samples / 2), 2)),np.random.normal(loc=[3, 3], scale=[1, 1], size=(int(n_samples / 2), 2))
], axis=0)# 类别 B 的数据
class_b_samples = np.concatenate([np.random.normal(loc=[-3, 3], scale=[1, 1], size=(int(n_samples / 2), 2)),np.random.normal(loc=[3, -3], scale=[1, 1], size=(int(n_samples / 2), 2))
], axis=0)# 合并数据
X = np.vstack([class_a_samples, class_b_samples])
y = np.array([0] * n_samples + [1] * n_samples)
然后我们用EM算法来估计每个类别的GMM参数:
# 使用 EM 算法估计 GMM 参数
gmm_a = GaussianMixture(n_components=2, random_state=0)
gmm_a.fit(class_a_samples)gmm_b = GaussianMixture(n_components=2, random_state=0)
gmm_b.fit(class_b_samples)# 打印估计的参数
print("Class A GMM Parameters:")
print(gmm_a.weights_)
print(gmm_a.means_)
print(gmm_a.covariances_)print("\nClass B GMM Parameters:")
print(gmm_b.weights_)
print(gmm_b.means_)
print(gmm_b.covariances_)
我们现在有了每个类别的GMM参数,所以此时可以使用这些参数来构建朴素贝叶斯分类器:
from sklearn.naive_bayes import GaussianNB
from sklearn.model_selection import train_test_split# 分割数据集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)# 实例化
gnb = GaussianNB()# 拟合
gnb.fit(X_train, y_train)# 预测
y_pred = gnb.predict(X_test)# 输出
accuracy = gnb.score(X_test, y_test)
print(f"Accuracy: {accuracy}")
对于上述的代码,我们可以得到输出如下:
Class A GMM Parameters:
[0.50409437 0.49590563]
[[ 3.00946851 2.9893725 ][-0.07822582 -0.03393617]]
[[[ 9.62748933e-01 8.04984394e-04][ 8.04984394e-04 9.56403951e-01]][[ 9.31013032e-01 -1.81947837e-02][-1.81947837e-02 9.93083259e-01]]]Class B GMM Parameters:
[0.49999999 0.50000001]
[[ 2.94322452 -2.98110962][-3.07723774 2.97478052]]
[[[ 1.01792369 -0.05199465][-0.05199465 1.04296822]][[ 0.94830722 0.04885493][ 0.04885493 0.87102073]]]
Accuracy: 0.8775
此上