用決策樹建模預測誰是潛在的客戶,這裏將客戶分爲兩種類型,根據訓練數據中的判定條件,構建決策樹,構建決策樹分爲以下幾個步驟:
決策樹的節點結構
class decisionnode 中包含了5個數據項,分別是- col: 代表該節點用訓練數據的哪一列作爲判斷條件
- value: 以cols條件的value值作爲切分節點,value若爲數值型屬性,則按照大於小於value進行切分;若Wie標稱屬性,則按照等於和不等於進行切分。總之,根據cols和value的值將父節點切分爲兩個葉子節點。
- result: 表示葉子節點中所含各種類型的訓練數據的字典,除葉節點外,其餘節點中該項均爲None.
- tb: 當判斷條件爲真時分裂得到的左子樹
- fb: 當判斷條件爲假時分裂得到的右子樹
衡量節點的類別混合程度:使用熵不純度和GINI不純度。熵爲0時代表純度很高,只有一種類別,熵不爲0時表示有類別混雜。熵值越大,表示混合程度越高。
獲取當前節點的最優劃分屬性。CART決策樹採用GINI指數,而ID3和C4.5決策樹則採用信息增益和信息增益比來決定最優的劃分屬性。方法是遍歷所有的劃分屬性,遍歷該劃分屬性的所有可能取值,計算在該劃分屬性和劃分取值下將父節點劃分爲兩個子節點後的信息增益。取所有劃分屬性和劃分取值中信息增益最大的劃分父節點。依次類推,遞歸的構建決策樹。僅當信息增益不再大於0時,節點的分裂結束。
注意,此種方法下,決策樹將會完全生長,完全生長的決策樹將會引發過擬合問題,故而CART決策樹有後剪枝步驟。剪枝: 剪枝的過程就是對有相同父節點的兩個葉節點進行檢查,若將其合併,判斷熵值的增加值是否小於某一特定的閾值,如果小於,則將兩個葉節點刪除,將父節點作爲葉節點,其包含的樣本爲原來兩個葉節點包含樣本的和。如果熵值的增加值小於該閾值,則結束剪枝過程。可以看出,剪枝的結果依賴於閾值的選擇,如果閾值太大,則剪枝後的決策樹將會很小。
附:當要使用決策樹作迴歸分析時,應當將衡量最優劃分條件的標準從熵值的減少量改爲方差的減少量。即一個葉節點中如果樣本的方差很小,表示該葉子節點中的樣本數值都很接近,表示該節點的切分效果很好。
代碼及註釋如下:
# -*- coding:utf-8 -*-
__author__ = 'Bai Chenjia'
# 訓練數據,每行前四項代表特徵,最後一項代表類別
my_data = [['slashdot', 'USA', 'yes', 18, 'None'],
['google', 'France', 'yes', 23, 'Premium'],
['digg', 'USA', 'yes', 24, 'Basic'],
['kiwitobes', 'France', 'yes', 23, 'Basic'],
['google', 'UK', 'no', 21, 'Premium'],
['(direct)', 'New Zealand', 'no', 12, 'None'],
['(direct)', 'UK', 'no', 21, 'Basic'],
['google', 'USA', 'no', 24, 'Premium'],
['slashdot', 'France', 'yes', 19, 'None'],
['digg', 'USA', 'no', 18, 'None'],
['google', 'UK', 'no', 18, 'None'],
['kiwitobes', 'UK', 'no', 19, 'None'],
['digg', 'New Zealand', 'yes', 12, 'Basic'],
['slashdot', 'UK', 'no', 21, 'None'],
['google', 'UK', 'yes', 18, 'Basic'],
['kiwitobes', 'France', 'yes', 19, 'Basic']]
# 決策樹的節點結構
class decisionnode:
# 決策樹節點初始化
def __init__(self, col=-1, value=None, results=None, tb=None, fb=None):
self.col = col # 代表該節點用訓練數據的哪一列作爲判斷條件
self.value = value # 表示col判斷條件爲value時爲真,不爲value爲假,以此劃分節點
self.results = results # 表示某一節點中各種類別的字典。除葉節點外,其餘節點該項都未None
self.tb = tb # 當value爲真時的子樹
self.fb = fb # 當value爲假時的子樹
# 拆分數據集合,rows爲父節點所有的數據,column代表根據第column的判斷條件拆分,等於value則爲真,否則爲假
def divideset(rows, column, value):
set1, set2 = [], []
# 如果是數值型數據,則以大於小於拆分
if isinstance(value, int) or isinstance(value, float):
for row in rows:
if row[column] >= value:
set1.append(row)
else:
set2.append(row)
# 如果是標稱數據,則以等於或不等於進行拆分
else:
for row in rows:
if row[column] == value:
set1.append(row)
else:
set2.append(row)
return (set1, set2)
# 待統計的數據集rows,返回一個字典,鍵爲類別,值爲該類別的數據個數
# 返回的字典用於下一步計算數據的混雜程度
def uniquecounts(rows):
results = {}
for row in rows:
results.setdefault(row[len(row) - 1], 0)
results[row[len(row) - 1]] += 1
return results
# 計算rows中不同類別的 熵 不純度
# 熵爲0時表示純度很高,只有一種類別。熵值越高,表明混雜程度越高
def entropy(rows):
from math import log
log2 = lambda x: log(x) / log(2)
results = uniquecounts(rows)
# 此處計算開始時的熵值
ent = 0.0
for r in results.keys():
p = float(results[r]) / len(rows)
ent -= p * log2(p)
return ent
# 以遞歸方式構造決策樹
def buildtree(rows, scoref=entropy):
if len(rows) == 0:
return decisionnode()
# 計算信息增益,從而確定劃分屬性,信息增益爲父節點的熵值減去兩個子節點熵值的加權平均和
# 信息增益越大,表明選擇該屬性作爲劃分越好
best_gain = 0.0
best_criteria = None
best_sets = None
column_count = len(rows[0]) - 1
# 循環列,循環判定條件
for col in range(column_count):
# 統計該判定條件所有的value
values = []
for row in rows:
if row[col] not in values:
values.append(row[col])
# 父節點的熵
e1 = scoref(rows)
for value in values:
set1, set2 = divideset(rows, col, value)
e2 = scoref(set1)
e3 = scoref(set2)
p = float(len(set1)) / float(len(rows))
gain = e1 - p * e2 - (1 - p) * e3
# 記錄信息增益最高的劃分條件
if best_gain < gain:
best_gain = gain
best_criteria = (col, value)
best_sets = (set1, set2)
# 創建子分支
if best_gain > 0:
trueBranch = buildtree(best_sets[0])
falseBranch = buildtree(best_sets[1]) # 遞歸繼續劃分子節點
return decisionnode(col=best_criteria[0], value=best_criteria[1], results=None,
tb=trueBranch, fb=falseBranch)
# 信息增益爲0或負數,表明應該停止分割,該節點爲葉節點,葉節點的results不爲空
else:
return decisionnode(results=uniquecounts(rows))
# 將建立的決策樹以文本方式打印輸出
def printtree(tree, indent=''):
# 這是一個葉節點嗎
if tree.results is not None:
print str(tree.results)
else:
# 打印判斷條件
print str(tree.col) + ':' + str(tree.value) + '? '
# 打印分支
print indent + 'T->',
printtree(tree.tb, indent+' ')
print indent + 'F->',
printtree(tree.fb, indent+' ')
# 對新觀測的數據進行分類
def classify(observation, tree):
# 如果不是葉節點
if tree.results is None:
if isinstance(tree.value, int) or isinstance(tree.value, float):
# 大於
if observation[tree.col] >= tree.value:
return classify(observation, tree.tb)
else:
return classify(observation, tree.fb)
else:
if observation[tree.col] == tree.value:
return classify(observation, tree.tb)
else:
return classify(observation, tree.fb)
else:
return tree.results
# 剪枝,剪枝的過程就是對具有相同父節點的兩個葉節點進行檢查,判斷如果將其合併,熵的增加量是否會小於某一
# 閾值,如果小於,則將這兩個葉節點合併成一個節點,否則結束
def prune(tree, mingain):
# 如果分支不是葉節點,則對其進行剪枝操作
if tree.tb.results is None:
prune(tree.tb, mingain) # 向下遞歸,直到到達葉節點爲止
if tree.fb.results is None:
prune(tree.fb, mingain) # 向下遞歸,知道到達葉節點爲止
# 如果兩個子分支都是葉節點,則考察能否進行剪枝
if tree.tb.results is not None and tree.fb.results is not None:
tb, fb = [], []
for v, c in tree.tb.results.items():
tb += [[v]] * c
for v, c in tree.fb.results.items():
fb += [[v]] * c
# 計算合併後熵的減少情況
delta = entropy(tb + fb) - (entropy(tb) + entropy(fb))/2
if delta < mingain:
tree.tb, tree.fb = None, None
tree.results = uniquecounts(tb + fb)
if __name__ == '__main__':
"""
# 測試函數divideset
set1, set2 = divideset(my_data, 2, 'yes')
print set1[:]
print set2[:]
"""
# 測試uniquecounts
"""
results = uniquecounts(my_data)
for key, value in results.items():
print key, value
"""
"""
e = entropy(my_data)
print e
"""
# 建立決策樹
tree = buildtree(my_data)
# 打印輸出決策樹
#printtree(tree)
# 預測
#res = classify(['(direct)', 'USA', 'yes', 5], tree)
#print "結果: ", res.items()[:]
# 剪枝
prune(tree, 1.0)
printtree(tree)