《機器學習_09_01_決策樹_ID3與C4.5》
阿新 • • 發佈:2020-05-27
### 簡介
先看一個例子,某銀行是否給使用者放貸的判斷規則集如下:
```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))
```