今天学习的内容是贝叶斯分类器。
在正式介绍之前,先说两个名词:
Bayes Decision theory是概率框架下实施决策的基本方法。对分类任务来说,在所有相关概率都已知的理想情形下,贝叶斯决策论考虑如何基于这些概率和误判损失来选择最优的类别标记。
假设有N种可能的类别标记{c1,c2,…,cn},λ(i,j)是将一个真实标记为cj的样本误分类为ci所产生的损失。于是基于后验概率P(ci|x)可获得将样本x分类为ci所产生的期望损失,即样本x上的条件风险。
R(ci|x)=Σλ(i,j)*P(cj|x)
显然我们的追求是寻找一个判定准则以最小化总体条件风险。这就产生了贝叶斯判定准则:为最小化总体风险,只需要在每个样本上选择那个能使条件风险R(c|x)最小的类别标记,该类别标记就称为贝叶斯最优分类器,与之对应的R称为贝叶斯风险,1-R反映了分类器所能达到的最好性能。
另外,如果目标是最小化分类器错误率,则误判损失λ(i,j)可以改写为
if i=j
λ(i,j)=0
else
λ(i,j)=1
此时条件风险R(c|x)=1-P(c|x)
然后基于贝叶斯定理,P(c|x)可以改写为
P(c|x)=P(c)*P(x|c)/P(x)
其中P(c)是类“先验”概率,P(x|c)是样本x相对于类标记c的类条件概率,或者称为“似然”(likelihood)。
类先验概率P(c)表达了样本空间中各类样本所占的比例,根据大数定律,当训练集包含充足的独立同分布(i.i.d)样本时,P(c)可以通过各样本出现的频率来进行估计。
估计类条件概率的一种常见策略是先假定其具有某种确定的概率分布形式,再基于训练样本对概率分布的参数进行估计。
事实上,模型训练过程就是参数估计过程。关于此,统计学界有两个学派。
频率主义学派:认为参数虽然未知,但是却是客观存在的固定值,可以通过优化似然函数来确定参数。
贝叶斯学派: 认为参数是未观察到的随机变量,本身也可有分布,因此,可假定参数服从一个先验分布。
综上,基于贝叶斯公式的来计算后验概率的主要困难在于:类条件概率P(x|c)是所有属性上的联合概率,难以从有限的训练样本中直接估计而得,为了避开这个障碍,朴素贝叶斯采用了“属性条件独立性假设”:对于已知类别,假设所有属性相互独立。
所以,朴素贝叶斯分类器的训练过程就是基于训练集D来估计类先验概率P(c),并为每个属性估计条件概率P(xi|c)
令Dc表示训练集D中第c类样本组成的集合,若有充足的独立同分布样本,则可以容易的估计出类先验概率
P(c)=|Dc|/|D|
对离散属性,令D(c,xi)表示Dc中在第i个属性上取值为xi的样本组成的集合,则
P(xi|c)=|D(c,xi)|/|Dc|
朴素贝叶斯采用了属性条件独立性假设,但在限时任务中这个假设往往很难成立,于是对条件进行一定程度的放松,于是就有了半朴素贝叶斯。
其适当考虑一部分属性间的相互依赖关系。比较常见的策略有ODE、SPODE、TAN、AODE
借助DAG来刻画属性之间的依赖关系,并使用条件概率表来描述属性的联合概率分布。
限于笔者目前能力,实战部分仅限与朴素贝叶斯,更有难度的部分to be continued
相关资源地址:
https://github.com/CallMeSp/MachineLearning.git
实战一:邮件分类
def spamTest():
docList=[];classList=[];fullText=[]
# 导入并解析文件
for i in range(1,26):
# 垃圾邮件
wordList=textParse(open('email/spam/%d.txt'%i,'r').read())
docList.append(wordList)
fullText.extend(wordList)
classList.append(1)
# 正常邮件
wordList=textParse(open('email/ham/%d.txt'%i,'r').read())
docList.append(wordList)
fullText.extend(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])
trainingMat=[];trainingClasses=[]
for docIndex in trainingSet:
trainingMat.append(setOfWords2Vec(vocabList,docList[docIndex]))
trainingClasses.append(classList[docIndex])
p0v,p1v,pSpam=trainNB0(trainingMat,trainingClasses)
errorCount=0
for docIndex in testSet:
wordVector=setOfWords2Vec(vocabList,docList[docIndex])
if classifyNB(wordVector,p0v,p1v,pSpam)!=classList[docIndex]:
errorCount+=1
print('the error rate is ',float(errorCount)/len(testSet))
所依赖的相关方法
# -*- coding: UTF-8 -*-
from numpy import *
import feedparser
import operator
import re
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
return postingList,classVec
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 bagofWords2Vec(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
# 见下方Tip
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-pClass1)
if p1>p0:
return 1
else:
return 0
# 单个短语分类测试
def testingNB():
listPosts,listClasses=loadDataSet()
myVocabList=createVocabList(listPosts)
trainMat=[]
for postinDoc in listPosts:
trainMat.append(setOfWords2Vec(myVocabList,postinDoc))
p0V,p1V,pAb=trainNB0(trainMat,listClasses)
testEntry=['love','my','dalmation']
thisDoc=array(setOfWords2Vec(myVocabList,testEntry))
print(testEntry,' is classified as :',classifyNB(thisDoc,p0V,p1V,pAb))
testEntry=['stupid','garbage']
thisDoc=array(setOfWords2Vec(myVocabList,testEntry))
print(testEntry,' is classified as :',classifyNB(thisDoc,p0V,p1V,pAb))
def textParse(bigString):
listofTokens=re.findall(r'\w+',bigString)
return [token.lower() for token in listofTokens if len(token)>2]
在上面44行的方法中,涉及到了概率值的计算,为防止连乘计算式中某项为0干扰结果,在估计概率值时常要进行“平滑”,常用拉普拉斯修正。N表示训练集D中可能的类别数,Ni表示第i个属性可能的取值数。则修正为
P(c)=(|Dc|+1)/(|D|+N)
P(xi|c)=(|D(c,xi)|+1)/(|Dc|+Ni)
实战二:判断属于哪类文章
if(__name__=='__main__'):
ny=feedparser.parse('http://losangeles.craigslist.org/tfr/index.rss')
sf=feedparser.parse('http://newyork.craigslist.org/res/index.rss')
localWords(ny,sf)
需要
pip install feedparser
另外在开头
import feedparser
def calcMostFreq(vocabList,fullText):
freqDict={}
for token in vocabList:
freqDict[token]=fullText.count(token)
sortedFrq=sorted(freqDict.items(),key=operator.itemgetter(1),reverse=True)
return sortedFrq[:7]
def localWords(feed1,feed0):
docList=[];classList=[];fullText=[]
minLen=min(len(feed1['entries']),len(feed0['entries']))
for i in range(minLen):
wordList=textParse(feed0['entries'][i]['summary'])
docList.append(wordList)
fullText.extend(wordList)
classList.append(0)
wordList=textParse(feed1['entries'][i]['summary'])
docList.append(wordList)
fullText.extend(wordList)
classList.append(1)
vocabList=createVocabList(docList)
print(vocabList)
# 去掉最高频的几个单词
topWords=calcMostFreq(vocabList,fullText)
for t in topWords:
# 字典中的key
if t[0] in vocabList:
vocabList.remove(t[0])
trainingSet=list(range(2*minLen))
testSet=[]
for i in range(5):
randIndex=int(random.uniform(0,len(trainingSet)))
testSet.append(trainingSet[randIndex])
del(trainingSet[randIndex])
trainingMat=[]
trainingClasses=[]
for docIndex in trainingSet:
trainingMat.append(bagofWords2Vec(vocabList,docList[docIndex]))
trainingClasses.append(classList[docIndex])
p0v,p1v,pSpam=trainNB0(trainingMat,trainingClasses)
errorCount=0
for docIndex in testSet:
wordVect=bagofWords2Vec(vocabList,docList[docIndex])
if classifyNB(wordVect,p0v,p1v,pSpam)!=classList[docIndex]:
errorCount+=1
print('the err rate is ',float(errorCount)/len(testSet))
后续还要对半朴素贝叶斯,贝叶斯网等进行进一步学习。