決策樹DecisionTreeClassifier學習
1. 什麼是決策樹
決策樹是什麼,我們來“決策樹”這個詞進行分詞,那麼就會是決策/樹。大家不妨思考一下,重點是決策還是樹呢?其實啊,決策樹的關鍵點在樹上。
我們平時寫代碼的那一串一串的If Else其實就是決策樹的思想了。看下面的圖是不是覺得很熟悉呢?
2. 決策樹介紹
決策樹之所以叫決策樹,就是因爲它的結構是樹形狀的,如果你之前沒了解過樹這種數據結構,那麼你至少要知道以下幾個名詞是什麼意思。
- 根節點:最頂部的那個節點
- 葉子節點:每條路徑最末尾的那個節點,也就是最外層的節點
- 非葉子節點:一些條件的節點,下面會有更多分支,也叫做分支節點
- 分支:也就是分叉
3. ID3 算法
- ID3算法是在每個結點處選取能獲得最高信息增益的分支屬性進行分裂
- 在每個決策結點處劃分分支、選取分支屬性的目的是將整個決策樹的樣本
純度提升 - 衡量樣本集合純度的指標則是
信息熵
舉例來說,如果有一個大小爲10的布爾值樣本集S𝑏,其中有6個真值、4個
假值,那麼該布爾型樣本分類的熵爲:
信息增益
- 計算分支屬性對於樣本集分類好壞程度的度量——信息增益
- 由於分裂後樣本集的純度提高,則樣本集的熵降低,熵降低的值即爲該分 裂方法的信息增益
脊椎動物分類訓練樣本集
共有14個樣本,其中8個正例,6個反例,設此樣本集爲 S,則分裂前的熵值爲
脊椎動物訓練樣本集以“飲食習性”作爲分支屬性的分裂情況
設“飲食習性”屬性爲Y,由此可以計算得出,作爲分支屬性進行分裂之後的
信息增益爲
同理, 計算可得,以“胎生動物”“水生動物”“會飛”作爲分支屬性時的信息 增益分別爲0.6893、0.0454、0.0454
由此可知“胎生動物”作爲分支屬性時能獲得最大的信息增益,即具有最強的區分樣本的能力,所以在此選擇使用“胎生動物”作爲分支屬性對根結點進行劃分
由根結點通過計算信息增益選取合適的屬性進行分裂,若新生成的結點的分類屬性不唯一,則對新生的結點繼續進行分裂,不斷重複此步驟,直至所有樣本屬於同 一類,或者達到要求的分類條件爲止
缺點
4. C4.5算法
C4.5算法總體思路與ID3類似,都是通過構造決策樹進行分類,其區別在於分支的處理,在分支屬性的選取上,ID3算法使用信息增益作爲度量,而C4.5算法引入了信息增益率作爲度量
由信息增益率公式中可見,當𝑣比較大時,信息增益率會明顯降低,從而在一定程度上能夠解決ID3算法存在的往往選擇取值較多的分支屬性的問題
在前面例子中,假設選擇“飲食習性”作爲分支屬性,其信息增益率爲
5. C5.0算法
- C5.0算法是Quinlan在C4.5算法的基礎上提出的商用改進版本,目的是對含有 大量數據的數據集進行分析
- C5.0算法與C4.5算法相比有以下優勢:
– 決策樹構建時間要比C4.5算法快上數倍,同時生成的決策樹規模也更小,擁有更少的葉子結
點數
– 使用了提升法(boosting),組合多個決策樹來做出分類,使準確率大大提高
– 提供可選項由使用者視情況決定,例如是否考慮樣本的權重、樣本錯誤分類成本等
6. CART算法
基尼指數 Gini指標
CART算法在分支處理中分支屬性的度量指標
在前面例子中,假設選擇“會飛”作爲分支屬性,其Gini指標爲
7. 連續屬性離散化
如果是連續的數值型是如年齡,我們一般把它離散化,如離散化爲幼年,中年,老年
因爲你不可能讓把每個年齡都分成一個特徵,那樣會很多,也沒必要。
8. 過擬合的解決方案
- 一方面要注意數據訓練集的質量,選取具有代表性樣本的訓練樣本集
- 要避免決策樹過度增長,通過限制樹的深度來減少數據中的噪聲對於決策樹構建的影響,一般可以採取剪枝的方法
- 剪枝包括預剪枝和後剪枝兩類
預剪枝的思路是提前終止決策樹的增長,在形成完全擬合訓練樣本集的決 策樹之前就停止樹的增長,避免決策樹規模過大而產生過擬合
後剪枝策略先讓決策樹完全生長,之後針對子樹進行判斷,用葉子結點或者子樹中最常用的分支替換子樹,以此方式不斷改進決策樹,直至無法改進爲止
9. 例子1 - 脊椎動物分類
脊椎動物分類訓練樣本集 test.csv 文件, 做了下面的變換
是:0, 否 : 1 ,雜食動物 : omnivorous animal, 肉食動物:carnivorous animals, : 草食動物, herbivore
omnivorous animal, 0, 1, 1, 0
omnivorous animal, 0, 1, 1, 0
carnivorous animals, 0, 1, 1, 0
carnivorous animals, 1, 1, 0, 1
carnivorous animals, 1, 0, 1, 1
carnivorous animals, 1, 1, 1, 1
omnivorous animal, 0, 1, 0, 0
herbivore, 0, 1, 1, 0
omnivorous animal, 1, 1, 0, 1
carnivorous animals, 1, 0, 1, 1
carnivorous animals, 0, 0, 1, 0
carnivorous animals, 1, 1, 1, 0
herbivore, 0, 1, 1, 0
carnivorous animals, 1, 1, 1, 1
代碼
import pandas as pd
import sklearn as sklearn
from sklearn.feature_extraction import DictVectorizer
from sklearn import tree
import pydotplus
from sklearn.externals.six import StringIO
# pandas 讀取 csv 文件,header = None 表示不將首行作爲列
data = pd.read_csv('data/test.csv', header=None)
# 指定列
data.columns = ['Diet Habits', 'viviparous animal', 'Aquatic animals', 'Can fly','mammal']
# sparse=False意思是不產生稀疏矩陣
vec = sklearn.feature_extraction.DictVectorizer(sparse=False)
# 先用 pandas 對每行生成字典,然後進行向量化
feature = data[['Diet Habits', 'viviparous animal', 'Aquatic animals']]
X_train = vec.fit_transform(feature.to_dict(orient='record'))
# 打印各個變量
print('show feature\n', feature)
print('show vector\n', X_train)
print('show vector name\n', vec.get_feature_names())
print('show vector name\n', vec.vocabulary_)
Y_train = data['mammal']
clf = tree.DecisionTreeClassifier(criterion='entropy')
clf.fit(X_train, Y_train)
dot_data = StringIO()
tree.export_graphviz(clf,feature_names=vec.get_feature_names(),out_file=dot_data)
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
graph.write_pdf("test.pdf")
show feature
Diet Habits viviparous animal Aquatic animals
0 omnivorous animal 0 1
1 omnivorous animal 0 1
2 carnivorous animals 0 1
3 carnivorous animals 1 1
4 carnivorous animals 1 0
5 carnivorous animals 1 1
6 omnivorous animal 0 1
7 herbivore 0 1
8 omnivorous animal 1 1
9 carnivorous animals 1 0
10 carnivorous animals 0 0
11 carnivorous animals 1 1
12 herbivore 0 1
13 carnivorous animals 1 1
show vector
[[1. 0. 0. 1. 0.]
[1. 0. 0. 1. 0.]
[1. 1. 0. 0. 0.]
[1. 1. 0. 0. 1.]
[0. 1. 0. 0. 1.]
[1. 1. 0. 0. 1.]
[1. 0. 0. 1. 0.]
[1. 0. 1. 0. 0.]
[1. 0. 0. 1. 1.]
[0. 1. 0. 0. 1.]
[0. 1. 0. 0. 0.]
[1. 1. 0. 0. 1.]
[1. 0. 1. 0. 0.]
[1. 1. 0. 0. 1.]]
show vector name
['Aquatic animals', 'Diet Habits=carnivorous animals', 'Diet Habits=herbivore', 'Diet Habits=omnivorous animal', 'viviparous animal']
show vector name
{'Diet Habits=omnivorous animal': 3, 'viviparous animal': 4, 'Aquatic animals': 0, 'Diet Habits=carnivorous animals': 1, 'Diet Habits=herbivore': 2}
10. 例子2
安裝panda 和 scikit-learn 如果你沒有安裝的話
conda install pandas
conda install scikit-learn
1. 準備數據及讀取
季節 | 時間已過 8 點 | 風力情況 | 要不要賴牀 |
---|---|---|---|
spring | no | breeze | yes |
winter | no | no wind | yes |
autumn | yes | breeze | yes |
winter | no | no wind | yes |
summer | no | breeze | yes |
winter | yes | breeze | yes |
winter | no | gale | yes |
winter | no | no wind | yes |
spring | yes | no wind | no |
summer | yes | gale | no |
summer | no | gale | no |
autumn | yes | breeze | no |
spring,no,breeze,1
winter,no,no wind,1
autumn,yes,breeze,1
winter,no,no wind,1
summer,no,breeze,1
winter,yes,breeze,1
winter,no,gale,1
winter,no,no wind,1
spring,yes,no wind,0
summer,yes,gale,0
summer,no,gale,0
autumn,yes,breeze,0
2. 決策樹的特徵向量化
sklearn的DictVectorizer能對字典進行向量化。什麼叫向量化呢?比如說你有季節這個屬性有[春,夏,秋,冬]四個可選值,那麼如果是春季,就可以用[1,0,0,0]表示,夏季就可以用[0,1,0,0]表示。不過在調用DictVectorizer它會將這些屬性打亂,不會按照我們的思路來運行,但我們也可以一個方法查看,我們看看代碼就明白了
通過DictVectorizer,我們就能夠把字符型的數據,轉化成0 1的矩陣,方便後面進行運算。額外說一句,這種轉換方式其實就是one-hot編碼。
import pandas as pd
import sklearn as sklearn
from sklearn.feature_extraction import DictVectorizer
from sklearn import tree
# pandas 讀取 csv 文件,header = None 表示不將首行作爲列
data = pd.read_csv('data/laic.csv', header=None)
# 指定列
data.columns = ['season', 'after 8', 'wind', 'lay bed']
# sparse=False意思是不產生稀疏矩陣
vec = DictVectorizer(sparse=False)
# 先用 pandas 對每行生成字典,然後進行向量化
feature = data[['season', 'after 8', 'wind']]
X_train = vec.fit_transform(feature.to_dict(orient='record'))
# 打印各個變量
print('show feature\n', feature)
print('show vector\n', X_train)
print('show vector name\n', vec.get_feature_names())
print('show vector name\n', vec.vocabulary_)
執行結果
show feature
season after 8 wind
0 spring no breeze
1 winter no no wind
2 autumn yes breeze
3 winter no no wind
4 summer no breeze
5 winter yes breeze
6 winter no gale
7 winter no no wind
8 spring yes no wind
9 summer yes gale
10 summer no gale
11 autumn yes breeze
show vector
[[1. 0. 0. 1. 0. 0. 1. 0. 0.]
[1. 0. 0. 0. 0. 1. 0. 0. 1.]
[0. 1. 1. 0. 0. 0. 1. 0. 0.]
[1. 0. 0. 0. 0. 1. 0. 0. 1.]
[1. 0. 0. 0. 1. 0. 1. 0. 0.]
[0. 1. 0. 0. 0. 1. 1. 0. 0.]
[1. 0. 0. 0. 0. 1. 0. 1. 0.]
[1. 0. 0. 0. 0. 1. 0. 0. 1.]
[0. 1. 0. 1. 0. 0. 0. 0. 1.]
[0. 1. 0. 0. 1. 0. 0. 1. 0.]
[1. 0. 0. 0. 1. 0. 0. 1. 0.]
[0. 1. 1. 0. 0. 0. 1. 0. 0.]]
show vector name
['after 8=no', 'after 8=yes', 'season=autumn', 'season=spring', 'season=summer', 'season=winter', 'wind=breeze', 'wind=gale', 'wind=no wind']
show vector name
{'season=spring': 3, 'after 8=no': 0, 'wind=breeze': 6, 'season=winter': 5, 'wind=no wind': 8, 'season=autumn': 2, 'after 8=yes': 1, 'season=summer': 4, 'wind=gale': 7}
3. 決策樹訓練
Y_train = data['lay bed']
clf = tree.DecisionTreeClassifier(criterion='entropy')
clf.fit(X_train, Y_train)
4. 決策樹可視化
當完成一棵樹的訓練的時候,我們也可以讓它可視化展示出來,不過sklearn沒有提供這種功能,它僅僅能夠讓訓練的模型保存到dot文件中。但我們可以藉助其他工具讓模型可視化,先看保存到dot的代碼:
with open("out.dot", 'w') as f :
f = tree.export_graphviz(clf, out_file = f,
feature_names = vec.get_feature_names())
5 預測結果
result = clf.predict([[1., 0., 0. ,1. , 0. , 0. , 1. , 0. , 0.]])
print(result)
[1]
然後可以執行下面命令生成一個out.pdf
dot out.dot -T pdf -o out.pdf
after 8=no | after 8=yes | season=autumn | season=spring | season=summer | season=winter | wind=breeze | wind=gale | wind=no wind | lay bed |
---|---|---|---|---|---|---|---|---|---|
1. | 0. | 0. | 1. | 0. | 0. | 1. | 0. | 0. | 1 |
1. | 0. | 0. | 0. | 0. | 1. | 0. | 0. | 1. | 1 |
0. | 1. | 1. | 0. | 0. | 0. | 1. | 0. | 0. | 1 |
1. | 0. | 0. | 0. | 0. | 1. | 0. | 0. | 1. | 1 |
1. | 0. | 0. | 0. | 1. | 0. | 1. | 0. | 0. | 1 |
0. | 1. | 0. | 0. | 0. | 1. | 1. | 0. | 0. | 1 |
1. | 0. | 0. | 0. | 0. | 1. | 0. | 1. | 0. | 1 |
1. | 0. | 0. | 0. | 0. | 1. | 0. | 0. | 1. | 1 |
0. | 1. | 0. | 1. | 0. | 0. | 0. | 0. | 1. | 0 |
0. | 1. | 0. | 0. | 1. | 0. | 0. | 1. | 0. | 0 |
1. | 0. | 0. | 0. | 1. | 0. | 0. | 1. | 0. | 0 |
0. | 1. | 1. | 0. | 0. | 0. | 1. | 0. | 0. | 0 |
6. Module persistence
兩種方式保持我們的模型
參考Sklearn 官網
1) 用Python有的pickle對我們訓練好的模型保存
import pickle
with open('decisive_tree_module.txt', 'wb') as f:
pickle.dump(clf, f)
with open('decisive_tree_module.txt', 'rb') as f:
clf2 = pickle.load(f)
#s = pickle.dumps(clf)
#clf2 = pickle.loads(s)
predict2 = clf2.predict([[1., 0., 0. ,1. , 0. , 0. , 1. , 0. , 0.]])
print('Predict result via loading pickle saved module :', predict2)
Predict result via loading pickle saved module : [1]
2) 用joblib’s保持如果你的模型裏有大量的 numpy arrays的話
from joblib import dump, load
dump(clf, 'jdecisive_tree_module.joblib')
clf3 = load('jdecisive_tree_module.joblib')
predict3 = clf3.predict([[1., 0., 0. ,1. , 0. , 0. , 1. , 0. , 0.]])
print('Predict result via loading joblib saved module :', predict3)
Predict result via loading joblib saved module : [1]
7. 自己算驗證熵的結果
import math
root_node_entropy = -(8/12)*(math.log(8/12, 2)) - (4/12)*(math.log(4/12, 2))
node1_left = (-(3/7)*(math.log(3/7, 2)) - (4/7)*(math.log(4/7,2)))
#node1_right = (-(5/5)*(math.log(5/5, 2)) - (0/5)*(math.log(0/5,2)))
node1_right = (-(5/5)*(0) - 0)
#node2_left = -(3/3)*(math.log(3/3, 2)) - (0/3)*(math.log(0/3, 2))
node2_left = -(3/3)*(0) - 0
node2_right = -(3/4)*(math.log(3/4, 2)) - (1/4)*(math.log(1/4, 2))
print('Entropy of season=winter ', root_node_entropy)
print('Entropy of wind=breeze ', node1_left)
print('Entropy of wind=breeze ', node1_right)
print('Entropy of node2_left', node2_left)
print('Entropy of node2_right', node2_right)
Entropy of season=winter 0.9182958340544896
Entropy of wind=breeze 0.9852281360342516
Entropy of wind=breeze -0.0
Entropy of node2_left -0.0
Entropy of node2_right 0.8112781244591328
8. 如果你用基尼指數, 也就是CART算法
只需要把entropy 改成 gini就可以了
clf = tree.DecisionTreeClassifier(criterion='gini')
9. 自己算驗證基尼指數的結果
import math
root_node_entropy = 1 -(8 / 12) * (8 / 12) - (4 / 12) *(4 / 12)
node1_left = 1-(3 / 7) * (3 / 7) - (4 / 7) * (4 / 7)
# node1_right = (-(5/5)*(math.log(5/5, 2)) - (0/5)*(math.log(0/5,2)))
node1_right = (-(5 / 5) * (0) - 0)
# node2_left = -(3/3)*(math.log(3/3, 2)) - (0/3)*(math.log(0/3, 2))
node2_left = -(3 / 3) * (0) - 0
node2_right = 1-(3 / 4) * (3 / 4) - (1 / 4) *(1 / 4)
node3_left = -(2 / 2) * (0) - 0
node3_right = 1-(1 / 2) * (1 / 2) - (1 / 2) *(1 / 2)
print('Entropy of season=winter ', root_node_entropy)
print('Entropy of wind=breeze ', node1_left)
print('Entropy of wind=breeze ', node1_right)
print('Entropy of node2_left', node2_left)
print('Entropy of node2_right', node2_right)
print('Entropy of node3_left', node3_left)
print('Entropy of node3_right', node3_right)
Entropy of season=winter 0.4444444444444445
Entropy of wind=breeze 0.489795918367347
Entropy of wind=breeze -0.0
Entropy of node2_left -0.0
Entropy of node2_right 0.375
Entropy of node3_left -0.0
Entropy of node3_right 0.5
10. 把數據集全部改成數字不用DictVectorizer做向量化
spring :1 , summer : 2, spring : 3 , winter : 4
時間已過 8 點-no : 0
時間已過 8 點-yes :1
breeze : 1 , no wind : 2 , gale :3
laic1.csv 文件
1,0,1,1
4,0,2,1
3,1,1,1
4,0,2,1
2,0,1,1
4,1,1,1
4,0,3,1
4,0,2,1
1,1,2,0
2,1,3,0
2,0,3,0
3,1,1,0
代碼
import pandas as pd
from sklearn import tree
data = pd.read_csv('data/laic1.csv', header=None)
# 指定列
data.columns = ['season', 'after 8', 'wind', 'lay bed']
X_train = data[['season', 'after 8', 'wind']]
Y_train = data['lay bed']
clf = tree.DecisionTreeClassifier(criterion='entropy')
clf.fit(X_train, Y_train)
with open("out1.dot", 'w') as f :
f = tree.export_graphviz(clf, out_file = f,
feature_names =['season', 'after 8', 'wind'])
結果, 可以看到決策樹圖其實都一樣的。
預測結果
result = clf.predict([[1,1,1]])
print('Predict result:', result)
Predict result: [0]
可能遇到問題
如果你這個graphvis 的問題 (GraphViz’s executables not found), 可以根據下面這個link解決它
https://blog.csdn.net/qq_40304090/article/details/88594813