用一棵树来表示数据的分类或回归规则。每个节点表示一个属性的判别,每个分支表示判别的结果,每个叶节点表示一个类别或一个数值。决策树的生成过程是不断地选择最优的属性来划分数据集,使得每个子集的纯度越来越高。
或者说,决策树是在不断的按照某个属性,把训练样本细分为多个子集,直到已经只含有某一类的样本。
决策树的纯度可以用信息熵或基尼系数等指标来度量,它们反映了数据集合中不同类别的混乱程度。
选择最优的划分属性
随着不断划分,我们希望决策树的结点纯度越来越高。
信息熵,可以表征随机变量分布的混乱程度,某个事件发生不确定度越大,熵越大,随机变量 X 中 i 事件发生可能性为 pi,(或者说,样本集 X 中 i 类样本所占比例为 pi),信息熵定义为:
Ent(X)=−i=1∑Npilog2pi
熵的计算只与事件概率有关,与值无关,且约定p=0时plogp=0
条件信息熵,已知X的条件下随机变量Y的不确定性。
Ent(Y∣X)=−x∈X∑P(x)Ent(Y∣X=x)=−x∈X∑P(x)y∈Y∑P(y∣x)logp(y∣x)=−x∈X,y∈Y∑P(x,y)logP(y∣x)=x∈X,y∈Y∑P(x,y)logp(x,y)p(x)
联合信息熵:
Ent(X,Y)=Ent(X,Y)−Ent(X)
互信息度量了两个变量之间相互依赖的程度
I(X;Y)=y∈Y,x∈X∑p(x,y)log(p(x)p(y)p(x,y))
信息增益表示在一个条件下,信息不确定性减少的程度(按某个特征划分数据集获得的增益):
Gain(X,Y)=H(Y)−H(Y∣X)
信息增益越大,意味着使用属性Y来进行划分所得的纯度提升越大,以此来选择最优划分属性。
若将数据序号这类作为条件,则不会有任何不确定性,但是这个条件是没有意义的,信息增益率在信息增益的基础上增加了惩罚项。
GainRate(X,Y)=H(Y)Gain(X,Y)
基尼系数(基尼不纯度)
与信息熵一样表征事件不确定性, 或者说,从数据集中随机抽取两个样本,其类别不一致的概率
Gini(X)=x∈X∑P(x)(1−P(x))=1−x∈X∑P(x)2
对于数据集Y,根据特征X的某个值x把Y分成Y∣X=x与Y∣X=x,此时条件基尼系数:
Gini(Y∣X=x)=P(Y∣X=x)Gini(Y∣X=x)+(1−P(Y∣X=x))Gini(Y∣X=x)
基尼系数也可以视为信息熵的近似,信息熵的泰勒展开第一项就是基尼指数。
剪枝 Pruning
是一种防止过拟合的方法,它可以通过删除不必要的子树或节点来降低决策树的复杂度,提高泛化能力。
- 预剪枝是在生成决策树的过程中,根据一些条件(如最小节点样本数、最大深度、信息增益、精度等)来判断是否继续划分
- 后剪枝是在生成完整的决策树后,从下往上检查每个子树是否对模型有贡献,如果没有或很小,就将其替换为叶节点或删除。
算法
ID3
ID3(Iterative Dichotomiser 3),是处理离散数据的决策树算法,可以归纳为以下几点:
- 使用所有没有使用的属性并计算与之相关的样本熵值]
- 选取其中熵值最小的属性
- 生成包含该属性的节点
python 伪代码,完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| dataset: list[list] def info_ent(dataset: list) -> float: ... def cond_info_ent(dataset: list, feature_id: list)->float:... def split_set(dataset, label_id, value): new_dataset = [] for sample in dataset: if sample[label_id] == value: new_dataset.append(list(sample)[:label_id] + list(sample)[label_id + 1:]) return np.array(new_dataset) def get_best_feature_label(dataset, labels) -> str: ent = info_ent(dataset) gains = [] for i in range(len(labels)): cond_ent = cond_info_ent(dataset, i) gain = ent - cond_ent gains.append(gain) best_feature = gains.index(max(gains)) return labels[best_feature] def create_tree(dataset, labels): classes = dataset[:, -1] if len(set(classes)) == 1: return classes[0] if len(labels) == 0: return max(list(classes), key=list(classes).count) best_label = get_best_feature_label(dataset, labels) best_label_id = labels.index(best_label) tree = {best_label: {}} del labels[best_label_id] feature_value = set([sample[best_label_id] for sample in dataset]) for value in feature_value: tree[best_label][value] = create_tree(split_set(dataset, best_label_id, value), labels) return tree create_tree(dataset, labels)
|
缺陷:
- 没有剪枝等操作,容易过拟合
- 无法处理带有缺失值的数据
- 信息增益计算方式会倾向选择特征选项较多的属性
- 类别不平衡会导致效果很差
变体:
- C4.5:改为使用信息增益率来选择属性,在树构造过程中进行悲观剪枝,对不完整数据进行处理(以不同概率划分到不同节点),讲连续属性离散化。
- CART:可以做回归任务,使用gini系数选择属性,采用代理测试估计缺失值,使用代价复杂性剪枝,对类别进行加权减少类别不平衡带来的影响,与 ID3 和 C4.5 的多叉树不同,CART是二叉树,CART 可多次重复使用特征。
CART
回归树
给定划分D+,D−,为了使得这个划分出的集合的值与标签误差最小,划分后节点的值显然应该是集合的平均值,ci=avg(yi∣xi∈D)。
对于选择哪个属性与分割方式,我们其最小化均方误差
E=xi∈D+∑(yi−ci)2+xi∈D−∑(yi−ci)2
由于给定划分时,ci我们通过取平均已知,所以只需要遍历所有可能的划分,取均方误差最小的划分方式即可。
分类树
分类树用基尼指数选择最优特征。
Gini(X)=x∈X∑P(x)(1−P(x))=1−x∈X∑P(x)2
具体处理与ID3算法类似
离散属性的划分
对离散属性选取一个值,进行二元分割为等于这个值的与不等于这个值的,{A,B,C} -> {A},{B,C} 或 {B},{A,C} 或 {C},{A,B}
连续属性的划分
对连续属性取属性相邻值的平均值作为间断点,二元分类,{1,2,5},ε=1.5 或ε=3.5 -> {1},{2,5} 或 {1,2},{5}
剪枝操作
- 预剪枝:
- 当分割后的样本个数小于预定阈值
- 对于连续属性的划分,若其最终均方误差E的减小幅度小于预定阈值,则停止划分
- 后剪枝:树建立完成后,使用测试集自上而下判断节点划分是否能够降低误差,如果不能则合并这些节点。