樸素貝葉斯分類應用——文檔過濾

樸素貝葉斯公式及其推導回來補上,今天只上代碼

一、提取文檔特徵

#提取文檔特徵,此處爲對文檔進行分詞處理,每個單詞即爲文檔的特徵
def getwords(doc):
    splitter=re.compile('\W*')
    words=[s.lower() for s in splitter.split(doc) if len(s)>2 and len(s)<20]
    return dict([(w,1) for w in words])

二、 基本分類器設計

class classifier:

    def __init__(self,getfeatures,filename=None):
        self.fc={}#各個特徵所屬不同分類的頻度字典
        self.cc={}#各個分類的頻度字典
        self.getfeatures=getfeatures#獲得提取特徵函數

    def incf(self,f,cat):
        self.fc.setdefault(f,{})
        self.fc[f].setdefault(cat,0)
        self.fc[f][cat]+=1#特徵f所屬分類cat的頻度加1

    def incc(self,cat):
        self.cc.setdefault(cat,0)
        self.cc[cat]+=1#分類cat的頻度加1

    def fcount(self,f,cat):
        if f in self.fc and cat in self.fc[f]:
            return float(self.fc[f][cat])#返回特徵f所屬分類cat的頻度
        return 0.0

    def catcount(self,cat):
        if cat in self.cc:
            return float(self.cc[cat])#返回分類cat的頻度
        return 0.0

    def totalcount(self):
        return sum(self.cc.values())#返回所有分類的頻度總和

    def categories(self):
        return self.cc.keys()#用列表返回所有分類

    def train(self,item,cat):
        features=self.getfeatures(item)#分詞提取特徵
        for f in features:
            self.incf(f,cat)#對每個特徵屬於cat分類的頻度加1
        self.incc(cat)#cat分類的頻度加1

    def fprob(self,f,cat):
        if self.catcount(cat)==0:
            return 0
        return self.fcount(f,cat)/self.catcount(cat)#計算在cat分類的條件下含有特徵f的條件概率

    #相當於對特殊情況(比如特徵f屬於分類cat的頻度爲0)計算條件概率進行的平滑處理
    def weightedprob(self,f,cat,prf,weight=1.0,ap=0.5):
        basicprob=prf(f,cat)
        totals=sum(self.fc[f].values())
        #totals=sum([self.fcount(f,c) for c in self.categories()])
        bp=(totals*basicprob+weight*ap)/(weight+totals)
        return bp

三、樸素貝葉斯分類器設計

class naivebayes(classifier):

    def __init__(self,getfeatures):
        classifier.__init__(self,getfeatures)
        self.thresholds={}
        #閾值,即最佳預測分類必須滿足特定條件才能作爲預測結果,不同的設定可影響預測的準確率
        #比如在分類垃圾郵件的應用中就可提高分類爲垃圾郵件的門檻,避免誤將重要郵件分類爲垃圾郵件造成損失

    def setthresholds(self,cat,t):
        self.thresholds[cat]=t#設定閾值

    def getthresholds(self,cat):
        if cat not in self.thresholds:
            return 1.0
        return self.thresholds[cat]#得到閾值

    def classfy(self,item,default=None):
        probs={}
        max=0.0
        best=default
        for cat in self.categories():
            probs[cat]=self.prob(item,cat)
            if probs[cat]>max:#選擇樸素貝葉斯分類器值最大的分類
                max=probs[cat]
                best=cat
        for cat in probs:
            if cat==best:
                continue
            #拿最佳分類和其他分類相比,如果前者的值除以後者小於最佳分類設定的閾值則返回爲默認分類
            if probs[cat]*self.getthresholds(best)>probs[best]:
                return default
            return best#滿足閾值設定才返回作爲預測結果

    def docprob(self,item,cat):
        features=self.getfeatures(item)
        p=1
        for f in features:
            p*=self.weightedprob(f,cat,self.fprob)#基於樸素貝葉斯假設將特徵概率全部乘起來
        return p

    def prob(self,item,cat):
        catprob=self.catcount(cat)/self.totalcount()#分類cat的概率
        docprob=self.docprob(item,cat)#在分類爲cat的條件下含有item所有特徵的概率
        return docprob*catprob#返回樸素貝葉斯分類器值

附加:費舍爾方法

class fisherclassifier(classifier):

    def __init__(self,getfeatures):
        classifier.__init__(self,getfeatures)
        self.minimums={}#閾值

    def setminimum(self,cat,min):
        self.minimums[cat]=min#設定閾值

    def getminimum(self,cat):
        if cat not in self.minimums:
            return 0
        return self.minimums[cat]#得到閾值

    def classfy(self,item,default=None):
        best=default
        max=0.0
        for c in self.categories():
            p=self.fisherprob(item,c)
            if p>self.getminimum(c) and p>max:
                best=c#選擇大於閾值設定的最佳分類
                max=p
        return best

    def cprob(self,f,cat):
        clf=self.fprob(f,cat)#f所屬爲cat分類的概率
        if clf==0:
            return 0
        freqsum=sum([self.fprob(f,c) for c in self.categories()])#f所屬爲各個分類的概率總和
        p=clf/freqsum#直接計算給定特徵f屬於分類cat的條件概率
        return p

    def fisherprob(self,item,cat):
        p=1
        features=self.getfeatures(item)
        for f in features:
            p*=self.weightedprob(f,cat,self.cprob)#item每個特徵屬於cat分類條件概率的乘積
        fscore=-2*math.log(p)#取自然對數,並乘以-2
        return self.invchi2(fscore,len(features)*2)#利用倒置對數卡方函數求得概率

    #倒置對數卡方函數
    #好吧,我知道你看到這裏很困惑,我和你一樣困惑,上網查了一些資料也沒推導出個所以然
    #但據說這種方法的預測準確率更高,就記錄下來作爲備用
    #所以,現在跟我一起後悔沒有本科選數學專業吧......
    def invchi2(self,chi,df):
        m=chi/2.0
        sum=term=math.exp(-m)
        for i in range(1,df//2):
            term*=m/i
            sum+=term
        return min(sum,1.0)

四、測試

def sampletrain(cl):
    cl.train('Nobody owns the water.','good')
    cl.train('the quick rabbit jumps fences','good')
    cl.train('buy pharmaceuticals now','bad')
    cl.train('make quick money at the online casino','bad')
    cl.train('the quick brown fox jumps','good')

cl_bayes=naivebayes(getwords)
sampletrain(cl_bayes)
print(cl_bayes.classfy('quick rabbit'))
print(cl_bayes.classfy('quick money'))
cl_bayes.setthresholds('bad',3.0)
for i in range(5):
    sampletrain(cl_bayes)
    print(i,cl_bayes.classfy('quick money',default='unknown'))
print(' ')
cl_fisher=fisherclassifier(getwords)
sampletrain(cl_fisher)
print(cl_fisher.classfy('quick rabbit'))
print(cl_fisher.classfy('quick money'))
cl_fisher.setminimum('bad',0.75)
for i in range(5):
    sampletrain(cl_fisher)
    print(i,cl_fisher.classfy('quick money',default='unknown'))

結果輸出

good
bad
0 unknown
1 unknown
2 unknown
3 bad
4 bad

good
bad
0 good
1 good
2 bad
3 bad
4 bad

從結果可以看出,設定閾值可以提升分類的門檻,在一些重要場合下可以降低誤分類的風險。同時,隨着訓練的不斷加深,超過閾值設定後的分類顯得“更有底氣”。

五、資源鏈接

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章