实验背景
朴素贝叶斯算法是用于分类问题的一种算法,该算法属于生成式模型,与其他算法的判别式模型不同。比如一封电子邮件是不是垃圾邮件,判别式模型是判断这封邮件是某种类型的概率,生成式模型是某种类型生成这封邮件的概率。 判别式模型: 生成式模型: 以判断电子邮件是否垃圾为例,如果采用判别式模型来判断,那么对于这个二分类(是垃圾,不是垃圾)问题来说,如果是垃圾邮件的可能性只有10%,那么根据简单的非A即B,这个邮件大概率不是垃圾邮件。但是如果这封邮件是一种全新的垃圾邮件,我们之前没有见过,那么这个分类就是错误的。生成式模型则不同,一封全新的垃圾邮件,如果是垃圾邮件的概率为10%,不是垃圾邮件的概率只有5%,那么很显然,这封邮件就是垃圾邮件。
1.算法原理
1.1.贝叶斯公式
已知两个独立事件A和B,事件B发生的前提下,事件A发生的概率可以表示为P(A|B),即上图中橙色部分占红色部分的比例,即: 其中P(A)为先验概率,通过训练数据集得出,比如10封邮件,里面有3封垃圾邮件和7封正常邮件,那么假设A代表垃圾邮件,P(A)就为30%。P(A|B)为后验概率,也就给定一封邮件B,判断是否为垃圾邮件的概率。P(B)为这封邮件在所有邮件中出现的概率,这个概率与我们判断是否为垃圾邮件无关,我们可以不去考虑,即 那么核心问题转化为求P(B|A),即垃圾邮件产生邮件B的概率,很显然P(B|A)是个联合分布概率,邮件B里有很多个单词,这些单词共同决定B是否为一封垃圾邮件。
1.1.2.朴素贝叶斯
很显然,如果我们要找到单词之间的关联需要大量的数据支持,为了减少数据量,我们认为单词之间是没有联系的,每个单词单独分布,由这些单词概率之积来决定联合概率,这就是朴素贝叶斯算法。 扩展到其他分类问题上,意味着属性条件独立分布,即每个属性独立地对分类结果发生影响。 假设分类结果为c,x为实例,那么可以将贝叶斯公式表达为: 其中d为属性数目,xi为 x 在第i个属性上的取值 举个例子: 那么对于这么一个样例x 他是否能够Play Tennis呢 首先我们统计下每个属性出现的次数。然后将其转换为概率。 对于YES和NO的概率来说,因为分母都是P(X),所以我们只需比较分子的大小,也就是说 因为NO的概率 比YES的概率大,所以我们把这个样本x分类为NO
1.1.3.拉普拉斯修正
有细心的朋友会发现,对于outlook属性中,overcast只在yes的样本中出现过,那么一个新的带有overcast属性的样本x,因为p(Overcast|NO)=0的关系,分类为NO的概率一定为0,也就是说只要新的样本x带有overcast属性,那么就一定会被分类为YES。这就是过拟合现象,过度地适应训练数据,不符合现实应用。 因此,我们采取最简单的修正方法,也就是拉普拉斯提出的加1法,即每个类别的属性数量都加上1,总数再加上类别的数量。
2.代码解析
2.1.防溢出策略
对于计算机来说因为属性数量的增多,属性概率过小,所以很可能会出现累乘结果下溢出的现象,而在代数中有ln(a*b)=ln(a)+ ln(b),因此我们可以把条件概率累乘转换为对数累加。这并不影响结果的判断。
2.2.词袋模型
一般来说,对于涉及文本的数据类型,我们都会选择词袋模型。 顾名思义,我们将一段话中的每个单词单独提取出来,统计每个单词出现的次数,因为只统计出现过的单词,所以最小值为1,同样,将统计完成的数据转化成概率后对其进行判断。还有一种模型叫词集模型,只统计单词是否出现,很显然,不同单词的出现频率很大程度上决定一封邮件是否为垃圾邮件,所以本次实验我们抛弃了这种模型。
2.3.垃圾邮件分类
from numpy import *
import re
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)
def setOfWords2Vec(vocabList, inputSet):
returnVec = [0]*len(vocabList)
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
def trainNB0(trainMatrix,trainCategory):
numTrainDocs = len(trainMatrix)
numWords = len(trainMatrix[0])
pAbusive = 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])
p1Vect = log(p1Num/p1Denom)
p0Vect = log(p0Num/p0Denom)
return p0Vect,p1Vect,pAbusive
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 spamTest():
docList = []
classList = []
fullText = []
for i in range(1, 26):
wordList = textParse(open('D:/vscode/python/.vscode/email/spam/%d.txt' % i, 'r').read())
docList.append(wordList)
fullText.append(wordList)
classList.append(1)
wordList = textParse(open('D:/vscode/python/.vscode/email/ham/%d.txt' % i, 'r').read())
docList.append(wordList)
fullText.append(wordList)
classList.append(0)
vocabList = createVocabList(docList)
trainingSet = list(range(50))
testSet = []
for i in range(10):
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])
p0V, p1V, pSpam = trainNB0(array(trainMat), array(trainClasses))
errorCount = 0
for docIndex in testSet:
wordVector = setOfWords2Vec(vocabList, docList[docIndex])
if classifyNB(array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
errorCount += 1
print('错误率:%.2f%%' % (float(errorCount) / len(testSet) * 100))
return float(errorCount)/len(testSet)
if __name__ == '__main__':
error=0.0
for i in range(10):
error+=spamTest()
error/=10
print('平均错误率:%.2f%%' %(error * 100))
3.测试结果
4.总结
对于分类而言,有时候使用概率会比使用硬规则更为有效。当我们缺少足够数据的时候,我们可以假设特征之间是条件独立的来减少数据量的需求。当然,这个假设并不适用于所有的情况,但针对文本分类而言,朴素贝叶斯仍然是一种有效的分类器。 对于编程语言来说,我们需要考虑计算机特有的问题,比如下溢出等,这些都可以通过数学的变化来得到数据之间关系不变但便于我们存储的数据。对于计算机人来说,数学不要求非常优秀,但起码得看得懂公式,且懂得如何降低存储难度来进行数据变换。 同时,本次实验还有很多可以改进的地方,比如文本中的连词,人称词等,有些词汇对于文本分类是没有意义的,对于如何更好的切分文本构建词袋是个很好的研究方向。
|