【ML】决策树(2)

习题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)

输出
output
依次切分点为 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=1TNt(1k=1K(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} α=Tt1C(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)=Tt1C(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)最优的.

参考资料

CART剪枝详解
决策树实现
决策树习题解答

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Quant0xff

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值