使用 C++
和 python
实现 ID3算法
的决策树模型,另有使用 Pythonsklearn.tree
中的 DecisionTreeClassifier
调包与可视化实例。
决策树原理 决策树作为一种机器学习算法,将 “特征 a 值小于 x,特征 b 值小于 y……=> 类别 1 “的逻辑规则流纳入树状数据结构。这种算法的优点是可解释性强 。银行可以向客户解释他们被拒绝贷款的原因:例如,客户没有房子、收入低于 5000 元。
我们可以确保在前面的例子中建立的树是最佳的。在其他分割条件下,产生的树会更深,即需要更多的 “问题 “来达到答案。 决策树构建的流行算法,如 ID3 或 C4.5,其核心是信息增益的贪婪最大化 原则:在每一步,算法选择在分裂时提供最大信息增益的变量。然后递归地重复这个过程,直到熵为零(或某个小值以考虑过拟合的情况)。不同的算法使用不同的启发式 “早期停止 “或 “截止”,以避免构建一个过度拟合的树。
1 2 3 4 5 6 7 8 9 10 11 def build (L ): create node t if the stopping criterion is True : assign a predictive model to t else : Find the best binary split L = L_left + L_right t.left = build(L_left) t.right = build(L_right) return t
步骤如下:
自顶向下的贪婪搜索遍历可能的决策树空间构造决策树;
从 “哪一个属性将在树的根节点被测试” 开始;
使用统计测试来确定每一个实例属性单独分类训练样例的能力 ,分类能力最好的属性作为树的根结点测试。
然后为根结点属性的每个可能值产生一个分支,并把训练样例排列到适当的分支(也就是说,样例的该属性值对应的分支)之下。
重复这个过程,用每个分支结点关联的训练样例来选取在该点被测试的最佳属性。
熵 对于具有 N 个可能状态的系统,香农熵定义如下:
$$ \Large Entropy(S)= -\sum_{i=1}^{N}p_i \log_2{p_i},
$$
其中 是使系统处于第 个状态的概率。这是物理学、信息论和其他领域中使用的一个非常重要的概念。熵可以描述为系统中的混沌程度:熵越高,系统的有序性越低,反之亦然。这将帮助我们进行有效的数据拆分。
当只有两种样例时,定义可写为:
$$ \Large Entropy(S)= -p_{pos} \log_2{p_{pos}} -p_{neg} \log_2{p_{neg}}
$$
其 C++ 实现如下:
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 double ComputeEntropy (vector <vector <string> > remain_state, string attribute, string value,bool ifparent) { vector<int > count (2 ,0 ) ; unsigned int i,j; bool done_flag = false ; for (j = 1 ; j < MAXLEN; j++){ if (done_flag) break ; if (!attribute_row[j].compare (attribute)){ for (i = 1 ; i < remain_state.size (); i++){ if ((!ifparent&&!remain_state[i][j].compare (value)) || ifparent){ if (!remain_state[i][MAXLEN - 1 ].compare (yes)){ count[0 ]++; } else count[1 ]++; } } done_flag = true ; } } if (count[0 ] == 0 || count[1 ] == 0 ) return 0 ; double sum = count[0 ] + count[1 ]; double entropy = -count[0 ]/sum*log (count[0 ]/sum)/log (2.0 ) - count[1 ]/sum*log (count[1 ]/sum)/log (2.0 ); return entropy; }
信息增益 $$ \Large IG(Q) = S_O - \sum_{i=1}^{q}\frac{N_i}{N}S_i
$$
分类问题中的评判标准 我们讨论了熵,但还有其他的评判标准:
基尼不确定性 Gini uncertainty(基尼杂质 Gini impurity): . 最大化基尼不确定性可以最大化同一子树中同一类的对象对的数量。
Misclassification error (误判误差):
实践中几乎从不使用误判误差,基尼不确定性和信息增益效果类似。
对于二元分类,熵和基尼不确定性采用以下形式:
其中 是对象具有标签 +
的概率。
如果我们根据 绘制这两个函数,就能发现看到熵是基尼不确定性的两倍。因此实践中这两个标准几乎相同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import warningswarnings.filterwarnings("ignore" ) import numpy as npimport pandas as pdimport seaborn as snssns.set () from matplotlib import pyplot as plt%config InlineBackend.figure_format = 'retina' plt.figure(figsize=(6 , 4 )) xx = np.linspace(0 , 1 , 50 ) plt.plot(xx, [2 * x * (1 - x) for x in xx], label="gini" ) plt.plot(xx, [4 * x * (1 - x) for x in xx], label="2*gini" ) plt.plot(xx, [-x * np.log2(x) - (1 - x) * np.log2(1 - x) for x in xx], label="entropy" ) plt.plot(xx, [1 - max (x, 1 - x) for x in xx], label="missclass" ) plt.plot(xx, [2 - 2 * max (x, 1 - x) for x in xx], label="2*missclass" ) plt.xlabel("p+" ) plt.ylabel("criterion" ) plt.title("Criteria of quality as a function of p+ (binary classification)" ) plt.legend()
优缺点 优点:
可解释性强
能被可视化
训练和预测速度快
模型参数少
支持数字特征和分类特征都支持
缺点:
对数据中的噪声敏感;稍微修改训练集(如删除一个特征,添加一些对象),整个模型可能会发生变化
由决策树构建的分离边界有其局限性 —— 它由垂直于坐标轴之一的超平面组成,在实践中,其质量不如其他一些方法。
会过拟合
不稳定。数据的微小变化能显著改变决策树。这个问题通过决策树集成来解决(下一次讨论)。
最优决策树搜索问题是 NP 完备的。在实践中使用了如贪婪搜索具有最大信息增益的特征,但并不能保证找到全局最优树。
难以应对数据中的缺失值。
模型只能内插不能外推(随机森林和树增强也如此)。决策树对特征空间外的对象预测结果相同。
实例 分类问题 为人造数据拟合决策树。先生成两类服从不同均值的正态分布样本。
数据生成 1 2 3 4 5 6 7 8 np.random.seed(17 ) train_data = np.random.normal(size=(100 , 2 )) train_labels = np.zeros(100 ) train_data = np.r_[train_data, np.random.normal(size=(100 , 2 ), loc=2 )] train_labels = np.r_[train_labels, np.ones(100 )]
数据可视化 此分类问题是建立边界来分隔两个类(红点和黄点)。一条直线太简单,为每个红点蜿蜒的复杂曲线太复杂,会导致我们在新样本上出错。直观地说,平滑的边界或一条直线或超平面可以很好地处理新数据。
1 2 3 4 5 6 7 8 9 plt.figure(figsize=(10 , 8 )) plt.scatter( train_data[:, 0 ], train_data[:, 1 ], c=train_labels, cmap="Set1" , linewidth=1.5 , ) plt.plot(range (-2 , 5 ), range (4 , -3 , -1 ));
模型搭建与训练 训练 Sklearn
决策树来区分这两个类,使用 max_depth
限制树的深度并可视化生成的分离边界。
1 2 3 4 5 from sklearn.tree import DecisionTreeClassifierclf_tree = DecisionTreeClassifier(criterion="entropy" , max_depth=3 , random_state=17 ) clf_tree.fit(train_data, train_labels)
1 2 3 4 5 6 7 8 9 10 11 def get_grid (data ): x_min, x_max = data[:, 0 ].min () - 1 , data[:, 0 ].max () + 1 y_min, y_max = data[:, 1 ].min () - 1 , data[:, 1 ].max () + 1 return np.meshgrid(np.arange(x_min, x_max, 0.01 ), np.arange(y_min, y_max, 0.01 )) xx, yy = get_grid(train_data) predicted = clf_tree.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape) plt.pcolormesh(xx, yy, predicted, cmap="Set1" ) plt.scatter(train_data[:, 0 ], train_data[:, 1 ], c=train_labels, cmap="Set1" , edgecolors="black" , linewidth=1.5 );
树模型可视化 这个代码很难在 kaggle
上跑通…… 直接放图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import pydotplusfrom sklearn.tree import export_graphvizdef tree_graph_to_png (tree, feature_names, png_file_to_save ): """ This requires GraphViz to be installed. """ tree_str = export_graphviz( tree, feature_names=feature_names, filled=True , out_file=None ) graph = pydotplus.graph_from_dot_data(tree_str) graph.write_png(png_file_to_save) tree_graph_to_png( tree=clf_tree, feature_names=["x1" , "x2" ], png_file_to_save="topic3_decision_tree1.png" , )
一开始有 200 个样本,每类 100 个。初始状态的熵最大,。然后,通过将 的值与 1.211 进行比较,将样本第一次分成 2 组,左右组的熵都随之减少。在上面这个可视化图片中,第一类的样本越多橙色越深,第二类的样本越多蓝色越深。一开始,两个类的样本数量相等,所以树的根节点是白色的。
理论上您可以构建一棵决策树直到每个叶子都只有一个实例,但这在构建单棵树时并不常见,因为它会过拟合或泛化能力降低,树底部将是一些不重要的特征。
以下两种情况可将树构建到最大深度:
随机森林。随机森林(一组树)平均单个树的输出。
修剪枝叶 (Pruning trees)。首先将树构造到最大深度,然后自下而上地通过比较具有和不具有该节点的树的质量来删除树的一些节点(使用交叉验证比较)。
下图是在过拟合树中构建的分割边界示例。
回归问题 预测数值型变量时,标准会发生变化:
$$ \Large D = \frac{1}{\ell} \sum\limits_{i =1}^{\ell} (y_i - \frac{1}{\ell} \sum\limits_{j=1}^{\ell} y_j)^2,
$$
we look for features that divide the training set in such a way that the values of the target feature in each leaf are roughly equal. 其中 是一个叶子的样本数, 是目标变量的值。简单地说,我们通过最小化方差寻找的特征是以这样一种方式划分训练集,即目标特征在每个叶子中的值大致相等。
让我们生成一些服从 的数据,并加上一些噪声。然后我们将使用这些数据和树所做的预测来训练一棵树。
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 n_train = 150 n_test = 1000 noise = 0.1 def f (x ): x = x.ravel() return np.exp(-(x ** 2 )) + 1.5 * np.exp(-((x - 2 ) ** 2 )) def generate (n_samples, noise ): X = np.random.rand(n_samples) * 10 - 5 X = np.sort(X).ravel() y = ( np.exp(-(X ** 2 )) + 1.5 * np.exp(-((X - 2 ) ** 2 )) + np.random.normal(0.0 , noise, n_samples) ) X = X.reshape((n_samples, 1 )) return X, y X_train, y_train = generate(n_samples=n_train, noise=noise) X_test, y_test = generate(n_samples=n_test, noise=noise) from sklearn.tree import DecisionTreeRegressorreg_tree = DecisionTreeRegressor(max_depth=5 , random_state=17 ) reg_tree.fit(X_train, y_train) reg_tree_pred = reg_tree.predict(X_test) plt.figure(figsize=(10 , 6 )) plt.plot(X_test, f(X_test), "b" ) plt.scatter(X_train, y_train, c="b" , s=20 ) plt.plot(X_test, reg_tree_pred, "g" , lw=2 ) plt.xlim([-5 , 5 ]) plt.title( "Decision tree regressor, MSE = %.2f" % (np.sum ((y_test - reg_tree_pred) ** 2 ) / n_test) ) plt.show()
不难发现,决策树用分段常数函数逼近数据集
使用决策树进行 MNIST 手写数字识别 加载数据 1 2 3 4 5 6 from sklearn.datasets import load_digitsdata = load_digits() X, y = data.data, data.target X[0 , :].reshape([8 , 8 ])
MNIST 可视化 1 2 3 f, axes = plt.subplots(1 , 4 , sharey=True , figsize=(16 , 6 )) for i in range (4 ): axes[i].imshow(X[i, :].reshape([8 , 8 ]), cmap="Greys" );
数据集划分 1 2 3 X_train, X_holdout, y_train, y_holdout = train_test_split( X, y, test_size=0.3 , random_state=17 )
模型建立与训练 1 2 3 tree = DecisionTreeClassifier(max_depth=5 , random_state=17 ) tree.fit(X_train, y_train)
模型预测与评定 1 2 3 tree_pred = tree.predict(X_holdout) accuracy_score(y_holdout, tree_pred)
调参 1 2 3 4 5 6 7 8 tree_params = { "max_depth" : [1 , 2 , 3 , 5 , 10 , 20 , 25 , 30 , 40 , 50 , 64 ], "max_features" : [1 , 2 , 3 , 5 , 10 , 20 , 30 , 50 , 64 ], } tree_grid = GridSearchCV(tree, tree_params, cv=5 , n_jobs=-1 , verbose=True ) tree_grid.fit(X_train, y_train)
显示最优参数:
1 tree_grid.best_params_, tree_grid.best_score_
交叉验证 1 2 3 np.mean( cross_val_score(RandomForestClassifier(random_state=17 ), X_train, y_train, cv=5 ) )
调库 sklearn.tree.DecisionTreeClassifier sklearn.tree.DecisionTreeClassifier
类的主要参数有:
1 class sklearn .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 , ccp_alpha=0.0 )
max_depth
– the maximum depth of the tree;
max_features
- the maximum number of features with which to search for the best partition (this is necessary with a large number of features because it would be “expensive” to search for partitions for all features);
min_samples_leaf
– the minimum number of samples in a leaf. This parameter prevents creating trees where any leaf would have only a few members.
Classifier 可选参数 参数:
参数
说明
criterion
{“gini”, “entropy”}, default=”gini” 这个参数是用来选择使用何种方法度量树的切分质量的。当 criterion 取值为 “gini” 时采用 基尼不纯度(Gini impurity)算法构造决策树,当 criterion 取值为 “entropy” 时采用信息增益( information gain)算法构造决策树.
splitter
{“best”, “random”}, default=”best” 此参数决定了在每个节点上拆分策略的选择。支持的策略是 “best” 选择 “最佳拆分策略”, “random” 选择 “最佳随机拆分策略”。
max_depth
int, default=None 树的最大深度。如果取值为 None, 则将所有节点展开,直到所有的叶子都是纯净的或者直到所有叶子都包含少于 min_samples_split 个样本。
min_samples_split
int or float, default=2 拆分内部节点所需的最少样本数:· 如果取值 int , 则将 min_samples_split
视为最小值。· 如果为 float,则 min_samples_split
是一个分数,而 ceil(min_samples_split * n_samples)
是每个拆分的最小样本数。 - 注释 在版本 0.18 中更改:增加了分数形式的浮点值。
min_samples_leaf
int or float, default=1 在叶节点处所需的最小样本数。 仅在任何深度的分裂点在左分支和右分支中的每个分支上至少留有 min_samples_leaf
个训练样本时,才考虑。 这可能具有平滑模型的效果,尤其是在回归中。· 如果为 int,则将 min_samples_leaf
视为最小值· 如果为 float,则 min_samples_leaf
是一个分数,而 ceil(min_samples_leaf * n_samples)
是每个节点的最小样本数。 - 注释: 在版本 0.18 中发生了更改:添加了分数形式的浮点值。
min_weight_fraction_leaf
float, default=0.0 在所有叶节点处(所有输入样本)的权重总和中的最小加权分数。 如果未提供 sample_weight
,则样本的权重相等。
max_features
int, float or {“auto”, “sqrt”, “log2”}, default=None 寻找最佳分割时要考虑的特征数量:- 如果为 int
,则在每次拆分时考虑 max_features
功能。- 如果为 float
,则 max_features
是一个分数,而 int(max_features * n_features)
是每个分割处的特征数量。- 如果为 “auto”
,则 max_features = sqrt(n_features)
。- 如果为 “sqrt”
,则 max_features = sqrt(n_features)
。- 如果为 “log2”
,则 max_features = log2(n_features)
。- 如果为 None
,则 max_features = n_features
。 注意:直到找到至少一个有效的节点样本分区,分割的搜索才会停止,即使它需要有效检查的特征数量多于 max_features
也是如此。
random_state
int, RandomState instance, default=None 此参数用来控制估计器的随机性。即使分割器设置为 “最佳”,这些特征也总是在每个分割中随机排列。当 max_features <n_features
时,该算法将在每个拆分中随机选择 max_features
,然后再在其中找到最佳拆分。但是,即使 max_features = n_features
,找到的最佳分割也可能因不同的运行而有所不同。 就是这种情况,如果标准的改进对于几个拆分而言是相同的,并且必须随机选择一个拆分。 为了在拟合过程中获得确定性的行为,random_state
必须固定为整数。 有关详细信息,请参见词汇表。
max_leaf_nodes
int, default=None 优先以最佳方式生成带有 max_leaf_nodes
的树。 最佳节点定义为不纯度的相对减少。 如果为 None,则叶节点数不受限制。
min_impurity_decrease
float, default=0.0 如果节点分裂会导致不纯度的减少大于或等于该值,则该节点将被分裂。 加权不纯度减少方程如下:N_t / N * (impurity - N_t_R / N_t * right_impurity
- N_t_L / N_t * left_impurity)
其中 N
是样本总数,N_t
是当前节点上的样本数,N_t_L
是左子节点中的样本数,N_t_R
是右子节点中的样本数。 如果给 sample_weight
传了值,则 N , N_t , N_t_R
和 N_t_L
均指加权总和。 在 0.19 版新增 。
min_impurity_split
float, default=0 树模型停止生长的阈值。如果节点的不纯度高于阈值,则该节点将分裂,否则为叶节点。 警告: 从版本 0.19 开始被弃用:min_impurity_split
在 0.19 中被弃用,转而支持 min_impurity_decrease
。min_impurity_split
的默认值在 0.23 中从 1e-7
更改为 0
,在 0.25 中将被删除。使用 min_impurity_decrease
代替。
class_weight
dict, list of dict or “balanced”, default=None 以 {class_label: weight}
的形式表示与类别关联的权重。如果取值 None, 所有分类的权重为 1。对于多输出问题,可以按照 y 的列的顺序提供一个字典列表。 注意多输出 (包括多标签) ,应在其自己的字典中为每一列的每个类别定义权重。例如:对于四分类多标签问题, 权重应为 [{0:1、1:1:1],{0:1、1:5},{0:1、1:1:1},{0:1、1: 1}],而不是 [{1:1},{2:5},{3:1},{4:1}]。 “平衡” 模式使用 y 的值自动将权重与输入数据中的类频率成反比地调整为 n_samples /(n_classes * np.bincount(y))
。 对于多输出,y 的每一列的权重将相乘。 请注意,如果指定了 sample_weight
,则这些权重将与 sample_weight
(通过 fit
方法传递)相乘。
presort
deprecated, default=’deprecated’ 此参数已弃用,并将在 v0.24 中删除。 注意:从 0.22 版开始已弃用。
ccp_alpha
non-negative float, default=0.0 用于最小化成本复杂性修剪的复杂性参数。 将选择成本复杂度最大且小于 ccp_alpha 的子树。 默认情况下,不执行修剪。 有关详细信息,请参见最小成本复杂性修剪。
属性
属性
说明
classes_
ndarray of shape (n_classes,) or list of ndarray 类标签(单输出问题)或类标签数组的列表(多输出问题)。
feature_importances_
ndarray of shape (n_features,) 返回特征重要程度数据。
max_features_
int max_features
的推断值。
n_classes_
int or list of int 整数的类别数(单输出问题),或者一个包含所有类别数量的列表(多输出问题)。
n_features_
int 执行模型拟合训练时的特征数量。
n_outputs_
int 执行模型拟合训练时的输出数量。
tree_
Tree 基础的 Tree 对象。请通过 help(sklearn.tree._tree.Tree)
查看 Tree 对象的属性,并了解决策树的结构 以了解这些属性的基本用法。
Classifier 使用示例 1 2 3 4 5 6 from sklearn.datasets import load_irisfrom sklearn.model_selection import cross_val_scorefrom sklearn.tree import DecisionTreeClassifierclf = DecisionTreeClassifier(random_state=0 ) iris = load_iris() cross_val_score(clf, iris.data, iris.target, cv=10 )
sklearn.tree.DecisionTreeRegressor 1 class sklearn .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='deprecated' , ccp_alpha=0.0 )
criterion
{“squared_error”, “friedman_mse”, “absolute_error”, “poisson”}, default=”squared_error” The function to measure the quality of a split. Supported criteria are “squared_error” for the mean squared error, which is equal to variance reduction as feature selection criterion and minimizes the L2 loss using the mean of each terminal node, “friedman_mse”, which uses mean squared error with Friedman’s improvement score for potential splits, “absolute_error” for the mean absolute error, which minimizes the L1 loss using the median of each terminal node, and “poisson” which uses reduction in Poisson deviance to find splits.
Regressor 可选参数 参数:
参数
说明
criterion
{“mse”, “friedman_mse”, “mae”}, default=”mse” 测量分割质量的函数。支持的标准是均方误差的 “mse”,等于方差减少作为特征选择标准,并使用每个终端节点的均值 “ friedman_mse” 来最小化 L2 损失,该方法使用均方误差和弗里德曼改进分数作为潜在值拆分,并使用 “ mae” 表示平均绝对误差,使用每个终端节点的中值将 L1 损失最小化。版本 0.18 中的新功能 :平均绝对误差(MAE)标准。
splitter
{“best”, “random”}, default=”best” 用于在每个节点上选择拆分的策略。支持的策略是 “best” 选择最佳拆分,“random” 选择最佳随机拆分。
max_depth
int, default=None 树的最大深度。如果为 None,则将节点展开,直到所有叶子都是纯净的,或者直到所有叶子都包含少于 min_samples_split 个样本。
min_samples_split
int or float, default=2 拆分内部节点所需的最少样本数: - 如果为 int,则认为 min_samples_split
是最小值。 - 如果为 float,min_samples_split
则为分数, ceil(min_samples_split * n_samples)
是每个拆分的最小样本数。在版本 0.18 中更改 :添加了分数的浮点值。
min_samples_leaf
int or float, default=1 在叶节点处需要的最小样本数。任何深度的分割点只有在左、右分支中至少留下 min_samples_leaf 训练样本时才会被考虑。这可能具有平滑模型的效果,尤其是在回归中。
min_weight_fraction_leaf
float, default=0.0 在所有叶节点处(所有输入样本)的权重总和中的最小加权分数。如果未提供 sample_weight,则样本的权重相等。
max_features
int, float or {“auto”, “sqrt”, “log2”}, default=None 寻找最佳分割时要考虑的特征数量: - 如果为 int,则 max_features
在每个分割处考虑特征。 - 如果为 float,max_features
则为小数,并且 int(max_features * n_features)
是在每次拆分时考虑要素。 - 如果为 “auto”,则为 max_features=n_features
。 - 如果是 “sqrt”,则 max_features=sqrt(n_features)
。 - 如果为 “log2”,则为 max_features=log2(n_features)
。 - 如果为”None“,则 max_features=n_features
。 注:直到找到至少一个有效的节点样本分区,分割的搜索才会停止,即使它需要有效检查多个 max_features
要素也是如此。
random_state
int, RandomState instance, default=None 控制估算器的随机性。即使 splitter
设置为 "best"
,这些特性总是在每次拆分时随机排列。当 max_features < n_features
时,算法将在每个分割处随机选择 max_features
特征,然后在其中找到最佳分割。即使 max_features=n_features
,但最佳分割可能因不同的运行而有所不同,这种情况下,如果标准的改进对于多个分割是相同的,必须随机选择一个分割。为了在拟合过程中获得确定性行为,必须将其固定为整数。有关详细信息,请参见词汇表 。
max_leaf_nodes
int, default=None 以最好的方式种植一棵树,树上有 max_l
节点。最佳节点定义为杂质相对减少。如果没有,则叶节点的数量不受限制。
min_impurity_decrease
float, default=0.0 如果节点分裂会导致杂质的减少大于或等于该值,则该节点将被分裂。 加权杂质减少方程如下:N_t / N * (impurity - N_t_R / N_t * right_impurity
N_t_L / N_t * left_impurity)
其中,N
是样本总数,N_t
是当前节点的样本数,N_t_L
是左子节点中的样本数,N_t_R
是右子节点中的样本数。N
,N_t
,N_t_R
和 N_t_L
都指的是加权和,如果 sample_weight
可以通过。版本 0.19 中的新功能。
min_impurity_split
float, (default=0) 尽早停止树的生长的阈值。如果节点的杂质高于阈值,则该节点将分裂,否则为叶。从版本 0.19 min_impurity_split
开始不推荐使用:在版本 0.19 中 不再推荐使用 min_impurity_decrease
。 min_impurity_split
的默认值在 0.23 中从 1e-7 更改为 0,将会在 0.25 中删除。使用 min_impurity_decrease
将其代替。
presort
deprecated, default=’deprecated’ 此参数已弃用,并将在 v0.24 中删除。从0.22版开始不推荐使用。
ccp_alpha
non-negative float, default=0.0 用于最小代价复杂度修剪的复杂度参数。选择代价复杂度最大且小于 ccp_alpha
的子树。默认情况下,不执行修剪。有关详细信息,请参见 最小成本复杂性修剪 。0.22 版中的新功能。
属性:
属性
说明
feature_importances_
ndarray of shape (n_features,) 返特征的重要性。
max_features_
int max_features 的推断值。
n_features_
int fit
执行时的特征数量。
n_outputs_
int fit
执行时的输出数量。
tree_
Tree 基础的 Tree 对象。有关树对象的属性请参阅 help(sklearn.tree._tree.Tree)
, 有关这些属性的基本用法,请参阅 决策树结构 。
Regressor 使用示例 1 2 3 4 5 6 from sklearn.datasets import load_diabetesfrom sklearn.model_selection import cross_val_scorefrom sklearn.tree import DecisionTreeRegressorX, y = load_diabetes(return_X_y=True ) regressor = DecisionTreeRegressor(random_state=0 ) cross_val_score(regressor, X, y, cv=10 )
实现 ID3 实现 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 50 51 52 53 54 55 56 57 58 59 60 61 62 Node * BulidDecisionTreeDFS (Node * p, vector <vector <string> > remain_state, vector <string> remain_attribute) { if (p == NULL ) p = new Node (); if (AllTheSameLabel (remain_state, yes)){ p->attribute = yes; return p; } if (remain_attribute.size () == 0 ){ string label = MostCommonLabel (remain_state); p->attribute = label; return p; } double max_gain = 0 , temp_gain; vector <string>::iterator max_it; vector <string>::iterator it1; for (it1 = remain_attribute.begin (); it1 < remain_attribute.end (); it1++){ temp_gain = ComputeGain (remain_state, (*it1)); if (temp_gain > max_gain) { max_gain = temp_gain; max_it = it1; } } vector <string> new_attribute; vector <vector <string> > new_state; for (vector <string>::iterator it2 = remain_attribute.begin (); it2 < remain_attribute.end (); it2++){ if ((*it2).compare (*max_it)) new_attribute.push_back (*it2); } p->attribute = *max_it; vector <string> values = map_attribute_values[*max_it]; int attribue_num = FindAttriNumByName (*max_it); new_state.push_back (attribute_row); for (vector <string>::iterator it3 = values.begin (); it3 < values.end (); it3++){ for (unsigned int i = 1 ; i < remain_state.size (); i++){ if (!remain_state[i][attribue_num].compare (*it3)){ new_state.push_back (remain_state[i]); } } Node * new_node = new Node (); new_node->arrived_value = *it3; if (new_state.size () == 0 ){ new_node->attribute = MostCommonLabel (remain_state); } else BulidDecisionTreeDFS (new_node, new_state, new_attribute); p->childs.push_back (new_node); new_state.erase (new_state.begin ()+1 ,new_state.end ()); } return p; }
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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 ''' 数据集:Mnist 训练集数量:60000 测试集数量:10000 ------------------------------ 运行结果:ID3(未剪枝) 正确率:85.9% 运行时长:356s ''' import timeimport numpy as npdef loadData (fileName ): ''' 加载文件 :param fileName:要加载的文件路径 :return: 数据集和标签集 ''' dataArr = []; labelArr = [] fr = open (fileName) for line in fr.readlines(): curLine = line.strip().split(',' ) dataArr.append([int (int (num) > 128 ) for num in curLine[1 :]]) labelArr.append(int (curLine[0 ])) return dataArr, labelArr def majorClass (labelArr ): ''' 找到当前标签集中占数目最大的标签 :param labelArr: 标签集 :return: 最大的标签 ''' classDict = {} for i in range (len (labelArr)): if labelArr[i] in classDict.keys(): classDict[labelArr[i]] += 1 else : classDict[labelArr[i]] = 1 classSort = sorted (classDict.items(), key=lambda x: x[1 ], reverse=True ) return classSort[0 ][0 ] def calc_H_D (trainLabelArr ): ''' 计算数据集D的经验熵,参考公式5.7 经验熵的计算 :param trainLabelArr:当前数据集的标签集 :return: 经验熵 ''' H_D = 0 trainLabelSet = set ([label for label in trainLabelArr]) for i in trainLabelSet: p = trainLabelArr[trainLabelArr == i].size / trainLabelArr.size H_D += -1 * p * np.log2(p) return H_D def calcH_D_A (trainDataArr_DevFeature, trainLabelArr ): ''' 计算经验条件熵 :param trainDataArr_DevFeature:切割后只有feature那列数据的数组 :param trainLabelArr: 标签集数组 :return: 经验条件熵 ''' H_D_A = 0 trainDataSet = set ([label for label in trainDataArr_DevFeature]) for i in trainDataSet: H_D_A += trainDataArr_DevFeature[trainDataArr_DevFeature == i].size / trainDataArr_DevFeature.size \ * calc_H_D(trainLabelArr[trainDataArr_DevFeature == i]) return H_D_A def calcBestFeature (trainDataList, trainLabelList ): ''' 计算信息增益最大的特征 :param trainDataList: 当前数据集 :param trainLabelList: 当前标签集 :return: 信息增益最大的特征及最大信息增益值 ''' trainDataArr = np.array(trainDataList) trainLabelArr = np.array(trainLabelList) featureNum = trainDataArr.shape[1 ] maxG_D_A = -1 maxFeature = -1 H_D = calc_H_D(trainLabelArr) for feature in range (featureNum): trainDataArr_DevideByFeature = np.array(trainDataArr[:, feature].flat) G_D_A = H_D - calcH_D_A(trainDataArr_DevideByFeature, trainLabelArr) if G_D_A > maxG_D_A: maxG_D_A = G_D_A maxFeature = feature return maxFeature, maxG_D_A def getSubDataArr (trainDataArr, trainLabelArr, A, a ): ''' 更新数据集和标签集 :param trainDataArr:要更新的数据集 :param trainLabelArr: 要更新的标签集 :param A: 要去除的特征索引 :param a: 当data[A]== a时,说明该行样本时要保留的 :return: 新的数据集和标签集 ''' retDataArr = [] retLabelArr = [] for i in range (len (trainDataArr)): if trainDataArr[i][A] == a: retDataArr.append(trainDataArr[i][0 :A] + trainDataArr[i][A+1 :]) retLabelArr.append(trainLabelArr[i]) return retDataArr, retLabelArr def createTree (*dataSet ): ''' 递归创建决策树 :param dataSet:(trainDataList, trainLabelList) <<-- 元祖形式 :return:新的子节点或该叶子节点的值 ''' Epsilon = 0.1 trainDataList = dataSet[0 ][0 ] trainLabelList = dataSet[0 ][1 ] print ('start a node' , len (trainDataList[0 ]), len (trainLabelList)) classDict = {i for i in trainLabelList} if len (classDict) == 1 : return trainLabelList[0 ] if len (trainDataList[0 ]) == 0 : return majorClass(trainLabelList) Ag, EpsilonGet = calcBestFeature(trainDataList, trainLabelList) if EpsilonGet < Epsilon: return majorClass(trainLabelList) treeDict = {Ag:{}} treeDict[Ag][0 ] = createTree(getSubDataArr(trainDataList, trainLabelList, Ag, 0 )) treeDict[Ag][1 ] = createTree(getSubDataArr(trainDataList, trainLabelList, Ag, 1 )) return treeDict def predict (testDataList, tree ): ''' 预测标签 :param testDataList:样本 :param tree: 决策树 :return: 预测结果 ''' while True : (key, value), = tree.items() if type (tree[key]).__name__ == 'dict' : dataVal = testDataList[key] del testDataList[key] tree = value[dataVal] if type (tree).__name__ == 'int' : return tree else : return value def model_test (testDataList, testLabelList, tree ): ''' 测试准确率 :param testDataList:待测试数据集 :param testLabelList: 待测试标签集 :param tree: 训练集生成的树 :return: 准确率 ''' errorCnt = 0 for i in range (len (testDataList)): if testLabelList[i] != predict(testDataList[i], tree): errorCnt += 1 return 1 - errorCnt / len (testDataList) if __name__ == '__main__' : start = time.time() trainDataList, trainLabelList = loadData('../Mnist/mnist_train.csv' ) testDataList, testLabelList = loadData('../Mnist/mnist_test.csv' ) print ('start create tree' ) tree = createTree((trainDataList, trainLabelList)) print ('tree is:' , tree) print ('start test' ) accur = model_test(testDataList, testLabelList, tree) print ('the accur is:' , accur) end = time.time() print ('time span:' , end - start)
C4.5 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 Function C4.5 (R:包含连续属性的无类别属性集合,C:类别属性,S:训练集) /*返回一棵决策树*/ Begin If S为空,返回一个值为Failure的单个节点; If S是由相同类别属性值的记录组成, If R为空,则返回一个单节点,其值为在S的记录中找出的频率最高的类别属性值; [注意未出现错误则意味着是不适合分类的记录]; For 所有的属性R(Ri) Do If 属性Ri为连续属性,则 Begin 将Ri的最小值赋给A1: 将Rm的最大值赋给Am;/*m值手工设置*/ For j From 2 To m-1 Do Aj=A1+j*(A1Am)/m; 将Ri点的基于{< =Aj,>Aj}的最大信息增益属性(Ri,S)赋给A; End ; 将R中属性之间具有最大信息增益的属性(D,S)赋给D; 将属性D的值赋给{dj/j=1 ,2 ...m}; 将分别由对应于D的值为dj的记录组成的S的子集赋给{sj/j=1 ,2 ...m}; 返回一棵树,其根标记为D;树枝标记为d1,d2...dm; 再分别构造以下树: C4.5 (R-{D},C,S1),C4.5 (R-{D},C,S2)...C4.5 (R-{D},C,Sm); End C4.5
资源
scikit-learn 决策树指南
Decision trees and k Nearest Neighbors are covered practically in every ML book. We recommend “Pattern Recognition and Machine Learning” (C. Bishop) and “Machine Learning: A Probabilistic Perspective” (K. Murphy).
The book “Machine Learning in Action” (P. Harrington) will walk you through implementations of classic ML algorithms in pure Python.
Scipy 2017 scikit-learn tutorial by Alex Gramfort and Andreas Mueller.
Implementations of many ML algorithms. Good to search for decision trees and k-NN.
参考
mlcourse.ai Author: Yury Kashnitsky . Translated and edited by Christina Butsko, Gleb Filatov, and Yuanyuan Pao .
CSDN 上的博客
Scikit-learn 文档
中文文档 和中文教程