如果你觉得这篇文章对你有帮助,记得点赞收藏哦。在这片文章中我将详细介绍sklearn.tree模型怎么使用,我查阅了很多资料,源码,国内外论坛,甚至查看了论文,解答了一些函数调用的疑惑,文章接着往下看,保证是干货。
(ps:这篇文章适合对决策树理论已经有详细了解的人阅读)可以查看我先前的文章理解理论知识:
机器学习之决策树(原理篇)
如果你有查看sklearn的官方文档,你就知道一共有4个简单的决策树模型,并且都被封装在了sklearn.tree中。它们分别是:
tree.DecisionTreeClassifier 分类树
tree.DecisionTreeRegressor 回归树
tree.ExtraTreeClassifier 额外的分类树(极其随机的分类树)
tree.ExtraTreeRegressor 额外的回归树(极其随机的回归树)
4种算法都可以选择性的使用信息熵或基尼系数作为分类标准,但信息熵并不影响它们是基于CART算法设计并改进的的。
我这里其实只需要全面介绍一种最经典的tree.DecisionTreeClassifier,其它三个就在它的改进上介绍进行,而且4者源代码几乎所有部分都是相同的。我将会以决策树构建的三要素,节点的选择,决策树的生成以及剪枝展开介绍。
其实节点的选择有两个重要的参数,分别是
1 criterion:{“gini”, “entropy”}, default=”gini”
2 splitter:{“best”, “random”}, default=”best”
很容易理解,这个参数代表你想选择的是信息熵或者基尼指数,决策树默认的是基尼指数。
你可能会问这两个参数选哪一个比较好,我可以告诉你绝大多数情况下默认gini系数就对了,没必要去动。原因是首先很多情况下它们两的实际效果差异几乎没啥区别。
但有些点要注意,一是信息熵对错误的分类相比基尼系数更加严格,更容易差生过拟合。二是信息熵由于公式中带对数计算,而基尼系数是平方,导致前者生成的树计算时间更长,在特征很多的时候表较明显。一般你要处理特征特别多的数据,也是基尼指数更好的,更快。默认就行,改变这个参数几乎不会对模型效果有啥影响。如果你想建造两棵不一样的决策树,倒是可以两个都试一下。
这个参数有必要好好讲讲。因为目前网上,不说绝对,有90%的说法是错误的,我也是查阅了很多资料才搞明白。
这个参数默认是 splitter = ‘‘best’‘
,它还有一个选项是random
。
如果你选择默认的best。那么对于离散特征 A A A,假设有三个分类 A 1 A_1 A1, A 2 A_2 A2, A 3 A_3 A3与连续特征 B B B来判断那个特征该是分支节点,并且分裂点具体是哪个。(看到这里,你必须清楚CART算法的原理才能继续下去)。我们知道CART算法是严格生成二叉树的,CART是如何判断特征 A A A还是 B B B作为分支节点的呢,CART算法会先对连续特征 B B B,与 A A A进行信息增益比或基尼系数的计算,得到两者的数值。
如果我们发现离散特征 A A A的基尼系数更小或者信息增益比更大,说明特征 A A A更适合,此时决策树会将三个类别细分为{ A 1 A_1 A1},{ A 2 A_2 A2, A 3 A_3 A3},或{ A 2 A_2 A2},{ A 1 A_1 A1, A 3 A_3 A3},{ A 3 A_3 A3},{ A 2 A_2 A2, A 1 A_1 A1},然后分别计算三者之间的基尼系数,最后选择基尼系数最小的一个组合作为二叉树生成的依据。
如果我们发现连续特征 B B B的基尼系数更小或者信息增益比更大,说明特征 B B B更适合,此时决策树就把当初特征 B B B中连续值之间的最优分割点(对于 B B B来说得到的基尼系数最小)作为生成二叉树的依据。
—————————————————————————————————————————
如果你选择的是random
,同样对于离散特征 A A A,连续特征 B B B,决策树在计算信息增益前会先对两个特征内部做分割点的随机操作,然后再计算基尼指数。并不是很多博客中模棱两可的说,只是随机选取一部分特征,具体过程也不告诉你。实际上对于random
,best
,所有特征都会被计算(它们真的没搞明白就乱写,批评看完源码,不要误导别人)
对于 A A A,比如这三个组合{ A 1 A_1 A1},{ A 2 A_2 A2, A 3 A_3 A3},或{ A 2 A_2 A2},{ A 1 A_1 A1, A 3 A_3 A3},{ A 3 A_3 A3},{ A 2 A_2 A2, A 1 A_1 A1}。他只会随机的选择三者中的一个,然后计算这个组合的基尼系数代表特征 A A A的基尼系数。
对于 B B B,因为他是连续特征嘛,假设数据中有m个值,序列为{ b 1 , b 2 , b 3 , . . . . b m {b_1,b_2,b_3,....b_m} b1,b2,b3,....bm},此时他会把其中最小值,最大值拿出来。然后只会在最小值最大值之间随机的选择一个值所作特征 B B B的分割值,然后用这个来计算特征 B B B的基尼指数并代表 B B B。最后用这两个代表的基尼系数来进行比较谁做为分割点
—————————————————————————————————————————
说完了这个过程你应该理解splitter
参数的作用,确实是体现了随机性,并且这个随机性你会觉得有点无厘头,如果都是连续特征,这怎么能生成合理的决策树呢。实际上是可以的,只不过生成的树很离谱,虽然它分类正确,你可以自己写代码试试。并且如果你的随机种子不一样,它每次都会生成完全不一样的树。这算是一种瞎猫碰上死耗子的算法,但它也是有一定的合理性,尽管他是随机的分,但越重要的特征,得到的基尼系数更小的可能性是越大的。
(如果说
best
是点到点的直线,那么random就是点到点的n多条离谱的曲线,但它依然能到达目的地)
这个random
参数被设计的初衷其实也是为了随机生成许多不一样但有效的树,保证树的多样性。别的博客说它可以用来解决过拟合,其实在我看来并不适合,因为在原理上体现是用极大的随机性来重新生成可能正确的树,是误打误撞。这个splitter
实际分类任务设置时默认best
就行,毕竟random
生成的树实在是过于离谱。如果你想对一个数据集有很多种不同的树模型,又不太关注合理性,可以选择random
。
如果你想有更多了解,在这篇06年的论文中有介绍。
[1]. P. Geurts, D. Ernst., and L. Wehenkel, “Extremely randomized trees”, Machine Learning, 63(1), 3-42, 2006.
现在来介绍控制树生长的参数,一共有9个,虽然看起来很多,但实际每个都很容易理解,只需简单介绍就行。分别为:
1 max_depth: int, default=None
2 min_samples_split: int or float, default=2
3 min_samples_leaf: int or float, default=1
4 min_weight_fraction_leaf: float, default=0.0
5 max_features: int, float or {“auto”, “sqrt”, “log2”}, default=None
6 random_state: int, RandomState instance or None, default=None
7 max_leaf_nodes: int, default=None
8 min_impurity_decrease: float, default=0.0
9 class_weight: dict, list of dict or “balanced”, default=None
就是树的最大深度,默认为None,就是不限制树的最大深度。这个参数第一次生成树的时候不使用,如果你已经构建了一颗树,并大致知道树的深度。在生成一颗新的树的时候就可以设置max_depth
的值来减轻过拟合。
同样很容易理解,就是一个节点能够分割的最小样本数,默认为2
,也是来防止过拟合的。一般数据非常多的情况下可以设置5-10
或者自己觉得合适的,来停止树的生长,起到减轻过拟合的效果。可以设置浮点数,此时算是比例表示ceil(min_samples_split * n_samples)
, ceil()
就是四舍五入的意思。
默认为1
,这个表明一个分支节点生成的两个叶子节点中的样本数量必须大于min_samples_leaf=1
。当数据非常多的时候可以设置为5
或者你喜欢的,来停止树的生长,起到减轻过拟合的效果。当你设置为浮点数的时候表明比例表示,与上面的相同,很容易理解。
默认值为0
,这个参数也是很多博客没有解释清楚的,甚至是错误的,包括sklearn官方文档也没做解释的很清楚,有点搞。在查阅github的sklearn开发者解释后我才明白什么意思。其实这个是配合min_samples_leaf
使用的,公式为:
min_samples_leaf = max(min_samples_leaf, int(ceil(self.min_weight_fraction_leaf * n_samples)))
看到这个公式你就很容易明白我们只能在 min_samples_leaf
与 min_weight_fraction_leaf
两个之中得到样本占比最大的那一个,实际情况我们只需要使用min_samples_leaf
,因为两者的本质含义其实是一样的,而且保不准后者会出现什么特殊情况。
这个也相当容易理解,默认是n_features
。作用是限制每次分支节点选择计算所使用的最大特征数。但要注意如果在其中没找到满足条件的特征,他就会跳出这个max_features
这个范围,遍历剩下的特征n_features - max_features
。如果剩下的也不满足的,该节点就不分裂,停止生长。要说明一下,在每个节点的max_features
中的特征都是在所有特征中随机选取的,受我们常说的随机种子影响,也就是参数random_state
.
这个参数也可以起到防止过拟合的效果,根据模型特征数自定。下面给出一张图你就能理解怎么使用。
其中的无与没有就是None,浮动是浮点数float,自动是auto
这个参数,很有意思。如果你了解随机数生成的原理以及随机种子random_seed
,我告诉你实际上random_seed
就是random_state
,只是名字不同,你基本上就理解了。这个参数是用来控制所有随机过程的,包括参数splitter
以及max_features
。默认设置为None
,意思是每次都是随机的,如果你设置为int
1或者5,代表固定的。很多第一次用决策树算法的小白,会发现自己同一个数据集得到的决策树不同,会很疑惑,而且他们只使用了两行代码
clf=sklearn.tree.DecisionTreeClassifier()
clf.fit(X,Y)
尽管默认的splitter=''best''
,max_features=n_features
。其实是因为在某些节点计算所有的特征的基尼指数,偶尔会出现最小基尼指数相同的特征,那么此时算法就根据random_state
随机种子来随机决定其中一个作为分裂节点,特征越多,线性相关性越大,这种情况出现的概率也就越大,最后生成的树就不一样了。这也是为什么我们如果希望别人复现我们的代码,我们需要特定设置一个random_state=常数
,来使得我们的随机过程是固定的,这样他们生成的模型会跟我们的相同。
这个意思是生成的整棵树的叶子节点的数量,默认为None
,也就是无。它也可以用来减轻过拟合,一般是你知道整棵树的叶节点大概有多少后,然后自定一个认为合适的。
先看英文直译,最小不纯度的减少,只要每次不纯度的减少大于或等于你设定的值,就允许分裂,默认设置为0.0。我们在计算决策树的时候,每个节点我们都可以计算不纯度,而这个不纯度其实是标签的信息熵或者基尼系数 (是标签!),一般标签的混乱程度越小,就是不纯度越小,表明标签多为同一种类别,实际反应的就是信息熵与基尼系数小。下面给出一张图,你应该能理解
这张图第一个不纯度,也就是基尼指数是
0.653
,后面的两个节点的不纯度分别为0.611
与0
,也就是它的子节点不纯度之和为0.611
,减少的不纯度为0.655-0.611=0.044
。如果我们设置min_impurity_decrease=0.045
,由于减小的不纯度0.044
小于需求的0.045
,节点不会分裂,最后上面的这棵树就只有根节点了。这个参数适当的设置也可能减小过拟合。
直译:类权重,就是类别之间的权重。比如一个二分类问题,类别一占10%
,类别二站90%
,我们训练一个模型,这个模型不管什么情况只输出类别二,正确率也有90%
,其实这是相当不合理的。这时我们需要一个权重来平衡样本,比如对样本少类别的的基尼系数在原本的基础上乘以0.9
,而样本多的乘以0.1
。虽然这种加权重的方法很不错,但样本极其不平衡的时候,也无济于事,因为决策树再怎么也学不到少样本类别的规律,数据不够太硬伤。一般都是在类别参差不齐的时候使用效果比较好。默认参数是None
,建议每次构建决策树的时候都改成balanced(自动平衡)
,比较实用,略微防止过拟合。balanced的公式给一下:
n s a m p l e s n c l a s s e s ∗ n p . b i c o u n t ( y ) \cfrac{n_{samples}}{n_{classes}*np.bicount(y)} nclasses∗np.bicount(y)nsamples
np.bicount(y) 是统计所有标签中每个类别的样本出现的次数
可以从这个公式中看出,样本数量越小的标签得到的权重越大
我们也可以自己设置权重例如三分类问题
[1.1,1.2,0.8]
,但一般都是用balanced,效果也不错。另外对于多输出问题,这里的输出是指我们一个样本经过决策树可以得到多个输出,我们目前遇到的几乎都是一输出。多输出问题一般不适合用决策树,出现的情况也很小,有个了解就行,比如决策树人脸预测,给一张人脸的上半部分像素点,预测下半部分,由于下半部分有很多像素点,所以是多输出,其实效果很差,给张sklearn的图给你们看看就行。
ccp_alphanon-negative float, default=0.0
ccp(Cost-Complexity Pruning)的意思就是代价复杂度剪枝。如果你知道CART算法剪枝原理,就明白具体剪枝过程与alpha
怎么设置。但有一点需要说明sklearn中的alpha
需要我们自己设置,而传统的CCP算法是不需要,它能计算每个节点的alpha
,然后选出最小的进行剪枝迭代。
在sklearn官方文档中说明的是默认为
0.0
表示不剪枝,并且它没有写自动迭代的算法,只写了用 α \alpha α(也许以后会完善)。所以我们只能手动调节alpha,一般调参技巧是从0开始,一点一点增大,0.001
到0.1
,具体根据经验选个合适的。(而且他们的 α \alpha α只能使用一次,生成一棵最优子树)
如果你有通天能耐,是个超级大牛,可以去github上完善这一部分的代码,不过光是决策树这部分,就已经有几万行代码了。。。。
我放一下sklearn官方如何使用 α \alpha α的代码。他们是手动使用 α \alpha α用for循环来检验的。(交叉验证的操作也要自己写代码实现!)
clfs = []
for ccp_alpha in ccp_alphas:
clf = DecisionTreeClassifier(random_state=0, ccp_alpha=ccp_alpha)
clf.fit(X_train, y_train)
clfs.append(clf)
print(
"Number of nodes in the last tree is: {} with ccp_alpha: {}".format(
clfs[-1].tree_.node_count, ccp_alphas[-1]
)
)
当模型训练完成后,我们想查看模型的属性。这里只简单的做个表格总结一下,一共有9个,具体详细方细节可以去sklearn官方网站上查看。毕竟这个真的是只要会用就能理解。
序号 | 属性 | 输出 | 介绍 |
---|---|---|---|
1 | classes_ |
ndarray of shape (n_classes,) or list of ndarray | 得到所有不同标签的列表 |
2 | feature_importances_ |
ndarray of shape (n_features,) | 得到特征重要性的矩阵 |
3 | max_features_ |
int | 最大特征的推断值,其实就是模型构建时的那个max_features_ |
4 | n_classes_ |
int or list of int | 数据集有多少个类别,单输出问题整数,多输出问题列表 |
5 | n_features_ |
int | 整个数据有多少特征,在未来的1.2版本中会移除 |
6 | n_features_in_ |
int | 也是整个数据有多少个特征 |
7 | feature_names_in_ |
ndarray of shape (n_features_in_,) | 特征的名字,如果你的特征有字符型的昵称,就展现出来,其他的都不展示 |
8 | n_outputs_ |
int | 单输出问题固定输出1,多输出看有多少个 |
9 | tree_ |
Tree instance | 树对象,单独也有一些树的属性,如tree_.impurity[i] 表明节点i 的不纯度,建议前往官方文档获取更其他属性 |
这个也是会用就能理解,能记住,了解细节去sklearn官网查看,这里放张图就行。
这个就介绍跟tree.DecisionTreeClassifier
不同的点吧,搞懂了分类树,这个回归树就很容易理解。
criterion{“squared_error”, “friedman_mse”, “absolute_error”, “poisson”}, default=”squared_error”
对于回归树就只有这一点不一样,就是特征选择标准,默认的也是“squared_error”
,其他的参数,方法,属性都一样。前提是你知道CART回归树的具体生成过程,看一下也就明白了。
tree.ExtraTreeClassifier
与tree.ExtraTreeRegressor
仅仅只是在分类树与回归树的基础上改变了splitter
与max_features
的默认值。前者默认改成了random
,后者默认改为了sqrt(n_features)
。原本这两个参数的默认值分别是best
与None
。其他都一样,源码也是相同的。
看到这里其实也就明白了额外树,改变默认参数,能够生成极其随机的树,这就是它的功能。如果你理解我上面写的
splitter
与max_features
参数功能,这里看一眼就能理解了。
理解上面的所有内容,你就能自如的调用sklearn决策树包做实验了。而不是像某些小白,只会掉包,却不理解原理与参数背后的意义。其实有些东西比如特征重要性怎么算法,这个在其他树模型例如随机森林,xgboost,还有回归模型中计算方法也是不一样,以后再慢慢讲解。还有包括树模型画图使用到的graphviz
库,暂时也不介绍,可以自行上网查阅,方法也是十分简单的。
另外附上sklearn.tree 树模型的官方网站[2]https://scikit-learn.org/stable/modules/tree.html.
[1] P. Geurts, D. Ernst., and L. Wehenkel, “Extremely randomized trees”, Machine Learning, 63(1), 3-42, 2006.
[2] https://scikit-learn.org/stable/modules/tree.html