决策树是一种用于分类和回归的非参数有监督学习方法,其目标是创建一个模型,通过学习从数据特性中推断出的简单决策规则来预测目标变量的值,决策树算法,简单来说,就是一系列的 if...then...else
组合
如表所示:
名称 | 体温 | 表皮 | 胎生 | 水生生物 | 飞行生物 | … | 标签 |
---|---|---|---|---|---|---|---|
人类 | 恒温 | 毛发 | 是 | 否 | 否 | … | 哺乳类 |
鲸鱼 | 恒温 | 毛发 | 是 | 是 | 否 | … | 哺乳类 |
鸽子 | 恒温 | 羽毛 | 否 | 否 | 是 | … | 鸟类 |
海龟 | 冷血 | 鳞片 | 否 | 半 | 否 | … | 爬行类 |
蝾螈 | 冷血 | 无 | 否 | 半 | 否 | … | 两栖类 |
蜘蛛 | … |
如果目标是对哺乳类和非哺乳类进行分类,则根据上表建立的决策树模型:
决策树的决策过程就是从根节点开始,测试待分类项中对应的特征属性,并按照其值选择输出分支,直到叶子节点,将叶子节点的存放的类别作为决策结果
建立决策树模型的一般步骤: (一)特征选择、(二)决策树的生成、(三)决策树的修剪
建立决策树的关键,即在当前状态下选择哪个属性作为分类依据,根据不同的目标函数,建立决策树主要有以下三种算法:
算法 | 支持场景 | 树结构 | 特征选择 | 连续值处理 | 缺失值处理 | 剪枝 | 特征属性多次使用 |
---|---|---|---|---|---|---|---|
ID3 | 分类 | 多叉树 | 信息增益 | 不支持 | 不支持 | 不支持 | 不支持 |
C4.5 | 分类 | 多叉树 | 信息增益率 | 支持 | 支持 | 支持 | 不支持 |
CART | 分类、回归 | 二叉树 | 基尼指数均方差 | 支持 | 支持 | 支持 | 支持 |
信息熵的表示式为:
H ( D ) = − ∑ i = 1 n p i ( log 2 p i ) H(D)=-\sum_{i=1}^n{p_i(\log_2{p_i})} H(D)=−i=1∑npi(log2pi)
其中, D D D 表示一个数据集, n n n 表示这是 n n n分类问题, i i i 表示某一标签,样本点属于标签 i i i 的概率为 p i p_i pi
如果数据集根据某个属性 A A A 被划分为 m m m 类,条件熵的表示式为:
H ( D ∣ A ) = ∑ j = 1 m D j D H ( D j ) H(D|A)=\sum_{j=1}^m{ {D_j \over D}H(D_j)} H(D∣A)=j=1∑mDDjH(Dj)
其中, D D D 表示数据集的样本数, A A A 是某一特征/属性, m m m 表示该属性有多少种不同类别, j j j 表示该属性的某一类, D j D_j Dj 表示属于 j j j 类的样本数目, H ( D j ) H(D_j) H(Dj) 表示属于 j j j 类的样本的信息熵
信息增益的表示式为:
G a i n ( D , A ) = H ( D ) − H ( D ∣ A ) Gain(D,A)=H(D)−H(D|A) Gain(D,A)=H(D)−H(D∣A)
其中, G a i n ( D , A ) Gain(D,A) Gain(D,A) 表示数据集 D D D 中,属性 A A A 的信息增益
例如: 下面训练集中,样本总数: 15 15 15,类别数: n = 2 n=2 n=2,显然这是二分类问题,计算 A = 年 龄 A=年龄 A=年龄 时划分的信息增益
其中标签为 否 的样本有6个,标签为 是 的样本有9个,因此训练集的信息熵为:
H = − ∑ i = 1 2 p i ( log 2 p i ) = − 6 15 ( log 2 6 15 ) − 9 15 ( log 2 9 15 ) ≈ 0.971 H=-\sum_{i=1}^2{p_i(\log_2p_i)}=-{6 \over 15}(\log_2{6 \over 15})-{9 \over 15}(\log_2{9 \over 15})≈0.971 H=−i=1∑2pi(log2pi)=−156(log2156)−159(log2159)≈0.971
如果按年龄划分,则此时
序号 | 年龄 | 数目 | 类别:是 | 类别:否 |
---|---|---|---|---|
1 | 青年 | 5 | 2 | 3 |
2 | 中年 | 5 | 3 | 2 |
3 | 老年 | 5 | 4 | 1 |
首先分别计算 H ( D j = 1 ) H(D_{j=1}) H(Dj=1), H ( D j = 2 ) H(D_{j=2}) H(Dj=2), H ( D j = 3 ) H(D_{j=3}) H(Dj=3) 的大小:
H ( D j = 1 ) = − 2 5 ( log 2 2 5 ) − 3 5 ( log 2 3 5 ) ≈ 0.971 H(D_{j=1})=-{2 \over 5}(\log_2{2 \over 5})-{3 \over 5}(\log_2{3 \over 5})≈0.971 H(Dj=1)=−52(log252)−53(log253)≈0.971
H ( D j = 2 ) = − 3 5 ( log 2 3 5 ) − 2 5 ( log 2 2 5 ) ≈ 0.971 H(D_{j=2})=-{3 \over 5}(\log_2{3 \over 5})-{2 \over 5}(\log_2{2 \over 5})≈0.971 H(Dj=2)=−53(log253)−52(log252)≈0.971
H ( D j = 3 ) = − 4 5 ( log 2 4 5 ) − 1 5 ( log 2 1 5 ) ≈ 0.7219 H(D_{j=3})=-{4 \over 5}(\log_2{4 \over 5})-{1 \over 5}(\log_2{1 \over 5})≈0.7219 H(Dj=3)=−54(log254)−51(log251)≈0.7219
此时,分别计算条件熵为:
H ( D ∣ 年 龄 ) = ∑ j = 1 3 D j D H ( D j ) = 5 15 × 0.971 + 5 15 × 0.971 + 5 15 × 0.7219 ≈ 0.8897 H(D|年龄)=\sum_{j=1}^3{ {D_j \over D}H(D_j)}={ {5 \over 15}\times0.971}+{ {5 \over 15}\times0.971}+{ {5 \over 15}\times0.7219}≈0.8897 H(D∣年龄)=j=1∑3DDjH(Dj)=155×0.971+155×0.971+155×0.7219≈0.8897
计算信息增益为:
G a i n ( D , 年 龄 ) = H ( D ) − H ( D ∣ 年 龄 ) = 0.971 − 0.8897 = 0.2491 Gain(D,年龄)=H(D)−H(D|年龄)=0.971-0.8897=0.2491 Gain(D,年龄)=H(D)−H(D∣年龄)=0.971−0.8897=0.2491
同理,我们可以计算出 A A A 分别为 {有工作, 有房子, 信用} 时的信息增益,最后比较 A A A 取不同属性时信息增益的大小,最终我们将选取信息增益最大的属性作为决策树的节点
C4.5 算法是对 ID3 算法的改进,其使用的是信息增益率而非信息增益来选择属性,并且加入了剪枝操作
信息增益的表示式为:
I G R = G a i n ( D , A ) H ( D ) , 其 中 G a i n ( D , A ) = H ( D ) − H ( D ∣ A ) IGR={Gain(D,A) \over H(D)},其中Gain(D,A)=H(D)−H(D|A) IGR=H(D)Gain(D,A),其中Gain(D,A)=H(D)−H(D∣A)
其中, H ( D ) H(D) H(D) 为训练集的信息熵,其实求信息增益率就是比求信息增益多一个除法运算的步骤
由于决策树容易过拟合,因此我们需要对决策树进行剪枝,以提高其泛化性能
剪枝的基本策略有“预剪枝”(prepruning)和“后剪枝”(post-pruning)
预剪枝策略:
预剪枝在划分节点前,就要先确定决策树是否继续增长,及时停止增长
停止增长的主要判断依据:
节点内数据样本已低于某一阈值
所有节点特征都已划分
节点划分前准确率比划分后准确率高
预剪枝的特点:
预剪枝不仅可以降低过拟合的风险而且还可以减少训练时间,但另一方面它是基于“贪心”策略,会带来欠拟合风险
后剪枝策略:
在已经生成的决策树上进行剪枝,从而得到简化版的剪枝决策树
后剪枝决策树通常比预剪枝决策树保留了更多的分支
后剪枝主要方法:
采用的悲观剪枝方法,用递归的方式从低往上针对每一个非叶子节点,评估用一个最佳叶子节点去代替这课子树是否有益,如果剪枝后与剪枝前相比其错误率是保持或者下降,则这棵子树就可以被替换掉
通过训练数据集上的错误分类数量来估算未知样本上的错误率
后剪枝的特点:
一般情况下,后剪枝的欠拟合风险更小,泛化性能往往优于预剪枝决策树
用基尼指数来选择属性(分类),或用均方差来选择属性(回归),基尼指数表示集合的不确定性
显然,CART 算法既可以用于创建分类树,也可以用于创建回归树,两者的构建过程稍有差异,如果目标变量是离散的,称为分类树,如果目标变量是连续的,称为回归树
在多分类问题中,概率分布的基尼指数为:
G i n i ( p ) = ∑ k = 1 K p k ( 1 − p k ) Gini(p)=\sum_{k=1}^K{p_k(1-p_k)} Gini(p)=k=1∑Kpk(1−pk)
其中, K K K 表示这是 K K K分类问题, k k k 表示某一标签,样本点属于标签 k k k 的概率为 p k p_k pk
在二分类问题中,概率分布的基尼指数为:
G i n i ( p ) = 2 p ( 1 − p ) = 1 − p 2 − ( 1 − p ) 2 Gini(p)=2p(1-p)=1-p^2-(1-p)^2 Gini(p)=2p(1−p)=1−p2−(1−p)2
其中, p p p 表示样本标签属于任一标签的概率, ( 1 − p ) (1-p) (1−p) 则与 p p p 互补
由于 CART 需要构建二叉树,因此应根据某一属性将数据集划分为两类,假如数据集根据属性 A A A 划分为 D 1 D_1 D1 和 D 2 D_2 D2 两部分,则基尼指数表示式为:
G i n i ( D , A ) = D 1 D G i n i ( D 1 ) + D 2 D G i n i ( D 2 ) Gini(D,A)={D_1 \over D}Gini(D_1)+{D_2 \over D}Gini(D_2) Gini(D,A)=DD1Gini(D1)+DD2Gini(D2)
其中, D D D 表示数据集的样本数,且满足 D = D 1 + D 2 D=D_1+D_2 D=D1+D2, G i n i ( D 1 ) Gini(D_1) Gini(D1) 或 G i n i ( D 2 ) Gini(D_2) Gini(D2) 中 p k p_k pk 分别是 D 1 D_1 D1 或 D 2 D_2 D2 中样本点属于标签 k k k 的概率
例如: 分别计算按:{ D 1 = 青 年 D_1=青年 D1=青年, D 2 = 其 他 D_2=其他 D2=其他 }、{ D 1 = 中 年 D_1=中年 D1=中年, D 2 = 其 他 D_2=其他 D2=其他 }、{ D 1 = 老 年 D_1=老年 D1=老年, D 2 = 其 他 D_2=其他 D2=其他 } 来划分数据集时的基尼指数
①按 { D 1 = 青 年 D_1=青年 D1=青年, D 2 = 其 他 D_2=其他 D2=其他 } 划分时:
G i n i ( D , A 1 = 青 年 ) = 5 15 [ 2 × 2 5 × ( 1 − 2 5 ) ] ⏟ G i n i ( D 1 ) + 10 15 [ 2 × 7 10 × ( 1 − 7 10 ) ] ⏟ G i n i ( D 2 ) = 0.44 Gini(D,A_1=青年)={5 \over 15}\underbrace{[2 \times{2 \over 5}\times(1-{2 \over 5})]}_{Gini(D_1)}+{10 \over 15}\underbrace{[2 \times{7 \over 10}\times(1-{7 \over 10})]}_{Gini(D_2)}=0.44 Gini(D,A1=青年)=155Gini(D1) [2×52×(1−52)]+1510Gini(D2) [2×107×(1−107)]=0.44
②按 { D 1 = 中 年 D_1=中年 D1=中年, D 2 = 其 他 D_2=其他 D2=其他 } 划分时:
G i n i ( D , A 2 = 中 年 ) = 5 15 [ 2 × 3 5 × ( 1 − 3 5 ) ] ⏟ G i n i ( D 1 ) + 10 15 [ 2 × 6 10 × ( 1 − 6 10 ) ] ⏟ G i n i ( D 2 ) = 0.48 Gini(D,A_2=中年)={5 \over 15}\underbrace{[2 \times{3 \over 5}\times(1-{3 \over 5})]}_{Gini(D_1)}+{10 \over 15}\underbrace{[2 \times{6 \over 10}\times(1-{6 \over 10})]}_{Gini(D_2)}=0.48 Gini(D,A2=中年)=155Gini(D1) [2×53×(1−53)]+1510Gini(D2) [2×106×(1−106)]=0.48
③按 { D 1 = 老 年 D_1=老年 D1=老年, D 2 = 其 他 D_2=其他 D2=其他 } 划分时:
G i n i ( D , A 3 = 老 年 ) = 5 15 [ 2 × 4 5 × ( 1 − 4 5 ) ] ⏟ G i n i ( D 1 ) + 10 15 [ 2 × 5 10 × ( 1 − 5 10 ) ] ⏟ G i n i ( D 2 ) = 0.44 Gini(D,A_3=老年)={5 \over 15}\underbrace{[2 \times{4 \over 5}\times(1-{4 \over 5})]}_{Gini(D_1)}+{10 \over 15}\underbrace{[2 \times{5 \over 10}\times(1-{5 \over 10})]}_{Gini(D_2)}=0.44 Gini(D,A3=老年)=155Gini(D1) [2×54×(1−54)]+1510Gini(D2) [2×105×(1−105)]=0.44
根据某一属性 = a =a A=a 进行划分,选择任一划分点 s 将数据集划分成 D 1 D_1 D1 和 D 2 D_2 D2 两个集合,求出使 D 1 D_1 D1 和 D 2 D_2 D2 各自集合的均方差最小,同时求出使 D 1 D_1 D1 和 D 2 D_2 D2 的均方差值的最小值之和具有最小值:
m = m i n a , s [ m i n c 1 ∑ x i ∈ D 1 ( y i − c 1 ) 2 + m i n c 2 ∑ x i ∈ D 2 ( y i − c 2 ) 2 ] m=min_{a,s}[min_{c_1}\sum_{x_i\in D_1}(y_i-c_1)^2+min_{c_2}\sum_{x_i\in D_2}(y_i-c_2)^2] m=mina,s[minc1xi∈D1∑(yi−c1)2+minc2xi∈D2∑(yi−c2)2]
其中, x i x_i xi 表示输入值, y i y_i yi 表示对应的输出值, c 1 c_1 c1 为 D 1 D_1 D1 数据集的所有样本输出值的均值, c 2 c_2 c2 为 D 2 D_2 D2 数据集的所有样本输出值的均值
使上式成立时的属性 a a a 和划分点 s s s 就是最佳输入值划分属性和最佳输入值划分点;此时的 c 1 c_1 c1 和 c 2 c_2 c2 分别就是划分出两类的输出预测值
例如: 看下表,计算按属性 A = 职 业 A=职业 A=职业 划分时, { “ 学 生 ” , “ 上 班 族 ” , “ 老 师 ” } \{“学生”,“上班族”,“老师”\} { “学生”,“上班族”,“老师”} 的最佳输入值划分点 s s s 和输出预测值
①按 { D 1 = 学 生 D_1=学生 D1=学生, D 2 = 其 他 D_2=其他 D2=其他 } 划分时:
c 1 = 12 + 18 + 21 3 = 17 , c 2 = 26 + 47 + 36 + 29 4 = 34.5 c_1={12+18+21 \over 3}=17,c_2={26+47+36+29 \over 4}=34.5 c1=312+18+21=17,c2=426+47+36+29=34.5
m = ∑ x i ∈ D 1 ( y i − c 1 ) 2 + ∑ x i ∈ D 2 ( y i − c 2 ) 2 = 303 , 其 中 y i ∈ { ( 12 , 18 , 21 ) , ( 26 , 47 , 36 , 29 ) } m=\sum_{x_i\in D_1}(y_i-c_1)^2+\sum_{x_i\in D_2}(y_i-c_2)^2=303,其中y_i\in\{(12,18,21),(26,47,36,29)\} m=xi∈D1∑(yi−c1)2+xi∈D2∑(yi−c2)2=303,其中yi∈{ (12,18,21),(26,47,36,29)}
②按 { D 1 = 老 师 D_1=老师 D1=老师, D 2 = 其 他 D_2=其他 D2=其他 } 划分时:
c 1 = 26 + 29 2 = 27.5 , c 2 = 12 + 18 + 47 + 36 + 21 5 = 26.5 c_1={26+29 \over 2}=27.5,c_2={12+18+47+36+21 \over 5}=26.5 c1=226+29=27.5,c2=512+18+47+36+21=26.5
m = ∑ x i ∈ D 1 ( y i − c 1 ) 2 + ∑ x i ∈ D 2 ( y i − c 2 ) 2 = 742.66 , 其 中 y i ∈ { ( 26 , 29 ) , ( 12 , 18 , 47 , 36 , 21 ) } m=\sum_{x_i\in D_1}(y_i-c_1)^2+\sum_{x_i\in D_2}(y_i-c_2)^2=742.66,其中y_i\in\{(26,29),(12,18,47,36,21)\} m=xi∈D1∑(yi−c1)2+xi∈D2∑(yi−c2)2=742.66,其中yi∈{ (26,29),(12,18,47,36,21)}
③按 { D 1 = 上 班 族 D_1=上班族 D1=上班族, D 2 = 其 他 D_2=其他 D2=其他 } 划分时:
c 1 = 47 + 36 2 = 41.5 , c 2 = 12 + 18 + 26 + 29 + 21 5 = 21.2 c_1={47+36 \over 2}=41.5,c_2={12+18+26+29+21 \over 5}=21.2 c1=247+36=41.5,c2=512+18+26+29+21=21.2
m = ∑ x i ∈ D 1 ( y i − c 1 ) 2 + ∑ x i ∈ D 2 ( y i − c 2 ) 2 = 238.8 , 其 中 y i ∈ { ( 47 , 36 ) , ( 12 , 18 , 26 , 29 , 21 ) } m=\sum_{x_i\in D_1}(y_i-c_1)^2+\sum_{x_i\in D_2}(y_i-c_2)^2=238.8,其中y_i\in\{(47,36),(12,18,26,29,21)\} m=xi∈D1∑(yi−c1)2+xi∈D2∑(yi−c2)2=238.8,其中yi∈{ (47,36),(12,18,26,29,21)}
比较知: 238.8 < 303 < 742.66 238.8<303<742.66 238.8<303<742.66,因此,当按 a = 职 业 a=职业 a=职业 划分时,有:
m i n 职 业 [ m i n c 1 ∑ x i ∈ D 1 ( y i − c 1 ) 2 + m i n c 2 ∑ x i ∈ D 2 ( y i − c 2 ) 2 ] min_{职业}[min_{c_1}\sum_{x_i\in D_1}(y_i-c_1)^2+min_{c_2}\sum_{x_i\in D_2}(y_i-c_2)^2] min职业[minc1xi∈D1∑(yi−c1)2+minc2xi∈D2∑(yi−c2)2]
此时,按 { D 1 = 上 班 族 D_1=上班族 D1=上班族, D 2 = 其 他 D_2=其他 D2=其他 } 作为输入划分,而 c 1 = 41.5 , c 2 = 21.2 c_1=41.5,c_2=21.2 c1=41.5,c2=21.2 则为输出预测值最佳
f ( x ) = { 41.5 , x ∈ { 上 班 族 } 21.2 , x ∈ { “ 老 师 ” , “ 学 生 ” } f(x)=\left\{ \begin{aligned} 41.5&,x\in\{上班族\} \\ 21.2&,x\in\{“老师”,“学生”\} \end{aligned} \right. f(x)={ 41.521.2,x∈{ 上班族},x∈{ “老师”,“学生”}
实际上,我们只计算了 A = 职 业 A=职业 A=职业 的情况,还需要计算 A = { “ 看 电 视 时 长 ” , “ 婚 姻 状 况 ” } A=\{“看电视时长”,“婚姻状况”\} A={ “看电视时长”,“婚姻状况”} 的情况,才能计算出均方差的最小值 m i n a , s [ m i n c 1 ∑ x i ∈ D 1 ( y i − c 1 ) 2 + m i n c 2 ∑ x i ∈ D 2 ( y i − c 2 ) 2 ] min_{a,s}[min_{c_1}\sum_{x_i\in D_1}(y_i-c_1)^2+min_{c_2}\sum_{x_i\in D_2}(y_i-c_2)^2] mina,s[minc1∑xi∈D1(yi−c1)2+minc2∑xi∈D2(yi−c2)2] 才能筛选出最佳的划分属性及其划分点和输出值
本文仅展示常用参数,若想了解更详细的参数,请参考:sklearn中文社区
# 导入决策树模块
from sklearn import tree
类方法 | 解释 |
---|---|
tree.DecisionTreeClassifier | 分类树 |
tree.DecisionTreeRegressor | 回归树 |
tree.export_graphviz | 将生成的决策树导出为DOT格式,实现决策树可视化 |
tree.ExtraTreeClassifier | 高随机版本的分类树 |
tree.ExtraTreeRegressor | 高随机版本的回归树 |
clf = tree.DecisionTreeClassifier(…)
初始化分类树
criterion
切分依据:{“gini”:基尼指数, “entropy”:信息增益}, default=”gini”
splitter
拆分策略选择:{“best”:最佳拆分策略, “random”:最佳随机拆分策略}, default=”best”
max_depth
树的最大深度:int, default=None,int 类型:树的最大深度,None:将所有节点展开
min_samples_split
一个节点必须要包含至少 min_samples_split 个训练样本,这个节点才允许被分枝,否则分枝就不会发生:int or float, default=2,int 类型:样本数,float 类型:占样本总数的比例,向下取整
min_samples_leaf
节点在分枝后的每个子节点必须包含至少 min_samples_leaf 个训练样本,否则不会分枝:int or float, default=1,int 类型:样本数,float 类型:占样本总数的比例,向下取整
(即该参数规定了叶子节点处所需的最小样本数)
max_features
寻找最佳划分属性时要考虑的属性数量:int, float or {“auto”, “sqrt”, “log2”}, default=None
min_impurity_decrease
如果节点分枝会导致不纯度的减少大于或等于该值,则该节点将被分裂分枝:float, default=0.0
返回值
分类决策树对象
clf.fit(X, y[, sample_weight, check_input, …])
根据训练集数据和标签(X,y)
建立决策树分类器
X
必须是二维及以上的数组
y
必须是一维数组,注:shape=(1,80) 仍然是二维数组,shape=(80, ) 才是一维数组
clf.score(X, y[, sample_weight])
使用测试集数据和标签(X,y)
测试决策树,并返回给定测试数据和标签上的平均准确度
clf.feature_importances_
查看各属性在决策树中的贡献度
clf.apply(X_test)
返回每个测试样本所在的叶子节点的索引
clf.predict(X_test)
返回每个测试样本的分类/回归预测结果
例如: 导入葡萄酒数据集,对决策树进行训练、测试,并输出准确度
# 导入相关库
import pandas as pd
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split
from sklearn import tree
# 加载并返回葡萄酒数据集
wine = load_wine()
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(wine.data, wine.target, test_size=0.3)
# 初始化决策树
clf = tree.DecisionTreeClassifier()
# 训练决策树
clf = clf.fit(X_train, y_train)
# 测试决策树
score = clf.score(X_test, y_test)
predict = clf.predict(X_test)
score, predict
# 导入相关库
import graphviz
dot_data = tree.export_graphviz(…)
绘图,把决策树可视化
decision_tree
用来做可视化的决策树对象
out_file
输出文件的名称,如果为 None,则结果以字符串形式返回
max_depth
描绘的最大深度,如果为 None,则这树完全生长
feature_names
每个特征的名字:list of strings, optional, default=None
class_names
每个目标类别/标签的名称(按升序排列):list of strings, bool or None, default=None
label
是否显示不纯度的信息性标签等:{‘all’:显示在每个节点上, ‘root’:显示在根节点上, ‘none’:显示任何节点上}, default=’all’
filled
bool, default=False,设置为True时,绘制节点以表示多数类用于分类问题,值的极值用于回归问题,或表示节点的纯度用于多输出问题,简单来说,就是给方框填充颜色,而颜色的深浅代表节点的纯度
rounded
设置为True时,绘制带有圆角的节点框,并使用赫维提卡字体 (Helvetica) 代替新罗马字体 (Times-Roman)
返回值
string,树模型 GraphViz dot 格式的字符串
graphviz.Source(dot_data)
将树模型的 GraphViz dot 格式字符串转化为可视化图片
例如: 将葡萄酒决策树可视化
# 导入相关库
import pandas as pd
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split
from sklearn import tree
import graphviz
# 加载并返回葡萄酒数据集
wine = load_wine()
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(wine.data, wine.target, test_size=0.3)
# 初始化决策树
clf = tree.DecisionTreeClassifier()
# 训练决策树
clf = clf.fit(X_train, y_train)
# 测试决策树
score = clf.score(X_test, y_test)
# 根据数据集,定义一个中文的属性名列表和分类标签列表
feature_names = ['酒精', '苹果酸', '灰', '灰的碱性', '镁',
'总酚', '类黄酮', '非黄烷类酚类', '花青素',
'颜色强度', '色调', 'od280/od315稀释葡萄酒', '脯氨酸']
class_names = ["琴酒", "雪莉", "贝尔摩德"]
# 将决策树可视化
dot_data = tree.export_graphviz(clf,
feature_names = feature_names,
class_names = class_names,
filled = True,
rounded = True)
graph = graphviz.Source(dot_data)
graph
输出结果如图所示,当然每次运作出来的结果可能不一样:(因为我们在调用 train_test_split()
方法时并没有设置随机数种子,因此它每次分配训练集和数据集都是随机的,因此我们可以设置 random_state=?
,使得每次构建的决策树是一样的,而我们可以通过调整 random_state
的值,来决定选择精度最高的决策树模型)
我们发现,所构建的决策树并没有使用全部属性:(其中输出的数是各属性对构建决策树的贡献度,根节点对决策树的贡献一般是最高的)
为了避免决策树过拟合,我们可以对决策树进行剪枝
DecisionTreeClassifier(…) 的剪枝相关参数:
max_depth
树的最大深度:int, default=None,int 类型:树的最大深度,None:将所有节点展开
min_samples_split
一个节点必须要包含至少 min_samples_split 个训练样本,这个节点才允许被分枝,否则分枝就不会发生:int or float, default=2,int 类型:样本数,float 类型:占样本总数的比例,向下取整
min_samples_leaf
节点在分枝后的每个子节点必须包含至少 min_samples_leaf 个训练样本,否则不会分枝:int or float, default=1,int 类型:样本数,float 类型:占样本总数的比例,向下取整
max_depth
一般越小越好,因为树的深度越低,泛化性能越好,树的深度越深,意味着对样本的数量需求翻倍,建议从 3 开始寻找min_samples_leaf
该参数太小会引起过拟合,太大会阻止模型学习数据,一般建议从 5 开始使用;对于类别不多的分类问题,1 通常就是最佳选择DecisionTreeClassifier(…) 的剪枝相关参数:(搭配
max_depth
使用,这些参数用于树的“精修”)
max_features
寻找最佳划分属性时要考虑的属性数量:int, float or {“auto”, “sqrt”, “log2”}, default=None
min_impurity_decrease
如果节点分枝会导致不纯度的减少大于或等于该值,则该节点将被分裂分枝:float, default=0.0
max_features
用来限制高维度数据的过拟合的剪枝参数,在不知道决策树中的各个特征的重要性的情况下,强行设定这个参数可能会导致模型学习不足,如果希望通过降维的方式防止过拟合,建议使用 PCA、ICA 或特征选择模块中的降维算法min_impurity_decrease
限制信息增益的大小,信息增益小于设定数值的分枝不会发生,该值通常在 0~0.5 之间DecisionTreeClassifier(…) 中较少使用,但也比较重要的剪枝参数:
class_weight
以 {class_label: weight} 的形式表示与类别/标签的权重,如果取值None,则所有分类的权重为1,对于多输出问题,可以按照 y 列的顺序提供一个字典列表:dict, list of dict or “balanced”, default=None
min_weight_fraction_leaf
float, default=0.0,在所有叶节点处(所有输入样本)的权重总和中的最小加权分数,如果未提供 sample_weight,则样本的权重相等
class_weight
有时候,有些数据的标签天生所占的比例较大,比如:航天公司想预测某一天的航班是否会发生事故,但我们知道,飞机发生事故的概率是极低的,在这种分类状况下,模型在训练的时候会更多地关注不发生事故的样本,而对发生事故的样本的捕捉度很低,即使模型什么也不做,全把结果预测成“不会发生事故”,其模型的正确率也会非常高class_weight
这个参数给样本数小的标签给予更高的权重,让模型更偏向于样本数小的标签min_weight_fraction_leaf
这个基于权重的剪枝参数来使用可以通过绘制超参数曲线来确定最优的剪枝参数
例如: 对 max_depth
循环调参,并绘制超参数曲线
# 导入相关库
import pandas as pd
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split
from sklearn import tree
import matplotlib.pyplot as plt
# 加载并返回葡萄酒数据集
wine = load_wine()
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(wine.data, wine.target, test_size=0.3, random_state=20)
# 创建空列表,作为绘图的纵坐标
test = []
for i in range(10):
clf = tree.DecisionTreeClassifier(max_depth=i+1, # 每次循环 max_depth + 1
criterion="entropy", # 使用信息增益率
random_state=30,
splitter="random",) # 最优随机拆分策略
clf = clf.fit(X_train, y_train) # 对设置的参数进行测试
score = clf.score(X_test, y_test)
test.append(score) # 将新的测试精度结果合并到 test 列表中
plt.figure() # 创建画布
plt.plot(range(1,11), test, color="b", label="max_depth") # 绘制折线图
plt.legend() # 显示图例
plt.show()
发现 max_depth
取值为 4、5 时 score 最高,此时模型最佳
clf = tree.DecisionTreeRegressor(…)
初始化回归树
几乎所有参数,属性和接口都和分类决策树树一样,需要注意的是,在回归树决策树中,没有标签分布是否均衡的问题(因为回归树是连续的),因此没有class_weight
这样的参数
criterion
计算分枝好坏的依据:{“mse”:均方误差, “friedman_mse”:弗里德曼均方误差, “mae”:平均绝对误差}, default=”mse”
虽然均方误差永远为正,但是sklearn当中使用均方误差作为评判标准时,却是计算”负均方误差“(neg_mean_squared_error),这是sklearn开发者为了区分而将误差归为负数,而均方误差本身也是一种损失量(Loss),因此实际上均方误差就是 neg_mean_squared_error 的负值
splitter
拆分策略选择:{“best”:最佳拆分策略, “random”:最佳随机拆分策略}, default=”best”
max_depth
树的最大深度:int, default=None,int 类型:树的最大深度,None:将所有节点展开
min_samples_split
一个节点必须要包含至少 min_samples_split 个训练样本,这个节点才允许被分枝,否则分枝就不会发生:int or float, default=2,int 类型:样本数,float 类型:占样本总数的比例,向下取整
min_samples_leaf
节点在分枝后的每个子节点必须包含至少 min_samples_leaf 个训练样本,否则不会分枝:int or float, default=1,int 类型:样本数,float 类型:占样本总数的比例,向下取整
(即该参数规定了叶子节点处所需的最小样本数)
max_features
寻找最佳划分属性时要考虑的属性数量:int, float or {“auto”, “sqrt”, “log2”}, default=None
min_impurity_decrease
如果节点分枝会导致不纯度的减少大于或等于该值,则该节点将被分裂分枝:float, default=0.0
返回值
回归决策树对象
clf.fit(X, y[, sample_weight, check_input, …])
根据训练集数据和标签(X,y)
建立决策树分类器
X
必须是二维及以上的数组
y
必须是一维数组,注:shape=(1,80) 仍然是二维数组,shape=(80, ) 才是一维数组
clf.score(X, y[, sample_weight])
使用测试集数据和标签(X,y)
测试决策树,并返回返回预测的确定系数 R 2 R^2 R2, R 2 → 1 R^2\rightarrow1 R2→1 越好
clf.feature_importances_
查看各属性在决策树中的贡献度
clf.apply(X_test)
返回X_test
中每个测试样本所在的叶子节点的索引
clf.predict(X_test)
预测X_test
的类或回归值
例如: 一维回归图像的绘制(涉及numpy的使用、降维和升维、matplotlib绘图,不熟悉可以参考我的数据挖掘)
(一)数据准备:
# 导入相关库
import numpy as np
from sklearn.tree import DecisionTreeRegressor
import matplotlib.pyplot as plt
rng = np.random.RandomState(1) # 设置随机数种子,返回随机数种子对象
X_train = np.sort(5 * rng.rand(80,1), axis=0) # 使用随机数种子,生成均匀分布在[0,5],shape=(80,1)的数组,并进行排序
y_train = np.sin(X_train).ravel() # 将X代入正弦函数,得到对应的[-1,1]的y值,并将多维数组转换为一维数组
y[::5] += 3 * (0.5 - rng.rand(16)) # 为y添加噪声
np.random.RandomState()
用于设置随机数种子,并返回随机数种子对象 rng
;rng.rand()
,可以使每次编译都生成相同的随机数;np.random.rand(d1, d2, ..., dn)
用于生成随机分布在 [0,1] 的浮点形数组,其中数组的形状由 shape=(d0, d1, …, dn) 决定;np.sort(5 * rng.rand(80,1), axis=0)
表示用随机数种子,生成随机布在[0,5],的 80×1 的数组,并按竖直列进行排序;ravel()
是降维函数,可以将 n 维数组变为 n-1 维数组,这里将 shape=(80, 1) 的二维数组降至一维;fit()
方法来训练模型,其传入的X必须是二维及以上的数组;y[::5] += 3 * (0.5 - rng.rand(16))
表示,y按步长 step=5 进行范围在 [-1.5, 1.5] 的随机数赋值破坏,以增加噪声;(二)构建回归决策树: 为了便于比较,我们构建了两棵最大深度不同的回归树
regr1 = DecisionTreeRegressor(max_depth=2) # 构建最大深度为2的回归树
regr2 = DecisionTreeRegressor(max_depth=5) # 构建最大深度为5的回归树
regr1.fit(X_train, y_train) # 训练回归树1
regr2.fit(X_train, y_train) # 训练回归树2
X_test = np.arange(0.0, 5.0, 0.01)[:, np.newaxis] # 生成[0,5),step=0.01的测试集,并升维为
y1 = regr1.predict(X_test) # 预测回归树1每个回归样本点得到的结果
y2 = regr2.predict(X_test) # 预测回归树2每个回归样本点得到的结果
[:, np.newaxis]
是一个升维切片,假设 array 是一个4元素数组 shape=(4, ),那么 array[:, np.newaxis]
可以把 array 转为 shape=(4, 1) 的二维数组;而 array[np.newaxis, :]
可以把 array 转为 shape=(1,4 ) 的二维数组[:, np.newaxis]
和 降维函数ravel()
的功能相反reshape()
方法升维(三)绘制回归预测结果:
plt.figure()
# 绘制原数据集散点图,s散点的大小,edgecolor设置散点边框颜色,c设置散点颜色
plt.scatter(X, y, s=20, edgecolor="black", c="darkorange", label="data")
# 分别绘制测试集对回归树1和回归树2的测试结果
plt.plot(X_test, y1, color="cornflowerblue", label="max_depth=2", linewidth=2)
plt.plot(X_test, y2, color="yellowgreen", label="max_depth=5", linewidth=2)
# 添加标题
plt.xlabel("data")
plt.ylabel("target")
plt.title("Decision Tree Regression")
# 显示图例,显示图像
plt.legend()
plt.show()
最终结果如图:(显然,max_depth=5 的回归树出现了过拟合)
交叉验证是用来观察模型的稳定性的一种方法,我们将数据划分为 n 份,依次使用其中一份作为测试集,剩下的 n-1 份作为训练集对模型进行训练,并计算模型的精确性来评估模型的平均准确程度
训练集和测试集的划分会干扰模型的结果,因此用交叉验证 n 次的结果求出的平均值,是评价模型的效果和泛化性的一个更好的指标
# 导入相关方法
from sklearn.model_selection import cross_val_score
cross_val_score(estimator, X, y=None…)
执行交叉验证
estimator
要进行交叉验证的对象,可以是 sklearn 中的各类机器学习算法模型
X
进行验证的数据集 (data)
y
在监督学习的情况下要尝试预测的目标变量 (target),default=None,对于连续数据,其标签为数字
cv
确定交叉验证切分数,即交叉验证的次数,default=5
scoring
string,如果为None,则使用估计器的默认评分器(如果可用),即指定使用scoring=?
来评估算法模型
返回值
scores
数组,分别表示每一次验证的结果
scoring = "neg_mean_squared_error"
,即负的均方误差值例如: 导入波士顿房价数据集 load_boston,建立回归决策树,并对回归树模型进行交叉验证
# 导入相关库
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)
# 10倍交叉验证
scores = cross_val_score(regressor, boston.data, boston.target, cv=10,
scoring="neg_mean_squared_error")
scores
(一)数据预处理: 包含 data.csv 和 test.csv,可以在 Kaggle泰坦尼克号 的 Data 中下载(不怎么容易),Github 上应该也能下载到
# 导入相关库
import numpy as
import pandas as pd
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import cross_val_score
import matplotlib.pyplot as plt
# 读取data.csv文件
data = pd.read_csv("./data.csv", index_col = [])
发现数据中含有 NaN 的数据,并且姓名、船票等可能是对预测无益的数据,而 Cabin 有大量的数据缺失值,需要进行数据清洗
# 删除缺失值过多的列,和对预测无益数据,inplace=True决定覆盖原表
data.drop(["Cabin","Name","Ticket"], inplace=True, axis=1)
data["Age"] = data["Age"].fillna(data["Age"].mean()) # 处理缺失值,采用平均值填补
data = data.dropna() # 删除含Nan的行,默认axis=0
其次,我们需要为 data["Sex"]
:["male", "female"]
和 data["Embarked"]
:['S', "C", "Q"]
编码成 {0, 1, 2} 的形式
# 将二分类变量转换为数值型变量
data["Sex"] = (data["Sex"]== "male").astype("int") # 性别 0,1 编码,转为int类型
# 将三分类变量转换为数值型变量
labels = list(data["Embarked"].unique()) # 先将data["Embarked"]所有可能值
data["Embarked"] = data["Embarked"].apply(lambda x: labels.index(x))
data["Sex"]== "male"
可以得到一个布尔数组 [True, False, …, True],然后使用 astype("int")
将 True 变成 1,将 False 变成 0labels = list(data["Embarked"].unique())
先将 data["Embarked"]
里的所有可能值变成一个列表并获取,即 ['S', 'C', 'Q']
,在列表中,它们在 labels
中的索引分别是 0,1,2labels
的索引,我们就可以通过 data["Embarked"].apply(lambda x: labels.index(x))
实现编码当然,到这里我们所使用的数据预处理手段很粗糙,以后在 “特征工程” 里会详细介绍数据集的预处理
接着,提取数据集
# 从data中取出数据集
X = data.iloc[:,data.columns != "Survived"]
y = data.iloc[:,data.columns == "Survived"]
# 数据集分组
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
因为 train_test_split()
会随机分组,因此 X_train, X_test, Y_train, Y_test 的行索引是乱序的,因此我们需要修正行索引
#修正测试集和训练集的索引
for i in [X_train, X_test, y_train, y_test]:
i.index = range(i.shape[0])
(二)建模:
老规矩,我们利用循环来寻找最佳的 max_depth
train_scores = []
test_scores = []
for i in range(10):
clf = DecisionTreeClassifier(random_state=25,
max_depth=i+1,
criterion="entropy")
clf = clf.fit(X_train, y_train)
train_score = clf.score(X_train, y_train) # 模型在训练集上的效果
test_score = cross_val_score(clf, X, y, cv=10).mean() # 模型在测试集上面的效果
train_scores.append(train_score) # 合并数据
test_scores.append(test_score) # 合并数据
# 画图
plt.plot(range(1,11), train_scores, color="r", label="train_scores")
plt.plot(range(1,11), test_scores, color="b", label="test_scores")
plt.xticks(range(1,11))
plt.legend()
plt.show()
发现模型效果并不理想,max_depth 大概取 3 这样子,最大深度太深了,模型就过拟合了
(三)自动化调参:网格搜索: 网格搜索可以同时调整多个参数,可以实现模型的多维度优化
GS = GridSearchCV(estimator, param_grid, …)
详尽搜索估计器的指定参数值,网格搜索
通过在参数网格上进行交叉验证的网格搜索,优化用于应用这些方法的估计器的参数
同时实现 score、cross_val_score 两个功能
estimator
算法模型对象,可以是 sklearn 里的各种算法模型
param_grid
以需要调参的参数名称作为键,以参数想要尝试的值组成的列表作为值的字典,如:{"max_depth": [1, 2, 3, 4, 5]}
scoring
评分器,即评分依据
cv
执行交叉验证策略的次数
GS.fit(X, y)
必须对GridSearchCV()
返回的 GS 对象进行训练,它的运行时间会比较长
因为是枚举,因此运行的时间会比较长,因此在param_grid
中尽量缩小参数的可选范围;
此外,该方法还有一个缺点,就是被指定了的参数,它就一定会使用那个范围里的参数,而不会选择默认值是否最优
GS.best_score_
best_estimator 的平均交叉验证得分:float
GS.best_params_
最优结果情况下的参数选择
# 参数预值字典
parameters = {
"splitter": ["best", "random"], # 最优策略,随机最优策略
"criterion": ["gini", "entropy"], # 基尼指数,信息增益
"max_depth": [*range(1, 8)], # [1,8)
"min_samples_leaf": [*range(1, 50, 5)], # 在[1,50), step=5中顺序取数
"min_impurity_decrease": [*np.linspace(0, 0.5, 20)]} # 在0~0.5中,等步长地取20个数(等差数列)
clf = DecisionTreeClassifier(random_state=25)
GS = GridSearchCV(clf, parameters, cv=10)
GS.fit(X_train, y_train)
GS.best_params_
GS.best_score_
不过似乎模型的效果还是很一般,Kaggle 上这个训练集的预测难度确实比较大
参考资料:
[1]机器学习-第七章:决策树.pdf,黄海广
[2]机器学习导论
[3]bilibili-2020机器学习全集,菜菜
[4]sklearn中文社区
[5]CART算法的原理以及实现