朴素贝叶斯算法可以实现对文档的分类,其中最著名的应用之一就是过滤垃圾邮件。先做一个简单的分类,以论坛的留言为例,构建一个快速的过滤器,来区分哪些留言是负面言论,哪些是正面言论。
我对算法思路的理解:首先计算训练集中每个词语分别在正面(负面)文档中出现的概率以及正面(负面)文档的概率,再计算待分类样本中的每个词语属于正面(负面)文档的概率和正面(负面)文档概率的乘积,即为该样本属于正面(负面)样本的概率,样本属于哪一类文档的概率较大就归为哪类文档(读着有点绕),下面详细介绍分类的过程。
1. 条件概率
首先来学习一下基于条件概率的分类思想。对于样本,它属于类别的概率为,属于样本的概率为,定义贝叶斯分类准则为:
- 如果,那么样本属于类别
- 如果,那么样本属于类别
完整的贝叶斯公式如下:
在此分类算法中,我们用它的简化形式:
用分类的思想可以这样理解这个公式:是待分类样本的特征集合,那么要求得属于类别的概率,就转化为求训练集中,类别的样本集中特征集的概率、类别的概率、特征集的概率,这3个值通过训练集数据可以更容易计算得出。
2. 准备数据——构建词袋模型
对每个文本,构建一个向量,向量的维度与词条列表的维度相同,向量的值是词条列表中每个词条在该文本中出现的次数,这种模型叫做词袋模型。使用Python编程实现,代码来源《机器学习实践》,创建bayes.py文件,加入如下代码:
# 加载数据集
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']]
classVec = [0, 1, 0, 1, 0, 1] # 1 为负面言论, 0 为正常言论
return postingList, classVec
# 创建不重复的词条列表
def createVocabList(dataSet):
vocabSet = set([])
for document in dataSet:
vocabSet = vocabSet | set(document)
return list(vocabSet)
# 朴素贝叶斯词袋模型
def bagOfWords2Vec(vocabList, inputSet):
returnVec = [0] * len(vocabList)
for word in inputSet:
if word in vocabList:
return Vec[vocabList.index(word)] += 1
else:
print("the word: %s is not in my Vocabulary!" % word)
return returnVec
看一下执行效果:
>>> import bayes
>>> listOPosts, listClasses = bayes.loadDataSet()
>>> myVocabList = bayes.createVocabList(listOPosts)
>>> myVocabList
['steak', 'dalmation', 'garbage', 'love', 'cute', 'please', 'help', 'stop', 'buying', 'worthless',
'ate', 'dog', 'to', 'I', 'food', 'is', 'how', 'not', 'mr', 'quit', 'flea', 'licks', 'problems',
'take', 'so', 'park', 'has', 'my', 'posting', 'stupid', 'maybe', 'him']
>>> bayes.bagOfWords2Vec(myVocabList, listOPosts[0])
[0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0]
>>> bayes.bagOfWords2Vec(myVocabList, listOPosts[1])
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1]
词条列表中索引为5的词语是please,它在第一个文档中出现过1次,最后一个词语是him,它在第二个文档中出现过1次。
3. 训练算法——从词向量计算概率
现在我们知道一个词是否出现在一篇文档中,也知道文档所属的类别。为了方便理解,把条件概率公式中的替换为(代表词向量)
之所以称为“朴素”贝叶斯,是因为假设样本中的所有特征是相互独立的,那么就有如下简化的计算方法:
其中,表示在第类样本中,第个词语出现的概率;
表示第类样本所占的概率,如示例中有6个样本,正负样本各3个,因此和均为0.5;
同样可以按的方法求得,但是因为我们只需比较和的大小来进行分类,作为相同的分母,在代码中没有计算。
因为要使用数值计算类库Numpy的一些函数,需要在文件的最上面添加引用语句:from numpy import *
# 朴素贝叶斯分类器训练函数,参数为样本矩阵和类别矩阵
def trainNB0(trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix) # 样本数
numWords = len(trainMatrix[0]) # 词条数
pAbusive = sum(trainCategory) / float(numTrainDocs) # 计算类别为1 的概率
p0Num = zeros(numWords)
p1Num = zeros(numWords)
p0Denom = 0.0
p1Denom = 0.0
for i in range(numTrainDocs):
if trainCategory[i] == 1: # 类别为1 的文档
p1Num += trainMatrix[i] # 类别为1 的词条矩阵
p1Denom += sum(trainMatrix[i]) # 类别为1 的文档的总词数
else:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
p1Vect = p1Num / p1Denom # 类别为1 的文档中,每个词出现的概率
p0Vect = p0Num / p0Denom
return p0Vect, p1Vect, pAbusive
测试一下效果:
>>> import bayes
>>> listOPosts, listClasses = bayes.loadDataSet()
>>> myVocabList = bayes.createVocabList(listOPosts)
>>> trainMat = []
>>> for postinDoc in listOPosts:
... trainMat.append(bayes.bagOfWords2Vec(myVocabList, postinDoc))
...
>>> p0V, p1V, pAb = bayes.trainNB0(trainMat, listClasses)
>>> p0V
array([0.04166667, 0.04166667, 0. , 0.04166667, 0.04166667,
0.04166667, 0.04166667, 0.04166667, 0. , 0. ,
0.04166667, 0.04166667, 0.04166667, 0.04166667, 0. ,
0.04166667, 0.04166667, 0. , 0.04166667, 0. ,
0.04166667, 0.04166667, 0.04166667, 0. , 0.04166667,
0. , 0.04166667, 0.125 , 0. , 0. ,
0. , 0.08333333])
>>> p1V
array([0. , 0. , 0.05263158, 0. , 0. ,
0. , 0. , 0.05263158, 0.05263158, 0.10526316,
0. , 0.10526316, 0.05263158, 0. , 0.05263158,
0. , 0. , 0.05263158, 0. , 0.05263158,
0. , 0. , 0. , 0.05263158, 0. ,
0.05263158, 0. , 0. , 0.05263158, 0.15789474,
0.05263158, 0.05263158])
>>> pAb
0.5
负面样本的概率pAb为0.5,与前面计算的相同。正面样本中共有23个词语,负面样本中共有19个词语,词条列表中的第一个词为steak,它在正面样本中出现一次,在负面样本中没有出现,该词的条件概率为分别为0.04166667和0,计算正确;倒数第3个词语是stupid,它在正面样本中没有出现,在负面样本中出现3次,该词的条件概率分别是0和0.15789474,计算正确。
至此,我们已经求得每个词语在某类文档中的条件概率:
,
,
在分类之前,有两个地方需要优化:
(1)根据公式,如果其中有一个词语的条件概率为0,那么计算结果就是0,为了避免这种情况,将所有词出现的次数初始化为1,分母初始化为2。
(2) 多个概率非常小的值相乘,最后四舍五入可能会得到0,可以采用求对数的方法来避免这种现象。
修改trainNB0()方法的第6到9行,和return前的2行,修改后的代码:
# 朴素贝叶斯分类器训练函数,参数为样本矩阵和类别矩阵
def trainNB0(trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix) # 样本数
numWords = len(trainMatrix[0]) # 词条数
pAbusive = sum(trainCategory) / float(numTrainDocs) # 计算类别为1 的概率
p0Num = ones(numWords)
p1Num = ones(numWords)
p0Denom = 2.0
p1Denom = 2.0
for i in range(numTrainDocs):
if trainCategory[i] == 1: # 类别为1 的文档
p1Num += trainMatrix[i] # 类别为1 的词条矩阵
p1Denom += sum(trainMatrix[i]) # 类别为1 的文档的总词数
else:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
p1Vect = log(p1Num / p1Denom) # 类别为1 的文档中,每个词出现的概率
p0Vect = log(p0Num / p0Denom)
return p0Vect, p1Vect, pAbusive
4. 测试算法——利用分类器对样本分类
有了前面的准备工作,现在可以构建分类器了,分类器classifyNB有4个参数,依次是待分类的向量,以及trainNB0计算的3个概率值。再写一个分类的测试函数testingNB,避免每次测试都要输入测试代码。
# 朴素贝叶斯分类函数
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
p1 = sum(vec2Classify * p1Vec) + log(pClass1)
p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
if p1 > p0:
return 1
else:
return 0
# 测试朴素贝叶斯
def testingNB():
listOPosts, listClasses = loadDataSet()
myVocabList = createVocabList(listOPosts)
trainMat = []
for postinDoc in listOPosts:
trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
p0v, p1v, pAb = trainNB0(trainMat, listClasses)
testEntry = ['love', 'my', 'dalmation']
thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as:', classifyNB(thisDoc, p0v, p1v, pAb))
testEntry = ['stupid', 'garbage']
thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as:', classifyNB(thisDoc, p0v, p1v, pAb))
看看实际效果,很明显‘love’一类的文本被归为正面言论,‘stupid’、‘garbage’被归为负面言论。
>>> import bayes
>>> bayes.testingNB()
['love', 'my', 'dalmation'] classified as: 0
['stupid', 'garbage'] classified as: 1
以上就是我对朴素贝叶斯算法的学习和理解,理论和代码主要来自《机器学习实践》,并尝试用更容易理解的表述和公式加以说明,不足之处欢迎探讨。