传送门
《统计学习方法》读书笔记——机器学习常用评价指标
《统计学习方法》读书笔记——感知机(原理+代码实现)
《统计学习方法》读书笔记——K近邻法(原理+代码实现)
《统计学习方法》读书笔记——朴素贝叶斯法(公式推导+代码实现)
写在前面
-
朴素贝叶斯法与贝叶斯估计是不同的概念。
- 损失函数与风险函数
- 损失函数用于度量一次预测的好坏;
- 风险函数用于度量平均意义下模型的好坏。 全概率公式与逆概率公式
-
设
A
1
,
A
2
,
.
.
.
,
A
n
A_1,A_2,...,A_n
A1,A2,...,An为一组完备事件组,则对任一事件
B
B
B,有如下
全概率公式:
P ( B ) = ∑ i = 1 n P ( A i ) P ( B ∣ A i ) P(B) =\sum_{i=1}^nP(A_i)P(B|A_i) P(B)=i=1∑nP(Ai)P(B∣Ai)
若 P ( B ) > 0 P(B)>0 P(B)>0,则有如下 贝叶斯公式,或称 逆概率公式:
P ( A i ∣ B ) = P ( A i B ) P ( B ) = P ( A i ) P ( B ∣ A i ) ∑ j = 1 n P ( A j ) P ( B ∣ A j ) P(A_i|B)=\frac{P(A_iB)}{P(B)}=\frac{P(A_i)P(B|A_i)}{\sum\limits_{j=1}^nP(A_j)P(B|A_j)} P(Ai∣B)=P(B)P(AiB)=j=1∑nP(Aj)P(B∣Aj)P(Ai)P(B∣Ai)
先验概率与后验概率
- 常称 P ( A i ) P(A_i) P(Ai)为事件 A i A_i Ai发生的 先验概率,而称 P ( A i ∣ B ) P(A_i|B) P(Ai∣B)为事件 A i A_i Ai发生的 后验概率。(概率论教程P25)
朴素贝叶斯法
“如果你不知道怎样踢球,就往球门方向踢 ” ——施拉普纳
要搞明白朴素贝叶斯法的原理,要先知道“球门方向”在哪里。
设输入空间
X
⊆
R
n
\mathcal{X}\subseteq{\bm{R}^n}
X⊆Rn为
n
n
n维向量的集合,输入空间为类标记集合
Y
=
{
c
1
,
c
2
,
.
.
.
,
c
K
}
\mathcal{Y} = \{c_1,c_2,...,c_K\}
Y={c1,c2,...,cK}。输入为特征向量
x
∈
X
x\in\mathcal{X}
x∈X,输入为类别
y
∈
Y
y\in\mathcal{Y}
y∈Y。
X
X
X是定义在输入空间上的随机变量,
Y
Y
Y是定义在输出空间
Y
\mathcal{Y}
Y上的随机变量。对于一个输入的数据
x
x
x,要预测
x
x
x所属的类别,相当于求以下概率的值:
P
(
Y
=
c
k
∣
X
=
x
)
P(Y=c_k|X=x)
P(Y=ck∣X=x)
根据贝叶斯公式(逆概率公式),可得:
P
(
Y
=
c
k
∣
X
=
x
)
=
P
(
X
=
x
∣
Y
=
c
k
)
P
(
Y
=
c
k
)
∑
k
P
(
X
=
x
∣
Y
=
c
k
)
P
(
Y
=
c
k
)
P(Y=c_k|X=x) = \frac{P(X=x|Y=c_k)P(Y=c_k)}{\sum\limits_k P(X=x|Y=c_k)P(Y=c_k)}
P(Y=ck∣X=x)=k∑P(X=x∣Y=ck)P(Y=ck)P(X=x∣Y=ck)P(Y=ck)
插入部分
上述的贝叶斯公式中,
P
(
Y
=
c
k
)
P(Y=c_k)
P(Y=ck)容易求得,统计数据集中各个类别样本的数量就可以计算出来。
因为
x
x
x是向量,即
x
=
(
x
(
1
)
,
x
(
2
)
,
.
.
.
,
x
(
n
)
)
x = (x^{(1)},x^{(2)},...,x^{(n)})
x=(x(1),x(2),...,x(n)),因此将
P
(
X
=
x
∣
Y
=
c
k
)
P(X=x|Y=c_k)
P(X=x∣Y=ck)部分展开来就是
P
(
X
(
1
)
=
x
(
1
)
,
X
(
2
)
=
x
(
2
)
,
.
.
.
,
X
(
n
)
=
x
(
n
)
∣
Y
=
c
k
)
P(X^{(1)}=x^{(1)},X^{(2)}=x^{(2)},...,X^{(n)}=x^{(n)} |Y=c_k)
P(X(1)=x(1),X(2)=x(2),...,X(n)=x(n)∣Y=ck)。
这样看来,假设对mnist数据集来说,每一张图片有
K
=
10
K=10
K=10种可能的预测结果(即
c
k
,
k
=
1
,
2
,
.
.
.
,
10
c_k,k=1,2,...,10
ck,k=1,2,...,10),每一张图片由28*28=784个像素点组成(即
x
(
j
)
x^{(j)}
x(j),
j
=
1
,
2
,
.
.
.
,
784
j=1,2,...,784
j=1,2,...,784),每一个像素点的取值范围为
0
<
c
(
j
)
<
255
0<c^{(j)}<255
0<c(j)<255,即
x
(
j
)
x^{(j)}
x(j)的可值为
S
j
=
255
S_j=255
Sj=255个。那么要计算的数据量为
K
∏
j
=
1
n
=
10
×
25
5
784
K\prod\limits_{j=1}^n=10\times255^{784}
Kj=1∏n=10×255784,无法计算。
朴素贝叶斯法对条件概率分布作了条件独立性的假设,朴素贝叶斯法也由此得名,即
P
(
X
=
x
∣
Y
=
c
k
)
=
P
(
X
(
1
)
=
x
(
1
)
,
X
(
2
)
=
x
(
2
)
,
.
.
.
,
X
(
n
)
=
x
(
n
)
∣
Y
=
c
k
)
=
P
(
X
(
1
)
=
x
(
1
)
∣
Y
=
c
k
)
P
(
X
(
2
)
=
x
(
2
)
∣
Y
=
c
k
)
.
.
.
P
(
X
(
n
)
=
x
(
n
)
∣
Y
=
c
k
)
=
∏
j
=
1
n
P
(
X
(
j
)
=
x
(
j
)
∣
Y
=
c
k
)
\begin{aligned} P(X=x|Y=c_k) &= P(X^{(1)}=x^{(1)},X^{(2)}=x^{(2)},...,X^{(n)}=x^{(n)} |Y=c_k)\\ &=P(X^{(1)}=x^{(1)}|Y=c_k)P(X^{(2)}=x^{(2)}|Y=c_k)...P(X^{(n)}=x^{(n)} |Y=c_k)\\ &=\prod\limits_{j=1}^nP(X^{(j)}=x^{(j)}|Y=c_k) \end{aligned}
P(X=x∣Y=ck)=P(X(1)=x(1),X(2)=x(2),...,X(n)=x(n)∣Y=ck)=P(X(1)=x(1)∣Y=ck)P(X(2)=x(2)∣Y=ck)...P(X(n)=x(n)∣Y=ck)=j=1∏nP(X(j)=x(j)∣Y=ck)
这一假设使朴素贝叶斯法变得简单,但会牺牲一定的分类准确率。
(因为向量的特征之间大概率是不独立地,如果我们独立了,会无法避免地抛弃一些前后连贯的信息,比方说我说“三人成?”,后面大概率就是个”虎“,这个虎明显依赖于前面的三个字。)
那么上面的贝叶斯公式就可以转换为以下形式:
P
(
Y
=
c
k
∣
X
=
x
)
=
P
(
X
=
x
∣
Y
=
c
k
)
P
(
Y
=
c
k
)
∑
k
P
(
X
=
x
∣
Y
=
c
k
)
P
(
Y
=
c
k
)
=
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
)
\begin{aligned} P(Y=c_k|X=x) &= \frac{P(X=x|Y=c_k)P(Y=c_k)}{\sum\limits_k P(X=x|Y=c_k)P(Y=c_k)} \\ &= \frac{P(Y=c_k)\prod\limits_{j=1}^nP(X^{(j)}=x^{(j)}|Y=c_k))}{\sum\limits_k P(Y=c_k)\prod\limits_{j=1}^nP(X^{(j)}=x^{(j)}|Y=c_k)} \end{aligned}
P(Y=ck∣X=x)=k∑P(X=x∣Y=ck)P(Y=ck)P(X=x∣Y=ck)P(Y=ck)=k∑P(Y=ck)j=1∏nP(X(j)=x(j)∣Y=ck)P(Y=ck)j=1∏nP(X(j)=x(j)∣Y=ck))
这是朴素贝叶斯法分类的基本公式。于是,朴素贝叶斯分类器可表示为
y
=
arg max
c
k
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
)
y= \argmax_{c_k}\frac{P(Y=c_k)\prod\limits_{j=1}^nP(X^{(j)}=x^{(j)}|Y=c_k))}{\sum\limits_k P(Y=c_k)\prod\limits_{j=1}^nP(X^{(j)}=x^{(j)}|Y=c_k)}
y=ckargmaxk∑P(Y=ck)j=1∏nP(X(j)=x(j)∣Y=ck)P(Y=ck)j=1∏nP(X(j)=x(j)∣Y=ck))
其中分母对所有
c
k
c_k
ck都是相同的,所以
y
=
arg max
c
k
P
(
Y
=
c
k
)
∏
j
=
1
n
P
(
X
(
j
)
=
x
(
j
)
∣
Y
=
c
k
)
)
y= \argmax_{c_k}P(Y=c_k)\prod\limits_{j=1}^nP(X^{(j)}=x^{(j)}|Y=c_k))
y=ckargmaxP(Y=ck)j=1∏nP(X(j)=x(j)∣Y=ck))
该公式即为朴素贝叶斯分类器。
至于式子里面的两项具体怎么求,我们首先看第一项。
P
(
Y
=
c
k
)
=
∑
i
=
1
N
I
(
y
i
=
c
k
)
N
,
k
=
1
,
2
,
⋯
,
K
P\left(Y=c_{k}\right)=\frac{\sum_{i=1}^{N} I\left(y_{i}=c_{k}\right)}{N}, \quad k=1,2, \cdots, K
P(Y=ck)=N∑i=1NI(yi=ck),k=1,2,⋯,K
N为训练样本的数目,假设我们手里现在有100个样本,那N就是100。
分子中
I
(
.
.
.
)
I(...)
I(...)是指示函数,括号内条件为真时指示函数为1,反之为0。分子的意思求在这100个样本里有多少是属于
c
k
c_k
ck分类的。
再看第二项
P
(
X
(
j
)
=
a
j
l
∣
Y
=
c
k
)
=
∑
i
=
1
N
I
(
x
i
(
j
)
=
a
j
l
,
y
i
=
c
k
)
∑
i
=
1
N
I
(
y
i
=
c
k
)
P(X^{(j)}=a_{j l} |Y=c_{k})=\frac{\sum_{i=1}^{N} I(x_{i}^{(j)}=a_{j l},y_{i}=c_{k})}{\sum_{i=1}^{N} I(y_{i}=c_{k})} \\
P(X(j)=ajl∣Y=ck)=∑i=1NI(yi=ck)∑i=1NI(xi(j)=ajl,yi=ck)
其中
j
=
1
,
2
,
.
.
.
,
n
;
l
=
1
,
2
,
.
.
.
,
S
j
;
k
=
1
,
2
,
.
.
.
,
K
j=1,2, ..., n ; \quad l=1,2, ..., S_{j};\quad k=1,2,..., K
j=1,2,...,n;l=1,2,...,Sj;k=1,2,...,K
构造原理和上面第一项的一样,也是通过指示函数来计数得出概率。那么两项都能求了,给出朴素贝叶斯算法。
但是,步骤(2)中,那么多概率连乘,如果其中有一个概率为0怎么办?那整个式子直接就是0了,这样不对。所以我们连乘中的每一项都得想办法让它保证不是0。
代码实现
# coding=utf-8
import numpy as np
import time
def loadData(fileName):
print('start to read data:' + fileName)
dataArr = []
labelArr = []
fr = open(fileName, 'r')
# 将文件按行读取
for line in fr.readlines():
# 对每一行数据按切割符','进行切割,返回字段列表
curLine = line.strip().split(',')
# 将mnist图像二值化
dataArr.append([int(int(num) > 128) for num in curLine[1:]])
labelArr.append(int(curLine[0]))
# 返回data和label
return dataArr, labelArr
def NaiveBayes(Py, Px_y, x):
featrueNum = 784
classNum = 10
P = [0] * classNum
for i in range(classNum):
sum = 0
for j in range(featrueNum):
sum += Px_y[i][j][x[j]]
P[i] = sum + Py[i]
return P.index(max(P))
def model_test(Py, Px_y, testDataArr, testLabelArr):
errorCnt = 0
for i in range(len(testDataArr)):
predict = NaiveBayes(Py, Px_y, testDataArr[i])
if predict != testLabelArr[i]:
errorCnt += 1
return 1 - (errorCnt / len(testDataArr))
def getAllProbability(trainDataArr, trainLabelArr):
featureNum = 784
classNum = 10
lambbaVal = 1
#初始化先验概率分布存放数组,后续计算得到的P(Y = 0)放在Py[0]中,以此类推
#数据长度为10行1列
Py = np.zeros((classNum, 1))
#对每个类别进行一次循环,分别计算它们的先验概率分布
#计算公式为书中"4.2节 朴素贝叶斯法的参数估计 公式4.8"
for i in range(classNum):
# 计算先验概率
Py[i] = ((np.sum(np.mat(trainLabelArr) == i)) + lambbaVal) / (len(trainLabelArr) + classNum * lambbaVal)
# 防止数据过小向下溢出使用log()
# 在似然函数中通常会使用log的方式进行处理
Py = np.log(Py)
# 计算条件概率 Px_y=P(X=x|Y = y)
# 计算分子部分
# 2表示一个像素点可取值的个数(因为对数据做了二值化处理)
Px_y = np.zeros((classNum, featureNum, 2))
for i in range(len(trainLabelArr)):
#获取当前循环所使用的标记
label = trainLabelArr[i]
#获取当前要处理的样本
x = trainDataArr[i]
#对该样本的每一维特诊进行遍历
for j in range(featureNum):
#在矩阵中对应位置加1
#这里还没有计算条件概率,先把所有数累加,全加完以后,在后续步骤中再求对应的条件概率
Px_y[label][j][x[j]] += 1
# 计算分母部分
for label in range(classNum):
for j in range(featureNum):
#获取y=label,第j个特诊为0的个数
Px_y0 = Px_y[label][j][0]
#获取y=label,第j个特诊为1的个数
Px_y1 = Px_y[label][j][1]
#对式4.10的分子和分母进行相除,再除之前依据贝叶斯估计,分母需要加上2(为每个特征可取值个数)
#分别计算对于y= label,x第j个特征为0和1的条件概率分布
Px_y[label][j][0] = np.log((Px_y0 + 1) / (Px_y0 + Px_y1 + 2))
Px_y[label][j][1] = np.log((Px_y1 + 1) / (Px_y0 + Px_y1 + 2))
#返回先验概率分布和条件概率分布
return Py, Px_y
if __name__ == "__main__":
start = time.time()
# 获取训练集、测试集
trainData, trainLabel = loadData('./mnist/mnist_train.csv')
testData, testLabel = loadData('./mnist/mnist_test.csv')
#开始训练,学习先验概率分布和条件概率分布
print('start to train')
Py, Px_y = getAllProbability(trainData, trainLabel)
#使用习得的先验概率分布和条件概率分布对测试集进行测试
print('start to test')
accuracy = model_test(Py, Px_y, testData, testLabel)
print('the accuracy is:', accuracy)
print('time span:', time.time() -start)
输出结果
start to read data:./mnist/mnist_train.csv
start to read data:./mnist/mnist_test.csv
start to train
start to test
the accuracy is: 0.8432999999999999
time span: 129.84226727485657
参考
原理:《统计学习方法》
代码: https://github.com/Dod-o/Statistical-Learning-Method_Code