基于概率论的分类方法:朴素贝叶斯
优点:在数据较少的情况下任然有效(但是准确率也属于一言难尽),可以处理多类别问题。
缺点:对于输入数据的方式较为敏感。
适用数据类型:标称型数据。
前置知识:条件概率,贝叶斯决策理论,相互独立
相互独立:相互独立是设A,B是两事件,如果满足等式 P ( A B ) = P ( A ) P ( B ) P(AB)=P(A)P(B) P(AB)=P(A)P(B),则称事件A,B相互独立,简称A,B独立。
条件概率: P ( g r e y ∣ b u c k e t B ) P(grey|bucketB) P(grey∣bucketB)表示的是在该球取自B桶的条件下,该球为灰色的概率。
条件概率计算方法
P
(
B
∣
A
)
=
P
(
A
B
)
P
(
A
)
P(B|A)=\frac{P(AB)}{P(A)}
P(B∣A)=P(A)P(AB)
贝叶斯准则:
通过条件概率公式代换,得到
P
(
A
∣
B
)
P(A|B)
P(A∣B)和
P
(
B
∣
A
)
P(B|A)
P(B∣A)之间的转换方法:
p
(
A
∣
B
)
=
P
(
B
∣
A
)
P
(
A
)
P
(
B
)
p(A|B)=\frac{P(B|A)P(A)}{P(B)}
p(A∣B)=P(B)P(B∣A)P(A)
贝叶斯决策理论:
核心思想:选择概率最高的决策
-
如果 p 1 ( x , y ) > p 2 ( x , y ) p1(x,y)>p2(x,y) p1(x,y)>p2(x,y),则(x,y)属于1类
-
如果 p 1 ( x , y ) < p 2 ( x , y ) p1(x,y)<p2(x,y) p1(x,y)<p2(x,y),则(x,y)属于2类
综合以上的内容,最终的到贝叶斯分类准则:
- 如果 p ( c 1 ∣ x , y ) > p ( c 2 ∣ x , y ) p(c_1|x,y)>p(c_2|x,y) p(c1∣x,y)>p(c2∣x,y),那么(x,y)属于1类
- 如果 p ( c 1 ∣ x , y ) < p ( c 2 ∣ x , y ) p(c_1|x,y)<p(c_2|x,y) p(c1∣x,y)<p(c2∣x,y),那么(x,y)属于2类
样本数:
在进行分类是,特征数决定着样本数,由统计学知,如果每个特征需要N个样本,那么对于M个特征需要 N M N^M NM个样本,该数量是指数爆炸的。
假如特征之间相互独立,那么所需要的样本数可以从 N M N^M NM下降到 N ∗ M N*M N∗M。
使用朴素贝叶斯进行文档分类:
该方法中将单词作为特征,特征数巨大。
”朴素“:
单词的分布实际上是相互影响的,一些特定语法,一些固定搭配。该方法采用以特征之间相互独立为前提,对文档进行分类,故为朴素,目的也是为了使用更少的样本。
-
准备数据:从文本中构建词向量
-
训练算法:从词向量计算概率
根据贝叶斯分类准则,我们需要求得 P ( c 0 ∣ w ) P(c_0|w) P(c0∣w)和 P ( c 1 ∣ w ) P(c_1|w) P(c1∣w),其中w是由多个单词(特征)组成的词向量。
首先,由贝叶斯准则,的到:
P
(
c
0
∣
w
)
=
P
(
w
∣
c
0
)
P
(
C
0
)
P
(
w
)
P(c_0|w)=\frac{P(w|c_0)P(C_0)}{P(w)}
P(c0∣w)=P(w)P(w∣c0)P(C0)
其次,由”朴素“假设得
P
(
w
∣
c
0
)
=
P
(
w
1
,
w
2
,
…
,
w
n
∣
c
0
)
=
P
(
w
1
∣
c
0
)
P
(
w
2
∣
c
0
)
…
P
(
w
n
∣
c
0
)
P(w|c_0)=P(w_1,w_2,\dots,w_n|c_0)=P(w_1|c_0)P(w_2|c_0)\dots P(w_n|c_0)
P(w∣c0)=P(w1,w2,…,wn∣c0)=P(w1∣c0)P(w2∣c0)…P(wn∣c0)
便可以得到以下伪代码:
计算每个类别中的文档数目
对每篇训练文档:
对每个类别:
如果词条出现文档中->增加该词条的计数值
增加所有词条的计数值
对每个类别:
对每个词条:
将该词条的数目除以总词条数目得到条件概率
返回每个类别的条件概率
该代码在实现过程中运用了numpy的特性。
并且本书中的一些地方也没有讲清楚:
如:
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
p1 = sum(vec2Classify * p1Vec) + log(pClass1) # element-wise mult
p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
if p1 > p0:
return 1
else:
return 0
计算p1,p2时为何要加log(),为什么是概率相加,不应该是相乘吗?
CSDN大佬给出了答案,对其进行吸收和整理:
在解决下溢问题(多个极小数相乘时,会出现精度消失)时,所以我们可以使用 l o g ( x ∗ y ) = l o g ( x ) + l o g ( y ) log(x*y)=log(x)+log(y) log(x∗y)=log(x)+log(y)这个特性。
之所以可以使用,是因为log函数,和概率函数的分布形态近视:
那么以上的代码就被很好的解释了。
实例:使用朴素贝叶斯过滤垃圾邮件
收集数据:提供文本文件。
准备数据:将文本文件解析成词条向量。
分析数据:检查词条确保解析的正确性。
训练算法:使用我们之前建立的trainNB0()函数。
测试算法:使用classifyNB(),并且构建一个新的测试函数来计算文档集的错误率。
使用算法:构建一个完整的程序对一组文档进行分类,将错分的文档输出到屏幕上。
利用正则表达式切分文本
import re
mySent='This book is the best book on python or M.L. i have ever laid eyes upon.'
regEx = re.compile('\\W')#\W表示非单词数字的字符
listOfTkens = regEx.split(mySent)#split()表示以mySent作为分割标志切割字符
to=[tok.lower() for tok in listOfTkens if len(tok)>0]#去掉长度为0的空串,且大小写不影响侮辱性
print(listOfTkens)
print(to)
留存交叉验证:从数据集中选择一定数量的作为测试集,剩余的用于训练。
实例:使用朴素贝叶斯分类器从个人广告获取区域倾向。
收集数据:从RSS源收集内容,这里需要对RSS源构建一个接口
准备数据:将文本文件解析成词条向量。
分析数据:检查词条确保解析的正确性。
训练算法:使用我们之前建立的trainNB0()函数。
测试算法:观察错误率,确保分类器可用。
使用算法:构建一个完整的程序对一组文档进行分类,将错分的文档输出到屏幕上。
使用了去除出现次数最多的30个词的方式降低错误率,这是由于语言中大部分的冗余造成的。
bayes.py
from numpy import *
#-----------------------------词表到向量的转换------------------------------------
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表示带有侮辱性,0表示没有。
return postingList, classVec
#---------------------------------建立词典-----------------------------------
def createVocabList(dataSet):
vocabSet = set([]) # 获取特征
for document in dataSet:
vocabSet = vocabSet | set(document) # union of the two sets,集合取并
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)#P(c1)
p0Num = ones(numWords)
p1Num = ones(numWords) # change to ones()
p0Denom = 2.0
p1Denom = 2.0 # 优化,由于是概率相乘,但有一个为0时,整体概率会为0,防止出现该现象,故将所有词的出现次数初始化为1,分母初始化为2
for i in range(numTrainDocs):
if trainCategory[i] == 1:
p1Num += trainMatrix[i]#array类型,存放的是每个单词出现的次数,整体相加,方便
p1Denom += sum(trainMatrix[i])
else:
p0Num += trainMatrix[i]#array类型,存放的是每个单词出现的次数,整体相加,方便
p0Denom += sum(trainMatrix[i])
p1Vect = log(p1Num / p1Denom) # 解决下溢问题,当多个较小的数相乘时,数值会很小,会出现精度丢失问题,利用激活函数调整分布
p0Vect = log(p0Num / p0Denom) # change to log(),log函数与概率密度函数特征类似,且不会出现下溢问题。
return p0Vect, p1Vect, pAbusive
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
p1 = sum(vec2Classify * p1Vec) + log(pClass1) # element-wise mult
p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
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(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))
def bagOfWords2VecMN(vocabList, inputSet):
returnVec = [0] * len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] += 1
return returnVec
def textParse(bigString): # input is big string, #output is word list
import re
listOfTokens = re.split(r'\W*', bigString)
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 = [] # create test set
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: # train the classifier (get probs) trainNB0
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
def calcMostFreq(vocabList, fullText):
import operator
freqDict = {}
for token in vocabList:
freqDict[token] = fullText.count(token)
sortedFreq = sorted(freqDict.items(), key=operator.itemgetter(1), reverse=True)
return sortedFreq[:30]
def localWords(feed1, feed0):
import feedparser
docList = []
classList = []
fullText = []
minLen = min(len(feed1['entries']), len(feed0['entries']))
for i in range(minLen):
wordList = textParse(feed1['entries'][i]['summary'])
docList.append(wordList)
fullText.extend(wordList)
classList.append(1) # NY is class 1
wordList = textParse(feed0['entries'][i]['summary'])
docList.append(wordList)
fullText.extend(wordList)
classList.append(0)
vocabList = createVocabList(docList) # create vocabulary
top30Words = calcMostFreq(vocabList, fullText) # remove top 30 words
for pairW in top30Words:
if pairW[0] in vocabList: vocabList.remove(pairW[0])
trainingSet = range(2 * minLen)
testSet = [] # create test set
for i in range(20):
randIndex = int(random.uniform(0, len(trainingSet)))
testSet.append(trainingSet[randIndex])
del (trainingSet[randIndex])
trainMat = []
trainClasses = []
for docIndex in trainingSet: # train the classifier (get probs) trainNB0
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('the error rate is: ', float(errorCount) / len(testSet))
return vocabList, p0V, p1V
def getTopWords(ny, sf):
import operator
vocabList, p0V, p1V = localWords(ny, sf)
topNY = []
topSF = []
for i in range(len(p0V)):
if p0V[i] > -6.0:
topSF.append((vocabList[i], p0V[i]))
if p1V[i] > -6.0:
topNY.append((vocabList[i], p1V[i]))
sortedSF = sorted(topSF, key=lambda pair: pair[1], reverse=True)
print("SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**")
for item in sortedSF:
print(item[0])
sortedNY = sorted(topNY, key=lambda pair: pair[1], reverse=True)
print("NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**")
for item in sortedNY:
print(item[0])
main.py
import re
import bayes
listOPosts,listClasses=bayes.loadDataSet()
myVocabList=bayes.createVocabList(listOPosts)
print(myVocabList)
print(bayes.setOfWords2Vec(myVocabList,listOPosts[0]))
trainMat = []
for postinDoc in listOPosts:
trainMat.append(bayes.setOfWords2Vec(myVocabList,postinDoc))
print(trainMat)#得到的是每一句话在字典中的分布
p0v,p1v,pAb=bayes.trainNB0(trainMat,listClasses)
print(pAb)
print(p0v)
print(p1v)
bayes.testingNB()
mySent='This book is the best book on python or M.L. i have ever laid eyes upon.'
regEx = re.compile('\\W')#\W表示非单词数字的字符
listOfTkens = regEx.split(mySent)#split()表示以mySent作为分割标志切割字符
to=[tok for tok in listOfTkens if len(tok)>0]#去掉长度为0的空串
print(listOfTkens)
print(to)
结语:
- 利用概率论的知识对事物进行分类是一种很自然的想法,基于统计学的知识,取得出一个结果,这也是对数理知识的一种应用。
- 再改模型中使用了特征之间的条件独立的假设,这也是能够快速求得概率的原因,虽然与事实不符,但在特定的数据集上表现十分好,可以将该方法作为模型化简方法之一。
- 根据语言的冗余性,移除出现频次高的词汇,反而降低了错误率,是一种很好的数据预处理的方法。
- 通过使用log( )函数来解决乘数下溢问题十分有技巧性。