朴素贝叶斯是一种有监督学习算法,这种算法基于贝叶斯的一个朴素的假设——每对特征和样本数据都是独立同分布的。最终可以推出朴素贝叶斯分类器的判定准则:
h n b ( x ) = a r g m a x c ∈ Υ P ( c ) ∏ i = 1 d P ( x i ∣ c ) h_{nb}(x)=\mathop{arg\ max}\limits_{c\in \varUpsilon}\ P(c) \prod_{i=1}^dP(x_i\ | \ c) hnb(x)=c∈Υarg max P(c)i=1∏dP(xi ∣ c)
我们之前说朴素贝叶斯是一系列算法,这是因为基于不同的条件独立性假设,朴素贝叶斯的分类器会有所不同。
虽然朴素贝叶斯看起来很简单(实际上推起来也很简单),但是它们在现实中的运用还是很不错的,主要是它们仅仅只需要少量训练数据就能估计几个必要的参数,这非常重要。
下面说一下朴素贝叶斯的优缺点:
优点:
缺点:
predict_proba
仅仅做参考就好了,一般都不太靠谱。贝叶斯的主要思路就是数数,统计每个条件组合下各种结果的概率,并根据这个概率做出分类。
但是,单纯的贝叶斯在计算机计算的时候会出现计算规模过大的情况,这里就需要 引入朴素条件,化简式子。
最终结果就是在计算时我们只需要统计单个条件下的结果,并据此计算得结果就好了,区别于先自由组合条件在统计结果。
朴素贝叶斯分类器的推导离不开概率论,这边顺便复习下概率论最基础的知识:
已知有一种疾病,在城市中的患病率为1%,在检查时有银兴阳性两种结果,在检查结果为阴性时有10%概率患病,在检查结果为阳性时有90%概率患病。
城市中有10000个人参与检查。
我们将这个问题带入到 0-1 评分下的脱单数据集中:
不难发现,直接使用贝叶斯公式时,我们遇到了麻烦。我们判断交往(R)的特征有5个,在计算时条件的结果有 2 5 2^5 25 种组合方式。如果我们有50个特征,那需要计算 2 50 2^{50} 250 种特征组合;如果特征还不是 0-1 变量,而是另一个脱单数据集那样的 0-10 评分数据,则需要计算 1 1 50 11^{50} 1150 种特征组合!甚至在连续数据下,这种方法是不可用的。
这个时候就体现出朴素贝叶斯下那个朴素的假设效果不朴素了。在此假设下,问题转化为:
需要注意的是, 朴素贝叶斯在加入朴素条件后变换式子还能提高算法的可靠性。在样本量不够大的时候很可能出现 P ( A 0 B 0 C 0 D 0 E 0 ∣ R 1 ) P(\ A_0\ B_0\ C_0\ D_0\ E_0\ |\ R_1) P( A0 B0 C0 D0 E0 ∣ R1) 为 0 和 P ( A 0 B 0 C 0 D 0 E 0 ) P(\ A_0\ B_0\ C_0\ D_0\ E_0) P( A0 B0 C0 D0 E0) 为 0 的情况,毕竟总有一类人的条件在相亲市场中不受欢迎,但是因为这是在小样本数据中的结果,并不能真实代表这个概率,直接判定 0 显然不可靠。
在朴素贝叶斯公式中, P ( A 0 ∣ R 1 ) P(\ A_0\ |\ R_1) P( A0 ∣ R1) 等条件累乘的结果显然大于等于 P ( A 0 B 0 C 0 D 0 E 0 ∣ R 1 ) P(\ A_0\ B_0\ C_0\ D_0\ E_0\ |\ R_1) P( A0 B0 C0 D0 E0 ∣ R1) ,这意味着变换后更不容易为0。
换一种说法, P ( A 0 ∣ R 1 ) P(\ A_0\ |\ R_1) P( A0 ∣ R1) 每个条件相较于 P ( A 0 B 0 C 0 D 0 E 0 ∣ R 1 ) P(\ A_0\ B_0\ C_0\ D_0\ E_0\ |\ R_1) P( A0 B0 C0 D0 E0 ∣ R1) 的可能性是更高的,他们每个不等于0的概率都比 P ( A 0 B 0 C 0 D 0 E 0 ∣ R 1 ) P(\ A_0\ B_0\ C_0\ D_0\ E_0\ |\ R_1) P( A0 B0 C0 D0 E0 ∣ R1) 大。
因此,在变换后,朴素贝叶斯公式更加稳定,在小样本数据中发挥更好。
为了模拟计算过程,我们回到 0-1 脱单数据集中:
我们之前提过一嘴,这样的方法只是最简单的数量统计,面对连续型数据时,简单的数量统计势必会使算法陷入维度灾难中,此外简单的统计数量也不能体现样本数据的连续性。因此,我们需要一种方法处理连续型数据,当然如果这种方法对连续型数据有效,那对离散型数据也同样适用。
我们这里根据身边朋友的相亲意向对如下脱单样本打标签,完成如下交往意向数据集。我们将这个数据集称为脱单数据集2.0。
样本 | 颜值(A) | 身材(B) | 性格(C) | 收入(D) | 学历(E) | 交往(R) |
---|---|---|---|---|---|---|
1 | 6 | 4 | 7 | 7 | 8 | 1 |
2 | 10 | 10 | 2 | 4 | 2 | 0 |
3 | 7 | 9 | 6 | 8 | 6 | 1 |
4 | 8 | 2 | 10 | 2 | 6 | 0 |
5 | 6 | 6 | 1 | 9 | 4 | 0 |
6 | 3 | 9 | 5 | 7 | 6 | 0 |
7 | 9 | 7 | 3 | 4 | 4 | 0 |
8 | 8 | 5 | 6 | 7 | 6 | 1 |
9 | 4 | 1 | 6 | 8 | 10 | 0 |
10 | 6 | 6 | 7 | 6 | 7 | 1 |
11 | 7 | 4 | 9 | 5 | 2 | 0 |
12 | 1 | 5 | 9 | 10 | 6 | 0 |
13 | 6 | 7 | 7 | 5 | 8 | 1 |
14 | 6 | 5 | 6 | 8 | 6 | 1 |
15 | 8 | 7 | 7 | 6 | 10 | 1 |
lovedata_2.csv
颜值(A),身材(B),性格(C),收入(D),学历(E),交往(R)
6,4,7,7,8,1
10,10,2,4,2,0
7,9,6,8,6,1
8,2,10,2,6,0
6,6,1,9,4,0
3,9,5,7,6,0
9,7,3,4,4,0
8,5,6,7,6,1
4,1,6,8,10,0
6,6,7,6,7,1
7,4,9,5,2,0
1,5,9,10,6,0
6,7,7,5,8,1
6,5,6,8,6,1
8,7,7,6,10,1
楼上的数据集不是0-1数据,不太适合说明问题,我们这里退化下交往意向数据集,将1-5的评分标记为0,6-10的评分标记为1,完成0-1交往意向数据集。暂时忽略重复数据,毕竟现实中也可能出现样本特性一样的情况。我们将这个数据集称为脱单数据集1.0。
样本 | 颜值(A) | 身材(B) | 性格(C) | 收入(D) | 学历(E) | 交往(R) |
---|---|---|---|---|---|---|
1 | 1 | 0 | 1 | 1 | 1 | 1 |
2 | 1 | 1 | 0 | 0 | 0 | 0 |
3 | 1 | 1 | 1 | 1 | 1 | 1 |
4 | 1 | 0 | 1 | 0 | 1 | 0 |
5 | 1 | 1 | 0 | 1 | 0 | 0 |
6 | 0 | 1 | 0 | 1 | 1 | 0 |
7 | 1 | 1 | 0 | 0 | 0 | 0 |
8 | 1 | 0 | 1 | 1 | 1 | 1 |
9 | 0 | 0 | 1 | 1 | 1 | 0 |
10 | 1 | 1 | 1 | 1 | 1 | 1 |
11 | 1 | 0 | 1 | 0 | 0 | 0 |
12 | 0 | 0 | 1 | 1 | 1 | 0 |
13 | 1 | 1 | 1 | 0 | 1 | 1 |
14 | 1 | 0 | 1 | 1 | 1 | 1 |
15 | 1 | 1 | 1 | 1 | 1 | 1 |
lovedata_1.csv
颜值(A),身材(B),性格(C),收入(D),学历(E),交往(R)
1,0,1,1,1,1
1,1,0,0,0,0
1,1,1,1,1,1
1,0,1,0,1,0
1,1,0,1,0,0
0,1,0,1,1,0
1,1,0,0,0,0
1,0,1,1,1,1
0,0,1,1,1,0
1,1,1,1,1,1
1,0,1,0,0,0
0,0,1,1,1,0
1,1,1,0,1,1
1,0,1,1,1,1
1,1,1,1,1,1
参考西瓜书上的数据,转化为整型变量。
watermelon.csv
编号,色泽,根蒂,敲声,纹理,脐部,触感,密度,含糖率,好瓜
1,0,0,0,0,0,0,0.697,0.46,1
2,1,0,1,0,0,0,0.774,0.376,1
3,1,0,0,0,0,0,0.634,0.264,1
4,0,0,1,0,0,0,0.608,0.318,1
5,2,0,0,0,0,0,0.556,0.215,1
6,0,1,0,0,1,1,0.403,0.237,1
7,1,1,0,1,1,1,0.481,0.149,1
8,1,1,0,0,1,0,0.437,0.211,1
9,1,1,1,1,1,0,0.666,0.091,0
10,0,2,2,0,2,1,0.243,0.267,0
11,2,2,2,2,2,0,0.245,0.057,0
12,2,0,0,2,2,1,0.343,0.099,0
13,0,1,0,1,0,0,0.639,0.161,0
14,2,1,1,1,0,0,0.657,0.198,0
15,1,1,0,0,1,1,0.36,0.37,0
16,2,0,0,2,2,0,0.593,0.042,0
17,0,0,1,1,1,0,0.719,0.103,0
def love1(filename, coiled=False):
# 计算p(a|b)
def calculate_frq_ab(data, label, coiled):
# 存储各个label对应的概率
label_p = {}
# unique就series能用
for label_value in label.unique():
# 存储这个label值下,这个特征值组合的概率
feature_p = {}
# 筛选label后的数据
data_filter = data.loc[label[label == label_value].index.values]
# 每个feature一个个数过去
for feature in data_filter.columns:
feature_p[feature] = data_filter.value_counts(feature) / data_filter.shape[0]
label_p[label_value] = feature_p
return label_p
# label = {
# label_value1: {feature1: df, feature2: df},
# label_value2: {feature1: df, feature2: df},
# }
# 计算p(a),p(b)
def calculate_frq_a_b(data, label, coiled):
p = {}
p[label.name] = label.value_counts() / label.shape[0]
for feature in data.columns:
p[feature] = data.value_counts(feature) / data.shape[0]
return p
# 分类器,a为条件,b为label
def clf(data, label, p_ab, p_a_b):
# label = label.unique()
ret = []
for sample in data.T.iteritems():
r = []
for label_i in label:
p_b_i = p_a_b[label.name][label_i]
p_a_i = 1
p_ab_i = 1
for index, feature_name in enumerate(data.columns):
try:
p_a_i *= p_a_b[feature_name][sample[1][index]]
p_ab_i *= p_ab[label_i][feature_name][sample[1][index]]
except KeyError as e:
# 出现概率为0的情况
p_a_i = 0
p_ab_i = 1
break
r.append(p_b_i * p_a_i)
# / p_ab_i
r = label[r.index(max(r))]
ret.append(r)
return ret
data_train, data_test, label_train, label_test = get_data(filename)
p_ab = calculate_frq_ab(data_train, label_train, coiled)
p_a_b = calculate_frq_a_b(data_train, label_train, coiled)
print("训练准确率为:")
eveluate(clf(data_train, label_train, p_ab, p_a_b), label_train)
print("测试准确率为:")
eveluate(clf(data_test, label_train, p_ab, p_a_b), label_test)
# 高斯朴素贝叶斯
def sk(files):
while 1:
for index, name in enumerate(files):
print(index, ". ", name)
print(len(files), ". 退出")
try:
choice = int(input())
file = files[choice]
except:
return 0
data_train, data_test, label_train, label_test = get_data(file)
clf = GaussianNB()
clf = clf.fit(data_train, label_train)
predict = clf.predict(data_test)
eveluate(predict, label_test)
一、脱单数据集1.0
二、脱单数据集2.0
三、西瓜数据集
四、sklearn
朴素贝叶斯有几个注意点,可能会在面试中被提到,还是比较能体现被面试者对这个算法的理解的。当然我们也不一定就是为了面试,搞清楚这些问题对帮助我们理解这个算法还是很有好处的。其中部分答案是博主自己的理解,如果有问题麻烦路过的大佬评论区指正。
证明:条件独立性假设不成立时,朴素贝叶斯分类器仍有可能产生最优贝叶斯分类器。
**答:**这篇论文给出了详细的解答《On the Optimality of the Simple Bayesian Classifier under Zero-One Loss》,我们浅浅尝试复述下。
实践中使用 h n b ( x ) = a r g m a x c ∈ Υ P ( c ) ∏ i = 1 d P ( x i ∣ c ) h_{nb}(x)=\mathop{arg\ max}\limits_{c\in \varUpsilon}\ P(c) \prod_{i=1}^dP(x_i\ | \ c) hnb(x)=c∈Υarg max P(c)∏i=1dP(xi ∣ c) 决定分类类别时,如果数据维度很高,则会导致 ∏ i = 1 d P ( x i ∣ c ) \prod_{i=1}^dP(x_i\ | \ c) ∏i=1dP(xi ∣ c) 的结果非常接近0,从而导致下溢。试述防止下溢的方法。
答: 这个问题其实西瓜书第153页有提及,方法跟很多的数学式子的思路相似,对原式取对数,将连乘转化为连加。 h n b ( x ) = a r g m a x c ∈ Υ P ( c ) ∏ i = 1 d P ( x i ∣ c ) h_{nb}(x)=\mathop{arg\ max}\limits_{c\in \varUpsilon}\ P(c) \prod_{i=1}^dP(x_i\ | \ c) hnb(x)=c∈Υarg max P(c)∏i=1dP(xi ∣ c) 因此转化为 h n b ( x ) = a r g m a x θ l o g ( P ( c ) ) + ∑ i = 1 d l o g ( P ( x i ∣ c ) ) h_{nb}(x)=\mathop{arg\ max}\limits_{\theta}\ log(P(c)) + \sum_{i=1}^dlog(P(x_i\ | \ c)) hnb(x)=θarg max log(P(c))+∑i=1dlog(P(xi ∣ c)) 。
import random
from sklearn import preprocessing
import numpy as np
from sklearn.naive_bayes import GaussianNB
import pandas as pd
def get_data(filename, to_one=False, train_size=0.5):
data = pd.read_csv(filename).sample(frac=1, random_state=1129)
labels = data[data.columns[-1]]
data_shuffled = data[data.columns[:-1]]
if to_one:
data_shuffled = pd.DataFrame(preprocessing.scale(data_shuffled), columns=data.columns[:-1])
# 划分训练集测试集
# 更喜欢pandas的计算,这里就任性点全变成dataframe的了
data_train = data_shuffled.head(int(data_shuffled.shape[0] * train_size)).reset_index(drop=True)
label_train = labels.head(int(data_shuffled.shape[0] * train_size)).reset_index(drop=True)
# .reset_index(drop=True)可重置index
data_test = data_shuffled.tail(int(data_shuffled.shape[0] * (1 - train_size))).reset_index(drop=True)
label_test = labels.tail(int(data_shuffled.shape[0] * (1 - train_size))).reset_index(drop=True)
return data_train, data_test, label_train, label_test
def eveluate(predict, result):
result = result.tolist()
correct = 0
for index, i in enumerate(predict):
correct += (i == result[index])
print("准确率为:", correct / len(predict) * 100, "%")
# love1.0
def love1(filename, coiled=False):
# 计算p(a|b)
def calculate_frq_ab(data, label, coiled):
# 存储各个label对应的概率
label_p = {}
# unique就series能用
for label_value in label.unique():
# 存储这个label值下,这个特征值组合的概率
feature_p = {}
# 筛选label后的数据
data_filter = data.loc[label[label == label_value].index.values]
# 每个feature一个个数过去
for feature in data_filter.columns:
feature_p[feature] = data_filter.value_counts(feature) / data_filter.shape[0]
label_p[label_value] = feature_p
return label_p
# label = {
# label_value1: {feature1: df, feature2: df},
# label_value2: {feature1: df, feature2: df},
# }
# 计算p(a),p(b)
def calculate_frq_a_b(data, label, coiled):
p = {}
p[label.name] = label.value_counts() / label.shape[0]
for feature in data.columns:
p[feature] = data.value_counts(feature) / data.shape[0]
return p
# 分类器,a为条件,b为label
def clf(data, label, p_ab, p_a_b):
# label = label.unique()
ret = []
for sample in data.T.iteritems():
r = []
for label_i in label:
p_b_i = p_a_b[label.name][label_i]
p_a_i = 1
p_ab_i = 1
for index, feature_name in enumerate(data.columns):
try:
p_a_i *= p_a_b[feature_name][sample[1][index]]
p_ab_i *= p_ab[label_i][feature_name][sample[1][index]]
except KeyError as e:
# 出现概率为0的情况
p_a_i = 0
p_ab_i = 1
break
r.append(p_b_i * p_a_i)
# / p_ab_i
r = label[r.index(max(r))]
ret.append(r)
return ret
data_train, data_test, label_train, label_test = get_data(filename)
p_ab = calculate_frq_ab(data_train, label_train, coiled)
p_a_b = calculate_frq_a_b(data_train, label_train, coiled)
print("训练准确率为:")
eveluate(clf(data_train, label_train, p_ab, p_a_b), label_train)
print("测试准确率为:")
eveluate(clf(data_test, label_train, p_ab, p_a_b), label_test)
# 高斯朴素贝叶斯
def sk(files):
while 1:
for index, name in enumerate(files):
print(index, ". ", name)
print(len(files), ". 退出")
try:
choice = int(input())
file = files[choice]
except:
return 0
data_train, data_test, label_train, label_test = get_data(file)
clf = GaussianNB()
clf = clf.fit(data_train, label_train)
predict = clf.predict(data_test)
eveluate(predict, label_test)
if __name__ == '__main__':
random.seed(1129)
choice = 0
while choice != 5:
# # 因为有个label载columns里,所以有data.columns-1个w,偏置b还有一列
# w = np.random.rand(data_shuffled.shape[1]).reshape(-1, 1)
print("1. 恋爱1.0数据集\n2. 恋爱2.0数据集\n3. 西瓜数据集(手写)\n4. sklearn高斯朴素贝叶斯\n5. 退出")
try:
choice = int(input())
except:
break
if choice == 1:
print("恋爱1.0数据集手写求解中...")
love1("lovedata_1.csv")
elif choice == 2:
print("恋爱2.0数据集手写求解中...")
love1("lovedata_2.csv")
elif choice == 3:
print("西瓜数据集求解中...")
love1("watermelon.csv")
elif choice == 4:
print('西瓜数据集sklearn yyds')
sk(["lovedata_1.csv", "lovedata_2.csv", "watermelon.csv"])
else:
print("退出成功")
choice = 6
break
参考:
吴恩达《机器学习》
sklearn官网