知识储备:
贝叶斯决策核心思想:选择具有最高概率的决策。
贝叶斯准则:
利用贝叶斯准则我们可以交换条件概率中的条件与结果。
朴素贝叶斯假设:1.每个特征相互独立。2.每个特征同等重要。
朴素贝叶斯分类器的优缺点:
优点:在数据较少的情况下仍然有效,可以处理多类别问题。
缺点:对于输入数据的准备方式较为敏感。
适用数据类型:标称型数据。
例子中朴素贝叶斯分类器的工作流程:
1.获取训练用的文档集合list以及保存了文档分类结果的向量listClasses。
2.从所有文档中建立不重复的词汇列表VocabList。
3.将每一文档转换为文档向量,建立所有文档向量组成的矩阵trainMat
4.利用trainMat与listClasses训练朴素贝叶斯分类器,获取参数p0v,p1v,pAb
5.利用训练好的分类器对未知样本进行分类。
代码学习:
词表到向量的转换函数:
#创建在文档中出现的不重复词的列表
def createVocabList(dataSet):
vocabSet = set([]) #create empty set
for document in dataSet:
vocabSet = vocabSet | set(document) #union of the two sets求并集
return list(vocabSet)
#创建与词汇表等长的所有元素都为0的向量
def setOfWords2Vec(vocabList, inputSet):
returnVec = [0]*len(vocabList)
for word in inputSet:
if word in vocabList:
#使用list.index()取索引
returnVec[vocabList.index(word)] = 1
else: print "the word: %s is not in my Vocabulary!" % word
return returnVec
重写以方便编程的贝叶斯准则:
其中w粗体表示向量,本例中向量包含的数值个数与词汇列表长度相同。
朴素贝叶斯分类器训练函数:
#trainMatrix为文档矩阵,其中每一行由上例的setOfWords2Vec()生成,记录文档中对词汇列表中词语的包含情况,0为该文档不包含词汇列表中对应的词语,1为包含
#trainCategory为记录每篇文档类别标签的列表,本例中记录每篇文档是否包含侮辱性词汇的情况,0即为对应文档没有侮辱性词汇,1为对应文档包含侮辱性词汇。
#值得注意的是,trainMatrix每一行长度与trainCategory相同,都为词汇列表的长度,即为词汇列表中词的个数。
def trainNB0(trainMatrix,trainCategory):
numTrainDocs = len(trainMatrix) #训练文档数
numWords = len(trainMatrix[0]) #词列表的长度,即不重复的词的数量
pAbusive = sum(trainCategory)/float(numTrainDocs) #p(ci)
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
朴素贝叶斯分类器:
#vec2Classify为需要分类的向量
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 bagOfWords2VecMN(vocabList, inputSet):
returnVec = [0]*len(vocabList)
for word in inputSet:
if word in vocabList:
#累计计算词语的出现次数
returnVec[vocabList.index(word)] += 1
return returnVec
在分词部分,书上为了去掉除数字与单词以外的部分书中结合正则表达式使用了split()方法进行分词,并在后期通过判断分词后的长度是否大于0来去掉空格,通过.lower()方法将单词全部转换为小写。具体代码在以下的测试函数里有。实际分词操作应该结合停用词表比较好,中文分词还得使用专业的分词程序,例如结巴分词。
完整的垃圾邮件测试函数,使用了留存交叉验证的方法进行测试:
def textParse(bigString):
import re
#正则表达式分词,\W匹配任何非单词字符
listOfTokens = re.split(r'\W*', bigString)
#又是这种酷炫的列表操作方法:小写转换、去掉长度小于2的分词结果,
return [tok.lower() for tok in listOfTokens if len(tok) > 2]
def spamTest():
docList=[]; classList = []; fullText =[]
for i in range(1,26):
wordList = textParse(open('email/spam/%d.txt' % i).read())
docList.append(wordList)
fullText.extend(wordList)
classList.append(1)
wordList = textParse(open('email/ham/%d.txt' % i).read())
docList.append(wordList)
fullText.extend(wordList)
classList.append(0)
vocabList = createVocabList(docList) #create vocabulary
trainingSet = range(50); testSet=[]
#随机选取测试集
for i in range(10):
#random.uniform()从0到len(trainingSet)中随机取样
randIndex = int(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(array(trainMat),array(trainClasses))
errorCount = 0
#计算错误率
for docIndex in testSet: #classify the remaining items
wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])
if classifyNB(array(wordVector),p0V,p1V,pSpam) != classList[docIndex]:
errorCount += 1
print "classification error",docList[docIndex]
print 'the error rate is: ',float(errorCount)/len(testSet)
#return vocabList,fullText
书中关于留存交叉验证的说明:“这种随机选择数据的一部分作为训练集,而剩余部分作为测试集的过程称为留存交叉验证(hold-out cross validation)”
小结
朴素贝叶斯分类器的不足之处在于其独立性假设过于理想:“可以通过特征之间的条件独立性假设,降低对数据量的需求。独立性假设是指一个词的出现概率并不依赖于文档中的其他词。当然我们也知道这个假设过于简单。这就是之所以称为朴素贝叶斯的原因。”
编程实现朴素贝叶斯分类器时需要考虑的一些问题:“利用现代编程语言来实现朴素贝叶斯时需要考虑很多实际因素。下溢出就是其中一个问题,它可以通过对概率取对数来解决。词袋模型在解决文档分类问题上比词集模型有所提高。还有其他一些方面的改进,比如说移除停用词,当然也可以花大量时间对切分器进行优化。”