1. 程式人生 > >《機器學習_09_01_決策樹_ID3與C4.5》

《機器學習_09_01_決策樹_ID3與C4.5》

### 簡介 先看一個例子,某銀行是否給使用者放貸的判斷規則集如下: ```python if 年齡==青年: if 有工作==是: if 信貸情況==非常好: 放 else: 不放 else: if 有自己的房子==是: if 信貸情況==一般: 不放 else: 放 else: if 信貸情況==非常好 or 信貸情況==好: 放 else: if 有工作==是: 放 else: 不放 elif 年齡==中年: if 有自己的房子==是: 放 else: if 信貸情況==非常好 or 信貸情況==好: 放 else: if 有工作==是: 放 else: 不放 elif 年齡==老年: if 有自己的房子==是: if 信貸情況==非常好 or 信貸情況==好: 放 else: 不放 else: if 信貸情況==非常好 or 信貸情況==好: if 有工作==是: 放 else: 不放 else: 不放 if 有自己的房子==是: 放 else: if 有工作==是: 放 else: 不放 ``` 眼力好的同學立馬會發現這程式碼寫的有問題,比如只要`信貸情況==非常好`的使用者都有放款,何必嵌到裡面去?而且很多規則有冗餘,為什麼不重構一下呀?但現實情況是你可能真不敢隨意亂動!因為指不定哪天專案經理又要新增加規則了,所以寧可讓程式碼越來越冗餘,越來越複雜,也不敢隨意亂動之前的規則,亂動兩條,可能會帶來意想不到的災難。簡單總結一下這種複雜巢狀的`if else`規則可能存在的痛點: (1)規則可能不完備,存在某些匹配不上的情況; (2)規則之間存在冗餘,多個`if else`情況其實是判斷的同樣的條件; (3)嚴重時,可能會出現矛盾的情況,即相同的條件,即有**放**,又有**不放**; (4)判斷規則的優先順序混亂,比如`信貸情況`因子可以優先考慮,因為只要它是`非常好`就可以放款,而不必先判斷其它條件 而決策樹演算法就能解決以上痛點,它能保證所有的規則**互斥且完備**,即使用者的任意一種情況一定能匹配上一條規則,且該規則唯一,這樣就能解決上面的痛點1~3,且規則判斷的優先順序也很不錯,下面介紹決策樹學習演算法。 ### 決策樹學習 決策樹演算法可以從已標記的資料中自動學習出`if else`規則集,如下圖([圖片來源>>>](https://www.cnblogs.com/jin-liang/p/9609144.html)),左邊是收集的一系列判斷是否打球的案例,包括4個特徵outlook,temperature,Humidity,Wind,以及y標籤是否打球,通過決策樹學習後得到右邊的決策樹,**決策樹的結構**如圖所示,它由節點和有向邊組成,而節點又分為兩種:葉子節點和非葉子節點,非葉子節點主要用於對某一特徵做判斷,而它下面所連結的有向邊表示該特徵所滿足的某條件,最終的葉子節點即表示例項的預測值(分類/迴歸) 決策樹學習主要分為兩個階段,**決策樹生成**和**決策樹剪枝**,決策樹生成階段最重要便是**特徵選擇**,下面對相關概念做介紹: #### 1.特徵選擇 特徵選擇用於選擇對分類有用的特徵,ID3和C4.5通常選擇的準則是資訊增益和資訊增益比,下面對其作介紹並實現 ##### 資訊增益 首先介紹兩個隨機變數之間的互資訊公式: $$ MI(Y,X)=H(Y)-H(Y|X) $$ 這裡$H(X)$表示$X$的熵,在最大熵模型那一節已做過介紹: $$ H(X)=-\sum_{i=1}^np_ilogp_i,這裡p_i=P(X=x_i) $$ 條件熵$H(Y|X)$表示在已知隨機變數$X$的條件下,隨機變數$Y$的不確定性: $$ H(Y|X)=\sum_{i=1}^np_iH(Y|X=x_i),這裡p_i=P(X=x_i) $$ 而資訊增益就是$Y$取分類標籤,$X$取某一特徵時的互資訊,它表示如果選擇特徵$X$對資料進行分割,可以使得分割後$Y$分佈的熵降低多少,若降低的越多,說明分割每個子集的$Y$的分佈越集中,則$X$對分類標籤$Y$越有用,下面進行python實現: ```python """ 定義計算熵的函式,封裝到ml_models.utils """ import numpy as np from collections import Counter import math def entropy(x,sample_weight=None): x=np.asarray(x) #x中元素個數 x_num=len(x) #如果sample_weight為None設均設定一樣 if sample_weight is None: sample_weight=np.asarray([1.0]*x_num) x_counter={} weight_counter={} # 統計各x取值出現的次數以及其對應的sample_weight列表 for index in range(0,x_num): x_value=x[index] if x_counter.get(x_value) is None: x_counter[x_value]=0 weight_counter[x_value]=[] x_counter[x_value]+=1 weight_counter[x_value].append(sample_weight[index]) #計算熵 ent=.0 for key,value in x_counter.items(): p_i=1.0*value*np.mean(weight_counter.get(key))/x_num ent+=-p_i*math.log(p_i) return ent ``` ```python #測試 entropy([1,2]) ``` 0.6931471805599453 ```python def cond_entropy(x, y,sample_weight=None): """ 計算條件熵:H(y|x) """ x=np.asarray(x) y=np.asarray(y) # x中元素個數 x_num = len(x) #如果sample_weight為None設均設定一樣 if sample_weight is None: sample_weight=np.asarray([1.0]*x_num) # 計算 ent = .0 for x_value in set(x): x_index=np.where(x==x_value) new_x=x[x_index] new_y=y[x_index] new_sample_weight=sample_weight[x_index] p_i=1.0*len(new_x)/x_num ent += p_i * entropy(new_y,new_sample_weight) return ent ``` ```python #測試 cond_entropy([1,2],[1,2]) ``` 0.0 ```python def muti_info(x, y,sample_weight=None): """ 互資訊/資訊增益:H(y)-H(y|x) """ x_num=len(x) if sample_weight is None: sample_weight=np.asarray([1.0]*x_num) return entropy(y,sample_weight) - cond_entropy(x, y,sample_weight) ``` 接下來,做一個測試,看特徵的取值的個數對資訊增益的影響 ```python import random import numpy as np import matplotlib.pyplot as plt %matplotlib inline #作epochs次測試 epochs=100 #x的取值的個數:2->class_num_x class_num_x=100 #y標籤類別數 class_num_y=2 #樣本數量 num_samples=500 info_gains=[] for _ in range(0,epochs): info_gain=[] for class_x in range(2,class_num_x): x=[] y=[] for _ in range(0,num_samples): x.append(random.randint(1,class_x)) y.append(random.randint(1,class_num_y)) info_gain.append(muti_info(x,y)) info_gains.append(info_gain) plt.plot(np.asarray(info_gains).mean(axis=0)) ```