標籤:

機器學習-高斯判別分析與樸素貝葉斯

生成式學習演算法與判別式學習演算法

如上一篇文章中的線性回歸與邏輯回歸都叫做判別式學習演算法。

  • 由判別式學習演算法的流程,可以知道我們主要是學習 P(y|x) 的分布,利用極大似然估計求取參數,之後利用得到的 h(x) 來預測結果。
  • 生成式學習演算法,我們主要學習的 P(x|y) 的分布,其中 x 為特徵,y 為類別,那麼可以看作每一個類別特徵的分布,然後同樣利用極大似然估計得到每種類別的分布,預測的時候利用下面的公式得到結果:

高斯判別分析

由上一小節中生成式學習演算法流程可知,如果要預測結果,我們必須要得到 P(x|y)P(y) 的分布,所以我們首先假設:

即:

上述公式中的參數有 phi,mu_{0},mu_{1},Sigma ,4個參數, mu 為是正態分布的均值向量, Sigma 為協方差矩陣。上述的兩個高斯分布為多元高斯分布,因為 x 作為特徵存在很多維。之後我們利用極大似然估計各個參數。首先構造 l(phi,mu_{0},mu_{1},Sigma) 函數:

極大似然估計結果:

得到分布後,我們就可以利用生成式學習演算法預測的方法,本例子中,我們就是分為 y=0y=1 兩種情況分別計算 P(x|y)P(y) ,哪個值更大,則屬於哪一類。

高斯判別分析與邏輯回歸

若我們令:

假設P(x|y) 服從高斯分布為假設①,

假設 P(y|x) 服從伯努利分布(即可用邏輯回歸)為假設②

我們可以從假設①推出假設②,而不能從假設②推出假設①,所以我們可以認為假設①要強於假設②。

此處我們把假設更強理解為,該假設的分布更符合實際數據的情況或分布。

所以可以得到以下結論:

  1. 若高斯分布的假設更符合實際數據,高斯判別效果會好於邏輯回歸。
  2. 如若實際數據的分布不確定,則用邏輯回歸更好。(只要 P(x|y) 服從指數分布簇,那麼都可以利用邏輯回歸)
  3. 高斯判別分析對於小數據效果更好,因為假設更強且準確。
  4. 邏輯回歸由於假設更弱,則健壯性更強,適應更多情況。

高斯判別分析代碼

# -*- coding: utf-8 -*-import numpy as npimport randomimport mathdef load_data(gauss_1, gauss_2, num=100): """ 隨機生成兩個高斯分布的數據 :param gauss_1: 第一個高斯分布參數 :param gauss_2: 第二個高斯分布參數 :param num: 生成數據個數 :return: 生成的數據集, 標籤 """ x = [] y = [] while len(x) < num: if random.random() > 0.5: x.append(random.gauss(gauss_1[0], gauss_1[1])) y.append(0) else: x.append(random.gauss(gauss_2[0], gauss_2[1])) y.append(1) return np.array(x), ydef train(train_x, train_y): """ 訓練函數,計算兩個高斯分布參數及類別概率 :param train_x: 訓練集 :param train_y: 標籤 :return: 所有參數 """ m = train_x.shape[0] phi = train_y.count(0) / float(m) # 是高斯分布 0 的概率 total_mean_0 = 0.0 total_mean_1 = 0.0 for i in range(len(train_y)): if not train_y[i]: total_mean_0 += train_x[i] else: total_mean_1 += train_x[i] miu_0 = total_mean_0 / float(train_y.count(0)) # 高斯分布 0 的均值 miu_1 = total_mean_1 / float(train_y.count(1)) # 高斯分布 1 的均值 total_variance_0 = 0.0 total_variance_1 = 0.0 for j in range(len(train_y)): if not train_y[j]: total_variance_0 = (train_x[j] - miu_0) ** 2 else: total_variance_1 = (train_x[j] - miu_1) ** 2 sigma_0 = total_variance_0 / float(train_y.count(0)) # 高斯分布 0 的方差 sigma_1 = total_variance_1 / float(train_y.count(1)) # 高斯分布 1 的方差 return phi, [miu_0, sigma_0], [miu_1, sigma_1]def predict(x, gauss): """ 預測函數 :param x: 預測值 :param gauss: 高斯分布參數及類別概率參數 :return: 概率值 """ p_y_0 = gauss[0] * math.exp(-1 * (x - gauss[1][0]) ** 2 / (2 * gauss[1][1])) p_y_1 = (1 - gauss[0]) * math.exp(-1 * (x - gauss[2][0]) ** 2 / (2 * gauss[2][1])) return p_y_0, p_y_1if __name__ == __main__: train_x, train_y = load_data([10, 3], [20, 3], 100) gauss_para = train(train_x, train_y) p0, p1 = predict(12, gauss_para) print(是高斯分布 0 的概率 : + str(p0 / (p1 + p0))) print(是高斯分布 1 的概率 : + str(p1 / (p1 + p0)))

樸素貝葉斯

在高斯判別方法中,我們假設 x 是連續的,如果假設 x 是離散的,那麼 P(x|y) 就應該是二項分布,此時學習演算法就叫貝葉斯演算法。由於特徵 X 的維數可能非常大,那樣將導致參數過多,所以我們需要假設 X 之中的各個特徵是相互獨立的(實際不一定),這樣可以簡化參數,所以叫做樸素貝葉斯。

例:我們做一個垃圾郵件分類,字典中一共有50000個單詞,針對一封郵件,若存在字典中單詞,我們設置為1,否則設置為0。如下圖:

由於是樸素貝葉斯,X中的各個特徵時獨立的,所以有:

針對這個模型中,我們需要的得到的參數有以下三個。其中 phi_{i|y=1} 的含義是在已知是垃圾郵件的前提下,存在第 i 個單詞的概率。 phi_{y} 的含義是一封郵件是垃圾郵件的概率:

phi_{i|y=1}=P(x_{i}=1|y=1)

phi_{i|y=0}=P(x_{i}=1|y=0)

phi_{y}=P(y=1)

構造似然函數如下:

得到參數的極大似然估計如下:

在預測時,我們需要分別計算 P(y=1|x)P(y=0|x) 的概率,並選擇其中概率更大的一個,例:

Laplace Smoothing

針對上述的樸素貝葉斯做法,如果存在一個單詞(字典中第35000個)在訓練集(垃圾郵件和正常郵件)中從來沒有出現過,此時採用極大似然估計將得到:

那麼在預測結果時:

我們無法確定結果的。

可以看到 P(x_{35000}|y=1)=0 ,這就代表當已知郵件是垃圾郵件的條件下,存在第35000個單詞的可能性為0,但這是不科學的,這個單詞只是沒有出現過,並不能表示這個事件是不可能發生的。

所以引入Laplace Smoothing:

假設一般的求 P(y=1) 的公式如下

P(y=1)=frac{num(y=1)}{num(y=0)+num(y=1)}

但是加了Laplace Smoothing,修改如下:

P(y=1)=frac{num(y=1)+1}{num(y=0)+1+num(y=1)+1}

這樣就算一個事件從未發生,我們也不會得到他不可能發生的估計。

所以針對下圖中的公式:

需要修改如下,其中 k 為 y 類別的個數。(針對伯努利分布,y只有兩類,k = 2):

所以加了Laplace Smoothing的參數的極大似然估計修改如下:

其他

針對X的取值是連續的情況,如果採用樸素貝葉斯,我們可以劃分取值範圍,使X的取值為離散的。

在郵件分類的例子中,假設輸入的向量 X 不是0或1,而是字典中單詞的索引,例: X=[123,47777,2342,...,8731] ,我們可以得到似然函數:

和極大似然估計:

加了Laplace Smoothing的估計:

參考

  • Andrew Ng的機器學習公開課

樸素貝葉斯代碼

預測言論是侮辱性的還是正常的,數據來自《機器學習實戰》,並且代碼加入了Laplace Smoothing。主要流程如下:

  1. 獲取訓練集與標籤。
  2. 根據訓練集生成單詞庫。
  3. 訓練得到參數。
  4. 預測。

# -*- coding: utf-8 -*-"""1.第一個版本2.實現功能:語句分類,標籤為1代碼侮辱性文字,標籤為0代碼正常言論3.添加了Laplace Smoothing"""import numpy as npdef load_data(): """ 功能:獲取訓練集(數據來自《機器學習實戰》) 返回:訓練集 """ 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]] labels = [0, 1, 0, 1, 0, 1] # 1為侮辱性文字,0為正常言論 return postingList, labelsdef create_vocabulary_list(dataSet): """ 功能:生成單詞庫 參數:訓練集 返回:單詞庫列表 """ vocabList = [] for statement in dataSet: for word in statement: if word not in vocabList: vocabList.append(word) return vocabListdef words2vector(statement, vocabList): """ 功能:語句轉化為向量 參數:語句,單詞庫 返回:向量 """ vector = [] for word in vocabList: if word in statement: vector.append(1) else: vector.append(0) return vectordef train(dataSet, labels, vocabList): """ 功能:訓練 參數:訓練集,標籤,單詞庫 返回:參數值 """ m = len(dataSet) num_y_1 = labels.count(1) # 侮辱性言論的個數 num_y_0 = labels.count(0) # 正常言論的個數 phi_y_1 = num_y_1 / float(m) # P(y=1)的概率 phi_y_0 = num_y_0 / float(m) # P(y=0)的概率 phi_j_y_1 = [] # p(xj=1|y=1)的概率 phi_j_y_0 = [] # p(xj=1|y=0)的概率 for word in vocabList: num_j_y_1 = 0 num_j_y_0 = 0 for i in range(len(labels)): if labels[i] == 1 and word in dataSet[i]: num_j_y_1 += 1 if labels[i] == 0 and word in dataSet[i]: num_j_y_0 += 1 phi_j_y_1.append((num_j_y_1+1) / (float(num_y_1)+2)) # Laplace Smoothing phi_j_y_0.append((num_j_y_0+1) / (float(num_y_0)+2)) return phi_j_y_1, phi_j_y_0, phi_y_1, phi_y_0def predict(words, vocabList, phi): """ 功能:預測 參數:預測言論序列,單詞庫,參數值 返回:參數值 """ vect = words2vector(words, vocabList) p_y_1 = phi[2] p_y_0 = phi[3] for i in range(len(vocabList)): if vect[i] == 1: p_y_1 *= phi[0][i] p_y_0 *= phi[1][i] else: p_y_1 *= (1 - phi[0][i]) p_y_0 *= (1 - phi[1][i]) return p_y_1, p_y_0if __name__ == __main__: DataSet, Labels = load_data() VocabList = create_vocabulary_list(DataSet) phi = train(DataSet, Labels, VocabList) p1, p0 = predict([mr, licks, ate, my, dog, food, stupid], VocabList, phi) print "是侮辱性言論的概率:" + str(p1 / (p1 + p0)) print "是正常言論的概率:" + str(p0 / (p1 + p0))

對於輸入變數是連續值的,應該使用高斯判別分析的。但是由於通常情況下,採用邏輯回歸效果更好,並且我們也可以對連續值分段後變為離散值,如下圖所示:

之後再使用樸素貝葉斯,效果也會好於高斯判別分析。

推薦閱讀:

樸素貝葉斯演算法和R語言
Cousera deeplearning.ai筆記 — 淺層神經網路(Shallow neural network)
[貝葉斯八]之極大似然估計
Tensorflow入門教程(10)
scikit-learn實戰

TAG:機器學習 |