决策树是一种基本的分类与回归方法,我们不讲回归,没错,因为我几乎没用过回归,所以我不打算现在学它。
一. 决策树模型与学习
1.1 决策树模型
定义:分类决策树模型是一种描述对实例进行分类的树形结构。决策树由结点和有向边组成。结点有两种类型:内部结点和叶结点。内部结点表示一个特征或属性,叶结点表示一个类。
用决策树分类,从根节点开始,对实例的某一特征进行测试,根据测试结果,将实例分配到其子结点;这时,每一个子结点对应着该特征的一个取值。如此递归地对实例进行测试并分配,直至达到叶结点。最后将实例分到叶结点地类中。
决策树这一块的逻辑就像是代码中的if,else结构,所以也是我当时学统计机器学习最先学会的模型,毕竟我觉得逻辑要比概率模型来的更加简单一些。
1.2 决策树与if-then规则
1.3 决策树与条件概率分布
决策树还表示给定特征条件下类的条件概率分布。
1.4 决策树学习
决策树学习是由训练数据集估计条件概率模型。基于特征空间划分的类的条件概率模型有无穷多个。
决策树学习用损失函数表示这一目标。决策树学习的损失函数通常是正则化的极大似然函数。
决策树学习的算法通常是一个递归地选择最优特征,并根据该特征对训练数据进行分割。
以上方法生成的决策树可能对训练数据有很好的分类能力,但对未知的测试数据却未必有很好的分类能力,即可能发生过拟合现象。(是的,我也这么觉得,然后后面就有了剪枝的操作)
二. 特征选择
2.1 特征选择问题
特征选择在于选取对训练数据具有分类能力的特征。这样可以提高决策树学习的效率。如果利用一个特征进行分类的结果与随机分类的结果没有很大差别,则称这个特征是没有分类能力的。经验上扔掉这样的特征对决策树学习的精度影响不大。通常特征选择的准则是信息增益或信息增益比。
其实我觉得这个特征选择的思路适用于绝大多数模型。
2.2 信息增益
我想这算是决策树中一个比较难的概念了,为了理解这个,跟老大请教了好几次。
在信息论与概率统计中,熵(entropy)是表示随机变量不确定性的度量。设X是一个去有限个值得离散随机变量,其概率分布为
则随机变量X得熵定义为
由定义可知,熵只依赖于X的分布,而与X的取值无关,所以也可将X的熵记作H(p),即
熵越大,随机变量的不确定性就越大,从定义可验证
嘻嘻,这个最小值就很好理解,只有一种可能就是0,最大值是怎么回事呢?-- 当所有可能情况的可能性都相同时,这个时候,不确定性最高,熵的值最大。至于为什么不确定性越大,熵越大,其实我的理解是,为了描述不确定性,才有了熵,所以这并不是很困扰的事情,不过看过不少文章,这个熵的来头很大,感兴趣的人可以多了解,我也不是很懂。
设有随机变量(X, Y), 其联合概率分布为
条件熵H(Y|X)表示在已知随机变量X的条件下随机变量Y的不确定性。随机变量X给定的条件下随机变量Y的条件熵(conditional entropy)H(Y|X),定义为X给定条件下Y的条件概率分布的熵对X的数学期望
这里,
当熵和条件熵中的概率由数据估计得到时,所对应的熵与条件熵分别称为经验熵(empirical entroy)和经验条件熵(empirical conditional entropy)。
信息增益表示得知特征X的信息而使得类Y的信息的不确定性减少的程度。
信息增益定义:特征A对训练数据集D的信息增益g(D,A),定义为集合D的经验熵H(D)与特征A给定条件下D的经验条件熵H(D|A)之差,即
一般地,熵H(Y)与条件熵H(Y|X)之差称为互信息(mutual information)。决策树学习中的信息增益等价于训练数据集中类与特征的互信息。
决策树学习应用信息增益准则选择特征。
2.3 信息增益比
以信息增益作为划分训练数据集的特征,存在偏向于选择取值较多的特征的问题。使用信息增益比可以对这一问题进行较正。
三. 决策树的生成
3.1 ID3算法
ID3算法的核心是在决策树各个结点上应用信息增益准则选择特征,递归地构建决策树。具体方法是:从根结点开始,对结点计算所有可能的特征的信息增益,选择信息增益最大的特征作为结点的特征,由该特征的不同取值建立子结点;再对子结点递归地调用以上方法,构建决策树;直到所有特征地信息增益均很小或没有特征可以选择为止。最后得到一个决策树。ID3相当于使用极大似然法进行概率模型的选择。
3.2 C4.5的生成算法
C4.5算法与ID3算法相似,C4.5算法对ID3算法进行了改进。C4.5在生成的过程中,用信息增益比来选择特征。
四. 决策树的剪枝
决策树生成算法递归地产生决策树,直到不能继续下去为止。这样产生地树往往对训练数据的分类很准确,但对位置的测试数据的分类却没有那么准确,即出现过拟合现象。过拟合的原因在于学习时过多地考虑如何提高对训练数据地正确分类,从而构建出过于复杂地决策树。决策这个问题地办法是考虑决策树地复杂度,对已生成的决策树进行简化。
在决策树学习中将已生成的树进行简化的过程称为剪枝(pruning)。具体地,剪枝从已生成的树上裁掉一些子树或叶结点,并将其根结点或父结点作为新的叶结点,从而简化分类树模型。
决策树的剪枝往往通过极小化决策树整体的损失函数或代价函数来实现。
具体的损失函数和算法流程请参考原书。
五. 代码
1. 熵,经验条件熵,信息增益。
这三个算是整个决策树的灵魂,而且代码实现都比较清晰简单。
# 熵
def calc_ent(datasets):
data_length = len(datasets)
label_count = {}
for i in range(data_length):
label = datasets[i][-1]
if label not in label_count:
label_count[label] = 0
label_count[label] += 1
ent = -sum([(p/data_length)*log(p/data_length, 2) for p in label_count.values()])
return ent
# 经验条件熵
def cond_ent(datasets, axis=0):
data_length = len(datasets)
feature_sets = {}
for i in range(data_length):
feature = datasets[i][axis]
if feature not in feature_sets:
feature_sets[feature] = []
feature_sets[feature].append(datasets[i])
cond_ent = sum([(len(p)/data_length)*calc_ent(p) for p in feature_sets.values()])
return cond_ent
# 信息增益
def info_gain(ent, cond_ent):
return ent - cond_ent
2. ID3算法生成决策树
这一部分代码的流程跟书中的一致,值得注意的就是递归生成中遍历某个特征的所有取值的步骤,在这个步骤中,会在当前层生成一个结点,这个结点作为父结点,然后再将所有取值生成的“树根”作为子结点。
def train(self, train_data):
"""
input:数据集D(DataFrame格式),特征集A,阈值eta
output:决策树T
"""
_, y_train, features = train_data.iloc[:, :-1], train_data.iloc[:, -1], train_data.columns[:-1]
# 1,若D中实例属于同一类Ck,则T为单节点树,并将类Ck作为结点的类标记,返回T
if len(y_train.value_counts()) == 1:
return Node(root=True,
label=y_train.iloc[0])
# 2, 若A为空,则T为单节点树,将D中实例树最大的类Ck作为该节点的类标记,返回T
if len(features) == 0:
return Node(root=True, label=y_train.value_counts().sort_values(ascending=False).index[0])
# 3,计算最大信息增益 同5.1,Ag为信息增益最大的特征
max_feature, max_info_gain = self.info_gain_train(np.array(train_data))
max_feature_name = features[max_feature]
# 4,Ag的信息增益小于阈值eta,则置T为单节点树,并将D中是实例数最大的类Ck作为该节点的类标记,返回T
if max_info_gain < self.epsilon:
return Node(root=True, label=y_train.value_counts().sort_values(ascending=False).index[0])
# 5,构建Ag子集
node_tree = Node(root=False, feature_name=max_feature_name, feature=max_feature)
feature_list = train_data[max_feature_name].value_counts().index
for f in feature_list:
sub_train_df = train_data.loc[train_data[max_feature_name] == f].drop([max_feature_name], axis=1)
# 6, 递归生成树
sub_tree = self.train(sub_train_df)
node_tree.add_node(f, sub_tree)
# pprint.pprint(node_tree.tree)
return node_tree
六. 引用
1. 《统计机器学习》 -- 李航