從零開始實現樸素貝葉斯分類演算法(連續特徵情形)
聲明:版權所有,轉載請聯繫作者並註明出處: 風雪夜歸子 - CSDN博客
知乎專欄: https://www.zhihu.com/people/feng-xue-ye-gui-zi
由於知乎不支持markdown格式,所以有公式的地方都使用了截圖,若影響閱讀效果,可移步我的Blog:風雪夜歸子 - CSDN博客,文章會同步更新!
樸素貝葉斯演算法是基於貝葉斯定理和特徵之間條件獨立假設的分類方法。對於給定的訓練數據集,首先基於特徵條件獨立假設學習輸入/輸出的聯合概率分布;然後基於此模型,對給定的輸入x,利用貝葉斯定理求出後驗概率最大的輸出y。樸素貝葉斯演算法實現簡單,學習和預測的效率都很高,是一種常用的方法。
本文考慮當特徵是連續情形時,樸素貝葉斯分類方法的原理以及如何從零開始實現貝葉斯分類演算法。此時,我們通常有兩種處理方式,第一種就是將連續特徵離散化(區間化),這樣就轉換成了離散情形,完全按照特徵離散情形即可完成分類,具體原理可以參見上一篇文章。第二種處理方式就是本文的重點,詳情請看下文:
1. 樸素貝葉斯演算法原理
與特徵是離散情形時原理類似,只是在計算後驗概率時有點不一樣,具體計算方法如下:
這時,可以假設每個類別中的樣本集的每個特徵均服從正態分布,通過其樣本集計算出均值和方差,也就是得到正態分布的密度函數。有了密度函數,就可以把值代入,算出某一點的密度函數的值。為了闡述的更加清楚,下面我摘取了一個實例,以供大家更好的理解。
2. 樸素貝葉斯的應用
下面是一組人類身體特徵的統計資料。
性別 身高(英尺) 體重(磅) 腳掌(英寸)
男 6 180 12
男 5.92 190 11
男 5.58 170 12
男 5.92 165 10
女 5 100 6
女 5.5 150 8
女 5.42 130 7
女 5.75 150 9
已知某人身高6英尺、體重130磅,腳掌8英寸,請問該人是男是女?
根據樸素貝葉斯分類器,計算下面這個式子的值。
P(身高|性別) x P(體重|性別) x P(腳掌|性別) x P(性別)
這裡的困難在於,由於身高、體重、腳掌都是連續變數,不能採用離散變數的方法計算概率。而且由於樣本太少,所以也無法分成區間計算。怎麼辦?
這時,可以假設男性和女性的身高、體重、腳掌都是正態分布,通過樣本計算出均值和方差,也就是得到正態分布的密度函數。有了密度函數,就可以把值代入,算出某一點的密度函數的值。
比如,男性的身高是均值5.855、方差0.035的正態分布。所以,男性的身高為6英尺的概率的相對值等於1.5789(大於1並沒有關係,因為這裡是密度函數的值,只用來反映各個值的相對可能性)。
從上面的計算結果可以看出,分母都一樣,因此,我們只需要比價分子的大小即可。顯然,P(不轉化|Mx上海)的分子大於P(轉化|Mx上海)的分子,因此,這個上海男性用戶的預測結果是不轉化。這就是貝葉斯分類器的基本方法:在統計資料的基礎上,依據某些特徵,計算各個類別的概率,從而實現分類。
有了這些數據以後,就可以計算性別的分類了。
P(身高=6|男) x P(體重=130|男) x P(腳掌=8|男) x P(男)
= 6.1984 x e-9
P(身高=6|女) x P(體重=130|女) x P(腳掌=8|女) x P(女)
= 5.3778 x e-4
可以看到,女性的概率比男性要高出將近10000倍,所以判斷該人為女性。
from __future__ import division, print_functionfrom sklearn import datasetsimport matplotlib.pyplot as pltimport mathimport numpy as npimport pandas as pd%matplotlib inlinedef shuffle_data(X, y, seed=None): if seed: np.random.seed(seed) idx = np.arange(X.shape[0]) np.random.shuffle(idx) return X[idx], y[idx]# 正規化數據集 Xdef normalize(X, axis=-1, p=2): lp_norm = np.atleast_1d(np.linalg.norm(X, p, axis)) lp_norm[lp_norm == 0] = 1 return X / np.expand_dims(lp_norm, axis)# 標準化數據集 Xdef standardize(X): X_std = np.zeros(X.shape) mean = X.mean(axis=0) std = X.std(axis=0) # 做除法運算時請永遠記住分母不能等於0的情形 # X_std = (X - X.mean(axis=0)) / X.std(axis=0) for col in range(np.shape(X)[1]): if std[col]: X_std[:, col] = (X_std[:, col] - mean[col]) / std[col] return X_std# 劃分數據集為訓練集和測試集def train_test_split(X, y, test_size=0.2, shuffle=True, seed=None): if shuffle: X, y = shuffle_data(X, y, seed) n_train_samples = int(X.shape[0] * (1-test_size)) x_train, x_test = X[:n_train_samples], X[n_train_samples:] y_train, y_test = y[:n_train_samples], y[n_train_samples:] return x_train, x_test, y_train, y_testdef accuracy(y, y_pred): y = y.reshape(y.shape[0], -1) y_pred = y_pred.reshape(y_pred.shape[0], -1) return np.sum(y == y_pred)/len(y)class NaiveBayes(): """樸素貝葉斯分類模型. """ def __init__(self): self.classes = None self.X = None self.y = None # 存儲高斯分布的參數(均值, 方差), 因為預測的時候需要, 模型訓練的過程中其實就是計算出 # 所有高斯分布(因為樸素貝葉斯模型假設每個類別的樣本集每個特徵都服從高斯分布, 固有多個 # 高斯分布)的參數 self.parameters = [] def fit(self, X, y): self.X = X self.y = y self.classes = np.unique(y) # 計算每一個類別每個特徵的均值和方差 for i in range(len(self.classes)): c = self.classes[i] # 選出該類別的數據集 x_where_c = X[np.where(y == c)] # 計算該類別數據集的均值和方差 self.parameters.append([]) for j in range(len(x_where_c[0, :])): col = x_where_c[:, j] parameters = {} parameters["mean"] = col.mean() parameters["var"] = col.var() self.parameters[i].append(parameters) # 計算高斯分布密度函數的值 def calculate_gaussian_probability(self, mean, var, x): coeff = (1.0 / (math.sqrt((2.0 * math.pi) * var))) exponent = math.exp(-(math.pow(x - mean, 2) / (2 * var))) return coeff * exponent # 計算先驗概率 def calculate_priori_probability(self, c): x_where_c = self.X[np.where(self.y == c)] n_samples_for_c = x_where_c.shape[0] n_samples = self.X.shape[0] return n_samples_for_c / n_samples # Classify using Bayes Rule, P(Y|X) = P(X|Y)*P(Y)/P(X) # P(X|Y) - Probability. Gaussian distribution (given by calculate_probability) # P(Y) - Prior (given by calculate_prior) # P(X) - Scales the posterior to the range 0 - 1 (ignored) # Classify the sample as the class that results in the largest P(Y|X) # (posterior) def classify(self, sample): posteriors = [] # 遍歷所有類別 for i in range(len(self.classes)): c = self.classes[i] prior = self.calculate_priori_probability(c) posterior = np.log(prior) # probability = P(Y)*P(x1|Y)*P(x2|Y)*...*P(xN|Y) # 遍歷所有特徵 for j, params in enumerate(self.parameters[i]): # 取出第i個類別第j個特徵的均值和方差 mean = params["mean"] var = params["var"] # 取出預測樣本的第j個特徵 sample_feature = sample[j] # 按照高斯分布的密度函數計算密度值 prob = self.calculate_gaussian_probability(mean, var, sample_feature) # 樸素貝葉斯模型假設特徵之間條件獨立,即P(x1,x2,x3|Y) = P(x1|Y)*P(x2|Y)*P(x3|Y), # 並且用取對數的方法將累乘轉成累加的形式 posterior += np.log(prob) posteriors.append(posterior) # 對概率進行排序 index_of_max = np.argmax(posteriors) max_value = posteriors[index_of_max] return self.classes[index_of_max] # 對數據集進行類別預測 def predict(self, X): y_pred = [] for sample in X: y = self.classify(sample) y_pred.append(y) return np.array(y_pred)def main(): data = datasets.load_iris() X = normalize(data.data) y = data.target X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.5) clf = NaiveBayes() clf.fit(X_train, y_train) y_pred = np.array(clf.predict(X_test)) accu = accuracy(y_test, y_pred) print ("Accuracy:", accu) if __name__ == "__main__": main()
參考文獻: http://www.ruanyifeng.com/blog/2013/12/naive_bayes_classifier.html
李航《統計學習方法》
推薦閱讀:
※使用LibSVM進行分類的注意事項
※重磅乾貨-Richard S. Sutton-2018年強化學習教程免費下載
※機器學習Part2:如何用邏輯回歸分類
※MLer必知的8個神經網路架構
※【機器學習·基本功】-1決策樹 | 演算法原理介紹