目录
引言
在之前讲述KNN以及决策树时。曾要求分类器做出决策给出“该实例属于哪一类”这类问题的明确答案。不过分类器有时候会产生错误的结果,这个时候要求分类器给出一个最优的类别猜测结果,同时给出这个猜测的概率估计值。之前决策树章节里信息熵的计算就涉及到了一些概率知识,接下来便在这个基础上深入讨论。
一、贝叶斯理论
1.1 准备知识:条件概率
因为朴素贝叶斯是贝叶斯决策理论的一部分,所以先了解一下贝叶斯决策理论。
假设有两类数据组成的数据集如下:
其中,假设两个概率分布的参数已知,并用p1(x,y)表示当前数据点(x,y)属于类别一的概率;用p2(x,y)表示当前数据点(x,y)属于类别二的概率。
贝叶斯决策理论的核心思想是:选择高概率所对应的类别,选择具有最高概率的决策。有时也被总结成“多数占优”的原则。
具体到实例,对于一个数据点(x,y),可以用如下规则判定它的类别:
若p1(x,y)>p2(x,y),那么点(x,y)被判定为类别一。
若p1(x,y)<p2(x,y),那么点(x,y)被判定为类别二。
在实际情况中,单单依靠以上的判定无法解决所有的问题,因为以上准则还不是贝叶斯决策理论的所有内容,使用p1(x,y)和p2(x,y) 只是为了简化描述。更多的,我们使用p(ci|x,y) 来确定给定坐标的点(x,y),该数据点来自类别ci的概率是多少。具体地,应用贝叶斯准则可得到,该准则可以通过已知的三个概率值来计算未知的概率值:
利用上面的公式,我们可以计算出在给定实例点的情况下,分类计算其属于各个类别的概率,然后比较概率值,选择具有最大概率的那么类作为点(x,y)的预测分类结果。
以上我们知道了通过贝叶斯准则来计算属于各个分类的概率值,那么具体而言,就是计算贝叶斯公式中的三个概率,只要得到了这三个概率值,显然我们就能通过贝叶斯算法预测分类的结果了。至此,我们就知道了朴树贝叶斯算法的核心所在。
1.2 “朴素”的含义
这个朴素的贝叶斯分类器有什么“朴素”的呢?
在推导5个特征的数据集方程时,我们只需乘以各个特征的所有条件概率,比如P(X1 | Y)*P(X2 | Y)….*P(X5 | Y)。当我们假设特征相互独立时,我们只能把总的条件概率写成特征的个别条件概率的乘积。这是我们在这里做的“天真”假设,是为了让贝叶斯定理对我们有用。
但是,在现实生活中,当特性彼此独立时,几乎从来没有这种情况。功能中总是有某种依赖关系。例如,如果一个特征是一个人的年龄,而另一个特征是年薪,那么在大多数情况下都有明显的依赖关系。
然而,我们仍然继续把这个定理应用于分类问题,甚至文本分类,它的效果会出奇的好。
1.3 朴素贝叶斯分类器
朴素贝叶斯分类器就是利用贝叶斯原理实现的,在分类问题中,类别C相当于事件A,特征X相当于影响因素B,那么贝叶斯公式可以改写为如下形式,它表示的含义是当特征为X时,X属于类别C的概率:
设类别C={undefinedy_{1},y_{2}…y_{m}},特征向量X={undefinedx_{1},x_{2}…x_{n}},贝叶斯分类器的工作原理如下:
1)求解在X条件下每个类别的概率即求得
2)中概率最大的项对应的类别即为X的预测类别
贝叶斯分类器的工作原理:寻找中的最大值,最大值对应的类别即为预测类别。然而在实际情况中往往无法直接求得,所以根据贝叶斯公式将求解转化为求解。P(X)与类别无关且为固定值,不会影响中的最大值的求解。
所以总结来说:贝叶斯分类器就是最大后验概率估计
1.4 源码实现
def NaiveBayes(traindata, trainlabel):
'''
通过训练集计算先验概率分布p(c)和条件概率分布p(x|c)
:param traindata: 训练数据集 (m,n)
:param trainLabel: 训练标记集 (m,1)
:return: p(c)和p(x|c)
'''
classes = 10 # 类别数
features = 784 # 样本的维度
sampleNum = trainlabel.shape[0]
# 计算p(c)
Pc = np.zeros((classes, 1))
for c in range(classes):
c_i = (trainlabel == c) # 统计标签中类别为c的样本数量
c_i_num = np.sum(c_i)
Pc[c] = (c_i_num+1)/sampleNum # Laplace校准
Pc = np.log(Pc)
# 计算p(x|c)
y_num = 2 # 每个特征可能的取值的个数
c_f_y_count = np.zeros((classes, features, y_num)) # 统计每个类别每个特征的每种可能取值出现的次数
for k in range(sampleNum):
c = trainlabel[k]
data = traindata[k]
for f in range(features):
y = data[f]
c_f_y_count[c][f][y] += 1
Px_c = np.zeros((classes, features, y_num)) # 统计每个类别每个特征的每种可能取值的概率
for c in range(classes):
for f in range(features):
c_f_y_num = np.sum(c_f_y_count[c][f])
for y in range(y_num):
Px_c[c][f][y] = np.log((c_f_y_count[c][f][y]+1)/c_f_y_num) # Laplace校准
return Pc, Px_c
def predict(Pc, Px_c, x):
'''
根据先验概率分布p(c)和条件概率分布p(x|c)对新样本进行预测
'''
classes = 10
features = 784
Pc_x = [0]*classes # 记录每个类别的后验概率
for c in range(classes):
Px_c_sum = 0
for f in range(features):
Px_c_sum += Px_c[c][f][x[f]] # 对概率值取log 连乘变成了求和运算
Pc_x[c] = Px_c_sum + Pc[c]
pre_c = Pc_x.index(max(Pc_x)) # 找到每个类别的后验概率中的最大值对应的类别
return pre_c
def test(Pc, Px_c, testdata, testlabel):
sampleNum = testlabel.shape[0]
count = 0.0
for i in range(sampleNum):
data = testdata[i]
label = testlabel[i]
pre_label = predict(Pc, Px_c, data)
if(pre_label == label):
count += 1
acc = count / sampleNum
return acc
if __name__ == '__main__':
#加载训练集和验证集
traindata, trainlabel = loadData('../Mnist/mnist_train.csv')
evaldata, evallabel = loadData('../Mnist/mnist_test.csv')
Pc, Px_c = NaiveBayes(traindata, trainlabel)
accuracy = test(Pc, Px_c, evaldata, evallabel)
print('accuracy rate is:', accuracy)
二、算法实例——垃圾邮件过滤
2.1 收集数据
提供文本文件,下载地址:http://download.csdn.net/detail/liyuefeilong/9106481,放在工程目录下并解压即可;
2.2 准备数据
将文本文件解析成词条向量;从文本中构建词向量,这里考虑出现在所有文档中的所有单词,并将每一篇文档转化为词汇表上的向量。下面的代码实现了功能,其中:
函数loadDataSet() 创建了一些实验样本postingList和对应的标签listClass,有一些样本被标签为带有侮辱性文字;
函数createNonRepeatedList() 统计并保存一个列表vocList,该列表包含所有文档中出现的词(不重复),这里使用了Python的set函数;
函数detectInput(vocList, inputStream)使用了词列表vocList, inputStream为待检测的word串,输出文档向量,向量的每一元素为1或0,分别表示词汇表中的单词在输入文档中是否出现。
# -*- coding: utf-8 -*-
"""
Created on Tue Sep 08 16:12:55 2015
@author: Administrator
"""
from numpy import *
# 创建实验样本,可能需要对真实样本做一些处理,如去除标点符号
def loadDataSet():
postingList=[['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'],
['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'],
['stop', 'posting', 'stupid', 'worthless', 'garbage'],
['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'],
['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']]
listClass = [0, 1, 0, 1, 0, 1] # 1代表存在侮辱性的文字,0代表不存在
return postingList, listClass
# 将所有文档所有词都存到一个列表中,用set()函数去除重复出现的词
def createNonRepeatedList(data):
vocList = set([])
for doc in data:
vocList = vocList | set(doc) # 两集合的并集
return list(vocList)
def detectInput(vocList, inputStream):
returnVec = [0]*len(vocList) # 创建和vocabList一样长度的全0列表
for word in inputStream:
if word in vocList: # 针对某段words进行处理
returnVec[vocList.index(word)] = 1 # ?
else:
print "The word :%s is not in the vocabulary!" % word
return returnVec
2.3 分析数据
检查词条确保解析的正确性;
2.4 训练算法
使用我们之前建立的trainNaiveBayes(trainMatrix, classLabel)函数;
-
计算每个类别中的文档数目
对每篇训练文档:
对每个类别:
如果词条出现在文档中 -> 增加该词条的计数值
增加所有词条的计数值
对每个类别:
将该词条的数目除以总词条数目得到条件概率
返回每个类别的条件概率贝叶斯分类器训练函数代码如下:
def trainNaiveBayes(trainMatrix, classLabel):
numTrainDocs = len(trainMatrix)
numWords = len(trainMatrix[0])
pBase = sum(classLabel) / float(numTrainDocs)
# The following Settings aim at avoiding the probability of 0
p0Num = ones(numWords)
p1Num = ones(numWords)
p0Denom = 2.0
p1Denom = 2.0
for i in range(numTrainDocs):
if classLabel[i] == 1:
p1Num += trainMatrix[i]
p1Denom += sum(trainMatrix[i])
else:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
p0 = log(p0Num / p0Denom)
p1 = log(p1Num / p1Denom)
return p0, p1, pBase
2.5 测试算法
使用函数naiveBayesClassify(vec2Classify, p0, p1, pBase),并且构建一个新的测试函数来计算文档集的错误率;测试分类器的效果:
def trainNaiveBayes(trainMatrix, classLabel):
numTrainDocs = len(trainMatrix)
numWords = len(trainMatrix[0])
pBase = sum(classLabel) / float(numTrainDocs)
# The following Settings aim at avoiding the probability of 0
p0Num = ones(numWords)
p1Num = ones(numWords)
p0Denom = 2.0
p1Denom = 2.0
for i in range(numTrainDocs):
if classLabel[i] == 1:
p1Num += trainMatrix[i]
p1Denom += sum(trainMatrix[i])
else:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
p0 = log(p0Num / p0Denom)
p1 = log(p1Num / p1Denom)
return p0, p1, pBase
trainMat = []
for doc in loadData:
trainMat.append(detectInput(vocList, doc))
p0,p1,pBase = trainNaiveBayes(trainMat, dataLabel)
#print "trainMat : "
#print trainMat
# test the algorithm
def naiveBayesClassify(vec2Classify, p0, p1, pBase):
p0res = sum(vec2Classify * p0) + log(1 - pBase)
p1res = sum(vec2Classify * p1) + log(pBase)
if p1res > p0res:
return 1
else:
return 0
def testNaiveBayes():
loadData, classLabel = loadDataSet()
vocList = createNonRepeatedList(loadData)
trainMat = []
for doc in loadData:
trainMat.append(detectInput(vocList, doc))
p0, p1, pBase = trainNaiveBayes(array(trainMat), array(classLabel))
testInput = ['love', 'my', 'dalmation']
thisDoc = array(detectInput(vocList, testInput))
print testInput, 'the classified as: ', naiveBayesClassify(thisDoc, p0, p1, pBase)
testInput = ['stupid', 'garbage']
thisDoc = array(detectInput(vocList, testInput))
print testInput, 'the classified as: ', naiveBayesClassify(thisDoc, p0, p1, pBase)
testNaiveBayes()
2.6 使用算法
构建一个完整的程序对一组文档进行分类,将错分的文档输出到屏幕上。
最后,对两组word串进行检测,第一段被判定为非侮辱性用语,而第二段则被判定为侮辱性用语,分类正确。
三、实验总结
3.1 贝叶斯原理、贝叶斯分类和朴素贝叶斯的关系
贝叶斯原理、贝叶斯分类和朴素贝叶斯这三者之间是有区别的。
贝叶斯原理是最大的概念,它解决了概率论中“逆向概率”的问题,在这个理论基础上,人们设计出了贝叶斯分类器,朴素贝叶斯分类是贝叶斯分类器中的一种,也是最简单,最常用的分类器。朴素贝叶斯之所以朴素是因为它假设属性是相互独立的,因此对实际情况有所约束,
如果属性之间存在关联,分类准确率会降低。
不过好在对于大部分情况下,朴素贝叶斯的分类效果都不错。
3.2 贝叶斯的优缺点
优点:
- 朴素贝叶斯模型发源于古典数学理论,有稳定的分类效率。
- 对小规模的数据表现很好,能个处理多分类任务,适合增量式训练,尤其是数据量超出内存时,我们可以一批批的去增量训练。
- 对缺失数据不太敏感,算法也比较简单,常用于文本分类。
缺点:
- 理论上,朴素贝叶斯模型与其他分类方法相比具有最小的误差率。但是实际上并非总是如此,这是因为朴素贝叶斯模型给定输出类别的情况下,假设属性之间相互独立,这个假设在实际应用中往往是不成立的,在属性个数比较多或者属性之间相关性较大时,分类效果不好。而在属性相关性较小时,朴素贝叶斯性能最为良好。对于这一点,有半朴素贝叶斯之类的算法通过考虑部分关联性适度改进。
- 需要知道先验概率,且先验概率很多时候取决于假设,假设的模型可以有很多种,因此在某些时候会由于假设的先验模型的原因导致预测效果不佳。
- 由于我们是通过先验和数据来决定后验的概率从而决定分类,所以分类决策存在一定的错误率。
- 对输入数据的表达形式很敏感。