本博客记录《机器学习实战》(MachineLearningInAction)的学习过程,包括算法介绍和python实现。
朴素贝叶斯
贝叶斯准则是用于计算条件概率的方法,公式如下:
用朴素贝叶斯方法进行分类器的基本思想就是计算出新数据分到每一个类别的概率。例如在垃圾邮件分类系统中,邮件总共分两类,
c1
表示是垃圾邮件,
c0
表示是普通邮件,把输入的邮件用
x
表示,这样条件概率
条件概率计算
以 p(c1|x) 为例, p(c1|x)=p(x|c1)p(c1)p(x) ,由于最终我们是通过比较 p(c1|x) 和 p(c2|x) 的大小来分类的,所以为简单起见对于相同的变量 p(x) 就无需考虑,只需要考虑分母的两个概率。其中 p(c1) 表示垃圾邮件在所有邮件中所占的比例,这个只要用训练集中的垃圾邮件数除以邮件总数就能够计算。
p(x|c1)
表示在所有的垃圾邮件中,这封邮件所占的比例,这个概率基本上就是0,你可以这样理解,从世界上所有的垃圾邮件里随机取一封,而这封邮件跟我们现在要分类的这封邮件一模一样,你说这个概率有多大。无论如何我们还是把公式写出来,通过一些假设条件来计算出这个值,首先把邮件
x
拆分成一个个单词
假设所有的单词独立,也就是说每一个词的出现与其他的词无关,朴素贝叶斯的朴素(naive)之处就在这里了,虽然这是一个很天真的假设,但是确能够达到很好的效果。在这个假设条件下,上式就可以简化成:
而这些因子就很好计算了,以 p(x1|c1) 为例,只要统计出单词 x1 在垃圾邮件的训练集中出现的次数,除以总词数就可以了。
数据准备
在训练分类器之前,需要对输入的数据进行一些处理以便更高效地训练。把输入的邮件拆分成单词,把邮件中出现的单词集合作为特征用于训练。
# 首先将输入文本拆分成单词列表
def textParse(bigString):
listOfTokens = re.split(r'\W*', bigString)
return [tok.lower() for tok in listOfTokens if len(tok) > 2]
# 接着创建词汇集合,该集合包含了训练集中出现的所有单词
def createVocabList(dataSet):
vocabSet = set([])
for document in dataSet:
vocabSet = vocabSet | set(document)
return list(vocabSet)
# 将输入的单词列表转化为等长的句子向量
# 向量中的每个元素(0/1)表示词汇集合中对应位置的单词是否在句子中出现
def setOfWord2Vec(vocabList, inputSet):
retVec = [0] * len(vocabList)
for word in inputSet:
if word in vocabList:
retVec[vocabList.index(word)] = 1
return retVec
训练分类器
输入训练集和分类标签进行训练,过程中需要注意的有两点:
一是为了防止某些词在类别中的出现概率为0导致最后计算的概率直接为0,我们把词汇表中的单词在每个类别中的出现次数初始化为1;
二是因为最终的概率由许多概率相乘得到,而这些概率都很小,这就导致最终计算出的概率非常接近0,可能产生下溢出。因为对数函数ln(x)是单调递增的,所以在比较大小的时候,把每个概率转化为对数,而最终的乘法也就转换为对数加法,这样就解决了下溢出的问题。
# 训练朴素贝叶斯分类器
def trainNB0(trainMatrix, trainCategory):
# 训练集大小
numTrainDocs = len(trainMatrix)
# 词汇集合大小
numWords = len(trainMatrix[0])
# p(c1),即训练集中1类所占比例
pClass1 = sum(trainCategory) / float(numTrainDocs)
# 词汇表中的单词在两类训练集中的出现次数
p0Num = ones(numWords)
p1Num = ones(numWords)
# 两类训练集中的所有单词个数
p0Denom = 2.0
p1Denom = 2.0
for i in range(numTrainDocs):
if trainCategory[i] == 1:
p1Num += trainMatrix[i]
p1Denom += sum(trainMatrix[i])
else:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
# 词汇表中每个单词在两个类别中的出现概率
p0Vect = p0Num / p0Denom
p1Vect = p1Num / p1Denom
# 转换为对数
for i in range(numWords):
p0Vect[i] = log(p0Vect[i])
p1Vect[i] = log(p1Vect[i])
return p0Vect, p1Vect, pClass1
使用分类器进行分类
训练得到的结果是词汇集合中每个单词在对应类别的出现概率 p(xi|c1) 以及训练集中出现每个类别的概率 p(c1) ,有了这些概率,在分类时,根据邮件中出现的单词从训练结果中提取出对应单词的概率相乘即可。
# 使用分类器进行分类
def classifyNB(inputVec, p0Vec, p1Vec, pClass1):
# 乘法改为对数的加法
p1 = sum(inputVec * p1Vec) + log(pClass1)
p0 = sum(inputVec * p0Vec) + log(1.0 - pClass1)
if p1 > p0:
return 1
else:
return 0
总结
朴素贝叶斯是一种使用概率进行分类的方法,通过条件独立性假设来完成概率的计算,虽然这种假设很不靠谱,但是却得到了很好的分类效果。对于概率论没有学的很好的同学(我),看这个方法可能会有点懵逼,我在理解的时候参考刘未鹏的这篇博客,可以说是简单又深刻,推荐有兴趣的同学看一手。