机器学习之决策树(python_sklearn)
1.sklearn中的决策树
- 模块sklearn.tree(scikit-learn)
sklearn中决策树的类都在”tree“这个模块之下。这个模块总共包含五个类:
| tree.DecisionTreeClassifier | 分类树 |
|---|---|
| tree.DecisionTreeRegressor | 回归树 |
| tree.export_graphviz | 将生成的决策树导出为DOT格式,画图专用 |
| tree.ExtraTreeClassifier | 高随机版本的分类树 |
| tree.ExtraTreeRegressor | 高随机版本的回归树 |
sklearn建模的基本流程:

在这个流程下,对应的代码为:
from sklearn import tree #导入需要的模块
clf = tree.DecisionTreeClassifier() #实例化
clf = clf.fit(X_train,y_train) #用训练集数据训练模型
result = clf.score(X_test,y_test) #导入测试集,从接口中调用需要的信息
2.DecisionTreeClassifier
classsklearn.tree.DecisionTreeClassifier(criterion=’gini’, splitter=’best’, max_depth=None,
min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features=None,
random_state=None,max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None,
class_weight=None, presort=False)
2.1重要参数
2.1.1criterion
Criterion这个参数是决定决策树不纯度的计算方式的。
sklearn提供了两种方式:
- 输入”entropy“,使用信息熵(Entropy)
- 输入”gini“,使用基尼系数(Gini Impurity)
二者有什么区别呢?
- 比起基尼系数,信息熵对不纯度更加敏感,对不纯度的惩罚最强。但是在实际使用中,信息熵和基尼系数的效果基
本相同。信息熵的计算比基尼系数缓慢一些,因为基尼系数的计算不涉及对数。另外,因为信息熵对不纯度更加敏
感,所以信息熵作为指标时,决策树的生长会更加“精细”,因此对于高维数据或者噪音很多的数据,信息熵很容易
过拟合,基尼系数在这种情况下效果往往比较好。当然,这不是绝对的。 - 信息熵其实算的是信息增益(也就是父节点和子节点的信息熵的差值,然后寻找使得差值最大的分类指标)
- 信息熵:$$Ent\left( D \right) =-\sum_{k=1}^{|y|}{p_k\log _2p_k} $$
- 假定当前样本集合D中第k类样本所占的比例为\(p_k(k=1,2,...|y|)\)
- Ent(D)的值越小,则D的纯度越大
- Ent(D)的最小值为0,最大值为\(log_2|y|\)
- y表示标签的种类
- 信息熵越小,不确定越小,已知的信息越多,树节点的纯度越大(熵是指变化,熵越大,说明不确定性越大,则已知信息越小)
- 离散属性a有V个可能的取值{\({a^1,a^2,...,a^n}\)},用a来进行划分,则会产生V个分支结点,其中第v个分支结点包含了所有在属性上取值为\(a^v\)的样本,记为\(D^v\)。则可计算出用属性a对样本集D进行划分所获得的“信息增益”:
-
\[Gain\left( D,a \right) =Ent\left( D \right) -\sum_{v=1}^V{\frac{|D^v|}{|D|}Ent\left( D^v \right)} \]
-
- 注意:信息增益倾向于选择产生大量小而纯的子集的分裂方案。用一个极端的例子来说明:当数据集的某个属性对于每一个实例存在一个不同属性值时,比如,一个标志码属性,按照信息增益值计算会被选择为分裂属性,但显然这是不合适的,因为ID属性对测试集中的分类是无益的。
- 信息熵:$$Ent\left( D \right) =-\sum_{k=1}^{|y|}{p_k\log _2p_k} $$
| 参数 | criterion |
|---|---|
| 如何影响模型? | 确定不纯度的计算方法,帮忙找出最佳节点和最佳分枝,不纯度越低,决策树对训练集的拟合越好 |
| 可能的输入有哪些? | 不填默认基尼系数,填写gini使用基尼系数,填写entropy使用信息增益 |
| 怎样选取参数? | 通常就使用基尼系数; 数据维度很大,噪音很大时使用基尼系数; 维度低,数据比较清晰的时候,信息熵和基尼系数没区别; 当决策树的拟合程度不够的时候,使用信息熵; 两个都试试,不好就换另外一个 |
[*zip(x,y)]这段代码什么意思?
这段代码是用来将两个列表 x 和 y 中的元素配对并返回一个新的列表。每个元素都是一个元组,其中第一个元素来自 x,第二个元素来自 y。例如:
x = [1, 2, 3]
y = ['a', 'b', 'c']
result = [*zip(x,y)]
print(result)
输出结果为:[(1, 'a'), (2, 'b'), (3, 'c')]
建立一颗树:
1.导入需要的算法库和模块
# 导入算法库和模块
from sklearn import tree
from sklearn.datasets import load_wine # 数据集,红酒数据
from sklearn.model_selection import train_test_split # 划分训练集和测试集
2.探索数据
wine = load_wine()
wine
合并数据和标签,形成一个表:
pd.concat([pd.DataFrame(wine.data), pd.DataFrame(wine.target,columns=['target'])], axis=1)
- axis=1表示按行索引合并
wine.feature_names # 属性名
wine.target_names # 标签名
3.训练集和测试集划分:
Xtrain, Xtest, Ytrain, Ytest = train_test_split(wine.data, wine.target, test_size=0.3)
# 这个train_test_split()函数有三个参数
#分别表示数据、标签、测试集和训练集比例
#0.3表示70%的训练集,30%的测试集,这里是随机划分的
#后面可以设置一个random_state参数,即设置一个随机数种子,使得划分固定
4.模型建立
clf = tree.DecisionTreeClassifier(criterion='entropy')
clf = clf.fit(Xtrain, Ytrain) # 模型训练
score = clf.score(Xtest, Ytest) # 返回模型精确度,accuracy
5.绘制决策树
feature_name = ['酒精','苹果酸','灰','灰的碱性','镁','总酚','类黄酮','非黄烷类酚类','花青素','颜色强度','色调','od280/od315稀释葡萄酒','脯氨酸']
import graphviz
dot_data = tree.export_graphviz(clf
,feature_names= feature_name
,class_names=['琴酒', '雪莉', '贝尔摩德']
,filled=True
,rounded=True)
graph = graphviz.Source(dot_data)
graph

6.探索决策树
[*zip(feature_name,clf.feature_importances_)]

这里可以用于判断不同的变量在决策过程中的重要性。(变量重要性)
2.1.2random_state & splitter
clf = tree.DecisionTreeClassifier(criterion="entropy",random_state=30) # 这里的random_state可以使score保持稳定
clf = clf.fit(Xtrain, Ytrain)
score = clf.score(Xtest, Ytest) #返回预测的准确度
score
上述代码和之前的模型构建部分有一点不同,在于设置了random_state这个参数。
那个这个参数有什么用呢?
使用new bing回答这个问题:
问1:sklearn中random_state参数有什么用?
答1:sklearn中的random_state参数是用来控制随机性的。它可以是一个整数,一个RandomState实例或者None。如果是一个整数,它就是一个随机种子,可以使得多次函数调用产生相同的随机结果。如果是None,它就使用np.random的默认RandomState实例。如果是一个RandomState实例,它就直接使用该实例生成随机数。
问2:random_state参数对模型性能有什么影响?
答2:根据搜索结果,random_state参数对模型性能有一定的影响,因为它决定了数据的随机性。不同的随机性可能导致不同的模型结果,例如聚类模型中的簇数。但是,random_state参数并不是一个需要调优的超参数,因为它只影响模型在特定数据集上的表现,而不是模型本身的泛化能力。如果想要保证多次运行模型或者划分数据时得到相同的结果,可以指定一个固定的random_state值。
问3:那么如何选择一个合适的random_state值呢?
答3:选择一个合适的random_state值并没有一个固定的规则,只要保证每次运行代码时使用相同的值就可以了。这样可以确保数据的划分或者模型的训练是可重复的。一般来说,可以使用任意一个整数作为random_state值,例如0或者42,或者使用默认的None。但是如果想要比较不同模型或者参数的效果,最好固定一个random_state值。
-
总结:random_state是便于多次训练得到同一棵树,防止模型无法复现,比如决策树分枝的时候,如果多个特征的不纯度一样,使用这个随机数就可以保证随机每次选取的划分特征是一样的,再例如一些连续特征,计算分割点就需要随机,模型需要复现,就要保证这个随机性每次都一样。
-
比如说如果像前面那样没有设置random_state参数,多次重复运行代码,得到的score值可能会不同。
可以运行下面这段代码验证:
score_cmp = []
for i in range(100):
clf = tree.DecisionTreeClassifier(criterion='entropy')
clf = clf.fit(Xtrain, Ytrain) # 模型训练
score = clf.score(Xtest, Ytest) # 返回模型精确度,accuracy
score_cmp.append(score)
plt.figure(dpi=200)
plt.plot(range(100), score_cmp, 'r-')
plt.show()
- 可以看出重复运行100次,得到的score的值会不同
score_cmp = []
for i in range(100):
clf = tree.DecisionTreeClassifier(criterion='entropy', random_state=30)
clf = clf.fit(Xtrain, Ytrain) # 模型训练
score = clf.score(Xtest, Ytest) # 返回模型精确度,accuracy
score_cmp.append(score)
plt.figure(dpi=200)
plt.plot(range(100), score_cmp, 'r-')
plt.show()
- 可以看出增加了random_state参数后,即使重复运行代码100遍,得到的score始终保持不变
至于splitter参数:
- splitter参数对模型的影响主要体现在分枝时的随机性和效率上。如果设置为'best',则决策树会优先选择更重要(按照不纯度设置的基尼系数或者信息熵的值)的特征进行分枝,这样可以得到更准确和更简洁的决策树,但是计算量也会更大。如果设置为'random',则决策树会更加随机地选择特征进行分枝,这样可以增加模型的多样性和鲁棒性,但是可能会降低模型的准确性和可解释性。
- 你可以根据你的数据集和目标来选择合适的splitter参数。一般来说,如果数据集较小或者特征较少,可以使用'best';如果数据集较大或者特征较多,可以使用'random'。
- 默认为‘best’
- 理解:就是说splitter设置为‘best’时是考虑所有的特征,在所有的特征中选择最好的一个作为划分结点的指标;而‘random’是先随机选择部分特征出来,再在这些筛选出来的指标中,找最好的一个作为划分结点的指标。这样操作可能会产生一些次优的分裂,但是却能够避免过拟合,提高模型的泛化能力(模型适用于新样本的能力)。
2.1.3剪枝参数
过拟合:学习器把训练样本学习的“太好”,将训练样本本身的特点当做所有样本的一般性质,导致模型的泛化能力下降。即训练集上表现的很好,但是在测试集上表现的很差。
# 模型对训练集的拟合程度
score_train = clf.score(Xtrain, Ytrain)
score_train
sklearn提供了不同的策略:
- max_depth
限制树的最大深度,超过设定深度的树枝全部剪掉
这是用得最广泛的剪枝参数,在高维度低样本量时非常有效。决策树多生长一层,对样本量的需求会增加一倍,所以限制树深度能够有效地限制过拟合。在集成算法中也非常实用。实际使用时,建议从=3开始尝试,看看拟合的效果再决定是否增加设定深度。
- min_samples_leaf & min_samples_split
min_samples_leaf限定,一个节点在分枝后的每个子节点都必须包含至少min_samples_leaf个训练样本,否则分枝就不会发生,或者,分枝会朝着满足每个子节点都包含min_samples_leaf个样本的方向去发生。
min_samples_split限定,一个节点必须要包含至少min_samples_split个训练样本,这个节点才允许被分枝,否则分枝就不会发生。
那么究竟如何确定最优的剪枝参数呢?
- 绘制超参数学习曲线
test = []
for i in range(10):
clf = tree.DecisionTreeClassifier(max_depth=i+1
,criterion="entropy"
,random_state=30
,splitter="best"
)
clf = clf.fit(Xtrain, Ytrain)
score = clf.score(Xtest, Ytest)
test.append(score)
plt.figure(dpi=200)
plt.plot(range(1,11), test, color="red", label="max_depth")
plt.legend()
plt.show()
- 超参数的学习曲线,是一条以超参数的取值为横坐标,模型的度量指标为纵坐标的曲线,它是用来衡量不同超参数取值下模型的表现的线。在我们建好的决策树里,我们的模型度量指标就是score。
2.1.4目标权重参数
- class_weight
对于样本不均衡问题,需要设置该参数。
在sklearn的决策树算法中,class_weight参数的取值可以是以下三种:
-
class_weight=None(默认值):表示不使用类别权重,所有类别的权重都为1。 -
class_weight='balanced':表示使用类别权重,但是权重大小是自动根据训练数据中的类别频率进行计算的。具体来说,权重大小为:\[\frac{n_{samples}}{(n_{classes} * np.bincount(y))}$$, 其中n_{samples}表示样本总数,n_{classes}表示类别总数,np.bincount(y)表示每个类别的样本数。 \]
2.2重要属性和接口
属性是在模型训练之后,能够调用查看的模型的各种性质。对决策树来说,最重要的是feature_importances_,能够查看各个特征对模型的重要性。
sklearn中许多算法的接口都是相似的,比如说我们之前已经用到的fit和score,几乎对每个算法都可以使用。除了这两个接口之外,决策树最常用的接口还有apply和predict。
apply中输入测试集返回每个测试样本所在的叶子节点的索引,predict输入测试集返回每个测试样本的标签。
至此,我们已经学完了分类树DecisionTreeClassififier和用决策树绘图(export_graphviz)的所有基础。我们讲解了决策树的基本流程,分类树的八个参数,一个属性,四个接口,以及绘图所用的代码。
八个参数:criterion,两个随机性相关的参数(random_state,splitter),五个剪枝参数(max_depth,min_samples_split,min_samples_leaf,max_feature,min_impurity_decrease)
一个属性:feature_importances_
四个接口:fit,score,apply,predict
3.DecisionTreeRegressor
- 回归树
classsklearn.tree.DecisionTreeRegressor (criterion=’mse’, splitter=’best’, max_depth=None,
min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features=None,
random_state=None, max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, presort=False)
几乎所有参数,属性及接口都和分类树一模一样。需要注意的是,在回归树中,没有标签分布是否均衡的问题,因此没有class_weight这样的参数。
3.1重要参数、属性及接口
criterion
1)输入"mse"使用均方误差mean squared error(MSE),父节点和叶子节点之间的均方误差的差额将被用来作为
特征选择的标准,这种方法通过使用叶子节点的均值来最小化L2损失
2)输入“friedman_mse”使用费尔德曼均方误差,这种指标使用弗里德曼针对潜在分枝中的问题改进后的均方误差
3)输入"mae"使用绝对平均误差MAE(mean absolute error),这种指标使用叶节点的中值来最小化L1损失
属性中最重要的依然是feature_importances_,接口依然是apply, fifit, predict, score最核心。
然而,回归树的接口score返回的是R平方,并不是MSE.
from sklearn.datasets import load_boston
from sklearn.model_selection import cross_val_score
from sklearn.tree import DecisionTreeRegressor
boston = load_boston() # 波士顿房价数据
regressor = DecisionTreeRegressor(random_state=0) #实例化
cross_val_score(regressor, boston.data, boston.target, cv=10, scoring = "neg_mean_squared_error")
# 不设置scoring返回的是R方
# cross_val_score:交叉验证
交叉验证是用来观察模型的稳定性的一种方法,我们将数据划分为n份,依次使用其中一份作为测试集,其他n-1份作为训练集,多次计算模型的精确性来评估模型的平均准确程度。训练集和测试集的划分会干扰模型的结果,因此用交叉验证n次的结果求出的平均值,是对模型效果的一个更好的度量。

- n次测试也叫n折交叉验证
注意:所有接口中要求输入X_train和X_test的部分,输入的特征矩阵必须至少是一个二维矩阵。sklearn不接受任何一维矩阵作为特征矩阵被输入。
-
包括fit、apply、predict、score等
-
增维可以考虑reshape(-1,1)和使用np.newaxis

浙公网安备 33010602011771号