一、本章内容
线性回归模型需要拟合所有样本(局部加权线性回归除外),当数据拥有众多特征且特征间关系复杂时,构建全局模型就显得太难了。一种可行的方法是将数据集切分成很多份易建模的数据,然后利用线性回归技术建模。如果首次切分后仍难以拟合线性模型就继续切分,在这种切分模式下,树结构和回归法相当有用。
CART(Classification And Regression Trees,分类回归树)算法,即可用于分类,也可用于回归。其中的树剪枝技术用于防止树的过拟合。
决策树不断将数据切分成小数据集,直到所有目标变量完全相同,或者数据不能再切分为止。决策树是一种贪心算法,它要在给定的时间内做出最佳选择,但不关心能否达到全局最优。
前面介绍的决策树构建算法是ID3。ID3的做法是每次选取当前最佳的特征来分隔数据,并按照该特征的所有可能来切分。这种切分过于迅速,且不能处理连续性特征。另外一种方法是二元切分法,它易于对树构建过程进行调整以处理连续性特征。
CART是十分著名的树构建算法,它使用二元切分来处理连续性变量,对其稍作修改就可处理回归问题。CART算法也使用一个字典来存储树的数据结构,该字典含:
- 待切分的特征
- 待切分的特征值
- 右子树,不需切分时,也可是单个值
- 左子树,右子树类似
CART可构建两种书:回归树(regression tree),其每个叶节点包含单个值;模型树(model tree),其每个叶节点包含一个线性方程。创建树的函数createTree()的伪代码大致如下:
找到最佳的待切分特征 :
如果该节点不能再分,将该节点存为叶节点
执行二元切分
在右子树调用createTree()方法
在左子树调用createTree()方法
1-1 CART用于回归
回归树假设叶节点是常数值,这种策略认为数据中的复杂关系可用树结构来概括。为了构建以分段常数为叶节点的树,需要度量数据的一致性。使用ID3算法构建的决策树进行分类,会在给定节点时计算数据的混乱度。计算连续性数值的混乱度是非常简单的,首先计算所有数值的均值,然后计算每条数据的值到均值的差值。为了对正负差值同等看待,一般使用绝对值或平方值来代替上述差值。这里使用的是总方差(平方误差的总值),总方差=均方差*样本数。
1-1-1 构建树
构建树首先要实现如何切分数据集,使用函数chooseBestSplit()函数切分数据集。给定误差计算方法,该函数寻找数据集上的最佳二元切分方式,一旦停止切分会生成一个叶节点。它遍历所有的特征及其可能的取值来找到使误差最小化的切分阈值。函数的伪代码如下:
对每个特征 :
对每个特征值 :
将数据集切分成两份
计算切分的误差
如果当前误差小于当前最小误差,那么将当前切分设定为最佳切分并更新最小误差
返回最佳切分的特征和阈值
切分停止的三个条件:
- 剩余特征值的数目为1
- 如果切分数据集后的误差提升不大,不应进行切分操作,而直接创建叶节点
- 两个切分后的子集中的一个的大小小于用户定义的参数tolN时
1-2 树剪枝
决策树也可使用测试集上某种交叉验证技术发现过拟合。通过降低决策树的复杂度来避免过拟合的过程称为剪枝(pruning),在chooseBestSplit()中提前终止条件,实际上是一种所谓的预剪枝(prepruning)操作。另一种剪枝需要使用测试集和训练集,称为后剪枝(postpruning)。
树构建算法对输入参数tolS和tolN(预剪枝)非常敏感。停止条件tolS对误差的数量级十分敏感。通过不断修改停止条件来得到合理结果不是好办法,事实上,我们常常不确定需要寻找什么样的结果,而这正是机器学习所关注的内容,计算机应可给出总体的概貌。
后剪枝,使用测试集来对树进行剪枝,不需要用户指定参数,是一种更理想化的剪枝方法。使用后剪枝,需要将数据集分为测试集和训练集。首先指定参数,使得构建的树足够大和复杂,便于剪枝。接着,从上到下找到叶节点,用测试集来判断将这些叶节点合并是否能够降低测试误差,如果是的话就合并,合并也称为塌陷处理,在回归树中一般采用取需要合并的所有子树的平均值。函数prune()的伪代码如下:
基于已有的树切分测试数据 :
如果存在任一子集是一棵树,则在该子集递归剪枝过程
计算将当前两个叶节点合并后的误差
计算不合并的误差
如果合并会降低误差的话,那就将叶节点合并
后剪枝可能不如预剪枝有效,一般,为了需求最佳模型可同时使用两种剪枝技术。
1-3 模型树
用树建模,除了把叶节点简单地设定为常数值外,还可把叶节点设定为分段线性函数,这里的分段线性是指模型由多个线性片段组成。模型树的可解析性是它由于回归的特点之一,它还具有更高的预测准确度。
模型树、回归树以及其他模型中那个更好,可使用相关系数 R2 来衡量。具体使用numpy.corrcoef(yHat, y, rowvar=0)
来求解。
1-4 使用Python的Tkinter库创建GUI
- 使用如下命令,会出现一个小窗口
>>> from Tkinter import *
>>> root = Tk()
- 在窗口显示一些文字,输入如下命令
>>> myLabel = Label(root, text="Hello World")
>>> myLabel.grid()
- 为了使程序完整,输入如下命令。此命令将启动事件循环,使窗口在众多事件中可以相应鼠标点击、按键和重绘等动作。
>>> root.mainloop()
Tkinter的GUI由一些小部件(Widget)组成。小部件,指的是文本框(TextBox)、按钮(Button)、标签(Label)和复选按钮(CheckButton)等对象。上例中myLabel的.grid()方法把myLabel的位置告诉了布局管理器。详细的实例见代码treeExplore.py
1-4-1 集成Matplotlib和Tkinter
可以将Matplotlib绘制的图像放在GUI上,通过修改Matplotlib后端达到在Tkinter的GUI上绘图的目的。Matplotlib的构建程序包含一个前端,即面向用户的一些代码,如plot()和scatter()方法。Matplotlib同时也创建了一个后端,用于实现绘图和不同应用间的接口。通过改变后端可将图像绘制在PNG、PDF、SVG等格式的文件上。
接下来,设置Matplotlib的后端为TkAgg(Agg是一个C++的库,可从图像创建光栅图),TkAgg可在所选的GUI框架上调用Agg,把Agg呈现在画布上。可在Tk的GUI上放置一个画布,并用.grid()来调整布局。
具体的集成代码,请参照treeExplore.py,treeExplore.py绘制的图形如下所示
图 默认的treeExplore图形用户界面,该界面同时显示了输入数据和一个回归树模型,其中参数tolN=10,tolS=1.0
图 用treeExplore的GUI构建的模型树,tolN=10,tolS=1.0。与回归树相比,模型树获得了更好的预测效果
1-5 使用的函数
函数 | 功能 |
---|---|
map(function, sequence[, sequence, …]) -> list | 根据提供的函数对指定序列做映射,简单的示例,map(float, curLine):对curLine中的元素作浮点转换 |
numpy.var(a) | 计算样本集的方差 |
pow(x, y[, z]) | 函数是计算x的y次方,如果z在存在,则再对结果进行取模,其结果等效于pow(x,y) %z,pow() 通过内置的方法直接调用,内置方法会把参数作为整型,而 math 模块则会把参数转换为 float。 |
numpy.linalg.det(xTx) | 求方阵的行列式 |
xTx.I | 对矩阵求逆 |
corrcoef(yHat, y, rowvar=0) | 求解yHa |