条件概率:
如果各个特征属性是条件独立的,则根据贝叶斯定理有如下推导:
计算条件概率的方法:贝叶斯准则
可以交换条件概率中的条件与结果
使用条件概率来分类:
规则:
- 如果p1(x,y) > p2(x,y),那么属于类别1
- 如果p1(x,y) < p2(x,y),那么属于类别2
使用贝叶斯准则,可以通过已知的三个概率值来计算未知的概率值。
使用朴素贝叶斯进行文档分类:
朴素贝叶斯是贝叶斯分类器的一个拓展,是用于文档分类的常用算法。
我们可以观察文档中出现的词,并把每个词的出现或者不出现作为一个特征,这样得到的特征数目就会跟词汇表中的词目一样多。
朴素贝叶斯的一般过程:
- 收集数据:可以使用任何方法
- 准备数据:数字型或布尔型
- 分析数据:有大量特征时,绘制特征作用不大,此时使用直方图效果更好
- 训练算法:计算不同的独立特征的条件概率
- 测试算法:计算错误率
- 使用算法:可以在任意的分类场景中使用朴素贝叶斯分类器,不一定非要是文本
样本数:
由统计学知,如果每个特征需要N个样本,那么对于10个特征将需要N^10 个样本,对于包含1000个特征的词汇表将需要N^1000 个样本。
所需要的样本数会随着特征数目增大而迅速增长。
如果特征之间相互独立,那么样本数就可以从N^1000 个样本减少到1000N。
所谓独立(independence)指的是统计意义上的独立,即一个特征或者单词出现的可能性与它和其他单词相邻没有关系。
两个假设:
- 朴素(naive)的含义:特征之间独立
- 每个特征同等重要
使用python进行文本分类:
要从文本中获取特征,需要先拆分文本。
每一个文本片段表示为一个词条向量,1表示词条出现在文档中,0表示词条未出现
- 特征:来自文本的词条(token)
- 一个词条是字符的任意组合
- 词条可以为单词,也可以为非单词(如URL、ip地址等)
准备数据:从文本中构建词向量
'''
词表到向量的转换函数
'''
# 创建了一些实验样本
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 is abusive, 0 not 1为侮辱性文字
return postingList,classVec
def createVocabList(dataSet):
vocabSet = set([]) #create empty set
for document in dataSet:
vocabSet = vocabSet | set(document) #union of the two sets 创建并集,即获得一个去重词汇列表
return list(vocabSet)
def setOfWords2Vec(vocabList, inputSet): # vocabList词汇表,inputSet需要检查的所有单词
returnVec = [0]*len(vocabList) # 与词汇表相同的0值列表
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] = 1 # 如果需要检查的单词列表出现在vocabList词汇表中,0值列表对应的位置设为1
else: print "the word: %s is not in my Vocabulary!" % word
return returnVec
if __name__ == '__main__':
postingList, classVec = loadDataSet()
print postingList
print '---------'
print classVec
print '---------'
myVocabList = createVocabList(postingList)
print myVocabList
print '--------'
print setOfWords2Vec(myVocabList, postingList[0])
# [0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1]
训练算法:从词向量到计算概率
重写贝叶斯准则,将之前的 x, y 替换为 w. 粗体的 w 表示这是一个向量,即它由多个值组成。
在这个例子中,数值个数与词汇表中的词个数相同。
我们使用上述公式,对每个类计算该值,然后比较这两个概率值的大小。
计算p(ci):
- 首先可以通过类别 i (侮辱性留言或者非侮辱性留言)中的文档数除以总的文档数来计算概率 p(ci) 。
计算 p(w | ci):
朴素贝叶斯假设。
如果将 w 展开为一个个独立特征,那么就可以将上述概率写作 p(w0, w1, w2…wn | ci) 。
这里假设所有词都互相独立,该假设也称作条件独立性假设(例如 A 和 B 两个人抛骰子,概率是互不影响的,也就是相互独立的,A 抛 2点的同时 B 抛 3 点的概率就是 1/6 * 1/6),
它意味着可以使用 p(w0 | ci)p(w1 | ci)p(w2 | ci)…p(wn | ci) 来计算上述概率,这样就极大地简化了计算的过程。
from numpy import *
from machinelearninginaction.Ch04.bayes import loadDataSet,createVocabList,setOfWords2Vec
def trainNB0(trainMatrix,trainCategory): #trainMatrix文档矩阵,trainCategory每篇文档类别标签所构成的向量
numTrainDocs = len(trainMatrix) # 训练样本集长度
numWords = len(trainMatrix[0]) # 每个样本的词数量
pAbusive = sum(trainCategory)/float(numTrainDocs) # 计算文档属于侮辱性文档的概率
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] # 矩阵,对应位置,出现侮辱性词条 累加矩阵
p1Denom += sum(trainMatrix[i]) # 计算所有侮辱性词条的总数
else: # 词向量值为0 -> 非侮辱性
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
p1Vect = p1Num/p1Denom #change to log() 词汇表对应位置上有侮辱性词条的出现的总数 除以 总词条数目得到条件概率 = 词汇表每个位置上出现侮辱性词条的概率
p0Vect = p0Num/p0Denom #change to log()
return p0Vect,p1Vect,pAbusive
# p0Vect 非侮辱性词语在词汇表每个位置出现的条件概率
if __name__ == '__main__':
postingList, classVec = loadDataSet()
myVocabList = createVocabList(postingList) # 获得一个去重的词汇表
trainMat = []
for postinDodc in postingList:
trainMat.append(setOfWords2Vec(myVocabList,postinDodc))
print 'trainMat: '
print trainMat
print '----------'
p0V,p1V,pAb = trainNB0(trainMat,classVec)
print p0V
print '---------'
print p1V
print '---------'
print pAb
p0Num :
- 矩阵,对应位置,出现非侮辱性词条的话,则累加起来。
- 最后每个位置的数字为该位置(某个单词)出现过多少次非侮辱性词条
p0Denom:
- 侮辱性词条的总数
p0Vect = p0Num/p0Denom
- 计算:词汇表每个位置上出现侮辱性词条的概率
测试算法:根据现实情况修改分类器
在利用贝叶斯分类器对文档进行分类时,要计算多个概率的乘积以获得文档属于某个类别的概率,即计算 p(w0|1) * p(w1|1) * p(w2|1)。如果其中一个概率值为 0,那么最后的乘积也为 0。
为降低这种影响,可以将所有词的出现数初始化为 1,并将分母初始化为 2 (取1 或 2 的目的主要是为了保证分子和分母不为0,大家可以根据业务需求进行更改)。
另一个遇到的问题是下溢出,这是由于太多很小的数相乘造成的。当计算乘积 p(w0|ci) * p(w1|ci) * p(w2|ci)… p(wn|ci) 时,由于大部分因子都非常小,所以程序会下溢出或者得到不正确的答案。(用 Python 尝试相乘许多很小的数,最后四舍五入后会得到 0)。一种解决办法是对乘积取自然对数。在代数中有 ln(a * b) = ln(a) + ln(b), 于是通过求对数可以避免下溢出或者浮点数舍入导致的错误。同时,采用自然对数进行处理不会有任何损失。
下图给出了函数 f(x) 与 ln(f(x)) 的曲线。可以看出,它们在相同区域内同时增加或者减少,并且在相同点上取到极值。它们的取值虽然不同,但不影响最终结果。
from numpy import *
from machinelearninginaction.Ch04.bayes import loadDataSet,createVocabList,setOfWords2Vec
def trainNB0(trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix)
numWords = len(trainMatrix[0])
pAbusive = sum(trainCategory) / float(numTrainDocs)
p0Num = ones(numWords)
p1Num = ones(numWords) # change to ones()
p0Denom = 2.0
p1Denom = 2.0 # change to 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) # change to log()
p0Vect = log(p0Num / p0Denom) # change to log()
return p0Vect, p1Vect, pAbusive
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1): # p0Vec非侮辱性的条件概率、p1Vec侮辱性的条件概率, pClass1侮辱性文档的概率
temp = vec2Classify * p1Vec
temp1 = sum(vec2Classify * p1Vec)
p1 = sum(vec2Classify * p1Vec) + log(pClass1) # element-wise mult
# 测试向量矩阵 * p1条件概率矩阵 * pClass1侮辱性文档的概率
print '\n'
print 'p1: ' + str(p1)
p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
print 'p0: ' + str(p0)
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(array(trainMat), array(listClasses))
testEntry = ['love', 'my', 'dalmation']
thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
print thisDoc
print '--------'
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)
if __name__ == '__main__':
testingNB()
准备数据:文档词袋模型
我们将每个词的出现与否作为一个特征,这可以被描述为 词集模型(set-of-words model)。
如果一个词在文档中出现不止一次,这可能意味着包含该词是否出现在文档中所不能表达的某种信息,这种方法被称为 词袋模型(bag-of-words model)。
- 词袋中,每个单词可以出现多次,
- 而在词集中,每个词只能出现一次。
为适应词袋模型,需要对函数 setOfWords2Vec() 稍加修改,修改后的函数为 bagOfWords2Vec() 。
如下给出了基于词袋模型的朴素贝叶斯代码。它与函数 setOfWords2Vec() 几乎完全相同,唯一不同的是每当遇到一个单词时,它会增加词向量中的对应值,而不只是将对应的数值设为 1 。
示例1:使用朴素贝叶斯过滤垃圾邮件
步骤:
- (1)收集数据:提供文本文件。
- (2)准备数据:将文本文件解析成词条向量。
- (3)分析数据:检查词条确保解析的正确性。
- (4)训练算法:使用我们之前建立的trainNBO()函数。
- (5)测试算法:使用classifyNB(),并且构建一个新的测试函数来计算文档集的错误率。
- (6)使用算法:构建一个完整的程序对一组文档进行分类,将错分的文档输出到屏幕上。
准备数据:切分文本
测试算法:使用朴素贝叶斯进行交叉验证
随机选择数据的一部分作为训练集,而剩余部分作为测试集的过程称为留存交叉验证(hold-out cross validaiton)
示例2:使用朴素贝叶斯分类器从个人广告中获取区域倾向
使用朴素贝叶斯来发现地域相关的用词
- (1)收集数据:从RSS源收集内容,这里需要对RSS源构建一个接口。
- (2)准备数据:将文本文件解析成词条向量。
- (3)分析数据:检查词条确保解析的正确性。
- (4)训练算法:使用我们之前建立的trainNBO()函数。
- (5)测试算法:观察错误率,确保分类器可用。可以修改切分程序,以降低错误率,提高
分类结果。 - (6)使用算法:构建一个完整的程序,封装所有内容。给定两个RSS源,该程序会显示最
常用的公共词。