本文代码摘自书籍《机器学习实战》,我仅稍加改正和整理,详细代码和数据集见GitHub
朴素贝叶斯概述
优点:在数据较少的情况下仍然有效,可以处理多类别问题。
缺点:对于输入数据的准备方式较为敏感
具体贝叶斯的数学原理自行百度,下面代码实战:
示例:过滤垃圾邮件
机器学习的一个重要应用就是文档的自动分类,依据文本的内容,如出现的单词进行分类。
文本预处理
书籍为我们提供了50封邮件,其中有25封垃圾邮件,每封邮件保存在一个txt文件中,大体内容如图所示。
我们先编写textParse
函数来切分文本
# bigString -- 文本字符串
def textParse(bigString):
import re
# 切分字符串,分隔符是除单词、数字外的任何字符串
reg=re.compile('\W')
listOfTokens = reg.split(bigString)
#当切分出来的单词过短如a,an,则认为该单词意义不大,舍弃
return [tok.lower() for tok in listOfTokens if len(tok) > 2]
我们还需要创建一个不含重复的词的词汇表
#dataSet -- 所有用于训练的文本
def createVocabList(dataSet):
#创建一个空集合
vocabSet = set([])
for document in dataSet:
#创建两个集合的并集
vocabSet = vocabSet | set(document)
return list(vocabSet)
获得词汇表后,便可以编写函数bagOfWords2VecMN
,该函数的输入参数为词汇表及某个文档,输出的是文档向量,向量的每个元素为对应词汇出现的次数。
# vocabList -- 词汇表
def bagOfWords2VecMN(vocabList, inputSet):
returnVec = [0] * len(vocabList)
for word in inputSet:
# 如果单词在词汇表中出现
if word in vocabList:
returnVec[vocabList.index(word)] += 1
return returnVec
朴素贝叶斯分类函数
接下来我们就可以计算贝叶斯公式的参数了。
# trainMatrix -- 文档矩阵,trainCategory -- 它们对应的标签向量
def trainNB0(trainMatrx, trainCategory):
numTrainDocs = len(trainMatrx)
numWords = len(trainMatrx[0])
# 计算垃圾邮件的概率
pAbusive = sum(trainCategory) / float(numTrainDocs)
# 为防止计算多个概率的乘积为0,将所有词的出现数初始化为1,将分母初始化为2
p0Num = np.ones(numWords)
p1Num = np.ones(numWords)
p0Denom = 2.0
p1Denom = 2.0
for i in range(numTrainDocs):
if trainCategory[i] == 1:
p1Num += trainMatrx[i]
p1Denom += len(trainMatrx[i])
else:
p0Num += trainMatrx[i]
p0Denom += len(trainMatrx[i])
p1Vect = np.log(p1Num / p1Denom)
p0Vect = np.log(p0Num / p0Denom)
return p0Vect, p1Vect, pAbusive
函数返回 单词在正常邮件,垃圾邮件出现的概率,以及垃圾邮件的概率。
接下来编写判断函数classifyNB
。当邮件是垃圾邮件的概率更大时,我们就认为该邮件时垃圾邮件,反之我们认为是正常邮件。
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
p1 = sum(vec2Classify * p1Vec) + np.log(pClass1)
p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1)
if p1 > p0:
return 1
else:
return 0
测试算法
def spamTest():
docList = []
classList = []
fullText = []
#得到50封邮件的文档矩阵和标签向量
for i in range(1, 26):
wordList = textParse(open('email/spam/%d.txt' % i).read())
docList.append(wordList)
classList.append(1)
wordList = textParse(open('email/ham/%d.txt' % i).read())
docList.append(wordList)
classList.append(0)
vocabList = createVocabList(docList)
trainingSet = list(range(50))
# 随机抽取10封邮件作为测试邮件
testSet = []
for i in range(10):
randIndex = int(np.random.uniform(0, len(trainingSet)))
testSet.append(trainingSet[randIndex])
del (trainingSet[randIndex])
#训练
trainMat = []
trainClasses = []
for docIndex in trainingSet:
trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))
trainClasses.append(classList[docIndex])
p0V, p1V, pSpam = trainNB0(np.array(trainMat), np.array(trainClasses))
# 测试算法性能
errorCount = 0
for docIndex in testSet:
wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])
if classifyNB(np.array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
errorCount += 1
print("classification error", docList[docIndex])
print('the error rate is: ', float(errorCount) / len(testSet))
运算结果如下:
classification error ['ryan', 'whybrew', 'commented', 'your', 'status', 'ryan', 'wrote', 'turd', 'ferguson', 'butt', 'horn']
the error rate is: 0.1
因为测试邮件都是随机抽取的,所以每次测试的结果都会有所不同,我这次测试的训练算法错误率为10%,也就是错了一封。
总结
对于分类而言,使用概率有时要比使用硬规则更为有效。贝叶斯概率及贝叶斯准则提供了一种利用已知值来估计未知概率的有效方法。
**朴素贝叶斯容易受到数据不平衡影响,倾向于选择训练样本较少的类别。**在实际应用中,应当做到数据平衡,可以在较少的类中多生成一些点或多采集一些点
本文没有考虑模型错误代价,比如当正常邮件被判断为垃圾邮件时,肯定严重的多。解决的方法有很多种,我会在以后的博客介绍。