1. 程式人生 > >樸素貝葉斯分類演算法理解及文字分類器實現

樸素貝葉斯分類演算法理解及文字分類器實現

貝葉斯分類是一類分類演算法的總稱,這類演算法均以貝葉斯定理為基礎,故統稱為貝葉斯分類。本文作為分類演算法的第一篇,將首先介紹分類問題,對分類問題進行一個正式的定義。然後,介紹貝葉斯分類演算法的基礎——貝葉斯定理。最後,通過例項討論貝葉斯分類中最簡單的一種:樸素貝葉斯分類。


分類問題綜述

對於分類問題,其實誰都不會陌生,說我們每個人每天都在執行分類操作一點都不誇張,只是我們沒有意識到罷了。例如,當你看到一個陌生人,你的腦子下意識判斷TA是男是女;你可能經常會走在路上對身旁的朋友說“這個人一看就很有錢、那邊有個非主流”之類的話,其實這就是一種分類操作。

從數學角度來說,分類問題可做如下定義:

其中C叫做類別集合,其中每一個元素是一個類別,而I叫做項集合,其中每一個元素是一個待分類項,f叫做分類器。分類演算法的任務就是構造分類器f。

這裡要著重強調,分類問題往往採用經驗性方法構造對映規則,即一般情況下的分類問題缺少足夠的資訊來構造100%正確的對映規則,而是通過對經驗資料的學習從而實現一定概率意義上正確的分類,因此所訓練出的分類器並不是一定能將每個待分類項準確對映到其分類,分類器的質量與分類器構造方法、待分類資料的特性以及訓練樣本數量等諸多因素有關。

例如,醫生對病人進行診斷就是一個典型的分類過程,任何一個醫生都無法直接看到病人的病情,只能觀察病人表現出的症狀和各種化驗檢測資料來推斷病情,這時醫生就好比一個分類器,而這個醫生診斷的準確率,與他當初受到的教育方式(構造方法)、病人的症狀是否突出(待分類資料的特性)以及醫生的經驗多少(訓練樣本數量)都有密切關係。

貝葉斯分類的基礎——貝葉斯定理

每次提到貝葉斯定理,我心中的崇敬之情都油然而生,倒不是因為這個定理多高深,而是因為它特別有用。這個定理解決了現實生活裡經常遇到的問題:已知某條件概率,如何得到兩個事件交換後的概率,也就是在已知P(A|B)的情況下如何求得P(B|A)。這裡先解釋什麼是條件概率:

樸素貝葉斯分類

樸素貝葉斯分類的原理與流程
樸素貝葉斯分類是一種十分簡單的分類演算法,叫它樸素貝葉斯分類是因為這種方法的思想真的很樸素,樸素貝葉斯的思想基礎是這樣的:對於給出的待分類項,求解在此項出現的條件下各個類別出現的概率,哪個最大,就認為此待分類項屬於哪個類別。通俗來說,就好比這麼個道理,你在街上看到一個黑人,我問你你猜這哥們哪裡來的,你十有八九猜非洲。為什麼呢?因為黑人中非洲人的比率最高,當然人家也可能是美洲人或亞洲人,但在沒有其它可用資訊下,我們會選擇條件概率最大的類別,這就是樸素貝葉斯的思想基礎。

樸素貝葉斯分類的正式定義如下:

根據上述分析,樸素貝葉斯分類的流程可以由下圖表示(暫時不考慮驗證):

可以看到,整個樸素貝葉斯分類分為三個階段:

第一階段——準備工作階段,這個階段的任務是為樸素貝葉斯分類做必要的準備,主要工作是根據具體情況確定特徵屬性,並對每個特徵屬性進行適當劃分,然後由人工對一部分待分類項進行分類,形成訓練樣本集合。這一階段的輸入是所有待分類資料,輸出是特徵屬性和訓練樣本。這一階段是整個樸素貝葉斯分類中唯一需要人工完成的階段,其質量對整個過程將有重要影響,分類器的質量很大程度上由特徵屬性、特徵屬性劃分及訓練樣本質量決定。

第二階段——分類器訓練階段,這個階段的任務就是生成分類器,主要工作是計算每個類別在訓練樣本中的出現頻率及每個特徵屬性劃分對每個類別的條件概率估計,並將結果記錄。其輸入是特徵屬性和訓練樣本,輸出是分類器。這一階段是機械性階段,根據前面討論的公式可以由程式自動計算完成。

第三階段——應用階段。這個階段的任務是使用分類器對待分類項進行分類,其輸入是分類器和待分類項,輸出是待分類項與類別的對映關係。這一階段也是機械性階段,由程式完成。
1.4.2、估計類別下特徵屬性劃分的條件概率及Laplace校準

這一節討論P(a|y)的估計。

由上文看出,計算各個劃分的條件概率P(a|y)是樸素貝葉斯分類的關鍵性步驟,當特徵屬性為離散值時,只要很方便的統計訓練樣本中各個劃分在每個類別中出現的頻率即可用來估計P(a|y),下面重點討論特徵屬性是連續值的情況。

當特徵屬性為連續值時,通常假定其值服從高斯分佈(也稱正態分佈)。即:

因此只要計算出訓練樣本中各個類別中此特徵項劃分的各均值和標準差,代入上述公式即可得到需要的估計值。均值與標準差的計算在此不再贅述。

另一個需要討論的問題就是當P(a|y)=0怎麼辦,當某個類別下某個特徵項劃分沒有出現時,就是產生這種現象,這會令分類器質量大大降低。為了解決這個問題,我們引入Laplace校準,它的思想非常簡單,就是對沒類別下所有劃分的計數加1,這樣如果訓練樣本集數量充分大時,並不會對結果產生影響,並且解決了上述頻率為0的尷尬局面。

樸素貝葉斯分類例項:檢測SNS社群中不真實賬號

下面討論一個使用樸素貝葉斯分類解決實際問題的例子,為了簡單起見,對例子中的資料做了適當的簡化。

這個問題是這樣的,對於SNS社群來說,不真實賬號(使用虛假身份或使用者的小號)是一個普遍存在的問題,作為SNS社群的運營商,希望可以檢測出這些不真實賬號,從而在一些運營分析報告中避免這些賬號的干擾,亦可以加強對SNS社群的瞭解與監管。

如果通過純人工檢測,需要耗費大量的人力,效率也十分低下,如能引入自動檢測機制,必將大大提升工作效率。這個問題說白了,就是要將社群中所有賬號在真實賬號和不真實賬號兩個類別上進行分類,下面我們一步一步實現這個過程。

首先設C=0表示真實賬號,C=1表示不真實賬號。

1、確定特徵屬性及劃分

這一步要找出可以幫助我們區分真實賬號與不真實賬號的特徵屬性,在實際應用中,特徵屬性的數量是很多的,劃分也會比較細緻,但這裡為了簡單起見,我們用少量的特徵屬性以及較粗的劃分,並對資料做了修改。

我們選擇三個特徵屬性:a1:日誌數量/註冊天數,a2:好友數量/註冊天數,a3:是否使用真實頭像。在SNS社群中這三項都是可以直接從資料庫裡得到或計算出來的。

下面給出劃分:a1:{a<=0.05, 0.05<a<0.2, a>=0.2},a1:{a<=0.1, 0.1<a<0.8, a>=0.8},a3:{a=0(不是),a=1(是)}。

2、獲取訓練樣本

這裡使用運維人員曾經人工檢測過的1萬個賬號作為訓練樣本。

3、計算訓練樣本中每個類別的頻率

用訓練樣本中真實賬號和不真實賬號數量分別除以一萬,得到:

4、計算每個類別條件下各個特徵屬性劃分的頻率

5、使用分類器進行鑑別

下面我們使用上面訓練得到的分類器鑑別一個賬號,這個賬號使用非真實頭像,日誌數量與註冊天數的比率為0.1,好友數與註冊天數的比率為0.2。

可以看到,雖然這個使用者沒有使用真實頭像,但是通過分類器的鑑別,更傾向於將此賬號歸入真實賬號類別。這個例子也展示了當特徵屬性充分多時,樸素貝葉斯分類對個別屬性的抗干擾性。
1.5、分類器的評價

雖然後續還會提到其它分類演算法,不過這裡我想先提一下如何評價分類器的質量。

首先要定義,分類器的正確率指分類器正確分類的專案佔所有被分類專案的比率。

通常使用迴歸測試來評估分類器的準確率,最簡單的方法是用構造完成的分類器對訓練資料進行分類,然後根據結果給出正確率評估。但這不是一個好方法,因為使用訓練資料作為檢測資料有可能因為過分擬合而導致結果過於樂觀,所以一種更好的方法是在構造初期將訓練資料一分為二,用一部分構造分類器,然後用另一部分檢測分類器的準確率。

Python程式碼實現:

#encoding:utf-8

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([])  #呼叫set方法,建立一個空集
    for document in dataSet:
        vocabSet = vocabSet | set(document)     #建立兩個集合的並集
    return list(vocabSet)
'''
def setOfWords2Vec(vocabList,inputSet):
    returnVec = [0]*len(vocabList)   #建立一個所含元素都為0的向量
    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 bagOfWords2VecMN(vocabList,inputSet):
    returnVec = [0]*len(vocabList)   #建立一個所含元素都為0的向量
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1
    return returnVec


#樸素貝葉斯分類器訓練集
def trainNB0(trainMatrix,trainCategory):  #傳入引數為文件矩陣,每篇文件類別標籤所構成的向量
    numTrainDocs = len(trainMatrix)      #文件矩陣的長度
    numWords = len(trainMatrix[0])       #第一個文件的單詞個數
    pAbusive = sum(trainCategory)/float(numTrainDocs)  #任意文件屬於侮辱性文件概率
    #p0Num = zeros(numWords);p1Num = zeros(numWords)        #初始化兩個矩陣,長度為numWords,內容值為0
    p0Num = ones(numWords);p1Num = ones(numWords)        #初始化兩個矩陣,長度為numWords,內容值為1
    #p0Denom = 0.0;p1Denom = 0.0                         #初始化概率
    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 = p1Num/p1Denom #對每個元素做除法
    #p0Vect = p0Num/p0Denom
    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.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))  #使用詞向量來填充trainMat列表
    p0V,p1V,pAb = trainNB0(array(trainMat),array(listClasses))  #訓練函式
    testEntry = ['love','my','dalmation']   #測試文件列表
    thisDoc = array(setOfWords2Vec(myVocabList,testEntry)) #宣告矩陣
    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)

使用方法:
進入該檔案所在目錄,輸入Python,執行
>>>import bayes
>>>bayes.testingNB()