习题5.1(page 89)
题面
根据表5.1的训练集,利用信息增益比(C4.5)算法生成决策树.
代码
import numpy as np
import pandas as pd
from pandas import DataFrame
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from collections import Counter
import math
from math import log, exp
def make_data():
data = [['青年', '否', '否', '一般', '否'],
['青年', '否', '否', '好', '否'],
['青年', '是', '否', '好', '是'],
['青年', '是', '是', '一般', '是'],
['青年', '否', '否', '一般', '否'],
['中年', '否', '否', '一般', '否'],
['中年', '否', '否', '好', '否'],
['中年', '是', '是', '好', '是'],
['中年', '否', '是', '非常好', '是'],
['中年', '否', '是', '非常好', '是'],
['老年', '否', '是', '非常好', '是'],
['老年', '否', '是', '好', '是'],
['老年', '是', '否', '好', '是'],
['老年', '是', '否', '非常好', '是'],
['老年', '否', '否', '一般', '否'],
]
labels = [u'年龄', u'有工作', u'有自己的房子', u'信贷情况', u'类别']
return (data, labels)
data, labels=make_data()
train_data=DataFrame(data, columns=labels)
# 计算熵
def ent(data):
n, cnt=len(data), {}
for i in range(n):
label=data[i][-1]
if label not in cnt: cnt[label]=0
cnt[label]+=1
ans=-sum([(p/n)*log(p/n, 2) for p in cnt.values()])
return ans
def npent(y):
hist=np.bincount(y)
ps=hist/np.sum(hist)
return -np.sum([p*np.log2(p) for p in ps if p>0])
# 经验条件熵
def cond_ent(data, axis=0):
n, fset=len(data), {}
for i in range(n):
f=data[i][axis]
if f not in fset:
fset[f]=[]
fset[f].append(data[i])
ans=sum([len(p)/n*ent(p) for p in fset.values()])
return ans
# 信息增益
def info_gain(ent, cond_ent):
return ent-cond_ent
def train_ig(data):
cnt=len(data[0])-1
_ent=ent(data)
best_f=[]
for i in range(cnt):
f_ig=info_gain(_ent, cond_ent(data, axis=i))
best_f.append((i, f_ig))
print('feature {} info gain {:.4f}'.format(labels[i], f_ig))
ans=max(best_f, key=lambda x: x[-1])
print('the chosen feature is {}'.format(labels[ans[0]]))
train_ig(np.array(data))
习题5.2
题面
使用平方误差损失准则生成一个二叉回归树
代码
# 平方误差准则生成二叉回归树
y=np.array([4.5, 4.75, 4.91, 5.34, 5.8, 7.05, 7.9, 8.23, 8.7, 9])
def CART(start, end, y):
if end-start>1:
ans=[]
for i in range(start, end, 1):
c1=[np.average(y[start:i+1])] # 左子树均值
c2=[np.average(y[i+1: end+1])] # 右子树均值
y1=y[start: i+1]
y2=y[i+1: end+1]
ans.append(sum((y1-c1)**2)+sum((y2-c2)**2)) # 计算平方误差损失
ind=start+np.argmin(ans) # 切分点的索引
print('cut point:{}\t left avg:{:.4f}\t right avg:{:.4f}'.format(ind, np.average(y[start:ind+1]), np.average(y[ind+1: end+1])))
CART(start, ind, y) # 递归左子树
CART(ind+1, end, y) # 递归右子树
else: return None
CART(0, 9, y)
输出
依次切分点为
x
[
4
]
,
x
[
2
]
,
x
[
0
]
,
x
[
6
]
,
x
[
7
]
x[4], x[2], x[0], x[6], x[7]
x[4],x[2],x[0],x[6],x[7].
ID3生成决策树
# ID3生成决策树
class Node:
def __init__(self, root=True, label=None, feature_name=None, feature=None):
self.root=root
self.label=label
self.feature_name=feature_name
self.feature=feature
self.tree={}
self.ans={
'label': self.label,
'feature': self.feature,
'tree': self.tree
}
def __repr__(self):
return '{}'.format(self.ans)
def add_node(self, val, node):
self.tree[val]=node
def predict(self, features):
if self.root: return self.label
return self.tree[features[self.feature]].predict(features)
class DTree:
def __init__(self, eps=0.1):
self.eps=eps
self._tree={}
# 熵
@staticmethod
def ent(data):
n=len(data)
dcnt={}
for i in range(n):
label=data[i][-1]
if label not in dcnt: dcnt[label]=0
dcnt[label]+=1
ans=-sum([(p/n)*log(p/n, 2) for p in dcnt.values()])
return ans
# 条件熵
@staticmethod
def cond_ent(data, axis=0):
n, fset=len(data), {}
for i in range(n):
f=data[i][axis]
if f not in fset:
fset[f]=[]
fset[f].append(data[i])
ans=sum([len(p)/n*DTree.ent(p) for p in fset.values()])
return ans
# 信息增益
@staticmethod
def info_gain(ent, cond_ent):
return ent-cond_ent
def train_ig(self, data):
cnt=len(data[0])-1
_ent=DTree.ent(data)
best_f=[]
for i in range(cnt):
f_ig=DTree.info_gain(_ent, DTree.cond_ent(data, axis=i))
best_f.append((i, f_ig))
ans=max(best_f, key=lambda x: x[-1])
return ans
def train(self, data):
y_train, features=data.iloc[:, -1], data.columns[:-1]
# 如果D中样本均属于同一类别Ck, 则T为单节点树,并将类Ck作为节点类标记,返回T
if len(y_train.value_counts())==1:
return Node(root=True, label=y_train.iloc[0])
# 如果A为空,则T为单节点树,将D中样本最大类Ck作为该点类标记,返回T
if not len(features):
return Node(root=True, label=y_train.value_counts().sort_values(ascending=False).index[0])
# 计算最大信息增益
mf, mig=self.train_ig(np.array(data))
mfn=features[mf]
# 如果最大信息增益小于eps,设置T为单点树,并将D中是样本数最大的类Ck作为该节点的类标记
if mig<self.eps:
return Node(root=True, label=y_train.value_counts().sort_values(ascending=False).index[0])
# 构建子集
node_tree=Node(root=False, feature_name=mfn, feature=mf)
feature_list=data[mfn].value_counts().index
for f in feature_list:
sub_data=data.loc[data[mfn]==f].drop([mfn], axis=1)
sub_tree=self.train(sub_data)
node_tree.add_node(f, sub_tree)
return node_tree
def fit(self, data):
self._tree=self.train(data)
return self._tree
def predict(self, X_test):
return self._tree.predict(X_test)
dt=DTree()
tree=dt.fit(train_data)
print(tree)
print(dt.predict(['老年', '否', '否', '一般']))
使用scikit-learn
提供的函数实现iris
数据集分类
import graphviz
from sklearn.tree import DecisionTreeClassifier
from sklearn.tree import export_graphviz
# iris
def make_data():
iris=load_iris()
df=DataFrame(iris.data, columns=iris.feature_names)
df['label']=iris.target
df.columns=['sepal length', 'sepal width', 'petal length', 'petal width', 'label']
data=np.array(df.iloc[:100, [0, 1, -1]])
return data[:, :2], data[:, -1]
X, y=make_data()
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
clf=DecisionTreeClassifier()
clf.fit(X_train, y_train)
print('score: {:.4f}'.format(clf.score(X_train, y_train)))
# 输出可视化决策树pdf文件
dtree_fig=export_graphviz(clf, out_file='dtree.pdf')
with open('dtree.pdf') as f:
g=f.read()
graphviz.Source(g)
习题5.3
题面
证明CART剪枝算法中,当 α \alpha α确定时,存在唯一最小的子树 T α T_\alpha Tα使损失函数 C α ( T ) C_\alpha(T) Cα(T).
解析
内部节点是否剪枝只与该节点为根节点的子树有关,计算子树的损失函数判断是否剪枝
C
α
(
T
)
=
C
(
T
)
+
α
C
(
T
)
=
∑
t
=
1
∣
T
∣
N
t
(
1
−
∑
k
=
1
K
(
N
t
k
N
t
)
2
)
\begin{aligned} &C_\alpha(T)=C(T)+\alpha\\ &C(T)=\sum\limits_{t=1}^{|T|}N_t(1-\sum\limits_{k=1}^K(\frac{N_{tk}}{N_t})^2) \end{aligned}
Cα(T)=C(T)+αC(T)=t=1∑∣T∣Nt(1−k=1∑K(NtNtk)2)
剪枝前子树为
T
0
T_0
T0,剪枝后子树为
T
1
T_1
T1,如满足
C
α
(
T
1
)
≤
C
α
(
T
0
)
C_\alpha(T_1)\leq C_\alpha(T_0)
Cα(T1)≤Cα(T0)则进行剪枝.
反证:设当
α
\alpha
α确定时,存在两棵子树
T
a
,
T
b
T_a, T_b
Ta,Tb均使得损失函数
C
α
C_\alpha
Cα最小.
情况(1):假设被剪枝的子树在同子树上,则必然有一个子树可以由另一个子树剪枝得到,因子只会存在一棵子树
情况(2):假设被剪枝的子树在不同子树上,被剪枝的子树均可使
C
α
C_\alpha
Cα最小,可以继续剪枝.
综上,当
α
\alpha
α确定时,仅有一棵子树使得损失函数
C
α
C_\alpha
Cα最小.
习题5.4
题面
证明CART剪枝算法中求出的子树序列 { T 0 , T 1 , … , T n } \{T_0, T_1, \dots, T_n\} {T0,T1,…,Tn}分别是区间 α ∈ [ α i , α i + 1 ) \alpha\in[\alpha_i, \alpha_{i+1}) α∈[αi,αi+1)的最优子树 T α T_\alpha Tα, i = 0 , 1 , … , n , 0 = α 0 < α 1 < ⋯ < α n < + ∞ i=0, 1, \dots, n, 0=\alpha_0<\alpha_1<\dots<\alpha_n<+\infty i=0,1,…,n,0=α0<α1<⋯<αn<+∞
解析
因为
α
\alpha
α是模型精确度与模型复杂度之间的平衡因子,所以当
α
=
0
\alpha=0
α=0时,无剪枝的生成树
T
0
T_0
T0是最优的,当
α
→
∞
\alpha\to\infty
α→∞时,节点组成的单点树
T
n
T_n
Tn是最优的.
剪枝考虑内部子节点的损失函数,从整体树
T
0
T_0
T0开始剪枝,对
T
0
T_0
T0的任意内部节点
t
t
t.
剪枝前:有
∣
T
t
∣
|T_t|
∣Tt∣个叶子节点,预测误差值为
C
(
T
t
)
C(T_t)
C(Tt)
剪枝后:只保留叶子节点本身,预测误差值为
C
(
t
)
C(t)
C(t),因此剪枝前的
t
t
t节点为根节点的子树损失函数为
C
α
(
T
t
)
=
C
(
T
t
)
+
α
∣
T
t
∣
C_\alpha(T_t)=C(T_t)+\alpha|T_t|
Cα(Tt)=C(Tt)+α∣Tt∣
剪枝后的损失函数为
C
α
(
t
)
=
C
(
t
)
+
α
C_\alpha(t)=C(t)+\alpha
Cα(t)=C(t)+α
当
C
α
(
t
)
=
C
α
(
T
t
)
C_\alpha(t)=C_\alpha(T_t)
Cα(t)=Cα(Tt)
α
=
C
(
t
)
−
C
(
T
t
)
∣
T
t
∣
−
1
\alpha=\frac{C(t)-C(T_t)}{|T_t|-1}
α=∣Tt∣−1C(t)−C(Tt)
对
T
0
T_0
T0中每一内部节点
t
t
t计算
g
(
t
)
=
C
(
t
)
−
C
(
T
t
)
∣
T
t
∣
−
1
g(t)=\frac{C(t)-C(T_t)}{|T_t|-1}
g(t)=∣Tt∣−1C(t)−C(Tt)
在
T
0
T_0
T0中剪去
g
(
t
)
g(t)
g(t)最小的
T
T
T,将得到的子树作为
T
1
T_1
T1,同时将最小的
T
1
T_1
T1设置为
α
1
\alpha_1
α1,
T
1
T_1
T1为区间
[
α
1
,
α
2
)
[\alpha_1, \alpha_2)
[α1,α2)的最优子树,依次可知
T
i
T_i
Ti是区间
[
α
i
,
α
i
+
1
)
[\alpha_i, \alpha_{i+1})
[αi,αi+1)最优的.