目录
一、朴素贝叶斯介绍
1.1 概念
朴素贝叶斯算法的基础是贝叶斯原理,其假定给定目标值时属性之间相互条件独立。也就是说没有哪个属性变量对于决策结果来说占有着较大的比重,也没有哪个属性变量对于决策结果占有着较小的比重。
基于贝叶斯原理,朴素贝叶斯的分类原理大体不变。收集并整理带有类别标签的训练数据集,将数据特征进行提取和量化。计算出先验概率和后验概率带入贝叶斯公式来预测新样本。
1.2 公式
基本公式:
对于给定的样本特征向量X=(x1,x2,⋯,xn)和类别集合C={c1,c2,⋯,ck},朴素贝叶斯分类器根据贝叶斯定理计算后验概率P(ci∣X),即已知特征向量X的情况下,样本属于类别ci的概率。计算公式为:
- P(C∣X)是后验概率,即已知特征向量X的情况下,样本属于类别C的概率。
- P(X∣C)是似然概率,是在已知类别C的情况下,观察到特征向量X的概率。
- P(C)是先验概率,是类别C发生的概率,不考虑任何特征信息。
- P(X)是证据因子,是观察到特征向量X的概率,它对于所有类别来说是一个常数。
独立性假设下的公式:
朴素贝叶斯算法假设特征之间相互独立,即给定类别ci时,每个特征xj的出现与其他特征无关。在这种假设下,P(X∣ci)可以分解为各个特征的条件概率之积:
因此,朴素贝叶斯分类器公式可以改写为 :
在实际应用中,通常不需要计算P(X),因为它对于所有类别都是相同的。所以,只需计算分子:
最终选择该值最大的类别ci作为样本的预测类别。
1.3 分类原理及过程
1.3.1 分类原理
- 在进行分类时,对于一个新的待分类样本,朴素贝叶斯算法计算该样本属于每个类别的后验概率,即计算P(Ci∣X),其中Ci是不同的类别。然后选择后验概率最大的类别作为该样本的预测类别。
- 由于P(X)对于所有类别是相同的,在比较不同类别Ci的后验概率时,可以忽略P(X)。因此,只需要计算P(X∣Ci)P(Ci)并比较它们的大小。
例如,在一个文本分类任务中,假设我们要将文档分为 “体育”“科技”“娱乐” 等类别。我们可以将文档中的单词作为特征,根据训练数据计算出每个类别中每个单词出现的概率(即P(xj∣Ci))以及每个类别的先验概率P(Ci)。对于一个新的文档,通过计算它各个类别在选择条件下的概率的乘积后,选择概率最大的类别作为该文档的分类结果。
1.3.2 分类过程
- 计算先验概率
- 计算每个属性的条件概率
- 计算离散属性的条件概率
- 计算连续属性的条件概率
- 计算后验概率
- 比较后验概率,取后验概率最大的作为分类结果。
二、案例分析(西瓜集)
数据集:
待测集:
1.计算先验概率
先验概率是指在没有任何特征信息的情况下,样本属于某一类别的概率。
数据集中总共有 17 个样本,其中 “好瓜” 有 8 个,“坏瓜” 有 9 个。
设 好瓜 表示样本是好瓜的先验概率,坏瓜 表示样本是坏瓜的先验概率。
2.计算条件概率
条件概率是指在已知样本属于某一类别的情况下,该样本具有特定特征的概率。
对于离散特征(色泽、根蒂、敲声、纹理、脐部、触感):
对于连续特征(密度、含糖率),假设其服从正态分布 N(μ,σ2),使用正态分布的概率密度函数计算条件概率:
对于好瓜的密度均值:
对于坏瓜的密度均值:
对于好瓜的含糖率均值:
对于坏瓜的含糖率均值:
3.计算后验概率
4.得到分类结果
比较后验概率 好瓜 和 坏瓜 的大小:
因为 0.053>0.00002,即 好瓜坏瓜,所以判断测试样本为 “好瓜”。
三、代码实现
1.计算先验概率
def prior():
dataSet = loadDataSet()[0] # 载入数据集
countG = 0 # 初始化好瓜数量
countB = 0 # 初始化坏瓜数量
countAll = len(dataSet)
for item in dataSet: # 统计好瓜个数
if item[-1] == "好瓜":
countG += 1
for item in dataSet: # 统计坏瓜个数
if item[-1] == "坏瓜":
countB += 1
# 计算先验概率P(c)
P_G = round(countG/countAll, 3)
P_B = round(countB/countAll, 3)
return P_G,P_B
计算得到好瓜和坏瓜的先验概率。通过遍历数据集统计好瓜和坏瓜的数量,然后分别除以样本总数得到先验概率,并保留三位小数返回。
2.计算离散属性的条件概率
def P(index, cla):
dataSet, testSet, labels = loadDataSet() # 载入数据集
countG = 0 # 初始化好瓜数量
countB = 0 # 初始化坏瓜数量
for item in dataSet: # 统计好瓜个数
if item[-1] == "好瓜":
countG += 1
for item in dataSet: # 统计坏瓜个数
if item[-1] == "坏瓜":
countB += 1
lst = [item for item in dataSet if (item[-1] == cla) & (item[index] == testSet[index])] # lst为cla类中第index个属性上取值为xi的样本组成的集合
P = round(len(lst)/(countG if cla=="好瓜" else countB), 3) # 计算条件概率
return P
计算离散属性的条件概率。根据给定的特征索引 index
和类别 cla
,统计该类别下特征取值与测试样本中该特征取值相同的样本数量,再除以该类别的样本总数,得到条件概率并保留三位小数返回。
3.计算连续属性的条件概率
def mean_std(feature, cla):
dataSet, testSet, labels = loadDataSet()
lst = [item[labels.index(feature)] for item in dataSet if item[-1]==cla] # 类别为cla中指定特征feature组成的列表
mean = round(np.mean(lst), 3) # 均值
std = round(np.std(lst), 3) # 标准差
return mean, std
def p():
dataSet, testSet, labels = loadDataSet() # 载入数据集
denG_mean, denG_std = mean_std("密度", "好瓜") # 好瓜密度的均值、标准差
denB_mean, denB_std = mean_std("密度", "坏瓜") # 坏瓜密度的均值、标准差
sugG_mean, sugG_std = mean_std("含糖率", "好瓜") # 好瓜含糖率的均值、标准差
sugB_mean, sugB_std = mean_std("含糖率", "坏瓜") # 坏瓜含糖率的均值、标准差
# p(密度|好瓜)
p_density_G = (1/(math.sqrt(2*math.pi)*denG_std))*np.exp(-(((testSet[labels.index("密度")]-denG_mean)**2)/(2*(denG_std**2))))
p_density_G = round(p_density_G, 3)
# p(密度|坏瓜)
p_density_B = (1/(math.sqrt(2*math.pi)*denB_std))*np.exp(-(((testSet[labels.index("密度")]-denB_mean)**2)/(2*(denB_std**2))))
p_density_B = round(p_density_B, 3)
# p(含糖率|好瓜)
p_sugar_G = (1/(math.sqrt(2*math.pi)*sugG_std))*np.exp(-(((testSet[labels.index("含糖率")]-sugG_mean)**2)/(2*(sugG_std**2))))
p_sugar_G = round(p_sugar_G, 3)
# p(含糖率|坏瓜)
p_sugar_B = (1/(math.sqrt(2*math.pi)*sugB_std))*np.exp(-(((testSet[labels.index("含糖率")]-sugB_mean)**2)/(2*(sugB_std**2))))
p_sugar_B = round(p_sugar_B, 3)
return p_density_G, p_density_B, p_sugar_G, p_sugar_B
计算连续属性(密度和含糖率)的条件概率。首先调用 mean_std
函数获取不同类别下密度和含糖率的均值和标准差,然后根据高斯分布公式计算条件概率,并保留三位小数返回。
4.计算后验概率
def bayes():
# 计算类先验概率
P_G, P_B = prior()
# 计算离散属性的条件概率
P0_G = P(0, "好瓜") # P(青绿|好瓜)
P0_B = P(0, "坏瓜") # P(青绿|坏瓜)
P1_G = P(1, "好瓜") # P(蜷缩|好瓜)
P1_B = P(1, "坏瓜") # P(蜷缩|好瓜)
P2_G = P(2, "好瓜") # P(浊响|好瓜)
P2_B = P(2, "坏瓜") # P(浊响|好瓜)
P3_G = P(3, "好瓜") # P(清晰|好瓜)
P3_B = P(3, "坏瓜") # P(清晰|好瓜)
P4_G = P(4, "好瓜") # P(凹陷|好瓜)
P4_B = P(4, "坏瓜") # P(凹陷|好瓜)
P5_G = P(5, "好瓜") # P(硬滑|好瓜)
P5_B = P(5, "坏瓜") # P(硬滑|好瓜)
# 计算连续属性的条件概率
p_density_G, p_density_B, p_sugar_G, p_sugar_B = p()
# 计算后验概率
isGood = P_G * P0_G * P1_G * P2_G * P3_G * P4_G * P5_G * p_density_G * p_sugar_G # 计算是好瓜的后验概率
isBad = P_B * P0_B * P1_B * P2_B * P3_B * P4_B * P5_B * p_density_B * p_sugar_B # 计算是坏瓜的后验概率
return isGood,isBad
根据朴素贝叶斯算法预测后验概率。先调用 prior
函数计算先验概率,再调用 P
函数计算离散属性的条件概率,调用 p
函数计算连续属性的条件概率,最后根据这些概率计算好瓜和坏瓜的后验概率并返回。
5.输出分类结果
if __name__=='__main__':
dataSet, testSet, labels = loadDataSet()
testSet = [testSet]
df = pd.DataFrame(testSet, columns=labels, index=[1])
print("=======================待测样本========================")
print(f"待测集:\n{df}")
isGood, isBad = bayes()
print("=======================后验概率========================")
print("后验概率:")
print(f"P(好瓜|xi) = {isGood}")
print(f"P(好瓜|xi) = {isBad}")
print("=======================预测结果========================")
print("predict ---> 好瓜" if (isGood > isBad) else "predict ---> 坏瓜")
主程序部分首先加载数据集,将测试样本转换为 pandas
的 DataFrame
并打印。然后调用 bayes
函数计算后验概率,打印后验概率值,最后根据后验概率大小判断并打印预测结果。不过代码中 print(f"P(好瓜|xi) = {isBad}")
存在笔误,应该是 print(f"P(坏瓜|xi) = {isBad}")
。
四、总结
优点:
- 算法简单高效:朴素贝叶斯算法原理清晰易懂,计算过程相对简单,在处理大规模数据集时,计算量较小,能够快速进行训练和预测。如在上述案例中,通过几个函数分别计算先验概率、条件概率等,最终得出分类结果,计算逻辑不复杂。
- 所需样本量较少:对于一些复杂模型可能需要大量的训练数据才能达到较好的效果,而朴素贝叶斯算法在样本量相对较少的情况下也能表现出不错的性能。在西瓜数据集案例中,仅使用了 17 个样本就可以进行分类预测。
- 适合多分类问题:可以很容易地扩展到多分类任务中,通过计算每个类别的后验概率,选择概率最大的类别作为预测结果。虽然上述案例只是二分类(好瓜和坏瓜),但同样的原理可应用于更多类别的分类。
缺点:
- 特征条件独立假设往往不成立:朴素贝叶斯算法的核心假设是特征之间相互独立,但在实际应用中,这个假设很难满足。例如在西瓜的特征中,“密度” 和 “含糖率” 等特征之间可能存在某种相关性。这种特征间的相关性可能导致算法的性能下降,分类准确率降低。
- 对输入数据的表达形式较为敏感:如果数据的预处理方式或特征的表示形式发生变化,可能会对分类结果产生较大影响。比如对于连续特征的处理,假设其服从正态分布来计算条件概率,如果实际数据分布与正态分布差异较大,会影响概率计算的准确性。
- 无法考虑特征之间的相互作用:由于假设特征独立,朴素贝叶斯算法不能很好地处理特征之间的相互作用和依赖关系。例如某些特征组合可能对类别判断有重要影响,但该算法无法捕捉到这种组合效应。
适用场景:
- 文本分类:在文本处理领域应用广泛,如垃圾邮件过滤、情感分析等。文本中的单词通常可看作相互独立的特征,符合朴素贝叶斯的假设,且算法的高效性使其能快速处理大量文本数据。
- 数据量较小的情况:当数据量有限时,朴素贝叶斯算法能凭借较少的数据进行有效的分类,相比一些复杂模型更具优势。
- 对实时性要求较高的场景:由于其计算简单快速,适合对实时性要求较高的分类任务,如实时的用户行为分类、网络流量分类等。
五、整体代码
import numpy as np
import math
import pandas as pd
def loadDataSet():
dataSet=[['青绿', '蜷缩', '浊响', '清晰', '凹陷', '硬滑', 0.697, 0.460, '好瓜'],
['乌黑', '蜷缩', '沉闷', '清晰', '凹陷', '硬滑', 0.774, 0.376, '好瓜'],
['乌黑', '蜷缩', '浊响', '清晰', '凹陷', '硬滑', 0.634, 0.264, '好瓜'],
['青绿', '蜷缩', '沉闷', '清晰', '凹陷', '硬滑', 0.608, 0.318, '好瓜'],
['浅白', '蜷缩', '浊响', '清晰', '凹陷', '硬滑', 0.556, 0.215, '好瓜'],
['青绿', '稍蜷', '浊响', '清晰', '稍凹', '软粘', 0.403, 0.237, '好瓜'],
['乌黑', '稍蜷', '浊响', '稍糊', '稍凹', '软粘', 0.481, 0.149, '好瓜'],
['乌黑', '稍蜷', '浊响', '清晰', '稍凹', '硬滑', 0.437, 0.211, '好瓜'],
['乌黑', '稍蜷', '沉闷', '稍糊', '稍凹', '硬滑', 0.666, 0.091, '坏瓜'],
['青绿', '硬挺', '清脆', '清晰', '平坦', '软粘', 0.243, 0.267, '坏瓜'],
['浅白', '硬挺', '清脆', '模糊', '平坦', '硬滑', 0.245, 0.057, '坏瓜'],
['浅白', '蜷缩', '浊响', '模糊', '平坦', '软粘', 0.343, 0.099, '坏瓜'],
['青绿', '稍蜷', '浊响', '稍糊', '凹陷', '硬滑', 0.639, 0.161, '坏瓜'],
['浅白', '稍蜷', '沉闷', '稍糊', '凹陷', '硬滑', 0.657, 0.198, '坏瓜'],
['乌黑', '稍蜷', '浊响', '清晰', '稍凹', '软粘', 0.360, 0.370, '坏瓜'],
['浅白', '蜷缩', '浊响', '模糊', '平坦', '硬滑', 0.593, 0.042, '坏瓜'],
['青绿', '蜷缩', '沉闷', '稍糊', '稍凹', '硬滑', 0.719, 0.103, '坏瓜']]
testSet= ['青绿', '蜷缩', '浊响', '清晰', '凹陷', '硬滑', 0.697, 0.460] # 待测集
labels = ['色泽', '根蒂', '敲声', '纹理', '脐部', '触感', '密度', '含糖率'] # 特征
return dataSet, testSet, labels
#? 计算先验概率P(c)
def prior():
dataSet = loadDataSet()[0] # 载入数据集
countG = 0 # 初始化好瓜数量
countB = 0 # 初始化坏瓜数量
countAll = len(dataSet)
for item in dataSet: # 统计好瓜个数
if item[-1] == "好瓜":
countG += 1
for item in dataSet: # 统计坏瓜个数
if item[-1] == "坏瓜":
countB += 1
# 计算先验概率P(c)
P_G = round(countG/countAll, 3)
P_B = round(countB/countAll, 3)
return P_G,P_B
#? 计算离散属性的条件概率P(xi|c)
def P(index, cla):
dataSet, testSet, labels = loadDataSet() # 载入数据集
countG = 0 # 初始化好瓜数量
countB = 0 # 初始化坏瓜数量
for item in dataSet: # 统计好瓜个数
if item[-1] == "好瓜":
countG += 1
for item in dataSet: # 统计坏瓜个数
if item[-1] == "坏瓜":
countB += 1
lst = [item for item in dataSet if (item[-1] == cla) & (item[index] == testSet[index])] # lst为cla类中第index个属性上取值为xi的样本组成的集合
P = round(len(lst)/(countG if cla=="好瓜" else countB), 3) # 计算条件概率
return P
#? 计算(不同类别中指定连续特征的)均值、标准差
def mean_std(feature, cla):
dataSet, testSet, labels = loadDataSet()
lst = [item[labels.index(feature)] for item in dataSet if item[-1]==cla] # 类别为cla中指定特征feature组成的列表
mean = round(np.mean(lst), 3) # 均值
std = round(np.std(lst), 3) # 标准差
return mean, std
#? 计算连续属性的条件概率p(xi|c)
def p():
dataSet, testSet, labels = loadDataSet() # 载入数据集
denG_mean, denG_std = mean_std("密度", "好瓜") # 好瓜密度的均值、标准差
denB_mean, denB_std = mean_std("密度", "坏瓜") # 坏瓜密度的均值、标准差
sugG_mean, sugG_std = mean_std("含糖率", "好瓜") # 好瓜含糖率的均值、标准差
sugB_mean, sugB_std = mean_std("含糖率", "坏瓜") # 坏瓜含糖率的均值、标准差
# p(密度|好瓜)
p_density_G = (1/(math.sqrt(2*math.pi)*denG_std))*np.exp(-(((testSet[labels.index("密度")]-denG_mean)**2)/(2*(denG_std**2))))
p_density_G = round(p_density_G, 3)
# p(密度|坏瓜)
p_density_B = (1/(math.sqrt(2*math.pi)*denB_std))*np.exp(-(((testSet[labels.index("密度")]-denB_mean)**2)/(2*(denB_std**2))))
p_density_B = round(p_density_B, 3)
# p(含糖率|好瓜)
p_sugar_G = (1/(math.sqrt(2*math.pi)*sugG_std))*np.exp(-(((testSet[labels.index("含糖率")]-sugG_mean)**2)/(2*(sugG_std**2))))
p_sugar_G = round(p_sugar_G, 3)
# p(含糖率|坏瓜)
p_sugar_B = (1/(math.sqrt(2*math.pi)*sugB_std))*np.exp(-(((testSet[labels.index("含糖率")]-sugB_mean)**2)/(2*(sugB_std**2))))
p_sugar_B = round(p_sugar_B, 3)
return p_density_G, p_density_B, p_sugar_G, p_sugar_B
#? 预测后验概率P(c|xi)
def bayes():
#? 计算类先验概率
P_G, P_B = prior()
#? 计算离散属性的条件概率
P0_G = P(0, "好瓜") # P(青绿|好瓜)
P0_B = P(0, "坏瓜") # P(青绿|坏瓜)
P1_G = P(1, "好瓜") # P(蜷缩|好瓜)
P1_B = P(1, "坏瓜") # P(蜷缩|好瓜)
P2_G = P(2, "好瓜") # P(浊响|好瓜)
P2_B = P(2, "坏瓜") # P(浊响|好瓜)
P3_G = P(3, "好瓜") # P(清晰|好瓜)
P3_B = P(3, "坏瓜") # P(清晰|好瓜)
P4_G = P(4, "好瓜") # P(凹陷|好瓜)
P4_B = P(4, "坏瓜") # P(凹陷|好瓜)
P5_G = P(5, "好瓜") # P(硬滑|好瓜)
P5_B = P(5, "坏瓜") # P(硬滑|好瓜)
#? 计算连续属性的条件概率
p_density_G, p_density_B, p_sugar_G, p_sugar_B = p()
#? 计算后验概率
isGood = P_G * P0_G * P1_G * P2_G * P3_G * P4_G * P5_G * p_density_G * p_sugar_G # 计算是好瓜的后验概率
isBad = P_B * P0_B * P1_B * P2_B * P3_B * P4_B * P5_B * p_density_B * p_sugar_B # 计算是坏瓜的后验概率
return isGood,isBad
if __name__=='__main__':
dataSet, testSet, labels = loadDataSet()
testSet = [testSet]
df = pd.DataFrame(testSet, columns=labels, index=[1])
print("=======================待测样本========================")
print(f"待测集:\n{df}")
isGood, isBad = bayes()
print("=======================后验概率========================")
print("后验概率:")
print(f"P(好瓜|xi) = {isGood}")
print(f"P(好瓜|xi) = {isBad}")
print("=======================预测结果========================")
print("predict ---> 好瓜" if (isGood > isBad) else "predict ---> 坏瓜")