别以为if slse很简单——决策树

1 篇文章 0 订阅

怎么分——熵与Gini指数

H(X)=-\sum_{x \in X} p(x)log(p(x)),表示信息量的期望,含义是混乱程度,也是对随机变量编码所需的最小比特数。请参考之前的文章

信息增益建立在熵之上,是选择某特征之后熵减少的多少(熵减少即信息增加),等于信息熵-条件熵,Gain(D,A) = H(D) - H( D | A)

基尼不纯度 Gini(X)= \sum_{x \in X}p(x)(1-p(x))=1- \sum^K_{k=1}p^2_k,它表示是分错的概率的期望。Gini不纯度其实可以看作是熵的近似值,形式一样没有取对数更容易计算,二分类时,都是概率取0.5时达到最大值。Gini不纯度是一种不等性度量,取值[0,1],当数据完全相等时取0.

K表示类别数目,那么对于数据集(样本集合)D,它的基尼不纯度:Gini(D)=1-\sum^K_{k=1}(\frac{C_k}{D})^2

D经过特征A后分裂为两个子集,新的不纯度就是两个子集不纯度的加权和(因为Gini不纯度本来也是加权和):Gini(D,A)= \frac{D_1}{D}Gini(D_1) + \frac{D_2}{D}Gini(D_2).我们的目标就是使得分裂后的不纯度最小。

ID3

ID3是最早的决策树,使用信息增益作为分裂准则。特点是特征必须是离散的,离散意味着特征是类别特征,那就也意味着特征不会被复用(该特征下的结果只能是属于或者不属于,而像连续特征可以换个阈值重新使用)。而既然是类别特征,当然类别越多分得越细(ID3可能是多叉树),所以ID3会倾向于选择类别较多的特征(直接念身份证号得了)。但其实不是说特征类别更多带来的增益就一定更大,从熵的公式看,他们极有可能是一样的,,只是可能性更大,即便带来的增益相等,类别过多将原有数据集划分为更多更小的子集,极容易过拟合。

此外ID3没有考虑缺失特征带来的影响。

ID3的作者Quinlan继续提出了C4.5。特征类别过多时这个特征自身的熵会升高(称为属性熵split info),所以可以将信息增益调整为信息增益率=信息增益/属性熵,这样就限制了属性熵的值不能过高。其实信息增益率可以看作是信息增益与代价的比(属性熵越大代价越大),核心目的仍然是为了让信息增益最大(在特征为连续值时,只使用信息增益)。信息增益率又有可能偏向于类别较少的特征,所以C4.5真正的做法是先选取信息增益高于均值的几种,在这几种里面再按照信息增益率选取。

对于特征值缺失的问题,意思是对于某一类别特征,有一些样本我们不知道它到底属于哪一类。所以在计算增益时就只以不缺失的数据来计算;当正好选取了缺失的特征后,对于不知道它属于哪一类的样本,就按照概率进行分类。

CART

CART树最大特定是既可以用于分类也可以用于回归。首先它是二叉树,递归划分,对于分类可以化为one vs others,对于连续特征,则选定某一特征的一个值,按大小分为两类。CART使用Gini不纯度代替熵,避免了对数运算。

怎么剪——剪枝

决策树在训练过程中采取贪婪策略的方法进行分裂,很容易过拟合。控制深度,叶子节点数的方法又有点武断(这种方法其实叫做预剪枝)。

关于后剪枝方法,C4.5使用悲观剪枝PEP(Pessimostic Error Pruning),CART树中可以使用代价复杂度的方法进行剪枝CCP。

PEP的好处是决策树的生成和剪枝使用相同的训练集。它比较的是待剪去的子树和新的叶子结点处的误判率。前者使用公式:

(\sum E_i +0.5*L)/N_iE_i表示该子树每个叶子结点的误判个数,L表示有多少个叶子结点,N表示该子树根节点处的总数。

而新叶子节点处的误判率就很好算了,误判数目/总数。新叶子节点误判率P_{newleaf}总是高于子树误判率P_{subtree},当差距小于一个阈值时就认为可以剪枝。这个阈值通过子树的标准差来确定:正确判断与误判看作二项分布,那么sigma^2=np(1-p)

CCP涉及到代价,那自然是在追求准确度的基础上增加一项正则项,正则项通过叶子节点的数目表示树结构的复杂度。所以代价复杂度函数就是:

R_\alpha(T) =R(T) + \alpha \cdot \left | f(T) \right |= 错误率 + \alpha \cdot叶子结点个数

假设在节点t处剪枝,以t为根节点的子树为T_t,剪枝前后的代价复杂度函数分别为R_\alpha(T)R_\alpha(T-T_t)

R_\alpha(T-T_t) - R_\alpha(T) \\ = R(T-T_t) -R(T) +\alpha (\left | f (T -T_t) \right | - \left | f(T) \right |) ........(1) \\ = R(t) -R(T_t) + \alpha(1-\left | f(T_t) \right |).......(2)

解释一下从(1)到(2)的推导。有两点问题首先要清楚,树的误差体现在其叶子节点的加权误差(\sum p(x) e(x));剪枝的过程本质是用一个节点代替其底部更深的所有节点。所以剪枝前后的结构对应的误差R(T-T_t) -R(T)等价于节点t处的误差和以t为根的树的叶子节点的误差:R(t) -R(T_t)。而剪枝前后的树的复杂度变化就是叶子节点个数的变化,剪枝之后叶子结点数首先减少了f(T_t)个,但剪枝处的节点t成为新的一个叶子节点,所以叶子节点的变化情况是(1-\left | f(T_t) \right |)

所以在确定了\alpha之后,所需要做的就是尝试删除一个个节点之下的树,寻找使得剪枝前后代价函数变化最小的那个节点。当然,剪枝之后还是要在测试集中测试,选择最优的作为最终的决策树。这也就是所谓的生成子树序列+测试集上的交叉验证。

但是,在更多的资料中,会令(2)等于0,可以得到\alpha = \frac{R(t) - R(T_t)}{ \left | f(T_t) \right | -1 }。这是认为剪枝的充分条件是剪枝前后的代价函数相等,剪枝前后的代价函数可以相等,但是误差肯定是增大的,所以分子大于0,所以\alpha>0。\alpha有无限多,我们不能一个个试,但是子树的结构是有限的。仔细看,\alpha其实可以代表剪枝带来的增益,分子越小,损失变化越小;分母越大,表示叶子节点减小得越多。所以真正的做法是遍历节点剪枝,得到\alpha最小的剪枝处,迭代多次直至剪枝之后只剩下根节点,就可以得到子树构成的序列和对应的\alpha构成的序列,将这些序列对应的模型再代入测试集验证。

怎么用——sklearn实战

傻瓜式操作,只需要创建一个实例类,然后对训练数据执行fit()操作。

在tree的实例类中可以指定我们分裂所使用的度量方法,可以是entropy,可以是gini(默认)

训练的数据应该是在csv文件中指定特征后的数据

这里的score不是指决策树预测的概率值,因为输入样本落入某节点必须是属于某一类的(硬分类)。注意到这个函数同时需要输入标签值,它返回的其实只是一个值,意为预测的准确率accuracy。

from sklearn import tree

clf = tree.DecisionTreeClassifier(criterion="entropy")# criterion选择entropy,这里表示选择ID3算法
clf = clf.fit(Xtrain, Ytrain)
score = clf.score(Xtest, Ytest) #返回预测的准确度accuracy

可视化

out_file指定生成的dot文件位置。dot文件是微软开发的用于页面布局的文件,可以使用Word打开。打开之后可以发现它保存的就是各个节点的信息:该节点的信息增益,颜色,所属类别等。

import graphviz
dot_data = tree.export_graphviz(clf, out_file=".\Tree.dot"
                                ,feature_names = feature_name
                                ,class_names=["琴酒","雪莉","贝尔摩德"]
                                ,filled=True
                                ,rounded=True
                               )

汉字支持

dot文件内有一个关键字fontname,它指定了字符的编码格式,所以我们为了支持汉字,核心就是将原有的编码格式Helvetica(在拉丁文中意为“瑞士的”)改为微软雅黑。可以使用代码自动修改。

import re
# 打开 dot_data.dot,修改 fontname="支持的中文字体"
f = open("./Tree.dot", "r+", encoding="utf-8")
open('./Tree_utf8.dot', 'w', encoding="utf-8").write(re.sub(r'fontname=helvetica', 'fontname="Microsoft YaHei"', f.read()))
f.close()

在修改之后可以open,得到一个TextIOWarpper类型的变量,对其取read()得到文件中的字符串,类型为str。然后可以使用 pydotplus.graph_from_dot_data保存:

with open((r'./Tree_utf8.dot',"r",encoding='utf-8') as f:

    text = f.read() 

    graph = pydotplus.graph_from_dot_data(text)

    graph.write_png("试试.png")   

    graph.write_pdf("iris.pdf") 

或者使用命令行保存:

dot -Tjpg Tree.dot -o tree.jpg

怎么解读画出的图像呢。

基尼不纯度gini应该是越来越小的。每个圆角矩形代表一个节点,在每个节点中alue的维度和标签的种类数一致,三种类别中符合以上条件的数目。他们的数目之和即这个节点的samples值,而每层的samples之和就是测试集的总数。有的节点的value中,只有一个非零值,代表只有该类符合之上的条件,所以gini不纯度也是0,这时候该节点就成为叶子节点,不会继续向下生长。决策树会倾向于一直生长(除非指定深度),直至使得每个叶子节点的gini等于0,所以容易过拟合,极端情况就是为每个样本量身定制了一份判断条件。所以为了防止过拟合,除了限制生长的深度,还可以指定叶子所需的最小样本数,样本数再小就没必要再生长了。

Reference:

1.sklearn手册https://sklearn.apachecn.org/docs/0.21.3/11.html

2.少年阿斌https://www.cnblogs.com/wqbin/p/11689709.html

3.https://zhuanlan.zhihu.com/p/85731206

4.CCPhttp://mlwiki.org/index.php/Cost-Complexity_Pruning

5.https://zhuanlan.zhihu.com/p/76667156

6.https://www.jianshu.com/p/b90a9ce05b28

7.https://zhuanlan.zhihu.com/p/93936294

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值