Python · 樸素貝葉斯(四)· MergedNB

(這裡是本章會用到的 GitHub 地址)

(剛發現推廣已經在這篇文章中說過了……那麼本章其實就已經是樸素貝葉斯系列的最後一章了 ( σω)σ)

(剛發現演算法敘述居然也說過了…… ( σω)σ)

本章主要介紹混合型樸素貝葉斯—— MergedNB 的實現。演算法的敘述已經在這篇文章中進行過說明,下面就直接看看如何進行實現。首先是初始化:

from b_NaiveBayes.Original.Basic import *nfrom b_NaiveBayes.Original.MultinomialNB import MultinomialNBnfrom b_NaiveBayes.Original.GaussianNB import GaussianNBnnclass MergedNB(NaiveBayes):n """n 初始化結構n self._whether_discrete:記錄各個維度的變數是否是離散型變數n self._whether_continuous:記錄各個維度的變數是否是連續型變數n self._multinomial、self._gaussian:離散型、連續型樸素貝葉斯模型n """n def __init__(self, whether_continuous):n self._multinomial, self._gaussian = (n MultinomialNB(), GaussianNB()n if whether_continuous is None:n self._whether_discrete = self._whether_continuous = Nonen else:n self._whether_continuous = np.array(whether_continuous)n self._whether_discrete = ~self._whether_continuousn

nnnnnnnnnnnnnnn接下來放出和模型的訓練相關的實現,這一塊將會大量重用之前在 MultinomialNB 和 GaussianNB 裡面寫過的東西:

def feed_data(self, x, y, sample_weight=None):n if sample_weight is not None:n sample_weight = np.array(sample_weight)n x, y, wc, features, feat_dics, label_dic = DataUtil.quantize_data(n x, y, wc=self._whether_continuous, separate=True)n # 若沒有指定哪些維度連續,則用 quantize_data 中樸素的方法判定哪些維度連續n if self._whether_continuous is None:n self._whether_continuous = wcn # 通過Numpy中對邏輯非的支持進行快速運算n self._whether_discrete = ~self._whether_continuousn # 計算通用變數n self.label_dic = label_dicn discrete_x, continuous_x = xn cat_counter = np.bincount(y)n self._cat_counter = cat_countern labels = [y == value for value in range(len(cat_counter))]n # 訓練離散型樸素貝葉斯n labelled_x = [discrete_x[ci].T for ci in labels]n self._multinomial._x, self._multinomial._y = x, yn self._multinomial._labelled_x, self._multinomial._label_zip = (n labelled_x, list(zip(labels, labelled_x)))n self._multinomial._cat_counter = cat_countern self._multinomial._feat_dics = [_dicn for i, _dic in enumerate(feat_dics) if self._whether_discrete[i]]n self._multinomial._n_possibilities = [len(feats)n for i, feats in enumerate(features) if self._whether_discrete[i]]n self._multinomial.label_dic = label_dicn # 訓練連續型樸素貝葉斯n labelled_x = [continuous_x[label].T for label in labels]n self._gaussian._x, self._gaussian._y = continuous_x.T, yn self._gaussian._labelled_x, self._gaussian._label_zip = labelled_x, labelsn self._gaussian._cat_counter, self._gaussian.label_dic = cat_counter, label_dicn # 處理樣本權重n self._feed_sample_weight(sample_weight)nn # 分別利用 MultinomialNB 和 GaussianNB 處理樣本權重的方法來處理樣本權重n def feed_sample_weight(self, sample_weight=None):n self._multinomial.feed_sample_weight(sample_weight)n self._gaussian.feed_sample_weight(sample_weight)nn # 分別利用 MultinomialNB 和 GaussianNB 的訓練函數來進行訓練n def _fit(self, lb):n self._multinomial.fit()n self._gaussian.fit()n p_category = self._multinomial.get_prior_probability(lb)n discrete_func, continuous_func = (n self._multinomial["func"], self._gaussian["func"])n # 將 MultinomialNB 和 GaussianNB 的決策函數直接合成最終決策函數n # 由於這兩個決策函數都乘了先驗概率、我們需要除掉一個先驗概率n def func(input_x, tar_category):n input_x = np.array(input_x)n return discrete_func(n input_x[self._whether_discrete].astype(n np.int), tar_category) * continuous_func(n input_x[self._whether_continuous], tar_category) / p_category[tar_category]n return funcn

(又臭又長啊喂……)

上述實現有一個顯而易見的可以優化的地方:我們一共在代碼中重複計算了三次先驗概率、但其實只用計算一次就可以。考慮到這一點不是性能瓶頸,為了代碼的連貫性和可讀性、我們就沒有進行這個優化(???)

nnnnnnnnnnnnnnnnnnn數據轉換函數則相對而言要複雜一點,因為我們需要跳過連續維度、將離散維度挑出來進行數值化:

# 實現轉換混合型數據的方法,要注意利用MultinomialNB的相應變數n def _transfer_x(self, x):n _feat_dics = self._multinomial["feat_dics"]n idx = 0n for d, discrete in enumerate(self._whether_discrete):n # 如果是連續維度,直接調用float方法將其轉為浮點數n if not discrete:n x[d] = float(x[d])n # 如果是離散維度,利用轉換字典進行數值化n else:n x[d] = _feat_dics[idx][x[d]]n if discrete:n idx += 1n return xn

至此,混合型樸素貝葉斯模型就搭建完畢了。為了比較合理地對它進行評估,我們不妨採用 UCI 上一個我認為有些病態的數據集進行測試。問題的描述大概可以概括如下:

nnnnnnnnnnnnnnnnnnn「訓練數據包含了某銀行一項業務的目標客戶的信息、電話銷售記錄以及後來他是否購買了這項業務的信息。我們希望做到:根據客戶的基本信息和歷史聯繫記錄,預測他是否會購買這項業務」。UCI 上的原問題描述則如下圖所示:

概括其主要內容、就是它是一個有 17 個屬性的二類分類問題。之所以我認為它是病態的,是因為我發現即使是 17 個屬性幾乎完全一樣的兩個人,他們選擇是否購買業務的結果也會截然相反。事實上從心理學的角度來說,想要很好地預測人的行為確實是一項非常困難的事情、尤其是當該行為直接牽扯到較大的利益時

nnnnnnnnnnnnnnn完整的數據集可以參見這裡(最後一列數據是類別)。按照數據的特性、我們可以通過和之前用來評估MultinomialNB的代碼差不多的代碼(注意額外定義一個記錄離散型維度的數組即可)得出如下圖所示的結果:

雖然準確率達到了 89%左右,但其實該問題不應該用準確率作為評判的標準。因為如果我們觀察數據就會發現、數據存在著嚴重的非均衡現象。事實上,88%的客戶最終都是沒有購買這個業務的、但我們更關心的是那一小部分購買了業務的客戶,這種情況我們通常會用 F1-score 來衡量模型的好壞。此外,該問題非常需要人為進行數據清洗、因為其原始數據非常雜亂。此外,我們可以對該問題中的各個離散維度進行可視化。該數據共 9 個離散維度,我們可以將它們合併在同一個圖中以方便獲得該數據離散部分的直觀(如下圖所示;由於各個特徵的各個取值通常比較長(比如"manager"之類的),為整潔、我們直接將橫坐標置為等差數列而沒有進行轉換):

其中天藍色代表類別 yes、亦即購買了業務;橙色則代表 no、亦即沒有購買業務。可以看到、所有離散維度的特徵都是前面所說的「無足輕重」的特徵

nn連續維度的可視化是幾乎同理的,唯一的差別在於它不是柱狀圖而是正態分布密度函數的函數曲線。具體的代碼實現從略、感興趣的觀眾老爺們可以嘗試動手實現一下,這裡僅放出程序運行的結果。該數據共 7 個連續維度,我們同樣把它們放在同一個圖中:

其中,天藍色曲線代表類別 yes、橙色曲線代表類別 no。可以看到,兩種類別的數據在各個維度上的正態分布的均值、方差都幾乎一致

nn從以上的分析已經可以比較直觀地感受到、該問題確實相當病態。特別地,考慮到樸素貝葉斯的演算法、不難想像此時的混合型樸素貝葉斯模型基本就只是根據各類別的先驗概率來進行分類決策

至此,樸素貝葉斯演算法的理論、實現就差不多都說了一遍,希望觀眾老爺們能夠喜歡~


推薦閱讀:

TAG:Python | 机器学习 | 贝叶斯分类 |