一、决策树
核心:分而治之
三步走:特征选择;决策树的生成;决策树的剪枝
主要算法:ID3(特征选择:信息增益)、C4.5(特征选择:信息增益率)和CART(特征选择:gini指数)
1. 概述
决策树的学习的过程是一个递归选择最优特征的过程,对应着对特征空间的划分。
开始,构建根节点,将所有的训练数据都放在根节点上,选择一个最优特征(特征选择),按照这一特征将训练数据集分割为子集,使得各个子集有一个在当前条件下最好的分类(损失函数优化)。
如果被分割的子集已正确分类,那么构建叶子节点。
如果还有子集不能正确分类,继续从剩下的特征中选择最优特征,继续分割数据集,构建相应的节点。
递归下去,知道所有训练数据子集被基本正确分类或者没有合适的特征为止。
最后每个子集都被分到叶子节点上,即有了明确的分类。
如上生成的决策树可能对训练数据有很好的分类能力,但是对未知的测试数据未必有很好的分类能力,即发生过拟合现象。
需要对已生成的树进行自下而上的剪枝,使模型变得简单,有更好地泛化能力。
具体的就是去掉过于细分的叶节点,使其父节点或更高的节点作为叶节点。
决策树的生成对应于模型的局部选择,决策树的剪枝对应于模型的全局选择。决策树的生成只考虑局部最优,决策树的剪枝考虑全局最优。
2. 特征选择
2.1. 信息增益
2.2. 信息增益率
2.3.基尼指数
# 对y的各种可能的取值出现的个数进行计数.。其他函数利用该函数来计算数据集和的混杂程度
def uniquecounts(rows):
results = {
}
for row in rows:
#计数结果在最后一列
r = row[len(row)-1]
if r not in results:results[r] = 0
results[r]+=1
return results # 返回一个字典
# 熵
def entropy(rows):
from math import log
log2 = lambda x:log(x)/log(2)
results = uniquecounts(rows)
#开始计算熵的值
ent = 0.0
for r in results.keys():
p = float(results[r])/len(rows)
ent = ent - p*log2(p)
return ent
3. 决策树的生成
决策树的生成算法有很多变形,这里介绍几种经典的实现算法:ID3算法,C4.5算法和CART算法。这些算法的主要区别在于分类结点上特征选择的选取标准不同。下面详细了解一下算法的具体实现过程。
3.1. ID3算法
ID3算法的核心是在决策树的各个结点上应用信息增益准则进行特征选择。具体做法是:
-
从根节点开始,对结点计算所有可能特征的信息增益,选择信息增益最大的特征作为结点的特征,并由该特征的不同取值构建子节点;
-
对子节点递归地调用以上方法,构建决策树;
-
直到所有特征的信息增益均很小或者没有特征可选时为止。
3.2. C4.5算法
C4.5算法与ID3算法的区别主要在于它在生产决策树的过程中,使用信息增益比来进行特征选择。
3.3. CART算法
分类与回归树(classification and regression tree,CART)与C4.5算法一样,由ID3算法演化而来。CART假设决策树是一个二叉树,它通过递归地二分每个特征,将特征空间划分为有限个单元,并在这些单元上确定预测的概率分布。
CART算法中,对于回归树,采用的是平方误差最小化准则;对于分类树,采用基尼指数最小化准则。
#定义节点的属性
class decisionnode:
def __init__(self,col = -1,value = None, results = None, tb = None,fb = None):
self.col = col # col是待检验的判断条件所对应的列索引值
self.value = value # value对应于为了使结果为True,当前列必须匹配的值
self.results = results #保存的是针对当前分支的结果,它是一个字典
self.tb = tb ## desision node,对应于结果为true时,树上相对于当前节点的子树上的节点
self.fb = fb ## desision node,对应于结果为true时,树上相对于当前节点的子树上的节点
# 基尼不纯度
# 随机放置的数据项出现于错误分类中的概率
def giniimpurity(rows):
total = len(rows)
counts = uniquecounts(rows)
imp =0
for k1 in counts:
p1 = float(counts[k1])/total
for k2 in counts: # 这个循环是否可以用(1-p1)替换?
if k1 == k2: continue
p2 = float(counts[k2])/total
imp+=p1*p2
return imp
# 改进giniimpurity
def giniimpurity_2(rows):
total = len(rows)
counts = uniquecounts(rows)
imp = 0
for k1 in counts.keys():
p1 = float(counts[k1])/total
imp+= p1*(1-p1)
return imp
#在某一列上对数据集进行拆分。可应用于数值型或因子型变量
def divideset(rows,column,value):
#定义一个函数,判断当前数据行属于第一组还是第二组
split_function = None
if isinstance(value,int) or isinstance(value,float):
split_function = lambda row:row[column] >= value
else:
split_function = lambda row:row[column]==value
# 将数据集拆分成两个集合,并返回
set1 = [row for row in rows if split_function(row)]
set2 = [row for row in rows if not split_function(row)]
return(set1,set2)
# 以递归方式构造树
def buildtree(rows,scoref = entropy):
if len(rows)==0 : return decisionnode()
current_score = scoref(rows)
# 定义一些变量以记录最佳拆分条件
best_gain = 0.0
best_criteria = None
best_sets = None
column_count = len(rows[0]) - 1
for col in range(0,column_count)