朴素贝叶斯是一个非常经典易懂的分类方法,其核心思想是
上式即为概率论中的Bayes定理,描述了两个条件概率之间的关系,那么先让我们来回顾一下概率论的知识。
α Bayes’ theorem
对于计算两个事件X和Y同时发生的概率,可以由Y发生的概率与Y已发生时会发生X的概率相乘得到,即 根据上式即可得 如果Y有很多分属性,且分属性之间相互独立,那么可以认为Y是其所有分属性同时发生的事件 ,而上式则可以变形为 到此Naive Bayes的理论部分就结束了,非常的简单,但你可能还是一头雾水,不要慌,go on~
β Naive Bayes
用一个例子来讲解会帮助你更好地理解,下面的例子取自西瓜书。
我们现在有一个关于西瓜的数据集(嗯,这很符合西瓜书的配置)
很显然,这是关于如何挑一个好瓜的故事。我们可以看到上面总共有17个西瓜,这里列举了每个西瓜的六大特征,最后一列呢则是根据某人的品味对这些西瓜的好坏进行了一个主观的评判。
那么当我们得知某个西瓜的六大特征时能否评估它(在那个人眼里的)好坏呢?显然这里的结果只会有两种,要么好要么坏。那么在我们完全凭主观判断时,其实会在潜意识里比较两者的概率。如果第六感告诉我们这个西瓜大概率是坏的,那我们就会推测它是坏的,这不失为一种感性的判断。
如何将这种模糊判断移植到程序上呢。虽然计算机不感性,但它可以计算概率。假设我们现有一个西瓜的六大特征数据 根据感性判断的思路,我们接下来需要比较 的大小,其中 是瓜的好坏事件,我们假定 表示坏瓜事件, 表示好瓜事件,那么 就表示瓜在具有特征X的情况下为好/坏瓜的概率。
接下来我们就用Bayes定理计算 , 你会发现我没有将分母展开,因为显然 与 的取值无关。因此事实上我们只需要比较分子即可,于是得到 和 的相对概率 上式就是攻克这个问题的它山之石,我们只需分别计算右边的两部分即可。
是显然的,17个生瓜蛋子里面有8个好瓜,9个坏瓜,因此 对于每个特征的 ,我们只需数一下 条件下的瓜里面有几个 即可。比如当 时,我们可以看到8个好瓜中有4个是乌黑的,因此 讲的有点啰嗦,相信你已经悟了。
γ Laplace Smooth
其实已经结束了,这只是一个小补充。在实际的朴素贝叶斯计算过程中,由于每个概率都是<1的,因此连乘后可能会得到一个很小的数以致计算机直接当0算了。为避免这个问题我们可以取对数相加解决。
另外还有一个问题——0概率问题。有时候某个特征值在我们的已知数据集中没有出现,但我们需要对具有这个特征值的西瓜进行归类,这时就会很尴尬地发现不管是 还是 , 。为了解决这个问题,我们引入拉普拉斯平滑,名字吓人,操作并不复杂。
对于
的计算,我们改用
其中D为样本总数,
为
样本的数量,N为
的取值个数,即最终归的类数。
对于
的计算,我们改用
其中
为
类别下第
个特征值为
的个数,
则为第
个特征可能取值的个数。
依本人拙见,可以这么理解,假设一个特征的每一种取值都是等可能的,那么理论上我们抽取 个样本后会出现1个 ,因此分子的+1可以理解为我们在原样本的基础上又另外抽取了 个样本。
C☺DE
@ train.py
import pandas as pd
import numpy as np
import joblib as jb
data = np.array(pd.read_csv('西瓜的数据集.csv'))
sample_num = len(data)
attribute_num = len(data[0]) - 1
# the size of value range of each attribute
values_num = []
# No. corresponding to each value in one attribute
dic = [{} for i in range(attribute_num)]
# calculate the number of possible values for each attribute via 'set'
for i in range(attribute_num):
values = set()
for j in range(sample_num):
values = values | {data[j][i]} # union of two set
values_num.append(len(values))
# number each different value
No = 0
for v in values:
dic[i][v] = No
No += 1
p1_cnt = [np.zeros(values_num[i]) for i in range(attribute_num)]
p0_cnt = [np.zeros(values_num[i]) for i in range(attribute_num)]
for i in range(sample_num):
cnt = p1_cnt if data[i][attribute_num] == '是' else p0_cnt
for j in range(attribute_num):
cnt[j][dic[j][data[i][j]]] += 1
p1 = (sum(p1_cnt[0]) + 1) / (sample_num + attribute_num)
p0 = (sum(p0_cnt[0]) + 1) / (sample_num + attribute_num)
for i in range(attribute_num):
p1_cnt[i] = (p1_cnt[i] + 1) / (sum(p1_cnt[i]) + len(dic[i]))
p0_cnt[i] = (p0_cnt[i] + 1) / (sum(p0_cnt[i]) + len(dic[i]))
# save outcome
jb.dump(p1_cnt, 'p1_cnt.pkl')
jb.dump(p0_cnt, 'p0_cnt.pkl')
jb.dump(dic, 'dic.pkl')
jb.dump(p1, 'p1.pkl')
jb.dump(p0, 'p0.pkl')
@ test.py
import joblib as jb
import numpy as np
import pandas as pd
import math
# load trained byes model
p1 = jb.load('p1.pkl')
p0 = jb.load('p0.pkl')
dic = jb.load('dic.pkl')
p1_cnt = jb.load('p1_cnt.pkl')
p0_cnt = jb.load('p0_cnt.pkl')
test_data = np.array(pd.read_csv('西瓜的验证集.csv'))
sample_num = len(test_data)
attribute_num = len(test_data[0]) - 1
right_cnt = 0
for i in range(sample_num):
p1_pred = p0_pred = 0
for j in range(attribute_num):
p1_pred += math.log(p1_cnt[j][dic[j][test_data[i][j]]])
p0_pred += math.log(p0_cnt[j][dic[j][test_data[i][j]]])
p1_pred += math.log(p1)
p0_pred += math.log(p0)
if ('是' if p1_pred > p0_pred else '否') == test_data[i][attribute_num]:
right_cnt += 1
print("正确个数:\t%d\n预测总数:\t%d\n正确率:\t%f" % (right_cnt, sample_num, right_cnt / sample_num))
Σ 一些说明
首先用集合set求出每个特征(attribute)可能取值的个数,并对每个特征内所有可能的取值进行编号(dic)
然后分别计数统计好瓜和坏瓜中每个特征的每个可能取值的个数
最后用拉普拉斯平滑法计算出 (pi)和 (pi_cnt)