周志華《機器學習》課後習題(第七章):貝葉斯分類

作者 | 我是韓小琦

鏈接 | https://zhuanlan.zhihu.com/p/51768750

7.1 試使用極大似然法估算回瓜數據集 3.0 中前 3 個屬性的類條件概率.

答:

以第一個屬性色澤爲例,其值計數如下:

  • 色澤 烏黑 淺白 青綠

  • 好瓜

  • 否 2 4 3

  • 是 4 1 3

令  表示好瓜中色澤爲“烏黑”的概率,  爲好瓜中“淺白”的概率,  ,  ,  表示好瓜的樣本,其他類同,於是色澤屬性的似然概率則可表示爲 ,其對數似然爲:  ,分別對  求偏導並使其爲零,即可得  的極大似然估計:  ,同理可得  的似然概率,進而求得類爲“否”時各取值條件概率的極大似然估計。

其他兩個屬性同理。

7.2* 試證明:條件獨立性假設不成立時,樸素貝葉斯分類器仍有可能產生最優貝葉斯分類器.

答:

令一組數據中有  三中屬性,其目標值爲Bool型變量,即取值  且其概率相同,即  。給定一個樣本  ,令  表示  ,  表示  中屬性  的取值。假設 完全獨立,而  即兩者完全相關,因此理論上對於最優貝葉斯分類器來說,屬性  應該被忽略,那麼基於最優貝葉斯分類器時,其決策準則(書中P7.15式)可描述爲:若  時,則將樣本  歸爲  類;而另一方面,考慮樸素貝葉斯分類器,在分類決策時會將屬性  也考慮在內,此時相當於將  計算了兩次,此時決策準則則描述爲:  時,將  歸爲  類。

根據貝葉斯理論,  ,且由於  ,則最優貝葉斯分類可表示爲:  ;而樸素貝葉斯則表示爲:  。取  ,於是最優貝葉斯分類器爲:  ,樸素貝葉斯爲  ,兩者決策邊界如下圖:

只有在兩者決策邊界之間(淺黃色區域),其分類情況是不同的,在其他區域,樸素貝葉斯分類結果和最優貝葉斯的分類結果是相同的,因此即便屬性之間獨立性假設不成立,樸素貝葉斯在某些條件(本例中就是屬性概率分佈在兩者相交區域之外)下任然是最優貝葉斯分類器。

參考:

《On the Optimality of the Simple Bayesian Classifier under Zero-One Loss》

ps.這個例子就是來自該論文,只做了一點翻譯工作。論文中給出了更全面的理論證明,和樸素貝葉斯產生最優貝葉斯分類的充分必要條件。本打算看完把理論證明也嘗試複述一遍,但能力有限,一方面沒有理解很透徹,另一方面證明過程有點長感覺表達能力有點不大夠用...

7.3 試編程實現拉普拉斯修正的樸素貝葉斯分類器,並以西瓜數據集 3.0 爲訓練集,對 p.151 "測1" 樣本進行判別.

答:

han1057578619/MachineLearning_Zhouzhihua_ProblemSets

import numpy as np
import pandas as pd
from sklearn.utils.multiclass import type_of_target
from collections import namedtuple




def train_nb(X, y):
    m, n = X.shape
    p1 = (len(y[y == '是']) + 1) / (m + 2)  # 拉普拉斯平滑


    p1_list = []  # 用於保存正例下各屬性的條件概率
    p0_list = []


    X1 = X[y == '是']
    X0 = X[y == '否']


    m1, _ = X1.shape
    m0, _ = X0.shape


    for i in range(n):
        xi = X.iloc[:, i]
        p_xi = namedtuple(X.columns[i], ['is_continuous', 'conditional_pro'])  # 用於儲存每個變量的情況


        is_continuous = type_of_target(xi) == 'continuous'
        xi1 = X1.iloc[:, i]
        xi0 = X0.iloc[:, i]
        if is_continuous:  # 連續值時,conditional_pro 儲存的就是 [mean, var] 即均值和方差
            xi1_mean = np.mean(xi1)
            xi1_var = np.var(xi1)
            xi0_mean = np.mean(xi0)
            xi0_var = np.var(xi0)


            p1_list.append(p_xi(is_continuous, [xi1_mean, xi1_var]))
            p0_list.append(p_xi(is_continuous, [xi0_mean, xi0_var]))
        else:  # 離散值時直接計算各類別的條件概率
            unique_value = xi.unique()  # 取值情況
            nvalue = len(unique_value)  # 取值個數


            xi1_value_count = pd.value_counts(xi1)[unique_value].fillna(0) + 1  # 計算正樣本中,該屬性每個取值的數量,並且加1,即拉普拉斯平滑
            xi0_value_count = pd.value_counts(xi0)[unique_value].fillna(0) + 1


            p1_list.append(p_xi(is_continuous, np.log(xi1_value_count / (m1 + nvalue))))
            p0_list.append(p_xi(is_continuous, np.log(xi0_value_count / (m0 + nvalue))))


    return p1, p1_list, p0_list




def predict_nb(x, p1, p1_list, p0_list):
    n = len(x)


    x_p1 = np.log(p1)
    x_p0 = np.log(1 - p1)
    for i in range(n):
        p1_xi = p1_list[i]
        p0_xi = p0_list[i]


        if p1_xi.is_continuous:
            mean1, var1 = p1_xi.conditional_pro
            mean0, var0 = p0_xi.conditional_pro
            x_p1 += np.log(1 / (np.sqrt(2 * np.pi) * var1) * np.exp(- (x[i] - mean1) ** 2 / (2 * var1 ** 2)))
            x_p0 += np.log(1 / (np.sqrt(2 * np.pi) * var0) * np.exp(- (x[i] - mean0) ** 2 / (2 * var0 ** 2)))
        else:
            x_p1 += p1_xi.conditional_pro[x[i]]
            x_p0 += p0_xi.conditional_pro[x[i]]


    if x_p1 > x_p0:
        return '是'
    else:
        return '否'




if __name__ == '__main__':
    data_path = r'C:\Users\hanmi\Documents\xiguabook\watermelon3_0_Ch.csv'
    data = pd.read_csv(data_path, index_col=0)


    X = data.iloc[:, :-1]
    y = data.iloc[:, -1]
    p1, p1_list, p0_list = train_nb(X, y)


    x_test = X.iloc[0, :]   # 書中測1 其實就是第一個數據


    print(predict_nb(x_test, p1, p1_list, p0_list))

這裏代碼很簡單。不怎麼規範。

7.4 實踐中使用式 (7.15)決定分類類別時,若數據的維數非常高,則概率連乘  的結果通常會非常接近於 0 從試述防止下溢的可能方案.而導致下溢.

答:

這在p153中已經給出答案了。即取對數將連乘轉化爲“連加”防止下溢。

即將式(7.15)改爲:  。

7.5 試證明:二分類任務中兩類數據滿足高斯分佈且方差相同時,線性判別分析產生貝葉斯最優分類器.

答:

首先看一下貝葉斯最優分類器:在書中p148中解釋了對於最小化分類錯誤率的貝葉斯最優分類器可表示爲:

 ,

由貝葉斯定理即轉換爲:

 。

那麼在數據滿足高斯分佈時有: 

在二分類任務中,貝葉斯決策邊界可表示爲 

再看看線性判別分析:

書中p62給出式3.39,其投影界面可等效於

 ,

注意爲了和上面的推導一致,這裏和書中給出的差了一個負號,但  位置沒有改變,只是改變了方向而已。在兩類別方差相同時有: 

 ,

兩類別在投影面連線的中點可爲 

  ,

那麼線性判別分析的決策邊界可表示爲

  。

推導到這裏發現貝葉斯最優分類器和線性判別分析的決策邊界只相差 

 ,

在題目左邊小字中有提及,“假設同先驗”,所以 

 ,

於是得證。

7.6 試編程實現 AODE 分類器,並以西瓜數據集 3.0 爲訓練集,對 p.151的"測1" 樣本進行判別.

答:

代碼在:

han1057578619/MachineLearning_Zhouzhihua_ProblemSets

'''
目前僅拿西瓜數據集測試過,運行正常,其他數據未測試
'''
import numpy as np
import pandas as pd
from sklearn.utils.multiclass import type_of_target


class AODE(object):


    def __init__(self, m):
        self.m_hat = m
        self.m = None
        self.n = None
        self.unique_y = None
        self.n_class = None
        self.is_continuous = None
        self.unique_values = None
        self.total_p = None


    def predict(self, X):
        X = np.array(X)
        if self.total_p == None:
            raise Exception('you have to fit first before predict.')


        result = pd.DataFrame(np.zeros((X.shape[0], self.unique_y.shape[0])), columns=self.unique_y)


        for i in self.total_p.keys():
            result += self._spode_predict(X, self.total_p[i], i)


        return self.unique_y[np.argmax(result.values, axis=1)]


    def fit(self, X, y):
        X = np.array(X)
        self.m, self.n = X.shape
        self.unique_y = np.unique(y)
        self.n_class = self.unique_y.size


        # 這裏轉爲list, 是因爲貌似type_of_target 有bug, 只有在pd.Series類型的時候才能解析爲'continuous',
        # 在這裏轉爲array類型後解析爲 'unknown'了
        is_continuous = pd.DataFrame(X).apply(lambda x: (type_of_target(x.tolist()) == 'continuous'))
        self.is_continuous = is_continuous


        unique_values = {}  # 離散型字段的各取值
        for i in is_continuous[~is_continuous].index:
            unique_values[i] = np.unique(X[:, i])


        self.unique_values = unique_values


        # 獲取可以作爲父節點的屬性索引,這裏在論文中取值爲30; 但在西瓜書中由於樣本很少, 所有直接取0就好
        parent_attribute_index = self._get_parent_attribute(X)


        total_p = {}
        for i in parent_attribute_index:
            p = self._spode_fit(X, y, i)
            total_p[i] = p


        self.total_p = total_p


        return self


    def _spode_fit(self, X, y, xi_index):
        p = pd.DataFrame(columns=self.unique_y, index=self.unique_values[xi_index])  # 儲存各取值下的條件概率
        nunique_xi = self.unique_values[xi_index].size  # 當前屬性的取值數量


        pc_xi_denominator = self.m + self.n_class * nunique_xi  # 計算 p(c, xi) 的分母 |D| + N * N_i


        for c in self.unique_y:
            for xi in self.unique_values[xi_index]:
                p_list = []  # 儲存y取值爲c, Xi取值爲xi下各個條件概率p(xj|c, xi)和先驗概率p(c, xi)


                c_xi = (X[:, xi_index] == xi) & (y == c)
                X_c_xi = X[c_xi, :]  # y 取值 爲c, Xi 取值爲xi 的所有數據


                pc_xi = (X_c_xi.shape[0] + 1) / pc_xi_denominator  # p(c, xi)


                # 實際上這裏在j = i時, 個人理解應該是可以跳過不計算的,因爲p(xi|c, xi) = 1, 在計算中都是一樣的但這裏爲了方便實現,就沒有跳過了。


                for j in range(self.n):
                    if self.is_continuous[j]:  # 字段爲連續值, 假設服從高斯分佈, 保存均值和方差
                        # 這裏因爲樣本太少。有時候會出現X_c_xi爲空或者只有一個數據的情況, 如何是離散值,依然可以計算;
                        # 但是連續值的情況下,np.mean會報warning, 只有一個數據時,方差爲0
                        # 所有這時, 均值和方差以類別樣本來替代。
                        if X_c_xi.shape[0] <= 1:
                            p_list.append([np.mean(X[y == c, j]), np.var(X[y == c, j])])
                        else:
                            p_list.append([np.mean(X_c_xi[:, j]), np.var(X_c_xi[:, j])])


                    else:
                        # 計算 p(xj|c, xi)
                        condi_proba_of_xj = (pd.value_counts(X_c_xi[:, j])[self.unique_values[j]].fillna(0) + 1) / (
                                X_c_xi.shape[0] + self.unique_values[j].size)
                        p_list.append(np.log(condi_proba_of_xj))
                p_list.append(np.log(pc_xi))  # p(c, xi)在最後的位置


                p.loc[xi, c] = p_list


        return p


    def _spode_predict(self, X, p, xi_index):


        assert X.shape[1] == self.n
        xi = X[:, xi_index]
        result = pd.DataFrame(np.zeros((X.shape[0], p.shape[1])), columns=self.unique_y)  # 儲存每個樣本爲不同類別的對數概率值
        for value in p.index:  # 爲了可以使用pandas的索引功能, 對於要預測的X值, 每一次循環求同一取值下樣本的條件概率和
            xi_value = xi == value
            X_split = X[xi_value, :]
            for c in p.columns:
                p_list = p.loc[value, c]  # 儲存p(xj|c, xi) 和 p(c, xi)的列表
                for j in range(self.n):  # 遍歷所有的條件概率, 將對應的條件概率相加
                    if self.is_continuous[j]:
                        mean_, var_ = p_list[j]
                        result.loc[xi_value, c] += (
                                -np.log(np.sqrt(2 * np.pi) * var_) - (X_split[:, j] - mean_) ** 2 / (2 * var_ ** 2))
                    else:
                        result.loc[xi_value, c] += p_list[j][X_split[:, j]].values


                result.loc[xi_value, c] += p_list[-1]  # 最後加上p(c, xi)


        return result


    def _get_parent_attribute(self, X):
        '''
        基於屬性下各取值的樣本數量,決定哪些屬性可以作爲父屬性。
        關於連續值的處理,在《機器學習》書中也沒有提及,AODE的原論文也沒有提及如何處理連續值,
        考慮到若將連續值x_j作爲父屬性時,如何求解p(x_i|c, x_j)條件概率會比較麻煩(可以通過貝葉斯公式求解),
        此外AODE本身就是要將取值樣本數量低於m的屬性去除的,從這個角度來說,連續值就不能作爲父屬性了。
        所以這裏連續值不作爲父屬性
        :param X:
        :return:
        '''


        enough_quantity = pd.DataFrame(X).apply(
            lambda x: (type_of_target(x.tolist()) != 'continuous') & (pd.value_counts(x) > self.m_hat).all())
        return enough_quantity[enough_quantity].index.tolist()




if __name__ == '__main__':
    data_path = r'C:\Users\hanmi\Documents\xiguabook\watermelon3_0_Ch.csv'
    data = pd.read_csv(data_path, index_col=0)


    X = data.iloc[:, :-1]
    y = data.iloc[:, -1]


    aode = AODE(0)
    print(aode.fit(X, y).predict(X.iloc[[0], :]))

提一下關於連續值處理的問題。這個書中和原論文(好像)都沒有提交,所以按照自己的理解來處理了。考慮到以下,實現過程中不將連續值作爲父屬性。

此外AODE本身就是要將取值樣本數量低於一定閾值(論文中給出的是30)的屬性去除的,從這個角度來說,連續值就不能作爲父屬性了,當前其實可以按照區間劃分將連續值離散化。

另外,雖然在樣本這麼小的情況下,看預測結果實際意義不大,但相比於樸素貝葉斯,AODE對於西瓜數據集的擬合更好(錯誤率更低)。

ps.書中給出的式(7.24)有錯誤的,分母的 改正爲  ,在第十次印刷的時候糾正了,看舊版書的同學要注意了。

7.7 給定 d 個二值屬性的二分類任務,假設對於任何先驗概率項的估算至少需 30 個樣例,則在樸素貝葉斯分類器式 (7.15) 中估算先驗概率項  需 30 x 2 = 60 個樣例.試估計在 AODE 式 (7.23) 中估算先驗概率項  所需的樣例數(分別考慮最好和最壞情形) .

答:

這裏“假設對於任何先驗概率項的估算至少需 30 個樣例”意味着在所有樣本中, 任意 的組合至少出現30次。

當  時,即只有一個特徵  ,因爲是二值屬性,假設取值爲  ,那爲了估計  至少需要30個樣本,同樣  需要額外30個樣本,另外兩種情況同理,所以在  時,最好和最壞情況都需要120個樣本。

再考慮  ,多加個特徵  同樣取值  ,爲了滿足求  已經有了120個樣本,且60個正樣本和60個負樣本;在最好的情況下,在60個正樣本中,正好有30個樣本  ,30個  ,負樣本同理,此時這120個樣本也同樣滿足計算  的條件,所有  時,最好的情況也只需要120個樣本,  時同理;在最壞的情況下,120個樣子中,  都取相同的值  ,那麼爲了估算  需要額外60個樣本,總計180個樣本,同理計算出  時的樣本數,即每多一個特徵,最壞情況需要多加額外60個樣本,  時,需要  個樣本。

那麼  個二值屬性下,最好情況需要120個樣本,最好情況需要  個樣本。

這個問題主要基於書中式7.26,就很容易理解了

首先考慮同父結構,根據式7.26,其聯合分佈可以表示爲:

 ,

在給定  時,

即同父結構中  關於  條件獨立;

在  取值未知時有:

 

是推不出  的,所以同父結構中  關於  邊際獨立不成立。

再考慮順序結構,其聯合分佈有:

給定  時,

即順序結構中  關於  條件獨立;

在  取值未知時有:

 ,

同樣推不出  ,所以順序結構中,同樣  關於  邊際獨立不成立。


好久沒更新...罪過,墮落了...前面八題一個月之前就寫好了,一直在看7.7(主要還是懶.)閱讀材料給出的貝葉斯網相關論文(主要是《A Tutorial on Learning With Bayesian Networks》),下面兩題應該還是需要寫代碼實現的,回頭有時間再補把。

7.9 以西瓜數據集 2.0 爲訓練集,試基於 BIC 準則構建一個貝葉斯網.

答:

關於貝葉斯網結構的構建,書中p160只稍微提了一下,不過還是挺好理解的,《A Tutorial on Learning With Bayesian Networks》11節給出了更詳細的描述。比較簡單是方法就是貪心法:

  • 1) 初始化一個網絡結構;

  • 2) 使用E表示當前合法的改變一條邊的操作集合,比如若兩個節點已經有連接,那麼合法操作可以刪除或者逆轉,如沒有連接則可以增加一條邊,當前必須是在保證不會形成迴路的情況;

  • 3) 從中選擇一個使得BIC下降最大的一個作爲實際操作;

  • 4) 循環2,3直到BIC不再下降。

論文中也給出了其他算法。

有時間再補代碼吧。

系列文章:

1. 周志華機器學習課後習題解析【第二章】

2. 周志華《機器學習》課後習題(第三章):線性模型

3. 周志華《機器學習》課後習題解析(第四章):決策樹

4. 周志華《機器學習》課後習題(第五章):神經網絡

5. 周志華《機器學習》課後習題(第六章):支持向量機


推薦閱讀

(點擊標題可跳轉閱讀)

乾貨 | 公衆號歷史文章精選

我的深度學習入門路線

我的機器學習入門路線圖

重磅

AI有道年度技術文章電子版PDF來啦!

掃描下方二維碼,添加 AI有道小助手微信,可申請入羣,並獲得2020完整技術文章合集PDF(一定要備註:入羣 + 地點 + 學校/公司。例如:入羣+上海+復旦。 

長按掃碼,申請入羣

(添加人數較多,請耐心等待)

 

最新 AI 乾貨,我在看 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章