【机器学习|机器学习(2)-朴素贝叶斯的理解和代码实现】本文主要从下面两个方面展开
朴素贝叶斯
- 我的理解
- 代码实现
我的理解 朴素贝叶斯的本质就是在假设输入特征相互独立的条件下(这个假设是为了方便计算似然函数 P ( X ∣ Y ) P(X|Y) P(X∣Y),因为这里的特征X可能有很多属性,而且取值也很多,先验概率还是后验概率的区分主要是先搞清楚研究的是什么,比如说这里我们想要知道的是类别Y,那么先验概率就是P(Y),后验概率就是P(Y|X)),利用后验概率对样本进行分类的学习方法,后验概率由贝叶斯理论可表示为
P ( Y ∣ X ) = P ( Y ) P ( X ∣ Y ) P ( X ) P(Y|X) = \frac{P(Y)P(X|Y)}{P(X)} P(Y∣X)=P(X)P(Y)P(X∣Y)?
对不同的y求出相应的后验概率,最终输出的y值为后验概率最大的y。在实际问题中,对于同一个输入 X , P ( X ) X,P(X) X,P(X)的值是固定的,因此可以将问题转为为 arg?max ? y P ( Y ) P ( X ∣ Y ) \argmax_yP(Y)P(X|Y) yargmax?P(Y)P(X∣Y),因为X的特征相互独立,所以 P ( X ∣ Y ) = ∏ i = 1 n P ( X ( i ) ∣ Y ) P(X|Y)=\displaystyle\prod_{i=1}^{n}P(X^{(i)}|Y) P(X∣Y)=i=1∏n?P(X(i)∣Y),n为输入特征维度,先验概率 P ( Y ) 和 条 件 概 率 P ( X ( i ) ∣ Y ) P(Y)和条件概率P(X^{(i)}|Y) P(Y)和条件概率P(X(i)∣Y)可以用最大似然估计求得,即可以用样本的频率代替
具体计算方法如下:
首先对公式中的变量进行一些说明, N N N表示样本总数, n n n表示的输入特征X的维度,即特征的个数, c k c_k ck?表示 Y Y Y的第k取个值,那么根据最大似然估计可以得到
P ( Y = c k ) = I ( ∑ i = 1 N y i = c k ) N P(Y=c_k) = \frac{I(\displaystyle\sum_{i=1}^{N}y_i=c_k)}{N} P(Y=ck?)=NI(i=1∑N?yi?=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_{jl}|Y=c_k)=\frac{\displaystyle\sum_{i=1}^NI(x_i^{(j)}=a_{jl}, y_i=c_k)}{\displaystyle\sum_{i=1}^NI(y_i=c_k)} P(X(j)=ajl?∣Y=ck?)=i=1∑N?I(yi?=ck?)i=1∑N?I(xi(j)?=ajl?,yi?=ck?)?
I I I为指示函数, a j l a_{jl} ajl?是特征 X ( j ) X^{(j)} X(j)的第 l l l个取值
直接用最大似然估计可能会导致计算条件概率是分母为零的情况,这个时候引入贝叶斯估计,得到先验概率和条件概率的计算公式如下:
P λ ( Y = c k ) = I ( ∑ i = 1 N y i = c k ) + λ N + K λ P_\lambda(Y=c_k) = \frac{I(\displaystyle\sum_{i=1}^{N}y_i=c_k)+\lambda}{N+K\lambda} Pλ?(Y=ck?)=N+KλI(i=1∑N?yi?=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 ) + S j λ P_\lambda(X^{(j)}=a_{jl}|Y=c_k)=\frac{\displaystyle\sum_{i=1}^NI(x_i^{(j)}=a_{jl}, y_i=c_k)+\lambda}{\displaystyle\sum_{i=1}^NI(y_i=c_k)+S_j\lambda} Pλ?(X(j)=ajl?∣Y=ck?)=i=1∑N?I(yi?=ck?)+Sj?λi=1∑N?I(xi(j)?=ajl?,yi?=ck?)+λ?
其中K表示Y的种类数, S j S_j Sj?是特征 X ( j ) X^{(j)} X(j)可以取得值的总数
代码实现 下面以手写数字识别为例,将图片的每个像素点作为一个特征,对于28*28的图像一共有784个特征,对像素值进行二值化,大于等于128的为1,小于128的为1,这样手写数字识别问题就转为一个十分类的问题,每个样本一共有784个特征值,每个特征值有两个取值(0和1),具体代码如下,先验概率和条件概率的计算方式都选择贝叶斯估计的方法:
"""
Mnist classification by NaiveBayes
"""
import numpy as np
import time
class NaiveBayes(object):
"""docstring for NaiveBayes"""
def __init__(self, featurenum, classnum):
super(NaiveBayes, self).__init__()
self.featurenum = featurenum
self.classnum = classnum
self.py = np.zeros(classnum)
self.py_x = np.zeros((classnum, featurenum, 2)) def train(self, data, label):
"""
data: np.array (n, m)# (samplenum, featurenum)
label:(n) belong to [0, classnum)
对先验概率和条件概率都取了对数,这样之后的连乘就可以改为加法
这里计算先验概率和条件概率的方法用的都是书中说的贝叶斯估计
"""
n, m = data.shape
#calculate priori probability p(y),用每一类的样本数除以总的样本数
for i in range(self.classnum):
self.py[i] = np.log((sum((label==i).astype('int')) + 1)/ (n + self.classnum))#计算条件概率p(x|y),循环遍历每一个类别,对每一个类别的data,在样本数量方向求和
#因为每个特征都只有0和1两个取值,求和就得到了特征取1的样本数
for i in range(self.classnum):
self.py_x[i,:,1] = (np.sum(data[label==i], axis=0) + 1)/ (sum((label==i).astype('int')) + 2)
self.py_x[:,:,0] = 1 - self.py_x[:,:,1]#用1减去类别为1的概率就得到了零的概率
self.py_x = np.log(self.py_x) def predict(self, data):
"""
data: np.array (n, m)
"""
n, m = data.shape
prob = np.zeros((n, self.classnum))
featureindex = list(range(self.featurenum))
# py_x = np.tile(self.py_x, (n, 1, 1, 1))
for j in range(n):
#减少了一层循环,速度就提高了大约25倍,原来要50s,现在测试只需要2秒
prob[j] = np.sum(self.py_x[:,featureindex, data[j]], axis=-1) + self.py
# for i in range(self.classnum):
## prob[j][i] = sum(self.py_x[i,list(range(self.featurenum)),data[j]]) + self.py[i]
## prob[j][i] = sum(self.py_x[i,featureindex,data[j]]) + self.py[i] #和上面一行相比时间就快了两秒,看来主要是循环造成的index = prob.argmax(axis=-1) #返回每一行最大值的索引
return indexdef getdata(path): data, label = [], []
with open(path, 'r') as f:
lines = f.readlines()
for line in lines:
items = line.strip().split(',')
label.append(int(items[0]))
data.append([int(int(num) >= 128) for num in items[1:]]) return np.array(data), np.array(label)if __name__ == '__main__':
traindata, trainlabel = getdata('../mnist_train.csv')
testdata, testlabel = getdata('../mnist_test.csv') bayes = NaiveBayes(len(traindata[0]), 10)
print('trian start')
start = time.time() #返回1970纪元后经过的浮点秒数
bayes.train(traindata, trainlabel)
print('train model spend {}s'.format(time.time() - start)) #0.93s
print('test start')
start = time.time()
predict = bayes.predict(testdata)
print('test spend {}s'.format(time.time() - start)) #2.59s accu = sum((predict == testlabel).astype('int')) / len(testlabel)
print('test accuracy is {}%'.format(accu * 100)) #84.3%
推荐阅读
- numpy|numpy库的使用-读取数据
- python|机器学习--朴素贝叶斯分类器(python手动实现)
- 人工智能|PPF(Point Pair Features)原理及实战技巧
- python|python-pandas dataframe正则筛选包含某字符串的列数据str.contains()
- Python全栈系列[更新中]|Python零基础入门篇 - 53 - 文件的读取
- Python全栈系列[更新中]|Python零基础入门篇 - 51 - 文件的创建与写入
- Python全栈系列[更新中]|Python零基础入门篇 - 52 - 文件操作的避坑指南
- Python全栈系列[更新中]|Python零基础入门篇 - 33 - 你了解编程范式吗(面向过程编程与面向对象编程的区别又是什么?)
- python|嵌入式软件工程师升职_我刚升职的软件工程师在第一年学到的5课