Q:什么是貝葉斯分類器?
一句話:以貝葉斯方法為核心原理構(gòu)造出來的分類程序統(tǒng)稱為貝葉斯分類器,主要包括樸素貝葉斯分類器,半樸素貝葉斯分類器,以及貝葉斯網(wǎng)絡(luò)。
Q:那么,貝葉斯方法又是什么?
貝葉斯方法是一位英國數(shù)學(xué)家托馬斯·貝葉斯在18世紀(jì)提出來的概率學(xué)方法。此法強(qiáng)大之處不多贅述,貝葉斯學(xué)派占據(jù)了概率江湖中的半壁江山(另外一半江山歸于頻率學(xué)派)。
舉個(gè)例子:
已知
——100名男生中有99個(gè)短發(fā),1個(gè)長發(fā);
——100名女生中有90個(gè)長發(fā),10個(gè)短發(fā);
那么
——從這200名學(xué)生中抽取一個(gè)學(xué)生,已知是男生,則該男生是長發(fā)的概率是多少?答案很簡單,是0.01000。
——從中200名學(xué)生中抽取一個(gè)學(xué)生,已知是長發(fā),則該長發(fā)學(xué)生是男生的概率是多少?答案稍難一點(diǎn),是0.01099。
由此可見,大多數(shù)的概率計(jì)算方法(古典概率模型、幾何概率模型、條件概率公式、全概率公式、聯(lián)合概率公式······)都是已知前提條件發(fā)生的情況下求各種可能結(jié)果發(fā)生的概率。而貝葉斯公式則是用于已知一件事情已經(jīng)發(fā)生,求導(dǎo)致其發(fā)生的各種可能前提條件成立的概率。
已知:A => a 或 b 或 c 求P(a)
用古典概率模型、幾何概率模型、條件概率公式、全概率公式、聯(lián)合概率公式······
已知:A 或 B 或 C => a 求P(A)
用貝葉斯公式
更多貝葉斯方法的知識(shí),可以查閱劉未鵬《數(shù)學(xué)之美番外:貝葉斯方法》
Q:怎樣應(yīng)用貝葉斯方法構(gòu)造分類器?
我們繼續(xù)以西瓜分類問題為例,用下面的色澤、根蒂等8個(gè)特征的數(shù)據(jù),判斷一個(gè)西瓜是好瓜還是壞瓜。
應(yīng)用貝葉斯方法的基本思想是,分別求出待分類西瓜是好瓜以及壞瓜的概率,哪一個(gè)概率高,就將其歸到那一類。
那么
這兩個(gè)概率怎么求?
首先我們假設(shè)樣本的所有屬性相互獨(dú)立,比如西瓜的色澤是黑是綠,與根蒂是直是曲無關(guān)。則根據(jù)貝葉斯方法,求一個(gè)西瓜是好瓜還是壞瓜的概率的基本公式是如下
其中c是類別,向量是待測樣本。因?yàn)槲覀儾⒉恍枰蟪?img class="math-inline" src="https://math.jianshu.com/math?formula=P(c%7C%5Cvec%7Bx%7D)" alt="P(c|\vec{x})" mathimg="1">的確切值,只需要知道
和
誰更大就行。因?yàn)榉帜窹(x)對(duì)于
和
是一樣的,所以可以一并舍去。所以關(guān)鍵要求出P(c)以及P(x|c),也就是
亦即
這兩條式子中基本上每一項(xiàng)都是一個(gè)簡單的條件概率。對(duì)于離散型變量,一般情況下可以直接用數(shù)數(shù)的方法加古典概型算出來。
數(shù)據(jù)集中的密度和含糖率是連續(xù)型變量,他們的條件概率怎么求?可以假設(shè)這些連續(xù)數(shù)據(jù)服從正態(tài)分布,然后算出訓(xùn)練數(shù)據(jù)集中這兩項(xiàng)屬性的均值和方差,代入下面概率密度函數(shù),即可算出。
用訓(xùn)練數(shù)據(jù)集算出各個(gè)特征的條件概率以及各個(gè)類別出現(xiàn)的概率以后,就可以應(yīng)用貝葉斯公式預(yù)測一個(gè)西瓜是好瓜以及壞瓜的概率了。
在上面的例子中,我們
- 用到了貝葉斯公式
- 假設(shè)各個(gè)屬性互不影響(實(shí)際上不是,比如敲聲可能受密度影響)
所以我們剛剛構(gòu)造出的分類器叫做樸素貝葉斯分類器(Naive Bayesian classifier)。
Q:我覺得樸素貝葉斯分類器太Naive,想要高逼格一點(diǎn),如何改進(jìn)?
雖然樸素貝葉斯算法很Naive,但是實(shí)踐顯示其性能很已經(jīng)不錯(cuò),尤其是在文本分類領(lǐng)域有廣泛應(yīng)用,很多垃圾郵件分類算法用的就是樸素貝葉斯。
當(dāng)然,可以不Naive一點(diǎn),用半樸素貝葉斯(semi-naive Bayesian classifier)。
樸素貝葉斯, naive之處在于假設(shè)各個(gè)屬性互不影響。這在實(shí)踐中不易見到,所以人們嘗試放寬點(diǎn)這個(gè)條件,比如改成“假設(shè)各個(gè)屬性最多受一個(gè)屬性影響(最多依賴于一個(gè)屬性)”。
如此一改,整個(gè)模型就由
變成了
兩條式子的唯一的不同之處在于各個(gè)條件概率項(xiàng)的條件部分,pai指的是被依賴的屬性。舉個(gè)例子——若敲聲受密度影響,則有
P(敲聲=沉悶|好瓜=是,密度>0.700)
P(敲聲=沉悶|好瓜=否,密度>0.700)
P(敲聲=沉悶|好瓜=是,密度<=0.700)
P(敲聲=沉悶|好瓜=否,密度<=0.700)
······
除此之外,其他與樸素貝葉斯無大出入。
Q:半樸素貝葉斯中還是有點(diǎn)naive,有沒有不naive的貝葉斯算法?
當(dāng)然有,比如貝葉斯網(wǎng)絡(luò)(Bayesian network),又稱信念網(wǎng)絡(luò)(Belief network),也就是當(dāng)我們完全放開了“屬性之間相互影響”的假設(shè)的時(shí)候,所誕生的算法。此時(shí)一個(gè)屬性可以影響多個(gè)其他屬性,也可以受多個(gè)屬性影響,這就比樸素貝葉斯和半樸素貝葉斯復(fù)雜多了。
貝葉斯網(wǎng)絡(luò)之間的屬性的影響關(guān)系,可以用一個(gè)有向無環(huán)圖描述,圖中每一個(gè)節(jié)點(diǎn)存儲(chǔ)著該屬性的條件概率。如
所以一個(gè)貝葉斯網(wǎng)絡(luò)由一個(gè)有向無環(huán)圖和一個(gè)條件概率表組成。
貝葉斯網(wǎng)絡(luò)是相當(dāng)高級(jí)和復(fù)雜的技術(shù),目前我還沒有全面理解,在此留白,等以后有機(jī)會(huì)再來補(bǔ)充。
Talk is cheap, show me the code!
使用樸素貝葉斯算法計(jì)算離散型和連續(xù)性變量時(shí)求條件概率的方法略有不同,但方便起見,我還是將其完全拆開放到兩個(gè)模型里。首先時(shí)離散型變量的樸素貝葉斯模型:
"""
Naive Bayes classifier for categorical variables
:file: supervised.py
:author: Richy Zhu
:email: rickyzhu@foxmail.com
"""
import numpy as np
class MyCategoricalNBC:
'''categorical naive bayes classifier'''
def __init__(self):
self.X = None
self.y = None
def fit(self, X, y):
'''
Train the nominal naive bayes classifier model
Parameters
----------
X: ndarray of shape (m, n)
sample data where row represent sample and column represent feature
y: ndarray of shape (m,)
labels of sample data
Returns
-------
self
trained model
'''
self.X = X
self.y = y
return self
def _predict(self, x):
'''
compute probabilities and make prediction.
'''
probas = {}
clss = list(set(self.y))
# compute probability for each attributes in x
for c in clss:
probas[c] = []
dat = self.X[self.y==c]
for attr_id, attr_val in enumerate(x):
count = 0
for row in dat:
if attr_val == row[attr_id]:
count += 1
# use laplace smoothing
probas[c].append((count+1)/(len(dat)+len(x)))
probas[c].append(len(dat)/len(self.X))
final_probas = {}
from functools import reduce
for c, prbs in probas.items():
# theoretically not final probability because not divided by Pr(x)
final_probas[reduce(lambda x,y:x * y, prbs)] = c
return final_probas[max(final_probas)]
def predict(self, X):
'''
Make prediction by the trained model.
Parameters
----------
X: ndarray of shape (m, n)
data to be predicted, the same shape as trainning data
Returns
-------
C: ndarray of shape (m,)
Predicted class label per sample.
'''
if self.X is None:
raise Exception("Model haven't been trained!")
return np.array([self._predict(x) for x in X])
測試代碼如下——因?yàn)閟klearn包沒有離散變量的數(shù)據(jù)集,又不想動(dòng)用新聞數(shù)據(jù)集(其實(shí)是我的代碼處理不了,會(huì)出bug??),所以只能將就著用一下重復(fù)10次的西瓜數(shù)據(jù)集:
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
print('\nDescrete Naive Bayes')
print('---------------------------------------------------------------------')
xigua2 = np.array([
['青綠', '蜷縮', '濁響', '清晰', '凹陷', '硬滑', '好瓜'],
['烏黑', '蜷縮', '沉悶', '清晰', '凹陷', '硬滑', '好瓜'],
['烏黑', '蜷縮', '濁響', '清晰', '凹陷', '硬滑', '好瓜'],
['青綠', '蜷縮', '沉悶', '清晰', '凹陷', '硬滑', '好瓜'],
['淺白', '蜷縮', '濁響', '清晰', '凹陷', '硬滑', '好瓜'],
['青綠', '稍蜷', '濁響', '清晰', '稍凹', '軟粘', '好瓜'],
['烏黑', '稍蜷', '濁響', '稍糊', '稍凹', '軟粘', '好瓜'],
['烏黑', '稍蜷', '濁響', '清晰', '稍凹', '硬滑', '好瓜'],
# ----------------------------------------------------
['烏黑', '稍蜷', '沉悶', '稍糊', '稍凹', '硬滑', '壞瓜'],
['青綠', '硬挺', '清脆', '清晰', '平坦', '軟粘', '壞瓜'],
['淺白', '硬挺', '清脆', '模糊', '平坦', '硬滑', '壞瓜'],
['淺白', '蜷縮', '濁響', '模糊', '平坦', '軟粘', '壞瓜'],
['青綠', '稍蜷', '濁響', '稍糊', '凹陷', '硬滑', '壞瓜'],
['淺白', '稍蜷', '沉悶', '稍糊', '凹陷', '硬滑', '壞瓜'],
['烏黑', '稍蜷', '濁響', '清晰', '稍凹', '軟粘', '壞瓜'],
['淺白', '蜷縮', '濁響', '模糊', '平坦', '硬滑', '壞瓜'],
['青綠', '蜷縮', '沉悶', '稍糊', '稍凹', '硬滑', '壞瓜']
])
from sklearn.preprocessing import LabelEncoder
for i in range(xigua2.shape[1]):
le = LabelEncoder()
xigua2[:,i] = le.fit_transform(xigua2[:,i])
xigua2 = xigua2.astype(np.int).repeat(20, axis=0)
X = xigua2[:, :-1]
y = xigua2[:, -1]
X_train, X_test, y_train, y_test = train_test_split(X, y)
mycnb = MyCategoricalNBC()
mycnb.fit(X_train, y_train)
print('My Accuracy:', accuracy_score(mycnb.predict(X_test), y_test))
from sklearn.naive_bayes import CategoricalNB
skcnb = CategoricalNB()
skcnb.fit(X_train, y_train)
print('SK Accuracy:', accuracy_score(skcnb.predict(X_test), y_test))
測試結(jié)果如下
$ python supervised_examples.py
Descrete Naive Bayes
---------------------------------------------------------------------
My Accuracy: 0.8235294117647058
SK Accuracy: 0.8235294117647058
下面是處理連續(xù)型變量數(shù)據(jù)集的樸素貝葉斯模型(假設(shè)所有連續(xù)變量都服從高斯分布):
"""
Naive Bayes classifier for continuous variables
:file: supervised.py
:author: Richy Zhu
:email: rickyzhu@foxmail.com
"""
import numpy as np
class MyGaussianNBC:
'''Gaussian continuous naive bayes classifier'''
def __init__(self):
self.X = None
self.y = None
def fit(self, X, y):
'''
Train the Gaussian continuous naive bayes classifier model
Parameters
----------
X: ndarray of shape (m, n)
sample data where row represent sample and column represent feature
y: ndarray of shape (m,)
labels of sample data
Returns
-------
self
trained model
'''
self.X = X
self.y = y
return self
def _predict(self, x):
'''compute probabilities and make prediction.'''
from scipy.stats import norm
probas = {}
clss = list(set(self.y))
# compute probability for each attributes in x
for c in clss:
probas[c] = []
dat = self.X[self.y==c]
for i, attr in enumerate(x):
probas[c].append(norm(np.mean(dat[:,i]), np.std(dat[:,i])).pdf(attr))
probas[c].append(len(dat)/len(self.X))
final_probas = {}
from functools import reduce
for c, prbs in probas.items():
# theoretically not final probability because not divided by Pr(x)
final_probas[reduce(lambda x,y:x * y, prbs)] = c
return final_probas[max(final_probas)]
def predict(self, X):
return np.array([self._predict(x) for x in X])
測試代碼如下
import numpy as np
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
print('\nContinuous Naive Bayes')
print('---------------------------------------------------------------------')
from sklearn.datasets import load_iris
X, y = load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y)
mygnb = MyGaussianNBC()
mygnb.fit(X_train, y_train)
print('My Accuracy:', accuracy_score(mygnb.predict(X_test), y_test))
from sklearn.naive_bayes import GaussianNB
skgnb = GaussianNB()
skgnb.fit(X_train, y_train)
print('Sk Accuracy:', accuracy_score(skgnb.predict(X_test), y_test))
測試結(jié)果如下
$ python supervised_examples.py
Continuous Naive Bayes
---------------------------------------------------------------------
My Accuracy: 0.9210526315789473
Sk Accuracy: 0.9210526315789473
更多代碼請參考https://github.com/qige96/programming-practice/tree/master/machine-learning
本作品首發(fā)于簡書 和 博客園平臺(tái),采用知識(shí)共享署名 4.0 國際許可協(xié)議進(jìn)行許可。