1. 程式人生 > >用起來不太樸素的樸素貝葉斯及其Python實現

用起來不太樸素的樸素貝葉斯及其Python實現

作為一個聽起來非常Naive的分類器,Naive Bayes Classifier使用了“屬性條件獨立性假設”,也就是假設所有屬性相互獨立。分類器的目的,是對任一測試樣本x,利用貝葉斯定理求出後驗概率最大的輸出類。假設y一共可以取N個標籤,yc代表第c類。那麼我們來看一下貝葉斯定理:

P(yc|x)=P(yc)P(x|yc)P(x)

來直觀理解一下貝葉斯定理,左邊,求的是當我們拿到了一個新樣本x,它屬於yc這一類的概率有多大?右邊,P(yc)代表了類別的先驗概率,可以理解為在拿到樣本之前,認為某一類出現的概率有多大,這個概率是已知的,也是通過統計得到的,比如說,我統計了過往一千年杭州地區國慶節的下雨情況,那麼就可以推斷出今年的下雨概率。那麼後驗概率,就是在先驗概率的基礎上進行修正得到的。在這個例子中,後驗概率就是式子左邊,我們想要得到的東西,如果我們沒有額外的資訊,我們就直接根據先驗概率——過往千年的下雨統計概率得到它。但是現在,有了額外資訊,x

中明天是否有颱風的這個屬性變為1,也就是我們知道國慶節要來臺風了,那是否對國慶節下雨的概率要加一個修正?這個修正就是右邊存在的意義。

我們來看一下貝葉斯定理的計算。P(yc)作為先驗概率,來自於客觀統計或者主觀給出。根據大數定律,當訓練集包含足夠多的獨立同分布樣本的時候,P(yc)可以通過各類樣本出現頻率來進行估計。也就是說,在過去有記錄的一千年裡,有七百年的國慶節(10月1號)下雨,那麼我們認為下雨這一類的先驗概率為0.7,晴天這一類的先驗概率為0.X,下其他的先驗概率為0.3-0.X。對於分類問題,我們可以發現,分母P(x)的取值對不同的類是一樣的,只是用來歸一化,對分類效果無影響,那麼接下來就只需要關注另外一個條件概率P

(x|yc)了。一看這個,第c類樣本里x出現的概率?這怎麼算,比如第c類代表國慶有雨,x的屬性是沒颱風、沒地震、有風、昨天有雨等等一系列N個屬性,這些屬性導致可能的組合有2N種,如果遠遠大於樣本數,那麼基本上過去的一千年(嚴格說,應該是七百年),很多觀測值我們是根本都碰不到的了。往往因為訓練集有限,有些觀測值沒有出現過,所以再用前面P(yc)那種利用出現頻率來估算的方法已經不可行了。樣本找不到,再細一點,樣本的屬性值總會出現過吧?回想我們初始的Naive的條件,如果所有屬性都相互獨立,也就是各自獨立地對分類結果產生影響,那麼,P(x|yc)可寫成:

P(x|yc)=P(xi|yc)
問題變成了,在第c類樣本中第i個屬性上取值為x
i
的樣本佔第c類樣本總數的比例。這樣好算多了,在這七百年裡,可能沒有一天同時出現沒颱風、沒地震、有風、昨天有雨再加上其他一些小眾屬性值的情況,但總有一天出現過沒颱風的情況,我們將這幾個屬性的條件概率求出來,連乘,可以得到P(x|yc)。當然,前提是我們認為國慶期間有沒有颱風、國慶前一天有沒有雨等屬性對國慶下雨的影響是獨立的,這也是貝葉斯判別器被稱為Naive的原因。

如果屬性值連續,那麼採用概率密度函式p(xi|yc)代替P(xi|yc),這裡是小p和大P的區別。再假設p(xi|yc)N(μyc,i,σ2yc,i),其中μyc,iσyc,i分別代表第c類樣本在第i個屬性上的均值和方差,那麼:

p(xi|c)=12πσyc,iexp((xiμyc,i)22σ2yc,i)

到這裡,演算法就講完了,原理很簡單。值得一提的是,儘管在利用貝葉斯定理進行分類時,為降低組合爆炸、樣本稀疏等問題,加入了在實際中很難存在的屬性條件獨立性假設,但實際證明,樸素貝葉斯分類器在很多時候都表現的非常好。這裡有兩個解釋:一是儘管樸素貝葉斯給不出精確的概率值,但是我們在分類時只是選擇條件概率最大的那一個,只要大小順序正確就可以;二則,如果屬性間的依賴關係對所有類別作用相同,或者依賴關係的影響可以互相抵消,那麼就不會對分類效能產生影響,同時顯著降低了計算開銷。

貝葉斯定理可以認為是機器學習領域除深度學習外的另外一個很重要的演算法,周志華在KDD China2016大會上強調了深度學習和人腦的生物神經網路結構不同,也沒有一種機器學習演算法是最好的。另外一個方面,我們的大腦又好像天生在使用貝葉斯推理。這一塊,大家見仁見智就OK。

Python原始碼

注:樸素貝葉斯分類器,對分類元數無要求。採用葡萄酒三元資料集進行測試,取測試集佔比0.4,在72個測試樣本上正確率為95%-100%(有不同的原因在於抽取樣本的隨機性),足見其實用性。

# !/usr/bin/env python3
# coding=utf-8
"""
Naive Bayes Classifier
Author  :Chai Zheng
Blog    :http://blog.csdn.net/chai_zheng/
Github  :https://github.com/Chai-Zheng/Machine-Learning
Email   :[email protected]
Date    :2017.10.7
"""

import math
import time
import numpy as np
from collections import Counter
from sklearn import preprocessing
from sklearn.model_selection import train_test_split

def calcProbDensity(meanLabel,stdLabel,test_X):     #計算某樣本的條件概率密度P(xi|c)
    numAttributes = len(test_X)
    MultiProbDensity = 1.0
    for i in range(numAttributes):  #對樣本test_X每個屬性的概率密度函式連乘
        MultiProbDensity = np.exp(-np.square(test_X[i]-meanLabel[i])/(2.0*np.square(stdLabel[i])))/(np.sqrt(2.0*math.pi)*stdLabel[i])*MultiProbDensity
    return MultiProbDensity

def calcPriorProb(Y_train):   #計算每一類的先驗概率P(c)
    i,j = 0,0
    global labelValue,classNum
    numSamples = Y_train.shape[0]
    labelValue = np.zeros((numSamples,1))    #前i行用來儲存標籤值
    Y_train_counter = sum(Y_train.tolist(),[])  #將Y_train轉化為可雜湊的資料結構
    cnt = Counter(Y_train_counter)    #計算標籤值的類別個數及各類樣例的個數
    for key in cnt:
        labelValue[i] = key
        i += 1
    classNum = i    #類別總數
    Pc = np.zeros((classNum,1))   #不同類的先驗概率
    eachLabelNum = np.zeros((classNum,1))  #每類樣例數
    for key in cnt:
        Pc[j] = cnt[key]/numSamples
        eachLabelNum[j] = cnt[key]
        j += 1
    return labelValue,eachLabelNum,classNum,Pc

def trainBayes(X_train,Y_train):
    startTime = time.time()
    numTrainSamples,numAttributes = X_train.shape
    labelValue,eachLabelNum,classNum,Pc = calcPriorProb(Y_train)
    meanlabelX,stdlabelX = [],[]    #存放每一類樣本在所有屬性上取值的均值和方差
    for i in range(classNum):
        k = 0
        labelXMatrix = np.zeros((int(eachLabelNum[i]),numAttributes)) #取出某一類所有樣本組成新矩陣
        for j in range(numTrainSamples):
            if Y_train[j] == labelValue[i]:
                labelXMatrix[k] = X_train[j,:]
                k += 1
        meanlabelX.append(np.mean(labelXMatrix,axis=0).tolist()) #求該矩陣的列均值與無偏標準差,append至所有類
        stdlabelX.append(np.std(labelXMatrix,ddof=1,axis=0).tolist())
    meanlabelX = np.array(meanlabelX).reshape(classNum,numAttributes)
    stdlabelX = np.array(stdlabelX).reshape(classNum,numAttributes)
    print('---Train completed.Took %f s.'%((time.time()-startTime)))
    return meanlabelX,stdlabelX,Pc

def predict(X_test,Y_test,meanlabelX,stdlabelX,Pc):
    numTestSamples = X_test.shape[0]
    matchCount = 0
    for m in range(X_test.shape[0]):
        x_test = X_test[m,:]    #輪流取測試樣本
        pred = np.zeros((classNum,1))   #對不同類的概率
        for i in range(classNum):
            pred[i] = calcProbDensity(meanlabelX[i,:],stdlabelX[i,:],x_test)*Pc[i]  #計算屬於各類的概率
        predict = labelValue[np.argmax(pred)]    #取出標籤
        if predict == Y_test[m]:
            matchCount += 1
    accuracy = float(matchCount/numTestSamples)
    return accuracy

if __name__ == '__main__':
    print('Step 1.Loading data...')
    #資料集下載http://download.csdn.net/download/chai_zheng/10009919
    data = np.loadtxt("Wine.txt",delimiter=',') #載入葡萄酒資料集
    print('---Loading completed.')
    x = data[:,1:14]
    y = data[:,0].reshape(178,1)
    print('Step 2.Splitting and preprocessing data...')
    X_train,X_test,Y_train,Y_test = train_test_split(x,y,test_size=0.4) #拆分資料集
    scaler = preprocessing.StandardScaler().fit(X_train)    #資料標準化
    X_train = scaler.transform(X_train)
    X_test = scaler.transform(X_test)
    print('---Splittinging completed.\n---Number of training samples:%d\n---Number of testing samples:%d'\
          %(X_train.shape[0],X_test.shape[0]))
    print('Step 3.Training...')
    meanlabelX,stdlabelX,Pc = trainBayes(X_train,Y_train)
    print('Step 4.Testing...')
    accuracy = predict(X_test,Y_test,meanlabelX,stdlabelX,Pc)
    print('---Testing completed.Accuracy:%.3f%%' %(accuracy*100))

測試效果如下:


貝葉斯_驗證