决策树算法
45 |
概述
46 |
决策树是一种实现分治策略的层次数据结构。
47 |
举个 🌰 要不要去见相亲对象呢?
48 |
A["年龄"] -->|>=30| no1("不见")
49 | A -->|<30| B["长相"]
50 | B -->|"丑"| no2("不见")
51 | B -->|"中等偏帅"| C["收入"]
52 | C -->|"<100k"| no3("不见")
53 | C -->|">=500"| yes1("见!💖")
54 | C -->|"(100k, 500k)"| D["程序🐵"]
55 | D -->|"是"| no4("什么鬼?")
56 | D -->|"否"| yes2("见!")
57 |
58 |
这就是一棵已经构造好的决策树,其中,每个矩形代表一个特征,每个圆角矩形代表一个类标签。输入数据后,根据不同的特征值过滤最终可以得到输入数据所属的类别。决策树算法的目标就是从一堆原始数据里构造这么一棵决策树,以作为预测、判断和决策的参考。
59 |
决策树算法可以用作分类(分类决策树)也可以用作回归(回归决策树),可以用于提取特征值、相关性分析、建立专家系统、搜索排序、邮件过滤等。
60 |
在2006年,国际数据挖掘社区推出的《数据挖掘十大算法》中评选出来的十大算法里有两个是决策树相关的算法(C4.5和CART),可见决策树算法算法在数据挖掘中应用的广泛程度。
61 |
ID3(Iterative Dichotomiser 3)
62 |

63 |
ID3诞生于70年代末,由Ross Quinlan提出。这个算法倾向更小的树,并且越能带来熵减的决策节点离根节点越近。
64 |
ID3每次确定划分数据集S的特征时,会计算每一个未使用的属性,计算其[熵(Entropy)](https://en.wikipedia.org/wiki/Entropy_(information_theory)H(S)
,然后选取导致最小熵值的属性作为特征值,分割数据集S得到子数据集。这是一种贪心算法,并不保证得到一棵最小树(找最小树的算法是NP完全算法),只是准确度和效率的一个权衡的结果。然后对每一个子数据集进行同样的处理,并且在下列情况下停止递归处理:
65 |
66 | - 子数据集所有元素归属同一类别,这时创建叶子节点并标记为该类别
67 | - 没有任何未使用的属性,这时创建叶子节点并标记为子数据集中出现次数最多的类别
68 | - 子数据集中没有元素,这时创建叶子节点,并标记为父数据集中出现最多的类别
69 |
70 |
熵值的计算方式为:
71 |
H(S) = - \sum_{x \in X}p(x)log_{2}p(x)
72 |
73 |
74 | - S: 当前数据集
75 | - X: S中的所有类别
76 | - p(x): 类别x在S中所占的比例
77 |
78 |
用ID3构建一棵决策树的过程可以用下述伪代码来表示:
79 |
tree = {};
80 | tree.root = ID3(S);
81 | function ID3(S){
82 | IF 数据集S纯度达到标准或者符合其它终止条件 THEN 返回类标签
83 | ELSE
84 | 计算所有特征的熵值
85 | 取最小熵值对应特征划分数据集S
86 | 创建分支节点B
87 | FOR 每个划分的子数据集S'
88 | B.splitS' = ID3(S')
89 | RETURN 分支节点B
90 | }
91 |
92 |
ID3可视化
93 |
<div id="visualize-id3">
94 | <nav class="toolbar">
95 | <div class="menu menu-horizontal u-1-2">
96 | <ul class="menu-list">
97 | <li class="menu-item">
98 | <span class="color-select menu-link" data-color="#33CCFF" style="background-color: #33CCFF;"> </span>
99 | </li>
100 | <li class="menu-item">
101 | <li class="menu-item">
102 | <span class="color-select menu-link" data-color="#009933" style="background-color: #009933;"> </span>
103 | </li>
104 | <li class="menu-item">
105 | <span class="color-select menu-link" data-color="#FF6600" style="background-color: #FF6600;"> </span>
106 | </li>
107 | <li class="menu-item">
108 | <span class="color-select menu-link" data-color="#FFC508" style="background-color: #FFC508;"> </span>
109 | </li>
110 | <li class="menu-item">
111 | <span class="btn-clear menu-link">清空</span>
112 | </li>
113 | <li class="menu-item">
114 | <span class="menu-link">选择颜色并画点</span>
115 | </li>
116 | </ul>
117 | </div>
118 | </nav>
119 | <canvas class="training-canvas" width="450" height="450"></canvas>
120 | <svg class="decision-tree" width="450" height="450"><g/></svg>
121 | </div>
122 |
123 |
./decision-tree/main.css
124 |
125 |
../lib/d3/d3.js
126 | ../lib/dagre-d3/dist/dagre-d3.js
127 | ./decision-tree/main.dist.js
128 |
129 |
forked from lagodiuk/decision-tree-js
130 |
数据示例(x和y为特征列,color为预测分类)
131 |
{
132 | x: 200,
133 | y: 100,
134 | color: '#FFC508'
135 | }
136 |
137 |
但这个例子不是非常纯粹的ID3实现,它对整数值进行了离散化。譬如这里是根据取值把坐标轴的划分为两个区间,每个区间对应同一个特征值的两个取值范围(譬如y>=200和y<200)。这里借鉴了一部分C4.5处理连续值的办法。
138 |
C4.5(successor of ID3)
139 |
这个算法也是Ross Quinlan提出的,作为对ID3的改进。
140 |
算法过程与ID3算法在宏观上一致。
141 |
改进点:
142 |
选择特征列是通过信息增益率(而不是熵值)
143 |
其中,**信息增益(IG)**指的是计算某一个属性导致的熵值下降的幅度。其计算方法为:
144 |
IG(S,a) = H(S) - H(S|a)
145 | = H(S) - (
146 | \sum_{v \in vals(a)} \frac{|\{x \in S | x_{a} = v\}|}{|S|} \cdot
147 | H({x \in S|x_{a} = v})
148 | )
149 |
150 |
而,**信息增益率(IGR)**则是
151 |
IGR(S,a) = IG(S,a)/IV(S,a)
152 |
153 |
其中,IV(S,a)的定义为
154 |
IV(S,a) = - \sum_{v \in vals(a)} \frac{|\{x \in S | x_{a} = v\}|}{|S|} \cdot
155 | log_{2}(\frac{|\{x \in S | x_{a} = v\}|}{|S|})
156 |
157 |
在树构造的过程中剪枝
158 |
所谓的剪枝,就是用节点的子树或者子叶子节点来替换这个节点,以得到更低的错误率。为解决ID3过度拟合的问题,C4.5的软件包实现了基于悲观剪枝(Pessimistic Pruning)方法的剪枝。
159 |
这个方法通过递归地计算目标节点分支的错误率来获得这个目标节点的错误率。例如,对于一个有N个实例和E个错误(也就是和该叶子节点类别不一致的实例)的叶子节点,用$(E + 0.5)/N$
表示这个叶子节点的错误率。假设一个节点有L个子节点,这些子节点共有$\sum E$
个错误和$\sum N$
个实例,那么该节点的错误率为$(\sum E + 0.5xL)/\sum N$
。假设这个节点被它的最佳子节点替代后,在训练集上得到的错误分类数为J,那么如果$(J + 0.5)$
在$(\sum E + 0.5xL)$
的1标准差范围内,悲观剪枝法就采用这个子节点来替代该节点。
160 |
处理连续数据
161 |
选取多决策准则来产生分支。譬如上述可视化的实现中,对连续值x采取了这样的策略:
162 |
163 | - 对每一个x的取值,把测试集分为(>= x)和(< x)两个分支
164 | - 对每一个分支计算信息增益
165 |
166 |
不过Quinlan论文中讲到的两个处理连续值的方法比这个复杂。第一个方法是对每一个属性用信息增益率选择阀值,第二个方法是用Risannen的最小描述长度原理(MDL)。
167 |
处理数据缺失
168 |
在Quinlan的文献中有很多处理数据缺失的议题,其中比较重点的是三个。这里简单介绍一下问题并且提几个典型的解法。
169 |
生成树分支的时候需要比较多个属性值,但有些属性在某些实例中没有值
170 |
171 | - 直接忽略实例
172 | - 填充属性值(常用值、均值或者最能带来信息增益的值等)
173 |
174 |
选定属性后,没有该属性值的实例没有办法放入这个训练的任何输出中
175 |
176 | - 直接忽略实例
177 | - 选取常用值
178 | - 切分用例放到每一个输出下
179 |
180 |
构建好的树测试实例时有可能被测试用例缺失某个测试节点对应的属性值,这时如何让测试进行下去
181 |
182 | - 如果对于缺失值有单独分支,走这个分支
183 | - 选取最常用分支走
184 | - 确定最可能的属性值,并填充这个属性值
185 | - 不再走余下分支测试,直接设置成最常用类别
186 |
187 |
扩展阅读
188 |
C5.0/See5
189 |
CART(Classification and Regression Tree)
190 |
和C4.5的不同在于:
191 |
192 | - CART算法可以不转换的情况下直接处理连续型和标称型数据
193 | - CART算法没有停止准则,树会一直生长到最大尺寸
194 | - CART算法的剪枝步骤采用的是代价复杂度(Cost-Complexity Pruning)
195 |
196 |
多变量树
197 |
上面讨论的算法都是基于一个输入维度来划分数据的,而构造多变量树时每个决策节点都可以使用所有的输入维度,因此更加一般化。当然这个已经超出本文的讨论范围了
198 |
199 |
200 |
附录
201 |
参考资料
202 |
207 |
208 |
214 |
215 |