一. 算法介绍
1.1 贝叶斯定理
在正式介绍朴素贝叶斯算法之前,我们需要简单介绍一下贝叶斯定理,其数学表达式如下:
P
(
A
∣
B
)
=
P
(
A
B
)
P
(
B
)
=
P
(
B
∣
A
)
⋅
P
(
A
)
P
(
B
)
=
P
(
A
)
⋅
P
(
B
∣
A
)
P
(
B
)
P(A|B)=\dfrac{P(AB)}{P(B)}=\dfrac{P(B|A)\cdot P(A)}{P(B)}=P(A)\cdot \dfrac{P(B|A)}{P(B)}
P(A∣B)=P(B)P(AB)=P(B)P(B∣A)⋅P(A)=P(A)⋅P(B)P(B∣A)
其中:
- P ( A ) P(A) P(A)为事件A发生的概率,在贝叶斯定理中被称为先验概率
- P ( A ∣ B ) P(A|B) P(A∣B)被称作后验概率,可看做是先验概率P(A)经过可能性函数( P ( B ∣ A ) P ( B ) \frac{P(B|A)}{P(B)} P(B)P(B∣A))修饰后的概率
- A事件与B事件相互独立
贝叶斯定理解决的是逆概率问题,与正概率相反,正概率问题描述的是已知条件求概率,而逆概率问题则是已知结论逆求条件。
举个简单的例子,假设有一个箱子装着一定数量的红蓝两种颜色的球,当我们知道箱子内的球的数量来求抽到红球的概率,这便是正概率问题,而逆概率问题则是已知抽到球的情况,转而推测箱子内部的两种球的比例。
贝叶斯定理的核心思想就是通过特征或其他事件修正先验概率P(A),使得到的后验概率P(A|B)其更接近于实际值,这使得我们可由已有的结果倒推未知的条件。
1.2 朴素贝叶斯算法
在朴素贝叶斯算法中,对于给定的数据集,首先基于特征条件独立假设学习输入和输出的联合概率分布;然后,对于一个新的输入样本,利用贝叶斯定理来计算并比较每个类别的后验概率,最终将后验概率最大的类别作为该样本的预测分类。
尽管在实际应用中,条件间往往并不相互独立,但仍不妨碍使用朴素贝叶斯算法的分类器效果极佳。
1.3 朴素贝叶斯例题
有数据集如下表格:
年龄 | 月收入(元) | 是否有房贷 | 是否有车贷 | 是否购买新手机 |
---|---|---|---|---|
25 | 8000 | 否 | 否 | 是 |
35 | 12000 | 是 | 否 | 否 |
28 | 9500 | 否 | 是 | 是 |
42 | 15000 | 是 | 是 | 否 |
30 | 10000 | 否 | 否 | 是 |
50 | 20000 | 是 | 否 | 是 |
33 | 11000 | 否 | 是 | 否 |
27 | 8500 | 否 | 否 | 是 |
45 | 16000 | 是 | 是 | 否 |
39 | 13000 | 是 | 否 | 是 |
29 | 9000 | 否 | 否 | 是 |
首先,我们需要计算先验概率P(是)和P(否)
-
先验概率
P(是) = 7/11 ≈ 0.6364
P(否) = 4/11 ≈ 0.3636
接着计算每个特征在购买新手机和不购买新手机情况下的条件概率
- 年龄
- 购买新手机:
20-30岁: 4/7 ≈ 0.5714
30-40岁: 2/7 ≈ 0.2857
40-50岁: 1/7 ≈ 0.1429 - 不购买新手机:
20-30岁: 1/4 = 0.25
30-40岁: 2/4 = 0.5
40-50岁: 1/4 = 0.25
- 购买新手机:
- 月收入
- 购买新手机:
8000-10000元: 4/7 ≈ 0.5714
10000-15000元: 2/7 ≈ 0.2857
15000-20000元: 1/7 ≈ 0.1429 - 不购买新手机:
8000-10000元: 0/4 = 0
10000-15000元: 3/4 = 0.75
15000-20000元: 1/4 = 0.25
- 购买新手机:
- 是否有房贷
- 购买新手机:
是: 3/7 ≈ 0.4286
否: 4/7 ≈ 0.5714 - 不购买新手机:
是: 2/4 = 0.5
否: 2/4 = 0.5
- 购买新手机:
- 是否有车贷
- 购买新手机:
是: 1/7 ≈ 0.1429
否: 6/7 ≈ 0.8571 - 不购买新手机:
是: 3/4 = 0.75
否: 1/4 = 0.25
- 购买新手机:
有了这些数据,便能对新样本进行预测了,假设有一个
年龄 | 月收入(元) | 是否有房贷 | 是否有车贷 | 是否购买新手机 |
---|---|---|---|---|
45 | 9000 | 否 | 否 | ? |
则有
P
(
是
∣
特征
)
=
0.1429
⋅
0.5714
⋅
0.5714
⋅
0.8571
⋅
0.6364
=
0.3562
P(是|特征) = 0.1429 \cdot 0.5714 \cdot 0.5714 \cdot 0.8571 \cdot 0.6364=0.3562
P(是∣特征)=0.1429⋅0.5714⋅0.5714⋅0.8571⋅0.6364=0.3562
P ( 否 ∣ 特征 ) = 0.25 ⋅ 0 ⋅ 0.5 ⋅ 0.25 ⋅ 0.3636 = 0 P(否|特征) = 0.25 \cdot 0 \cdot 0.5 \cdot 0.25 \cdot 0.3636 =0 P(否∣特征)=0.25⋅0⋅0.5⋅0.25⋅0.3636=0
在这里,我们会发现在第二项式子中,有一个部分值为0,导致整体的值都变成0了,这明显是不合理的,由此,我们在实际操作过程中会引入一个概念——拉普拉斯平滑。
对于有某一项特征值数值为0的特征,我们可以通过以下公式进行处理:
P
(
特征值
∣
类别
)
=
x
+
λ
总计数
+
λ
×
特征值数量
P(特征值|类别) = \frac{x + \lambda}{总计数 + \lambda \times 特征值数量}
P(特征值∣类别)=总计数+λ×特征值数量x+λ
经过该公式,我们对数据项重新进行处理,得到:
-
8000-10000元:
P ( 8000 − 10000 元 ∣ 否 ) = 0 + 1 4 + 1 × 3 = 1 7 = 0.1429 P(8000-10000元|否) = \frac{0 + 1}{4 + 1 \times 3} = \frac{1}{7} = 0.1429 P(8000−10000元∣否)=4+1×30+1=71=0.1429 -
10000-15000元:
P ( 10000 − 15000 元 ∣ 否 ) = 3 + 1 4 + 1 × 3 = 4 7 = 0.5714 P(10000-15000元|否) = \frac{3 + 1}{4 + 1 \times 3} = \frac{4}{7} = 0.5714 P(10000−15000元∣否)=4+1×33+1=74=0.5714 -
15000-20000元:
P ( 15000 − 20000 元 ∣ 否 ) = 1 + 1 4 + 1 × 3 = 2 7 = 0.2857 P(15000-20000元|否) = \frac{1 + 1}{4 + 1 \times 3} = \frac{2}{7} = 0.2857 P(15000−20000元∣否)=4+1×31+1=72=0.2857
则
P
(
否
∣
特征
)
=
0.25
⋅
0.1429
⋅
0.5
⋅
0.25
⋅
0.3636
=
0.0016
P(否|特征) = 0.25 \cdot 0.1429 \cdot 0.5 \cdot 0.25 \cdot 0.3636 = 0.0016
P(否∣特征)=0.25⋅0.1429⋅0.5⋅0.25⋅0.3636=0.0016
由此比较P(是|特征)大于P(否|特征)的值,则说明此项样本更有可能不买手机。
二. 代码实现
本篇文章的实现不包含数据集的提供,可以使用自行使用代码生成或者编撰较小的数据集
2.1 切分
def split_dataset(dataset):
features = [data[:-1] for data in dataset]
labels = [data[-1] for data in dataset]
return features, labels
将数据集分成两个部分,前四个值为特征,最后一个值为分类
2.2 计算先验概率
def calculate_prior(labels):
total = len(labels)
classes = set(labels)
prior = {label: labels.count(label) / total for label in classes}
return prior
- 首先,函数接收一个包含标签(类别)的列表作为输入。
- 然后,它计算数据集中每个类别的出现次数,并将其除以总样本数,得到每个类别的先验概率。
- 最后,函数返回一个字典,其中键是类别,值是对应的先验概率。
2.3 计算条件概率
def calculate_likelihood(features, labels, laplace=1):
feature_count = {}
likelihood = {}
for feature in zip(*features):
unique = set(feature)
for value in unique:
for label in set(labels):
key = (value, label)
feature_count[key] = feature_count.get(key, 0) + laplace
for label in set(labels):
label_count = labels.count(label)
for value in set(feature):
key = (value, label)
likelihood[key] = feature_count[key] / (label_count + laplace * len(set(feature)))
return likelihood
- 首先,函数接收特征列表、标签列表和一个可选的拉普拉斯平滑参数作为输入。
- 然后,它遍历每个特征的不同取值,计算每个特征值对应每个类别的出现次数,并应用拉普拉斯平滑处理。
- 最后,函数返回一个字典,其中键是特征值和类别的组合,值是对应的条件概率。
2.4 贝叶斯分类
def naive_bayes_classifier(test, prior, likelihood):
results = {}
for label in prior:
probability = prior[label]
for index, value in enumerate(test):
key = (value, label)
probability *= likelihood.get(key, 0)
results[label] = probability
return max(results, key=results.get)
- 首先,函数创建一个空字典
results
,用于存储每个类别的总概率。 - 然后,对于先验概率字典中的每个类别,函数计算该类别的总概率。它通过将类别的先验概率与测试实例中每个特征值的条件概率相乘来实现。
- 如果条件概率字典中没有对应的键值对,则使用
0
作为默认值。这意味着如果测试实例中的特征值在训练数据中从未与该类别一起出现过,那么这个特征值对于该类别的概率将被视为0
。 - 最后,函数通过
max
函数找到具有最大总概率的类别,并将其作为预测结果返回。