原理
朴素贝叶斯(Naive Bayes)法是基于贝叶斯定理和特征条件独立的假设(这是一个较强的假设,虽然使得方法变得简单,但有时会牺牲一定的分类准确率)的分类方法,属于生成(Generative Approach)方法的一种。
为什么说它属于生成方法呢?
它通过训练数据集学习联合概率分布 p ( X , Y ) p(X,Y) p(X,Y) , 所以就可以从统计的角度表示数据的分布情况,能够反映同类数据本身的相似度。
具体的,我们的目标是求后验概率 p ( Y = c k ∣ X = x ( 1 ) , . . . . , x ( n ) ) p(Y=c_k|X=x^{(1)},....,x^{(n)}) p(Y=ck∣X=x(1),....,x(n)), 即已知输入 X X X取值,求输出 Y = c k Y = c_k Y=ck的概率,其中概率最大的就是分类结果。
而由条件概率公式 : P ( Y ∣ X ) = P ( X ∣ Y ) ∗ P ( Y ) / P ( X ) P(Y|X) = P (X|Y) *P(Y) / P(X) P(Y∣X)=P(X∣Y)∗P(Y)/P(X)
其中先验概率分布 P ( Y = c k ) P(Y=c_k) P(Y=ck) k = 1,2,…n 可以直接从训练数据得到。
对于条件概率
P
(
X
=
x
∣
Y
=
c
k
)
P (X=x|Y=c_k)
P(X=x∣Y=ck) 展开如下面,发现它是有指数级的参数,其估计实际是不可能的。:
这里就体现朴素贝叶斯方法简单(朴素)的特点:我们对这里的条件概率分布做了条件独立性的假设。
这里条件假设独立假设等于说 用于分类的特征在类确定的条件下都是条件独立的。
而我们的目标
P
(
Y
∣
X
)
P(Y|X)
P(Y∣X)中分母由P(X)全概率公式展开为:
再带入上面的条件独立性假设公式:
而最后我们选择是概率最大的结果,所以贝叶斯分类器可以表示为:
注意到,分母对于所有的
c
k
c_k
ck都是相同的,所以实际上最后分类器只用求:
实战
文档词语的分类:朴素贝叶斯训练和分类函数
准备数据
先构造一个用朴素贝叶斯对文档词语进行分类的小数据集:
def loadDataSet():
"""
创建数据集
:return: 单词列表postingList, 所属类别classVec
"""
postingList = [['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'], #[0,0,1,1,1......]
['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']]
classVec = [0, 1, 0, 1, 0, 1] # 1 is abusive, 0 not
return postingList, classVec
根据Nlp里对词表示的方法,我们很直接把文本看成单词向量,这里利用One-Hot的表示方法:先构建一个所有单词集合,然后对于文档里出现单词向量值为1,反之为0:
def createVocabList(dataSet):
"""
获取所有单词的集合
:param dataSet: 数据集
:return: 所有单词的集合(即不含重复元素的单词列表)
"""
vocabSet = set() # create empty set
for document in dataSet:
# 操作符 | 用于求两个集合的并集
vocabSet = vocabSet | set(document) # union of the two sets
return list(vocabSet)
def setOfWords2Vec(vocabList, inputSet):
"""
遍历查看该单词是否出现,出现该单词则将该单词置1
:param vocabList: 所有单词集合列表
:param inputSet: 输入数据集
:return: 匹配列表[0,1,0,1...],其中 1与0 表示词汇表中的单词是否出现在输入的数据集中
"""
# 创建一个和词汇表等长的向量,并将其元素都设置为0
returnVec = [0] * len(vocabList) #[0,0......]
# 遍历文档中的所有单词,如果出现了词汇表中的单词,则将输出的文档向量中的对应值设为1
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] = 1
else:
print("the word: %s is not in my Vocabulary!" % word)
return returnVec
训练函数
这部分就是利用数据训练出条件概率
p
(
X
=
x
(
1
)
,
.
.
.
.
,
x
(
n
)
∣
Y
=
c
k
)
p(X=x^{(1)},....,x^{(n)}|Y=c_k)
p(X=x(1),....,x(n)∣Y=ck)。
输入就是用上面函数的生成字词矩阵和结果标签向量。
代码里对公式有两点改进:
- 如果某一类特征没有出现过 p ( X = x ( 1 ) , . . . . , x ( n ) ∣ Y = c k ) p(X=x^{(1)},....,x^{(n)}|Y=c_k) p(X=x(1),....,x(n)∣Y=ck) 中某一项为0,会影响判断,于是统计时默认所有特征出现数从1开始,分母从2开始。
- 累乘可能会导致小数的溢出,对这个式子取log。
然后相对于《机器学习实战》上面只处理二分类问题,我将代码小小的修改一下可以处理多分类的问题。
def trainNB0(trainMatrix, trainCategory):
"""
训练数据优化版本
:param trainMatrix: 文件单词矩阵
:param trainCategory: 文件对应的类别
:return:
"""
# 总文件数
numTrainDocs = len(trainMatrix)
# 总单词数
numWords = len(trainMatrix[0])
#统计结果总共有多少种分类
Category = set()
for cat in trainCategory :
Category.add(cat)
#存不同结果ci的 P(ci)
pStatus = list(ones(len(Category)))
#print(pStatus)
for item in Category:
cnt = 0
for train in trainCategory:
if train == item:
cnt += 1
pStatus[item] = cnt / float(numTrainDocs)
# 构造单词出现次数列表
pNum = [] # pNum[ci] 统计输出结果为ci的输入数据里各个特征出现值之和 ,由于如果出现次数为0,相乘都是0,所以修改版用1初始化
pDenom = [] # 整个数据集单词出现总数,2.0根据样本/实际调查结果调整分母的值(2主要是避免分母为0,同时分子为,当然值可以调整)
for cat in Category:
pNum.append(ones(numWords))
pDenom.append(2.0)
for i in range(numTrainDocs):
for cat in Category:
if trainCategory[i] == cat:
pNum[cat] += trainMatrix[i] # 统计结果cat的i位置特征的值之和,是一个数组
pDenom[cat] += sum(trainMatrix[i]) # 统计结果cat的所有位置特征值之和,是一个值
pVect = list(ones(len(Category)))
for cat in Category:
pVect[cat] = log(pNum[cat] / pDenom[cat]) # 后面累乘运算可能小数位数过多,结果会溢出,所以取Log,变成累加。
return pVect, pStatus
分类函数
这里就选出log(P(w|c1) * P(c1))最大的那一类别。
def classifyNB(vec2Classify,pVect ,pStatus):
ans = float('-Inf')
ansindex = 0
for index in range(len(pStatus)):
p = sum(vec2Classify * pVect[index]) + log(pStatus[index]) # log(P(w|c1) * P(c1))
if p > ans:
ans = p
ansindex = index
return ansindex
使用朴素贝叶斯过滤垃圾邮件
#!/usr/bin/python3
# encoding: utf-8
# Author MrYx
# @Time: 2019/6/27 22:03
from NaiveBayes.bayes import setOfWords2Vec, createVocabList , trainNBO, classifyNB
import random ,os
from numpy import *
def textParse(bigString):
'''
Desc:
接收一个大字符串并将其解析为字符串列表
Args:
bigString -- 大字符串
Returns:
去掉少于 2 个字符的字符串,并将所有字符串转换为小写,返回字符串列表
'''
import re
# 使用正则表达式来切分句子,其中分隔符是除单词、数字外的任意字符串
listOfTokens = re.split(r'\W*', bigString)
return [tok.lower() for tok in listOfTokens if len(tok) > 2]
def spamTest():
'''
Desc:
对贝叶斯垃圾邮件分类器进行自动化处理。
Args:
none
Returns:
对测试集中的每封邮件进行分类,若邮件分类错误,则错误数加 1,最后返回总的错误百分比。
'''
docList = []
classList = []
fullText = []
end = 23
for i in range(1, 26):
# 切分,解析数据,并归类为 1 类别
try:
#print(os.getcwd() + '\email\ham\%d.txt')
wordList = textParse(open(os.getcwd()+'\email\spam\%d.txt' % i).read())
docList.append(wordList)
classList.append(1)
# 切分,解析数据,并归类为 0 类别
#print(os.getcwd()+'\email\ham\%d.txt')
wordList = textParse(open(os.getcwd()+'\email\ham\%d.txt' % i).read())
docList.append(wordList)
fullText.extend(wordList)
classList.append(0)
except:
end = i
pass
# 创建词汇表
vocabList = createVocabList(docList)
trainingSet = list(range(46))
testSet = []
# 随机取 10 个邮件用来测试
for i in range(10):
# random.uniform(x, y) 随机生成一个范围为 x ~ y 的实数
randIndex = int(random.uniform(0, len(trainingSet)))
testSet.append(trainingSet[randIndex])
del trainingSet[randIndex]
trainMat = []
trainClasses = []
for docIndex in trainingSet:
trainMat.append(setOfWords2Vec(vocabList, docList[docIndex]))
trainClasses.append(classList[docIndex])
pVect, pStatus = trainNBO(array(trainMat), array(trainClasses))
errorCount = 0
for docIndex in testSet:
wordVector = setOfWords2Vec(vocabList, docList[docIndex])
# print(classifyNB(array(wordVector), pVect, pStatus) )
# print(classList[docIndex])
# c = input('???')
if classifyNB(array(wordVector), pVect, pStatus) != classList[docIndex]:
errorCount += 1
print('the errorCount is: ', errorCount)
print('the testSet length is :', len(testSet))
print('the error rate is :', float(errorCount)/len(testSet))
if __name__ == '__main__':
spamTest()
spamTest()
spamTest()
spamTest()
参考
本文内容是博主总结于《统计学习方法》,《机器学习实战》的笔记,非原创。