朴素贝叶斯分类算法是一组监督学习算法,基于贝叶斯公式并假设各个特征独立分布。
公式推导:
给定类别变量 y 以及特征向量( x1,...,xn ),
联合概率公式:
P(y,x1,...,xn)=P(y|x1,...,xn)P(x1,...,xn)=P(x1,...,xn|y)P(y)推导出贝叶斯公式:
P(y|x1,...,xn) = P(y)P(x1,...,xn|y)P(x1,...,xn)朴素独立性假设:
P(xi|y,x1,...,xi−1,xi+1,...,xn)=P(xi|y)代入贝叶斯公式:
P(y|x1,...,xn) = P(y)∏ni=1P(xi|y)P(x1,...,xn)对于给定的特征向量( x1,...,xn ),P( x1,...,xn )为常量,因此上式消去分母,等式变为等比关系式:
P(y|x1,...,xn)∝P(y)∏ni=1P(xi|y)类别变量 y 应该取使条件概率P(y | x1,...,xn )最大的 y 值,根据上述等比公式,得出分类规则如下:
ŷ =argmaxyP(y)∏ni=1P(xi|y)
算法分解:
给定训练数据
import numpy as np X = np.array([[-1, -1], [-2, -1], [-3, -2], [1, 1], [2, 1], [3, 2]]) Y = np.array([1, 1, 1, 2, 2, 2])
计算类别先验概率:P(y) = 类别为 y 的样本数/总样本数
class_prior_y1 = X[Y==1, :].shape[0] / float(X.shape[0]) # 0.5 class_prior_y2 = X[Y==2, :].shape[0] / float(X.shape[0]) # 0.5
类条件概率密度函数 P( xi | y)参数估计 :用最大似然(MLE)估计分布的参数(也即最大后验估计MAP),下面分别描述高斯分布、多项式分布、伯努利分布的参数估计步骤:
高斯分布参数估计(适用于连续特征值)
P(xi|y) = 12πσ2y,i√exp(−(xi−μy,i)22σ2y,i)
-用最大似然估计分布参数 μy,i 、 σ2y,i
-计算参数 μy,i :样本中各个类别的各个特征的均值mu_y1_x1 = np.mean( X[Y==1, :1], axis=0) # -2 mu_y1_x2 = np.mean( X[Y==1, 1:2], axis=0) # -1.33333333 mu_y2_x1 = np.mean( X[Y==2, :1], axis=0) # 2 mu_y2_x2 = np.mean( X[Y==2, 1:2], axis=0) # 1.33333333
-计算参数 σ2y,i :样本中各个类别的各个特征的方差(偏移量平方的均值,开方为标准差 σy,i )
sigma_y1_x1 = np.var( X[Y==1, :1], axis=0) + epsilon # 0.66666667 sigma_y1_x2 = np.var( X[Y==1, 1:2], axis=0) + epsilon # 0.22222222 sigma_y2_x1 = np.var( X[Y==2, :1], axis=0) + epsilon # 0.66666667 sigma_y2_x2 = np.var( X[Y==2, 1:2], axis=0) + epsilon # 0.22222222
-epsilon:附加的极小值,防止任意特征的方差为零导致的计算错误,通常取最大方差(基于全体样本)的一个很小的分数
epsilon = 1e-9 * np.var(X, axis=0).max() #4.6666666666666671e-09
-分类判别:为便于计算,对分类得分公式取自然对数(自然对数为单调递增函数)
log(P(y))+∑ni=1(−12log(2πσ2y,i)−12(xi−μy,i)2σ2y,i)
给定测试数据,分别计算各个分类的得分,取得分最大的类别为判定类别NewX = [-0.8, -1] log_likelihood_y1 = np.log(class_prior_y1) \ - 0.5 * (np.log(2. * np.pi * sigma_y1_x1) + np.log(2. * np.pi * sigma_y1_x2)) \ - 0.5 * ((NewX[0] - mu_y1_x1) ** 2 / sigma_y1_x1 + (NewX[1] \ -mu_y1_x2) ** 2 / sigma_y1_x2) # -2.906253 log_likelihood_y2 = np.log(class_prior_y2) \ - 0.5 * (np.log(2. * np.pi * sigma_y2_x1) + np.log(2. * np.pi * sigma_y2_x2)) \ - 0.5 * ((NewX[0] - mu_y2_x1) ** 2 / sigma_y2_x1 + (NewX[1] \ - mu_y2_x2) ** 2 / sigma_y2_x2) # -19.70625271 NewL = ('1' if log_likelihood_y1 >= log_likelihood_y1 else '2') #"1"
多项式分布参数估计(适用于频次计数类特征值,如文本分类)
log(P(xi|y))=xilog(θy,i)
θy,i = Ny,i+αNy+αn
log(θy,i)=log(Ny,i+α)−log(Ny+αn)-a 为先验平滑因子(>=0),保证样本中没有出现的特征不会出现0值,a=1 称为 Laplace 平滑,a <1 称为Lidstone平滑
alpha = 1
-由于特征值代表特征出现的计数,因此所有特征的值应该具有相同的量纲(频次或权重),且为非负数,给定有效的训练数据:
X = np.array([[10, 1], [9, 2], [8, 3], [1, 10], [2, 9], [3, 8]]) Y = np.array([1, 1, 1, 2, 2, 2])
-n = 特征的数量
features_num = X.shape[1] #2
- θy,i 为特征 xi 出现在类别为 y 的样本中的概率(类条件特征概率),用最大似然估计计算(人话:频次占比), log(θy,i) =取对数。
- Ny,i = 类别为 y 的所有样本中特征 xi 值的和(即加权频次和)
- Ny = 类别为 y 的所有样本的所有特征值的总和(即加权频次总和)
log_theta_y1_x1 = np.log(np.sum(X[Y==1, :1]) + alpha) - \ np.log(np.sum(X[Y==1, :]) + features_num*alpha) # -0.223143551314 log_theta_y1_x2 = np.log(np.sum(X[Y==1, 1:2]) + alpha) - \ np.log(np.sum(X[Y==1, :]) + features_num*alpha) # -1.60943791243 log_theta_y2_x1 = np.log(np.sum(X[Y==2, :1]) + alpha) - \ np.log(np.sum(X[Y==2, :]) + features_num*alpha) # -1.60943791243 log_theta_y2_x2 = np.log(np.sum(X[Y==2, 1:2]) + alpha) - \ np.log(np.sum(X[Y==2, :]) + features_num*alpha) # -0.223143551314
-类别先验概率P(y) =类别为 y 的样本数/总样本数,对数形式=分子的对数-分母的对数:
class_log_prior_1 = np.log(X[Y==1, :].shape[0]) - np.log(X.shape[0]) # -0.69314718056 class_log_prior_2 = np.log(X[Y==2, :].shape[0]) - np.log(X.shape[0]) # -0.69314718056
-分类判别:分类得分公式取自然对数,得到线性分类器的形式(截距+参数矢量)
log(P(y))+∑ni=1(xilog(θy,i))log_pro_y1 = class_log_prior_1 + \ NewX[0] * log_theta_y1_x1 + NewX[1] * log_theta_y1_x2 # -6.8603222257475043 log_pro_y2 = class_log_prior_2 + \ NewX[0] * log_theta_y2_x1 + NewX[1] * log_theta_y2_x2 # -11.019205309107175 NewL = ("1" if log_pro_y1 > log_pro_y2 else “2") # “1”
伯努利分布参数估计(适用于布尔特征值,即:表示是否出现某特征)
log(P(xi|y))=xilog(θy,i)+(1−xi)log(1−θy,i)
θy,i = Ny,i+αNy+2α
log(θy,i)=log(Ny,i+α)−log(Ny+2α)
log(1−θy,i)=log(1−exp(log(θy,i))-给定有效的训练数据、以及平滑因子a(见上节说明):
X = np.array([[1, 0], [1, 0], [1, 0], [0, 1], [0, 1], [1, 1]]) Y = np.array([1, 1, 1, 2, 2, 2]) alpha = 1 #Laplace
- log(P(xi|y)) =类条件特征概率对数形式,与上述多项式密度函数情形主要的不同是对值为0的特征做了负向的惩罚(即:加号后面部分)
- θy,i 为特征 xi 出现在类别为 y 的样本中的概率,用最大似然估计计算, log(θy,i) =取对数。
- Ny,i = 类别为 y 的所有样本中特征 xi =1的样本数量(即特征值总和)
- Ny = 类别为 y 的所有样本的数量
log_theta_y1_x1 = np.log(np.sum(X[Y==1, :1]) + alpha) \ - np.log(X[Y==1, :].shape[0] + 2*alpha) # -0.223143551314 log_theta_y1_x2 = np.log(np.sum(X[Y==1, 1:2]) + alpha) \ - np.log(X[Y==1, :].shape[0] + 2*alpha) # -1.60943791243 log_theta_y2_x1 = np.log(np.sum(X[Y==2, :0]) + alpha) \ - np.log(X[Y==2, :].shape[0] + 2*alpha) # -0.916290731874 log_theta_y2_x2 = np.log(np.sum(X[Y==2, 1:2]) + alpha) \ - np.log(X[Y==2, :].shape[0] + 2*alpha) # -0.223143551314
-分类先验概率P(y) =类别为 y 的样本数/总样本数,对数形式=分子的对数-分母的对数:
class_log_prior_1 = np.log(X[Y==1, :].shape[0]) - np.log(X.shape[0]) # -0.69314718056 class_log_prior_2 = np.log(X[Y==2, :].shape[0]) - np.log(X.shape[0]) # -0.69314718056
-分类判别公式-对数形式:
log(P(y)∏ni=1P(xi|y))
=log(P(y))+∑ni=1(log(P(xi|y)))
=log(P(y))+∑ni=1(xilog(θy,i)+(1−xi)(log(1−θy,i)))log_pro_y1 = class_log_prior_1 + \ NewX[0] * log_theta_y1_x1 + (1-NewX[0]) * np.log(1-np.exp(log_theta_y1_x1)) + \ NewX[1] * log_theta_y1_x2 + (1-NewX[1]) * np.log(1-np.exp(log_theta_y1_x2)) # -2.5257286443082556 log_pro_y2 = class_log_prior_2 + \ NewX[0] * log_theta_y2_x1 + (1-NewX[0]) * np.log(1-np.exp(log_theta_y2_x1)) + \ NewX[1] * log_theta_y2_x2 + (1-NewX[1]) * np.log(1-np.exp(log_theta_y2_x2)) # -2.8134107167600364 NewL = ("1" if log_pro_y1 > log_pro_y2 else "2") # “1”
概率估计:将[分类得分]归一化计算出各分类的条件概率(实际上NB模型估计的概率并不精确),由于分类得分为条件概率的对数量,因此先对分类得分做指数运算,然后计算占比:
P(yi) = exp(yi)∑cn=1exp(yn)
=exp(yi−log(∑cn=1exp(yn)))
=exp(yi−max(y)−log(∑cn=1exp(yn−max(y)))
对分母做exp-log变换后将exp提到最外层,从而将除法化为减法运算(分母部分变为log-sum-exp公式),避免了小数乘除的溢出问题。对分类得分做简单的线性规范化(统一减去最大值)可以减小计算的成本,加快计算的速度。import math log_likelihood_max = max(log_likelihood_y1,log_likelihood_y2) # -2.906253,-19.70625271 log_likelihood_acc = math.log(math.exp(log_likelihood_y1 - log_likelihood_max) + \ math.exp(log_likelihood_y2 - log_likelihood_max)) prob_y1 = math.exp(log_likelihood_y1 - log_likelihood_max - log_likelihood_acc) # 0.9999999494346746 prob_y2 = math.exp(log_likelihood_y2 - log_likelihood_max - log_likelihood_acc) # 0.0000000505653254
补充说明:
只需要少量的训练样本来估计模型参数
在文本分类及垃圾过滤方面表现良好
相比其他复杂的分类算法速度极快,得益于特征独立性假设,使得类条件特征分布的估计都是一维的
相对于好的分类效果,其概率估计值并不准确,算是差的Estimator(概率估计算法)
可以设置样本的权重参数、可以指定类别先验概率(不用从样本中学习)或采用均分概率
可以分批次增量的方式训练模型,从而支持大样本、在线学习、以及分布式并行计算,通过将新增批量样本(或并行计算的各个分区样本)的学习结果与现有计算结果合并的方式实现。
上述示例代码采用python编写,目的是以分解的方式分步讲述算法的执行过程,非面向工程实现。