机器学习系列(三十六)——回归决策树与决策树总结

本篇主要内容:回归决策树原理、回归树学习曲线、决策树总结

回归决策树原理

回归决策树树是用于回归的决策树模型,回归决策树主要指CART算法, 同样也为二叉树结构。以两个特征预测输出的回归问题为例,回归树的原理是将特征平面划分成若干单元,每一个划分单元都对应一个特定的输出。因为每个结点都是yes和no的判断,所以划分的边界是平行于座标轴的。对于测试数据,我们只要将特征按照决策过程将其归到某个单元,便得到对应的回归输出值。

如上图所示的划分和相应的回归树,如果现在新来一个数据的特征是(6,7.5),按照回归树,它对应的回归结果就是C5。节点的划分的过程也就是树的建立过程,每划分一次,随即确定划分单元对应的输出,也就多了一个结点。当根据相应的约束条件终止划分的时候,最终每个单元的输出也就确定了,输出也就是叶结点。这看似和分类树差不多,实则有很大的区别。划分点的寻找和输出值的确定是回归决策树的两个核心问题。
一个输入空间的划分的误差是用真实值和划分区域的预测值的最小二乘来衡量的:
\sum_{x_{i}\in R_{m}}(y_{i}-f(x_{i}))^2

其中,f(x_i)是每个划分单元的预测值,这个预测值是该单元内每个样本点的值的某种组合,比如可取均值:
f(x_{i})=c_{m}=ave(y_{i}|x_{i}\in R_{m})

(输入特征空间划分为R_1,R_2,...,R_m
那么求解最优划分即是求解最优化问题:
min_{j,s}[min_{c_{1}}\sum_{x_{i}\in R_{1}(j,s)}(y_{i}-c_{1})^2+min_{c_{2}}\sum_{x_{i}\in R_{2}(j,s)}(y_{i}-c_{2})^2]

其中,R_1(j,s)=\langle x|x^{j}\leq s\rangleR_2(j,s)=\langle x|x^{j}\leq s\rangle是每次划分形成的两个区域。
关于该最优化问题的求解这里不再介绍,下面直接使用skleaen中的决策回归树来看一下决策树的回归效果,数据集使用Boston房价数据:

import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
boston=datasets.load_boston()
x=boston.data
y=boston.target

from sklearn.model_selection import train_test_split
x_train,x_test,y_train,y_test=train_test_split(x,y,random_state=666)
from sklearn.tree import DecisionTreeRegressor
dt_reg=DecisionTreeRegressor()
dt_reg.fit(x_train,y_train)
dt_reg.score(x_test,y_test)

不进行调参的话,可以看到在测试集上R方是0.59,显然这是不太好的结果,但是一个有趣的现象是,在训练集上:

R方值是1.0,也就是在训练集上决策树预测的回归结果完全吻合毫无偏差,这显然是过拟合。这个例子也说明了决策树算法是非常容易产生过拟合的,当然我们可以通过调参来缓解过拟合。


学习曲线

下面绘制学习曲线来直观看一下决策树回归模型的表现,首先绘制基于MSE的学习曲线:

from sklearn.tree import DecisionTreeRegressor
from sklearn.metrics import mean_squared_error

def plot_learning_curve(algo, X_train, X_test, y_train, y_test):
    train_score = []
    test_score = []
    for i in range(1, len(X_train)+1):
        algo.fit(X_train[:i], y_train[:i])
    
        y_train_predict = algo.predict(X_train[:i])
        train_score.append(mean_squared_error(y_train[:i], y_train_predict))
    
        y_test_predict = algo.predict(X_test)
        test_score.append(mean_squared_error(y_test, y_test_predict))
        
    plt.plot([i for i in range(1, len(X_train)+1)], 
                               np.sqrt(train_score), label="train")
    plt.plot([i for i in range(1, len(X_train)+1)], 
                               np.sqrt(test_score), label="test")
    plt.legend()
    plt.show()
    
plot_learning_curve(DecisionTreeRegressor(), X_train, X_test, y_train, y_test)

学习曲线如下:

再绘制基于R方的学习曲线:

from sklearn.metrics import r2_score
def plot_learning_curve_r2(algo, X_train, X_test, y_train, y_test):
    train_score = []
    test_score = []
    for i in range(1, len(X_train)+1):
        algo.fit(X_train[:i], y_train[:i])
    
        y_train_predict = algo.predict(X_train[:i])
        train_score.append(r2_score(y_train[:i], y_train_predict))
    
        y_test_predict = algo.predict(X_test)
        test_score.append(r2_score(y_test, y_test_predict))
        
    plt.plot([i for i in range(1, len(X_train)+1)], 
                               train_score, label="train")
    plt.plot([i for i in range(1, len(X_train)+1)], 
                               test_score, label="test")
    plt.legend()
    plt.axis([0, len(X_train)+1, -0.1, 1.1])
    plt.show()
    
plot_learning_curve_r2(DecisionTreeRegressor(), X_train, X_test, y_train, y_test)

上面两种都是在默认情况下也就是不进行决策树深度和叶子节点个数等条件的限制得到的结果。发现在训练集上,如果不进行限制,可以做到0偏差,这是明显的过拟合。接下来调节参数再绘制学习曲线,为节约篇幅,只调节决策树深度这一个参数,而且只绘制基于R方的学习曲线:
max_depth=1时

plot_learning_curve_r2(DecisionTreeRegressor(max_depth=1), X_train, X_test, y_train, y_test)

max_depth=3时

plot_learning_curve_r2(DecisionTreeRegressor(max_depth=3), X_train, X_test, y_train, y_test)

max_depth=5时

plot_learning_curve_r2(DecisionTreeRegressor(max_depth=5), X_train, X_test, y_train, y_test)

随着深度的增加,模型复杂度越来越高,过拟合现象也越来越明显,可以测试,当max_depth=20时,在训练集上又为一条y=1的无偏差直线。有兴趣的仍然可以修改其它参数绘制学习曲线。


决策树总结

决策树的局限性:

  • 决策树最严重的局限性是决策树生成的决策边界是平行于座标轴的直线的组合,旋转数据集则决策边界会改变,因此决策不稳定;
  • 另外决策树对个别数据敏感。(这几乎是所有非参数学习算法的弊端之一)

使用本系列上篇文章中的鸢尾花数据,来看一下决策树对个别数据敏感会导致的结果,在本系列上篇文章中,使用信息熵划分,其余参数默认情况下绘制的决策边界是:

接着我们删除索引为138的数据,再来绘制决策边界:

X_new = np.delete(x,138,axis=0)
y_new = np.delete(y,138)
dt_clf2 = DecisionTreeClassifier(max_depth=2,criterion="entropy")
dt_clf2.fit(X_new,y_new)#用数据训练模型

plot_decision_boundary(dt_clf2,axis=[0.5,7.5,0,3])
plt.scatter(x[y==0,0],x[y==0,1])
plt.scatter(x[y==1,0],x[y==1,1])
plt.scatter(x[y==2,0],x[y==2,1])
plt.show()

发现此时的决策边界已经完全不同了,而这仅仅只是一个数据点的影响。


综上我们知道决策树实际是一种不够稳定的算法,它的表现极度依赖调参和数据,不过虽然决策树本身不是一种高效的机器学习算法,但是它们基于集成学习的组合——随机森林(RF)却是一个很鲁棒的机器学习算法,这将在下篇开始介绍。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章