朴素贝叶斯(Naive Bayes)(原理+Python实现)
创作背景
本菜鸡最近想学学 机器学习
,这不,学到了 朴素贝叶斯。
如果觉得我这篇文章写的好的话,能不能给我 点个赞
,评论
一波。如果要点个 关注
的话也不是不可以🤗
算法分类
对于 分类算法 来说,一般可分为 生成类算法 和 判别类算法 。
生成类算法
通俗来讲,这类算法是在给定数据为 各个特征某一取值 的情况下,求出 属于标签某种取值 的概率,这类算法的典型是 朴素贝叶斯。
判别类算法
通俗来讲,这类算法是 给定一些特征值,按照 各特征对标签值的影响程度,判断属于哪一类数据,这类算法的典型是 决策树。下一篇文章会讲到。
区别
生成类算法 注重 特征值组合对标签值的影响;而 判别类算法 注重 单个特征对标签值的影响 。
知识补充
假设有两个事件 A
和 B
,出现的概率分别为 P(A)
和 P(B)
,两个事件同时出现 的概率为
P
(
A
,
B
)
P(A, B)
P(A,B)。那么,在事件 A
发生的条件下,事件 B
发生的概率可以表示为 P(B|A)
,计算公式为
P
(
B
∣
A
)
=
P
(
A
,
B
)
P
(
A
)
=
P
(
A
∣
B
)
⋅
P
(
B
)
P
(
A
)
P(B|A) = \frac{P(A, B)}{P(A)}=\frac{P(A|B) \cdot P(B)}{P(A)}
P(B∣A)=P(A)P(A,B)=P(A)P(A∣B)⋅P(B)
一般来说
P(B|A) ≠ P(A)
,也就是A
和B
不是相互独立的,事件B
的发生对事件A
的发生 有影响。- 如果
P(B|A) > P(A)
, 说明事件B
的发生对事件A
的发生有 促进 作用。 - 反之,有 抑制 作用。
- 如果
- 如果 二者相等 ,则说明事件
B
的发生对事件A
的发生 没有影响 。
朴素贝叶斯算法
朴素贝叶斯 中我们认为 特征之间 是 相互独立 的,目的是在 各个特征值组合 的条件下,预测 标签各值 出现的概率。
假设数据集
T
=
{
(
x
1
,
y
1
)
,
(
x
2
,
y
2
)
…
(
x
n
,
y
n
)
}
T=\{(x_1, y_1), (x_2, y_2) \dots (x_n, y_n)\}
T={(x1,y1),(x2,y2)…(xn,yn)} 由 P(X, Y)
独立同分布 产生,其中 X
和 Y
是两个特征,则
- 先验概率 :某个特征各种取值的概率,eg: P ( Y = c k ) , k = 1 , 2 , … , K P(Y=c_k), \: k=1,2,\dots,K P(Y=ck),k=1,2,…,K 。
- 条件概率 :在 特征
Y
取值为 c k c_k ck 的条件下, 特征X
取值为x
的概率,表示为P(X=x|Y=c_k)
,计算公式如下:
P ( X = x ∣ Y = c k ) = P ( X 1 = x 1 , X 2 = x 2 , … , X n = x n ∣ Y = c k ) , k = 1 , 2 , … , K P(X=x|Y=c_k) = P(X_1=x_1, X_2=x_2, \dots , X_n=x_n|Y=c_k), \: k=1,2,\dots,K P(X=x∣Y=ck)=P(X1=x1,X2=x2,…,Xn=xn∣Y=ck),k=1,2,…,K
在 条件独立性 假设下,可以合为
P
(
X
=
x
∣
Y
=
c
k
)
=
∏
j
=
1
n
P
(
X
j
=
x
j
∣
Y
=
c
k
)
P(X=x|Y=c_k) = \prod \limits_{j=1}^{n}{P(X_j=x_j|Y=c_k)}
P(X=x∣Y=ck)=j=1∏nP(Xj=xj∣Y=ck) 。
- 后验概率 :与先验概率相反,指的是 特征
X
取值为x
的条件下,特征Y
取值为 c k c_k ck 的概率,表示为P(Y=c_k|X=x)
,计算公式如下:
P ( Y = c k ∣ X = x ) = P ( X = x , Y = c k ) P ( X = x ) = P ( Y = c k ) ⋅ P ( X = x ∣ Y = c k ) P ( X = x ) = P ( Y = c k ) ⋅ ∏ j = 1 n P ( X j = x j ∣ Y = c k ) ∑ k P ( Y = c k ) ⋅ ∏ j = 1 n P ( X j = x j ∣ Y = c k ) P(Y=c_k|X=x) = \frac{P(X=x, Y=c_k)}{P(X=x)}=\frac{P(Y=c_k) \cdot P(X=x|Y=c_k)}{P(X=x)} \\ =\frac{P(Y=c_k) \cdot \prod \limits_{j=1}^{n}{P(X_j=x_j|Y=c_k)}}{\displaystyle \sum _{k}{P(Y=c_k) \cdot \prod \limits_{j=1}^{n}{P(X_j=x_j|Y=c_k)}}} P(Y=ck∣X=x)=P(X=x)P(X=x,Y=ck)=P(X=x)P(Y=ck)⋅P(X=x∣Y=ck)=k∑P(Y=ck)⋅j=1∏nP(Xj=xj∣Y=ck)P(Y=ck)⋅j=1∏nP(Xj=xj∣Y=ck)
朴素贝叶斯 最后求得就是 每个标签值 的 后验概率,将结果预测为 后验概率最大 的标签值。
举个栗子
假如我们有这样一个数据集(自己造的),如 下表:
序号 | x1 | x2 | y |
---|---|---|---|
1 | 小 | 少 | 0 |
2 | 小 | 中 | 0 |
3 | 小 | 中 | 1 |
4 | 小 | 少 | 1 |
5 | 小 | 少 | 0 |
6 | 中 | 少 | 0 |
7 | 中 | 中 | 0 |
8 | 中 | 中 | 1 |
9 | 中 | 多 | 1 |
10 | 中 | 多 | 1 |
11 | 大 | 多 | 1 |
12 | 大 | 中 | 1 |
13 | 大 | 中 | 1 |
14 | 大 | 多 | 1 |
15 | 大 | 多 | 0 |
其中,
- 特征 x1 表示 年龄 ,有三种取值,分别为 { 小 , 中 , 大 } \{小, 中, 大\} {小,中,大}。
- 特征 x2 表示 收入 ,有三种取值,分别为 { 少 , 中 , 多 } \{少, 中, 多\} {少,中,多}。
- 标签 y 表示 被诈骗过 ,有两种取值,分别为 { 0 , 1 } \{0, 1\} {0,1}。
如果有一个人 年龄中等,收入少,那他 是否被诈骗过 ?
这就需要用到 贝叶斯 来判断。
求解思路
- 分别求出 被诈骗过 和 没有被诈骗过 的人数占所有人数的 概率 ,即 P ( Y = 1 ) P(Y=1) P(Y=1) 和 P ( Y = 0 ) P(Y=0) P(Y=0) 。
- 求出 年龄中等 和 收入少 的数据分别占 被诈骗过 和 没有被诈骗过 的数据的 概率 ,即 P ( x 1 = 中 ∣ Y = 0 ) , P ( x 1 = 中 ∣ Y = 1 ) P(x_1=中|Y=0), P(x_1=中|Y=1) P(x1=中∣Y=0),P(x1=中∣Y=1) 和 P ( x 2 = 少 ∣ Y = 0 ) , P ( x 2 = 少 ∣ Y = 1 ) P(x_2=少|Y=0), P(x_2=少|Y=1) P(x2=少∣Y=0),P(x2=少∣Y=1)。
- 求出以 年龄中等 和 收入少 为前提的情况下,这个人 被诈骗过 和 没被诈骗过 的概率,以 概率高的 作为最后分类结果。
求解过程(数学计算)
先验概率
P
(
Y
=
1
)
=
9
15
,
P
(
Y
=
0
)
=
6
15
P
(
x
1
=
中
)
=
5
15
,
P
(
x
2
=
少
)
=
4
15
P
(
x
1
=
中
,
x
2
=
少
)
=
P
(
x
1
=
中
)
×
P
(
x
2
=
少
)
=
4
45
P(Y=1)=\frac{9}{15},\: P(Y=0)=\frac{6}{15} \\ P(x_1=中)=\frac{5}{15},\: P(x_2=少)=\frac{4}{15} \\ P(x_1=中,x_2=少)=P(x_1=中) \times P(x_2=少)=\frac{4}{45}
P(Y=1)=159,P(Y=0)=156P(x1=中)=155,P(x2=少)=154P(x1=中,x2=少)=P(x1=中)×P(x2=少)=454
条件概率(假设 特征之间相互独立)
P
(
x
1
=
中
∣
Y
=
0
)
=
2
6
,
P
(
x
1
=
中
∣
Y
=
1
)
=
3
9
P
(
x
2
=
少
∣
Y
=
0
)
=
3
6
,
P
(
x
2
=
少
∣
Y
=
1
)
=
1
9
P
(
x
1
=
中
,
x
2
=
少
∣
Y
=
0
)
=
P
(
x
1
=
中
∣
Y
=
0
)
×
P
(
x
2
=
少
∣
Y
=
0
)
=
1
6
P
(
x
1
=
中
,
x
2
=
少
∣
Y
=
1
)
=
P
(
x
1
=
中
∣
Y
=
1
)
×
P
(
x
2
=
少
∣
Y
=
1
)
=
1
27
P(x_1=中|Y=0)=\frac{2}{6},\: P(x_1=中|Y=1)=\frac{3}{9} \\ P(x_2=少|Y=0)=\frac{3}{6}, P(x_2=少|Y=1)=\frac{1}{9} \\ P(x_1=中,x_2=少|Y=0)=P(x_1=中|Y=0) \times P(x_2=少|Y=0)=\frac{1}{6} \\ P(x_1=中,x_2=少|Y=1)=P(x_1=中|Y=1) \times P(x_2=少|Y=1)=\frac{1}{27}
P(x1=中∣Y=0)=62,P(x1=中∣Y=1)=93P(x2=少∣Y=0)=63,P(x2=少∣Y=1)=91P(x1=中,x2=少∣Y=0)=P(x1=中∣Y=0)×P(x2=少∣Y=0)=61P(x1=中,x2=少∣Y=1)=P(x1=中∣Y=1)×P(x2=少∣Y=1)=271
后验概率
P
(
Y
=
0
∣
x
1
=
中
,
x
2
=
少
)
=
P
(
x
1
=
中
,
x
2
=
少
∣
Y
=
0
)
×
P
(
Y
=
0
)
P
(
x
1
=
中
,
x
2
=
少
)
=
1
6
×
6
15
4
45
=
3
4
P
(
Y
=
1
∣
x
1
=
中
,
x
2
=
少
)
=
P
(
x
1
=
中
,
x
2
=
少
∣
Y
=
1
)
×
P
(
Y
=
1
)
P
(
x
1
=
中
,
x
2
=
少
)
=
1
27
×
9
15
4
45
=
1
4
P(Y=0|x_1=中,x_2=少)=\frac{P(x_1=中,x_2=少|Y=0) \times P(Y=0)}{P(x_1=中,x_2=少)}=\frac{\frac{1}{6} \times \frac{6}{15}}{\frac{4}{45}}=\frac{3}{4} \\ P(Y=1|x_1=中,x_2=少)=\frac{P(x_1=中,x_2=少|Y=1) \times P(Y=1)}{P(x_1=中,x_2=少)}=\frac{\frac{1}{27} \times \frac{9}{15}}{\frac{4}{45}}=\frac{1}{4}
P(Y=0∣x1=中,x2=少)=P(x1=中,x2=少)P(x1=中,x2=少∣Y=0)×P(Y=0)=45461×156=43P(Y=1∣x1=中,x2=少)=P(x1=中,x2=少)P(x1=中,x2=少∣Y=1)×P(Y=1)=454271×159=41
∵ P ( Y = 0 ∣ x 1 = 中 , x 2 = 少 ) > P ( Y = 1 ∣ x 1 = 中 , x 2 = 少 ) ∴ 这 个 人 很 大 概 率 没 有 被 骗 过 。 \because P(Y=0|x_1=中,x_2=少) > P(Y=1|x_1=中,x_2=少) \\ \therefore 这个人很大概率 \: 没有被骗过 \: 。 ∵P(Y=0∣x1=中,x2=少)>P(Y=1∣x1=中,x2=少)∴这个人很大概率没有被骗过。
代码实现
自己实现
from collections import Iterable
class NaiveBayes:
def __init__(self):
# 先验概率
self.pri = pd.DataFrame()
# 条件概率
self.cond = pd.DataFrame()
# 标签列,默认为最后一列
self.y_col = ''
# 特征列名列表
self.featrues = []
def get_frequency(self, data, name=None, key=None):
# 获得频率
freq = data.value_counts(normalize=True)
# 设置返回的 dataframe 的列名和索引
name = freq.name if name is None else name
key = freq.name if key is None else key
freq = freq.to_frame(name=name)
freq.index = pd.MultiIndex.from_product([[key], freq.index.tolist()])
return freq
def get_priori(self, data):
# 初始化结果
result = pd.DataFrame()
# 遍历特征列
for column in data.columns:
# 获得频率
p = self.get_frequency(data[column], 'value')
# 保存结果
result = result.append(p)
# 返回先验概率
return result
def get_conditional(self, data):
# 初始化结果
result = None
# 以标签列分类
for cate, cate_index in data.groupby(self.y_col).groups.items():
# 每个标签取值对应的数据
x = data.loc[cate_index, self.featrues]
# 初始化标签取值对应各特征取值频次,即 P(X=x|Y=y)
cate_df = None
# 遍历特征列
for column in self.featrues:
# 获得对应频次
freq = self.get_frequency(x[column], name=cate, key=column)
# 如果为 None,初始化
if cate_df is None:
cate_df = freq
# 否则,拼接结果
else:
cate_df = pd.concat([cate_df, freq])
# 逻辑同上
if result is None:
result = cate_df
else:
result = pd.concat([result, cate_df], axis=1)
# 返回条件概率
return result
def train(self, data, y_col='y'):
if y_col not in data.columns:
print(f'Column of y named {y_col} not in data. It will go by column {data.columns[-1]}')
y_col = data.columns[-1]
# 设置标签列列名
self.y_col = y_col
# 获得特征列名列表
self.featrues = data.columns.tolist()
self.featrues.remove(y_col)
# 先验概率
self.pri = self.get_priori(data)
# 条件概率
self.cond = self.get_conditional(data)
return
def forward(self, pred_dict):
# 预测结果,每一列,列名为标签各取值,值为各标签值在指定条件下的概率
result = None
# 遍历要预测的特征值字典,获得各标签值概率中 ΠP(X=x)
for key, value in pred_dict.items():
# 如果特征及值不存在,报错
if (key, value) not in self.cond.index:
raise ValueError(f'The key or value of input data not exists.')
# 逻辑类似于 self.get_conditional
if result is None:
result = self.cond.loc[(key, value)].to_frame().T
else:
result *= self.cond.loc[(key, value)]
# 每列乘上 P(Y=y)
result = result.apply(lambda x: x * self.pri.loc[(self.y_col, x.name)].values[0])
result /= result.sum(axis=1).values[0]
return result.iloc[0]
def predict(self, item):
# 判断输入的用于预测的数据是否可迭代
# 是的话就预测,不是的话就报错
if isinstance(item, Iterable):
# 输入特征值个数不对,报错
if len(item) != len(self.featrues):
raise ValueError(f'The number of input value is [{len(item)}], '
f'which is not equal to the number of featrue [{len(self.featrues)}].')
# 初始化特征值字典
pred_dict = {}
# 如果输入数据为字典,直接覆盖
if isinstance(item, dict):
pred_dict = item
# 否则默认按照列名顺序传值
else:
for i, v in enumerate(item):
if isinstance(i, dict):
pred_dict.update(v)
else:
pred_dict[self.featrues[i]] = v
# 返回预测结果
return self.forward(pred_dict).idxmax()
else:
raise TypeError(f'The type of input data [{type(item)}] is not iterable.')
测试一下(data
如 上表):
Input[]: model = NaiveBayes()
model.train(data)
x_pred = {'x1': '中', 'x2': '少'}
prediction = model.predict(x_pred)
print(f'Value: {", ".join([f"{key}: {value}" for key, value in x_pred.items()])}. Prediction: {prediction}')
-------------------------------------------------------------------
Output[]: Value: x1: 中, x2: 少. Prediction: 0
预测结果正确!!!
使用 sklearn
Input[]: import numpy as np
from sklearn import preprocessing
from sklearn.naive_bayes import GaussianNB
encoders = {}
for column in train_data:
if train_data[column].dtype == object:
encoders[column] = preprocessing.LabelEncoder()
train_data[column] = encoders[column].fit_transform(train_data[column])
model = GaussianNB()
model.fit(train_data[train_data.columns[:-1]], train_data['y'])
model.predict(np.array([encoders[key].transform([value])[0] for key, value in x_pred.items()]).reshape(1, -1))
-------------------------------------------------------------------
Output[]: array([0], dtype=int64)
其中,train_data
如 上表。
结尾
上边的例子就是使用 朴素贝叶斯 算法进行 分类。
有想要一起学习 python
的小伙伴可以 私信我
进群哦。
以上就是我要分享的内容,因为学识尚浅,会有不足,还请各位大佬指正。
有什么问题也可在评论区留言。