回归树和分类树的实现原理(调包实现和自己编写)

决策树

决策树是一种基本的分类与回归方法
基于信息增益的大小对特征空间进行划分

一、用于分类的决策树

\qquad 在分类问题中,基于特征对实例进行分类的过程。可以认为是 i f − t h e n if-then ifthen规则的集合,也可以认为是定义是在特征空间与类空间上的条件概率分布。

(一)决策树学习

假设给定训练数据集
  D = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , ⋯   , ( x N , y N ) } \qquad\qquad\ D=\{(x_1,y_1),(x_2,y_2),\cdots,(x_N,y_N)\}  D={(x1,y1),(x2,y2),,(xN,yN)}
其中, x i = ( x i ( 1 ) , x i ( 2 ) , ⋯   , x i ( n ) ) x_i=(x^{(1)}_i,x^{(2)}_i,\cdots, x^{(n)}_i) xi=(xi(1),xi(2),,xi(n))为输入实例(即特征向量), n n n 为特征个数, y i ∈ { 1 , 2 , ⋯   , K } y_i \in \{1,2,\cdots,K\} yi{1,2,,K}为类标记, i = 1 , 2 , ⋯   , N , N i=1,2,\cdots,N,N i=1,2,,N,N为样本容量。构建决策树模型,使它能够对实例进行正确的分类。

(二)特征选择

1.问题

\qquad 特征选择在于选取对训练数据具有分类能力的特征。特征选择的准则是信息增益或信息增益比。比如:
在这里插入图片描述 \qquad 上表是一个由15个样本组成的贷款申请训练数据,该数据有四个特征:年龄,是否有工作,是否有自己的房子,信贷情况。表的最后一列表示类别,是否同意贷款。如果用该数据集构建一个决策树模型,用以对未来的贷款申请进行分类,那么用哪个特征来划分特征空间。

2.信息增益

( 1 ) 熵 (1)\color{red}{熵} 1是表示随机变量不确定性的度量。
X X X是一个取有限值的离散随机变量,其概率分布为
P ( X = x i ) = p i , i = 1 , 2 , ⋯   , n \qquad\qquad P(X=x_i)=p_i,i=1,2,\cdots,n P(X=xi)=pi,i=1,2,,n
则随机变量 X X X的熵定义为
H ( X ) = − ∑ i = 1 n p i log ⁡ p i \qquad\qquad H(X)=-\sum_{i=1}^{n}p_i\log{p_i} H(X)=i=1npilogpi
这里的对数以2为底或以 e e e为底。由定义可知,熵只依赖于 X X X的分布,与 X X X的取值无关。
熵越大,随机变量的不确定性就越大。
0 ≤ H ( X ) ≤ log ⁡ n \qquad\qquad 0\leq H(X)\leq \log{n} 0H(X)logn
在这里插入图片描述
( 2 ) 条 件 熵 (2)\color{red}{条件熵} 2 H ( Y ∣ X ) H(Y|X) H(YX)表示在已知随机变量X的条件下随机变量 Y Y Y的不确定性。
设 随机变量 ( X , Y ) (X,Y) (X,Y),其联合概率分布为
P ( X = x i , Y = y j ) = p i j , i = 1 , 2 ⋯   , n ;    j = 1 , 2 , ⋯   , m \qquad\qquad P(X=x_i,Y=y_j)=p_{ij},i=1,2\cdots,n;\ \ j=1,2,\cdots,m P(X=xi,Y=yj)=pij,i=1,2,n;  j=1,2,,m
H ( Y ∣ X ) = ∑ i = 1 n p i H ( Y ∣ X = x i ) \qquad\qquad H(Y|X)=\sum_{i=1}^{n} p_iH(Y|X=x_i) H(YX)=i=1npiH(YX=xi)
p i = P ( X = x i ) , i = 1 , 2 , ⋯   , n . p_i=P(X=x_i),i=1,2,\cdots,n. pi=P(X=xi),i=1,2,,n.
( 3 ) 信 息 增 益 (3)\color{red}{信息增益} 3表示得知特征 X X X的信息而使得类 Y Y Y的信息的不确定性减少的程度。
特征 A A A对训练数据集 D D D的信息增益 g ( D , A ) = H ( D ) − H ( D ∣ A ) g(D,A)=H(D)-H(D|A) g(D,A)=H(D)H(DA)
\qquad 一般地, H ( Y ) − H ( Y ∣ X ) H(Y)-H(Y|X) H(Y)H(YX)称为互信息。
\qquad 决策树学习中的信息增益等价于训练数据集中类与特征的互信息。

3.决策树学习应用信息增益选择特征

\qquad 给定训练数据集 D D D和特征 A A A,经验熵 H ( D ) H(D) H(D)表示对数据集 D D D进行分类的不确定性。而经验条件熵 H ( D ∣ A ) H(D|A) H(DA)表示在特征 A A A给定的条件下对数据集 D D D进行分类的不确定性,它们的差,即信息增益就表示由于特征 A A A而使得对数据集 D D D的分类的不确定性减少的程度。所以对数据集 D D D而言,信息增益依赖于特征,不同的特征往往具有不同的信息增益。信息增益大的特征具有更强的分类能力。
根 据 信 息 增 益 准 则 的 特 征 选 取 方 法 : \color{red}根据信息增益准则的特征选取方法:
\qquad 对训练数据集 D D D,计算出每个特征的信息增益,比较它们的大小,选取信息增益最大的特征。
信 息 增 益 的 算 法 \color{red}信息增益的算法
\qquad 设训练数据集为 D D D ∣ D ∣ |D| D表示其样本容量。设有 K K K个类 C k , k = 1 , 2 , ⋯   , K , ∣ C k ∣ C_k,k=1,2,\cdots,K,|C_{k}| Ck,k=1,2,,KCk为属于类 C k C_k Ck的样本个数, ∑ k = 1 K ∣ C k ∣ = ∣ D ∣ \sum_{k=1}^{K}|C_k|=|D| k=1KCk=D。设特征 A A A n n n个不同的取值 { a 1 , a 2 , ⋯   , a n } \{a_1,a_2,\cdots,a_n\} {a1,a2,,an},根据特征 A A A的取值将 D D D 划分为 n n n个子集 D 1 , D 2 , ⋯   , D n , ∣ D i ∣ D_1,D_2,\cdots,D_n,|D_i| D1,D2,,Dn,Di D i D_i Di的样本个数, ∑ i = 1 n ∣ D i ∣ = ∣ D ∣ \sum_{i=1}^{n}|D_i|=|D| i=1nDi=D
记子集 D i D_i Di中属于类 C k C_k Ck的样本的集合为 D i k D_{ik} Dik,即 D i k = D i ∩ C k , ∣ D i k ∣ D_{ik}=D_i\cap C_k,|D_{ik}| Dik=DiCk,Dik D i k D_{ik} Dik的样本个数。
输入:训练数据集 D D D和特征 A A A
输出:特征 A A A对训练数据集 D D D的信息增益 g ( D , A ) g(D,A) g(D,A)

(1)计算数据集 D D D的经验熵 H ( D ) H(D) H(D)

H ( D ) = − ∑ k = 1 K ∣ C k ∣ ∣ D ∣ log ⁡ 2 ∣ C k ∣ ∣ D ∣ \qquad\qquad H(D)=-\sum_{k=1}^{K} \frac{|C_k|}{|D|}\log_{2}{\frac{|C_k|}{|D|}} H(D)=k=1KDCklog2DCk

(2)计算特征 A A A对数据集 D D D的经验条件熵 H ( D | A ) H(D|A) H(DA)

P ( A = a i ) = ∣ D i ∣ ∣ D ∣ \qquad\qquad P(A=a_i)= \frac{|D_i|}{|D|} P(A=ai)=DDi
P ( C k ∣ A = a i ) = D i k D i \qquad\qquad P(C_k|A=a_i)=\frac{D_{ik}}{D_i} P(CkA=ai)=DiDik
H ( D ∣ A = a i ) = − ∑ k = 1 K D i k D i log ⁡ 2 D i k D i \qquad\qquad H(D|A=a_i)=-\sum_{k=1}^{K} \frac{D_{ik}}{D_i}\log_2{\frac{D_{ik}}{D_i}} H(DA=ai)=k=1KDiDiklog2DiDik
H ( D ∣ A ) = − ∑ i = 1 n ∣ D i ∣ ∣ D ∣ ∑ k = 1 K D i k D i log ⁡ 2 D i k D i \qquad\qquad H(D|A)=-\sum_{i=1}^{n} \frac{|D_i|}{|D|}\sum_{k=1}^{K} \frac{D_{ik}}{D_i}\log_2{\frac{D_{ik}}{D_i}} H(DA)=i=1nDDik=1KDiDiklog2DiDik

(3)计算信息增益

g ( D , A ) = H ( D ) − H ( D ∣ A ) \qquad\qquad g(D,A)=H(D)-H(D|A) g(D,A)=H(D)H(DA)

4.信息增益比

\qquad 以信息增益作为划分训练数据集的特征,存在偏向于选择取值较多的特征的问题。使用信息增益比可校正这一问题。
信 息 增 益 比 \color{red}{信息增益比}
g R ( D , A ) = g ( D , A ) H A ( D ) \qquad\qquad g_R(D,A)=\frac{g(D,A)}{H_A(D)} gR(D,A)=HA(D)g(D,A)
其中 H A ( D ) = − ∑ i = 1 n ∣ D i ∣ ∣ D ∣ log ⁡ 2 ∣ D i ∣ ∣ D ∣ H_A(D)=-\sum_{i=1}^{n}\frac{|D_i|}{|D|}\log_2\frac{|D_i|}{|D|} HA(D)=i=1nDDilog2DDi

(三)决策树的生成算法

1.ID3算法

\qquad 在决策树各个结点上应用信息增益准则选择特征。
输入:训练数据集D,特征集A,阈值 ϵ ; \epsilon; ϵ;
输出:决策树 T 。 T。 T
(1)若 D D D中所有实例属于同一个类 C k C_k Ck,则 T T T为单结点树,并将类 C k C_k Ck作为该结点的类标记,返回T;
(2)若 A = ∅ A=\emptyset A=,则 T T T为单结点树,并将 D D D中实例数最大的类 C k C_k Ck作为该结点的类标记,返回T;
(3)否则,按信息增益算法计算A中各特征对 D D D的信息增益,选择信息增益最大的特征 A g A_g Ag
(4)如果 A g A_g Ag的信息增益小于阈值 ϵ \epsilon ϵ,则置 T T T为单结点树,并将 D D D中实例数最大的类 C k C_k Ck作为该结点的类标记,返回T;
(5)否则,对 A g A_g Ag的每一可能值 a i a_i ai,依 A g = a i A_g=a_i Ag=ai D D D分割为若干非空子集 D i D_i Di,将 D i D_i Di中实例数最大的类作为标记,构建子结点,由结点及其子结点构成树 T T T,返回 T T T
(6)对第 i i i个子结点,以 D i D_i Di为训练集,以 A − { A g } A-\{A_g\} A{Ag}为特征集,递归的调用步(1)~(5),得到子树 T i T_i Ti,返回 T i T_i Ti
容 易 过 拟 合 \color{red}{容易过拟合}

2.C4.5算法

\qquad 在决策树各个结点上应用信息增益比选择特征。
输入:训练数据集D,特征集A,阈值 ϵ ; \epsilon; ϵ;
输出:决策树 T 。 T。 T
(1)若 D D D中所有实例属于同一个类 C k C_k Ck,则 T T T为单结点树,并将类 C k C_k Ck作为该结点的类标记,返回T;
(2)若 A = ∅ A=\emptyset A=,则 T T T为单结点树,并将 D D D中实例数最大的类 C k C_k Ck作为该结点的类标记,返回T;
(3)否则,按信息增益比计算公式计算A中各特征对 D D D的信息增益比,选择信息增益比最大的特征 A g A_g Ag
(4)如果 A g A_g Ag的信息增益比小于阈值 ϵ \epsilon ϵ,则置 T T T为单结点树,并将 D D D中实例数最大的类 C k C_k Ck作为该结点的类标记,返回T;
(5)否则,对 A g A_g Ag的每一可能值 a i a_i ai,依 A g = a i A_g=a_i Ag=ai D D D分割为若干非空子集 D i D_i Di,将 D i D_i Di中实例数最大的类作为标记,构建子结点,由结点及其子结点构成树 T T T,返回 T T T;(6)对第 i i i个子结点,以 D i D_i Di为训练集,以 A − { A g } A-\{A_g\} A{Ag}为特征集,递归的调用步(1)~(5),得到子树 T i T_i Ti,返回 T i T_i Ti

(四)决策树的剪枝

1.剪枝的提出

\qquad 决策树生成算法递归的产生决策树,直到不能继续下去为止。这样产生的数往往对训练数据的分类很准确,但对未知的测试数据的分类没有那么准确,即出现过拟合。
\qquad 过拟合的原因在于学习时过多的考虑如何提高对训练数据的正确分类,从而构建出过于复杂的决策树。解决这个问题的办法是考虑决策树的复杂度,对已生成的决策树进行简化。
剪 枝 : \color{red}{剪枝}: :在决策树学习中将已生成的树进行简化的过程称为剪枝。剪枝从已生成的树上裁掉一些子树或叶结点,并将其根结点或父结点作为新的叶结点,从而简化分类树模型。

2.损失函数

\qquad 决策树的剪枝往往通过极小化决策树整体的损失函数或代价函数来实现。
\qquad 设树 T T T的叶结点个数为 ∣ T ∣ , t |T|,t Tt是树 T T T的叶结点,该结点有 N t N_t Nt个样本点,其中 k k k类的样本点有 N t k N_{tk} Ntk个, k = 1 , 2 , ⋯   , K , H t ( T ) k=1,2,\cdots,K,H_t(T) k=1,2,,K,Ht(T)为叶结点 t t t上的经验熵, α ≥ 0 \alpha\geq0 α0为参数,则 决 策 树 学 习 的 损 失 函 数 可 以 定 义 为 \color{red}决策树学习的损失函数可以定义为
C α ( T ) = ∑ t = 1 ∣ T ∣ N t H t ( T ) + α ∣ T ∣ \qquad\qquad C_{\alpha}(T)=\sum_{t=1}^{|T|}N_tH_t(T)+\alpha|T| Cα(T)=t=1TNtHt(T)+αT
其中经验熵为
H t ( T ) = − ∑ k = 1 K N t k N t log ⁡ N t k N t \qquad\qquad H_t(T)=-\sum_{k=1}^{K}\frac{N_{tk}}{N_t}\log{\frac{N_{tk}}{N_t}} Ht(T)=k=1KNtNtklogNtNtk

C ( T ) = ∑ t = 1 ∣ T ∣ N t H t ( T ) = − ∑ t = 1 ∣ T ∣ ∑ k = 1 K N t k log ⁡ N t k N t C(T)=\sum_{t=1}^{|T|}N_tH_t(T)=-\sum_{t=1}^{|T|}\sum_{k=1}^{K}N_{tk}\log{\frac{N_{tk}}{N_t}} C(T)=t=1TNtHt(T)=t=1Tk=1KNtklogNtNtk
所以
C α ( T ) = C ( T ) + α ∣ T ∣ \qquad\qquad \color{red}C_{\alpha}(T)=C(T)+\alpha|T| Cα(T)=C(T)+αT
C ( T ) \color{red}{C(T)} C(T)表示模型对训练数据的预测误差,即模型与训练数据的拟合程度 ∣ T ∣ \color{red}{|T|} T表示模型复杂度 参 数 α ≥ 0 \color{red}{参数\alpha\geq0} α0控制两者之间的影响。较大的 α \alpha α促使选择较简单的模型(树),较小的 α \alpha α促使选择较复杂的模型(树)。 α = 0 \alpha=0 α=0意味着只考虑模型与训练数据的拟合程度,不考虑模型的复杂度。

3.剪枝的原理

\qquad 剪枝就是 α \alpha α确定时,选择损失函数最小的模型 ,即损失函数最小的子树。当 α \alpha α值确定时,子树越大,训练数据往往拟合的越好,但是模型的复杂度也越高;相反,子树越小,模型的复杂度就越低,训练数据往往拟合的也不好。损失函数正好平衡了二者。
\qquad 决策树生成只考虑了通过提高信息增益或信息增益比对训练数据进行更好的拟合。而决策树剪枝通过优化损失函数考虑了减少模型的复杂度。决策树生成学习局部的模型,决策树剪枝学习整体的模型。
\qquad 损失函数的极小化等价于正则化的极大似然估计。所以,利用损失函数最小原则进行剪枝就是用正则化的极大似然估计进行模型选择。

4.树的剪枝算法

输入:生成算法产生的整个树 T T T,参数 α ; \alpha; α;
输出:修剪后的子树 T α T_\alpha Tα
(1)计算每个结点的经验熵。
(2)递归的从树的叶结点向上回缩。
\qquad 设一组叶结点回缩到其父结点之前与之后的整体树分别为 T B T_B TB T A T_A TA,其对应的损失函数值分别是 C α ( T B ) C_\alpha(T_B) Cα(TB) C α ( T A ) C_\alpha(T_A) Cα(TA),如果
C α ( T A ) ≤ C α ( T B ) \qquad\qquad C_\alpha(T_A)\leq C_\alpha(T_B) Cα(TA)Cα(TB)
则进行剪枝,即将父结点变为新的叶结点。
(3)返回(2),直至不能继续为止,得到损失函数最小的子树 T α T_\alpha Tα
在这里插入图片描述

二、CART算法

\qquad 分类与回归树(classification and regression tree)即可用于分类也可用于回归。

(一)CART的介绍

1.CART决策树

\qquad CART是在给定输入随机变量 X X X条件下输出随机变量 Y Y Y的条件概率分布的学习方法。
\qquad CART假设决策树是二叉树,内部结点特征的取值为“是”或“否”,左分支取值为“是”的分支,右分支取值为“否”的分支。这样的决策树等价于递归的二分每个特征,将输入空间即特征空间划分为有限个单元,并在这些单元上确定预测的概率分布,即在输入给定的条件下输出的条件概率分布。

2.CART算法思路

(1)决策树生成:基于训练数据集生成决策树,生成的决策树要尽量大;
(2)决策树剪枝:用验证数据集对已生成的树进行剪枝并选择最优子树,用损失函数最小作为剪枝的标准。

(二)CART的生成

\qquad 递归的构建二叉决策树,对回归树用平方误差最小化准则,对分类树用基尼指数最小化准则

1.回归树的生成

(1)回归树的模型

\qquad 假设 X X X Y Y Y分别为输入和输出变量,并且 Y Y Y连续变量,给定训练数据集
D = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , ⋯   , ( x N , y N ) } \qquad\qquad D=\{(x_1,y_1),(x_2,y_2),\cdots,(x_N,y_N)\} D={(x1,y1),(x2,y2),,(xN,yN)}
如何生成回归树。
\qquad 一颗回归树对应着输入空间(即特征空间)的一个划分以及在划分的单元上的输出值。
\qquad 假设已将输入空间划分为 M M M个单元 R 1 , R 2 , ⋯   , R M R_1,R_2,\cdots,R_M R1,R2,,RM,并且在每个单元 R m R_m Rm上有一个固定的输出值 c m c_m cm,于是回归树的模型为
f ( x ) = ∑ m = 1 M c m I ( x ∈ R m ) \qquad\qquad f(x)=\sum_{m=1}^{M}c_mI(x\in R_m) f(x)=m=1McmI(xRm)
\qquad 当输入空间的划分确定时,可以用平方误差
∑ x i ∈ R m ( y i − f ( x i ) ) 2 \qquad\qquad \sum_{x_i\in R_m}(y_i-f(x_i))^2 xiRm(yif(xi))2
来表示回归树对于训练数据的预测误差,用平方误差最小的准则求解每个单元上的最优输出值。
单元 R m R_m Rm上的 c m c_m cm的最优值 c ^ m \hat{c}_m c^m
c ^ m = a v e ( y i ∣ x i ∈ R m ) \qquad\qquad \hat{c}_m=ave(y_i|x_i\in R_m) c^m=ave(yixiRm)
(纯度的解释)
在这里插入图片描述
在这里插入图片描述

(2)回归树如何划分输入空间

\qquad 选择第 j j j个变量 x ( j ) x^{(j)} x(j)和它取的值 s s s,作为切分变量和切分点,
R 1 ( j , s ) = { x ∣ x ( j ) ≤ s } 和 R 2 ( j , s ) = { x ∣ x ( j ) > s } \quad R_1(j,s)=\{x|x^{(j)}\leq s\}和R_2(j,s)=\{x|x^{(j)}> s\} R1(j,s)={xx(j)s}R2(j,s)={xx(j)>s}
寻找最优切分变量 j j j和最优切分点 s s s
min ⁡ j , s [ min ⁡ c 1 ∑ x i ∈ R 1 ( j , s ) ( y i − c 1 ) 2 + min ⁡ c 2 ∑ x i ∈ R 2 ( j , s ) ( y i − c 2 ) 2 ] \min_{j,s}[\min_{c_1}\sum_{x_i\in R_1(j,s)}(y_i-c_1)^2+\min_{c_2}\sum_{x_i\in R_2(j,s)}(y_i-c_2)^2] minj,s[minc1xiR1(j,s)(yic1)2+minc2xiR2(j,s)(yic2)2]
固定输入变量 j j j,可以找到最优切分点 s s s
c ^ 1 = a v e ( y i ∣ x i ∈ R 1 ( j , s ) ) 和 c ^ 2 = a v e ( y i ∣ x i ∈ R 2 ( j , s ) ) \hat{c}_1=ave(y_i|x_i\in R_1(j,s))和\hat{c}_2=ave(y_i|x_i\in R_2(j,s)) c^1=ave(yixiR1(j,s))c^2=ave(yixiR2(j,s))
**遍历所有输入变量,找到最优切分变量 j j j,构成一个对 ( j , s ) (j,s) (j,s)。**依此将输入空间划分为两个区域。接着,对每个区域重复上述划分过程,直到满足停止条件为止。这样就生成一颗回归树,这样的回归树称为最小二乘回归树。

(3)最小二乘回归树生成算法

输入:训练数据集 D D D
输出:回归树 f ( x ) f(x) f(x)
\qquad 在训练数据集所在的输入空间中,递归的将每个区域划分为两个子区域并决定每个子区域上的输出值,构建二叉决策树:
(1)选择最优切分变量 j j j和切分点 s s s,求解
min ⁡ j , s [ min ⁡ c 1 ∑ x i ∈ R 1 ( j , s ) ( y i − c 1 ) 2 + min ⁡ c 2 ∑ x i ∈ R 2 ( j , s ) ( y i − c 2 ) 2 ] \min_{j,s}[\min_{c_1}\sum_{x_i\in R_1(j,s)}(y_i-c_1)^2+\min_{c_2}\sum_{x_i\in R_2(j,s)}(y_i-c_2)^2] minj,s[minc1xiR1(j,s)(yic1)2+minc2xiR2(j,s)(yic2)2]
遍历变量 j j j,对固定的切分变量 j j j扫描切分点 s s s,选择使上式达到最小值的对 ( j , s ) (j,s) (j,s)
(2)用选定的 ( j , s ) (j,s) (j,s)划分区域并决定相应的输出值:
R 1 ( j , s ) = { x ∣ x ( j ) ≤ s } , R 2 ( j , s ) = { x ∣ x ( j ) > s } \quad R_1(j,s)=\{x|x^{(j)}\leq s\},R_2(j,s)=\{x|x^{(j)}> s\} R1(j,s)={xx(j)s}R2(j,s)={xx(j)>s}

c ^ m = 1 N m ∑ x i ∈ R m ( j , s ) y i , x ∈ R m , m = 1 , 2 \hat{c}_m=\frac{1}{N_m}\sum_{x_i\in R_m(j,s)}y_i,x\in R_m,m=1,2 c^m=Nm1xiRm(j,s)yi,xRm,m=1,2
(3)继续对两个子区域调用步骤(1),(2)直至满足停止条件。
(4)将输入空间划分为 M M M个单元 R 1 , R 2 , ⋯   , R M R_1,R_2,\cdots,R_M R1,R2,,RM,生成决策树:
f ( x ) = ∑ m = 1 M c ^ m I ( x ∈ R m ) \qquad\qquad f(x)=\sum_{m=1}^{M}\hat{c}_mI(x\in R_m) f(x)=m=1Mc^mI(xRm)

2.分类树的生成

(1)基尼指数

\qquad 分类问题中,假设有 K K K个类,样本点属于第 k k k类的概率为 p k p_k pk,则概率分布的基尼指数为
G i n i ( p ) = ∑ k = 1 K p k ( 1 − p k ) = 1 − ∑ k = 1 K P k 2 \qquad\qquad Gini(p)=\sum_{k=1}^{K}p_k(1-p_k)=1-\sum_{k=1}^{K}P_k^2 Gini(p)=k=1Kpk(1pk)=1k=1KPk2
\qquad 对于二分类问题,若样本点属于第1个类的概率是 p p p,则概率分布的基尼指数为
G i n i ( p ) = 2 p ( 1 − p ) \qquad\qquad Gini(p)=2p(1-p) Gini(p)=2p(1p)
\qquad 对于给定的样本集合 D D D,基尼指数为
G i n i ( D ) = 1 − ∑ k = 1 K ∣ C k ∣ ∣ D ∣ \qquad\qquad Gini(D)=1-\sum_{k=1}^{K}\frac{|C_k|}{|D|} Gini(D)=1k=1KDCk
C k C_k Ck D D D中属于第 k k k类的样本子集, K K K是类的个数。
\qquad 如果样本集合 D D D根据特征 A A A是否取某一可能值 a a a被分割成 D 1 D_1 D1 D 2 D_2 D2两部分,
D 1 = { ( x , y ) ∈ D ∣ A ( x ) = a } , D 2 = D − D 1 \qquad D_1=\{(x,y)\in D|A(x)=a\},D_2=D-D_1 D1={(x,y)DA(x)=a},D2=DD1
则在特征 A A A的条件下,集合 D D D的基尼指数定义为
G i n i ( D , A ) = ∣ D 1 ∣ D G i n i ( D 1 ) + ∣ D 2 ∣ D G i n i ( D 2 ) \qquad Gini(D,A)=\frac{|D_1|}{D}Gini(D_1)+\frac{|D_2|}{D}Gini(D_2) Gini(D,A)=DD1Gini(D1)+DD2Gini(D2)
\qquad 基尼指数 G i n i ( D ) Gini(D) Gini(D)表示集合 D D D的不确定性,基尼指数 G i n i ( D , A ) Gini(D,A) Gini(D,A)表示经 A = a A=a A=a分割后集合 D D D的不确定性。基尼指数越大,样本集合的不确定性也就越大,这一点与熵相似。
在这里插入图片描述

(2)CART生成算法

输入:训练数据集 D D D,停止计算的条件
输出:CART决策树
\qquad 根据训练数据集,从根节点开始,递归的对每个结点进行以下操作,构建二叉决策树。
(1)设结点的训练数据集为 D D D,计算现有特征对该数据集的基尼指数。此时,对每一个特征 A A A,对其可能取的每个值 a a a,根据样本点对 A = a A=a A=a的测试为“是”或“否”将 D D D分割成 D 1 D_1 D1 D 2 D_2 D2两部分,计算 A = a A=a A=a的基尼指数。
(2)在所有可能的特征 A A A以及它们所有可能的切分点 a a a中,选择基尼指数最小的特征及其对应的切分点作为最优特征和最优切分点。依最优特征和最优切分点,从现结点生成两个子结点,将训练数据集依特征分配到两个子结点中。
(3)对两个子结点递归的调用(1),(2),直至满足停止条件
(4)生成CART决策树。
\qquad 算法停止计算的条件是结点中的样本个数小于预定阈值,或样本集的基尼指数小于预定阈值(样本集属于同一类),或者没有更多特征。

(三)CART剪枝

1.CART剪枝算法思路

(1)从生成算法产生的决策树 T 0 T_0 T0底端开始不断剪枝,直到 T 0 T_0 T0的根结点,形成一个子树序列 { T 0 , T 1 , ⋯   , T n } \{T_0,T_1,\cdots,T_n\} {T0,T1,,Tn}
(2)通过交叉验证法在独立的验证数据集上对子树序列进行测试,从中选择最优子树。

2.

\qquad 在剪枝过程中,计算子树的损失函数:
C α ( T ) = C ( T ) + α ∣ T ∣ \qquad\qquad C_\alpha(T)=C(T)+\alpha|T| Cα(T)=C(T)+αT
其中, T T T为任意子树, C ( T ) C(T) C(T)为对训练数据的预测误差(如基尼指数), ∣ T ∣ |T| T为子树的叶结点个数, α ≥ 0 \alpha\geq0 α0为参数, C α ( T ) C_\alpha(T) Cα(T)为参数是 α \alpha α时的子树的整体损失。参数 α \alpha α权衡训练数据的拟合程度和模型的复杂程度。

三、决策树的应用

数据连接
链接: 数据资源
密码: 3aoo

(一)调包实现

1.回归树

from sklearn.tree import DecisionTreeRegressor
from sklearn import tree
df=pd.read_csv('/Users/renjianmei/Downloads/深度学习/concrete.csv')
X=pd.get_dummies(df.iloc[:,:-1],drop_first=False) #get_dummies()特征提取(虚拟变量)
y=df["Compressive.strength"]

#决策回归树
reg=DecisionTreeRegressor(random_state=0,max_depth=3)
reg=reg.fit(X,y)
fig=plt.figure(figsize=(27,7.5))
_plt=tree.plot_tree(reg,feature_names=X.columns,class_names='y',filled=True)

在这里插入图片描述

2.分类树

from sklearn.tree import DecisionTreeClassifier
from sklearn import tree
w=pd.read_csv('/Users/renjianmei/Downloads/深度学习/Bidding.csv')

X=w.iloc[:,3:-1]
y=w['Class'].astype('category')

reg=DecisionTreeClassifier(random_state=0,max_depth=3)
reg.fit(X,y)
fig=plt.figure(figsize=(27,7.5))
_plt=tree.plot_tree(reg,feature_names=X.columns,rounded=True,filled=True)

在这里插入图片描述

(二)自己编写实现代码

1.回归树

(1)一些基本函数的定义

#1. 
def split_point(self,x):
    '''变量x的切分点集合'''
    '''切分点————变量x的相邻两个取值的中点'''
    sorted_x=np.sort(x.unique())
    if len(sorted_x)<2: #变量x的取值都是同一数,那么没有切分点。(因为不能把该变量的取值把该变量划分成两部分)
        return  None
     else:   #len(sorted_x)>=2
        points=[(sorted_x[i]+sorted_x[i+1])/2 for i in range(len(sorted_x)-1)]  
        return points

#2.
def findBest_s(self,df,x): #df是训练集(X,y),x是变量
    '''变量x的最优切分点'''
    points=self.split_point(x)
    if points is None:
        return None 
    else:   #len(points)>=1
        mid_all=[]
        for s in points:
            R1=df[x<=s]
            R2=df[x>s]
            c1=R1.iloc[:,-1].sum()/len(R1)  #因变量
            c2=R2.iloc[:,-1].sum()/len(R2)
            mid_all.append(((R1.iloc[:,-1]-c1)**2).sum()+((R2.iloc[:,-1]-c2)**2).sum())

        index=np.argsort(np.array(mid_all))[0]  #mid_all.index(min(j_all))
        s=points[index]
        square_error=mid_all[index]
        return s,square_error

#3.
def findBest_j_s(self,df):  #df是训练集(X,y)
    '''最优切分变量以及对应的最优切分点'''
    squareError_s_all=[]
    for i in range(len(df.columns)-1):
        returns=self.findBest_s(df,df.iloc[:,i])
        if returns is None:
            continue
        else:
            s,square_error=returns
            squareError_s_all.append([square_error,s,i])
               
    index=np.argsort(np.array(squareError_s_all)[:,0])[0]
    var_name=df.columns[squareError_s_all[index][2]]
    s=squareError_s_all[index][1]
    res={'最优切分变量':var_name,'切分点':s}
    res_=var_name+'<='+str(s)
    return res,res_,var_name,s

#4.
def split_two_field(self,df):
    '''根据最优切分变量和最优切分点将df分成两个子集'''
    res=self.findBest_j_s(df)[0]
    var_name=res['最优切分变量']
    s=res['切分点']
    R1=df[df[var_name]<=s]
    R2=df[df[var_name]>s]
    R1.index=range(len(R1))
    R2.index=range(len(R2))
    return R1,R2

#5.
def compute_value(self,df):
    '''计算单元上的输出值'''
    return df.iloc[:,-1].mean()

#6.
def compute_mse(self,df):
    '''计算mse'''
    c=df.iloc[:,-1].mean()
    mse=((df.iloc[:,-1]-c)**2).sum()/len(df)
    return mse

(2)类的定义

'''回归树'''
class Node():
    def __init__(self,data=None):  
        '''初始化结点的属性'''
        self.data=data  #存放数据
        #这里保存的是数据
        #应该保存索引,可以节省空间
        self.left=None
        self.right=None
        self.value=None
        self.mse=None
        self.samples=None
        self.split_result=None
        self.feature=None
        self.split_point=None
class RegressionTree():
    def __init__(self):
        '''初始化树的属性'''
        self.root=Node()
        self.depth=1
        
    def split_point(self,x):
        '''变量x的切分点集合'''
        '''切分点————变量x的相邻两个取值的中点'''
        sorted_x=np.sort(x.unique())
        if len(sorted_x)<2: #变量x的取值都是同一数,那么没有切分点。(因为不能把该变量的取值把该变量划分成两部分)
            return  None
        else:   #len(sorted_x)>=2
            points=[(sorted_x[i]+sorted_x[i+1])/2 for i in range(len(sorted_x)-1)]  
            return points
        
    def findBest_s(self,df,x): #df是训练集(X,y),x是变量
        '''变量x的最优切分点'''
        points=self.split_point(x)
        if points is None:
            return None 
        else:   #len(points)>=1
            mid_all=[]
            for s in points:
                R1=df[x<=s]
                R2=df[x>s]
                c1=R1.iloc[:,-1].sum()/len(R1)  #因变量
                c2=R2.iloc[:,-1].sum()/len(R2)
                mid_all.append(((R1.iloc[:,-1]-c1)**2).sum()+((R2.iloc[:,-1]-c2)**2).sum())

            index=np.argsort(np.array(mid_all))[0]  #mid_all.index(min(j_all))
            s=points[index]
            square_error=mid_all[index]
            return s,square_error

    def findBest_j_s(self,df):  #df是训练集(X,y)
        '''最优切分变量以及对应的最优切分点'''
        squareError_s_all=[]
        for i in range(len(df.columns)-1):
            returns=self.findBest_s(df,df.iloc[:,i])
            if returns is None:
                continue
            else:
                s,square_error=returns
                squareError_s_all.append([square_error,s,i])               
        index=np.argsort(np.array(squareError_s_all)[:,0])[0]
        var_name=df.columns[squareError_s_all[index][2]]
        s=squareError_s_all[index][1]
        res={'最优切分变量':var_name,'切分点':s}
        res_=var_name+'<='+str(s)
        return res,res_,var_name,s

    def split_two_field(self,df):
        '''根据最优切分变量和最优切分点将df分成两个子集'''
        res=self.findBest_j_s(df)[0]
        var_name=res['最优切分变量']
        s=res['切分点']
        R1=df[df[var_name]<=s]
        R2=df[df[var_name]>s]
        R1.index=range(len(R1))
        R2.index=range(len(R2))
        return R1,R2
    
    def compute_value(self,df):
        '''计算单元上的输出值'''
        return df.iloc[:,-1].mean()
    
    def compute_mse(self,df):
        '''计算mse'''
        c=df.iloc[:,-1].mean()
        mse=((df.iloc[:,-1]-c)**2).sum()/len(df)
        return mse
    
    def fit(self,X,y,max_depth=4,min_samples_split=2):
        '''拟合'''
        '''X————DataFrame形式(这样含有变量名)y————Series形式,即y=df.iloc[:,-1]'''
        df=pd.concat([X,y],axis=1)
        self.root.data=df
        '''构建二叉决策树,采用广度优先遍历+队列'''
        queue=[(self.depth,self.root)]  
        while queue:
            depth,cur_node=queue.pop(0)
           
            if depth>max_depth:
                '''限制树的最大深度,'''
                break
            if len(cur_node.data)<min_samples_split:
                '''限制分裂后的子样本量'''
                continue
            cur_node.samples=len(cur_node.data)
            cur_node.value=self.compute_value(cur_node.data)
            cur_node.mse=self.compute_mse(cur_node.data)
            cur_node.split_result=self.findBest_j_s(cur_node.data)[1]
            
            cur_node.feature=self.findBest_j_s(cur_node.data)[2]
            cur_node.split_point=self.findBest_j_s(cur_node.data)[3]                     
            R1,R2=self.split_two_field(cur_node.data)
            cur_node.left=Node(R1)
            queue.append((depth+1,cur_node.left))
            cur_node.right=Node(R2)
            queue.append((depth+1,cur_node.right))
    
    def print(self):
        '''打印'''
        '''构建二叉决策树,采用广度优先遍历+队列'''
        queue=[self.root]  
        while queue:
            cur_node=queue.pop(0)
            
            dict_={'mse':cur_node.mse,'value':cur_node.value,'samples':cur_node.samples}
            print( cur_node.split_result,dict_)
            if cur_node.left.value is not None:
                queue.append(cur_node.left)
            if cur_node.right.value is not None:
                queue.append(cur_node.right)
        
    def predict(self,X):
        '''预测'''
        '''X———DataFrame形式(这样含有变量名)'''
        y_pred=[]
        for i in range(len(X)):
            x=X.iloc[i]
            '''预测单个样本'''
            cur_node=self.root
            while (cur_node.left.value) and (cur_node.right.value):
                if x[cur_node.feature]<=cur_node.split_point:
                    cur_node=cur_node.left
                else:
                    cur_node=cur_node.right
            y_pred.append(cur_node.value)
        return np.array(y_pred)
             
df=pd.read_csv('/Users/renjianmei/Downloads/深度学习/concrete.csv')
X=pd.get_dummies(df.iloc[:,:-1],drop_first=False) #get_dummies()特征提取(虚拟变量)
y=df["Compressive.strength"]

tree=RegressionTree()
tree.fit(X,y)
tree.print()

在这里插入图片描述

2.分类树

(1)类的定义

'''分类树'''
class Node(object):
    def __init__(self,idx=None):
        self.idx=idx
        self.left=None
        self.right=None
        self.gini=None
        self.samples=None
        self.value=None
        self.split=None
        self.feature=None
        self.s=None
class ClassifierTree(object):
    def __init__(self):
        self.root=Node()
        self.depth=1

    #1.
    def split_point(self,x):
        '''变量x的切分点集合'''
        '''切分点准则'''
        sorted_x=np.sort(x.unique())
        if len(sorted_x)<2:
            return None
        else:
            points=[(sorted_x[i]+sorted_x[i+1])/2 for i in range(len(sorted_x)-1)]  
            return points

    #2.基尼指数的计算
    def gini(self,df):
        categories=df.iloc[:,-1].unique()
        giniValue=1
        for i in range(len(categories)):
            category_i_num=np.sum(df.iloc[:,-1]==categories[i])
            p_i=category_i_num/len(df)
            giniValue-=p_i**2
        return giniValue
    
    #3.寻找最优切分点
    def findBest_split(self,df,x):
        points=self.split_point(x)
        if points is None:
            return None 
        else:   #len(points)>=1
            gini_x_all=[]
            for i in range(len(points)):
                df1=df[x<=points[i]]
                df2=df[x>points[i]]
                gini_x_i=len(df1)/len(df)*self.gini(df1)+len(df2)/len(df)*self.gini(df2)
                gini_x_all.append(gini_x_i)
            index=np.argsort(np.array(gini_x_all))[0]
            s=points[index]
            gini_x=gini_x_all[index]
            return gini_x,s
    #4.
    def findBest_variable(self,df):
        '''寻找最优切分变量'''
        feature_num=df.shape[1]-1
        point_gini=[]
        for i in range(feature_num):
            returns=self.findBest_split(df,df.iloc[:,i])
            if returns is None:
                continue
            else:
                point_gini.append([returns[0],returns[1],i])       
        index=np.argsort(np.array(point_gini)[:,0])[0]
        var_name=df.columns[point_gini[index][2]]
        s=point_gini[index][1]
        return var_name,s
    #5.
    def split_two_field(self,df,idx):
        sub=df.loc[idx]
        '''计算gini'''
        gini_sub=self.gini(sub)                         
        '''samples'''
        samples=len(sub)

        '''value'''
        sub_dist=sub.iloc[:,-1].value_counts()  #每个单元上的类别情况
        sub_d={}
        for i in range(len(sub_dist)):
            sub_d[str(sub_dist.index[i])]=sub_dist.values[i]

        '''切分变量和切分点'''
        var_name,s=self.findBest_variable(sub)
        text=var_name+'<='+str(s)

        left_idx=sub[sub[var_name]<=s].index
        right_idx=sub[sub[var_name]>s].index
        return text,gini_sub,samples,sub_d,left_idx,right_idx,var_name,s

    def fit(self,X,y,max_depth=4,min_samples_split=2):
        '''构建分类决策二叉树 广度优先+队列'''
        '''X————DataFrame形式(这样含有变量名)y————Series形式,即y=df.iloc[:,-1]'''
        df=pd.concat([X,y],axis=1)
        self.root=Node(df.index)
        queue=[(self.depth,self.root)]
        while queue:
            depth,cur_node=queue.pop(0)
            if depth>max_depth:
                break
            if len(cur_node.idx)<=min_samples_split:
                '''限制分裂后的子样本量'''
                continue
            text,gini_sub,samples,sub_d,l_idx,r_idx,var_name,s=self.split_two_field(df,cur_node.idx)
            cur_node.split=text
            cur_node.gini=gini_sub
            cur_node.samples=samples
            cur_node.value=sub_d
            cur_node.feature=var_name
            cur_node.s=s
            cur_node.left=Node(l_idx)
            queue.append((depth+1,cur_node.left))
            cur_node.right=Node(r_idx)
            queue.append((depth+1,cur_node.right))

    def print_(self):
        queue=[self.root]
        while queue:
            cur_node=queue.pop(0)
            print(cur_node.split,cur_node.gini,cur_node.samples,cur_node.value)
            if cur_node.left.split is not None:
                queue.append(cur_node.left)
            if cur_node.right.split is not None:
                queue.append(cur_node.right)    
    def predict(self,X):
        output=[]
        for i in range(len(X)):
            x=X.iloc[i]
            cur_node=self.root
            while cur_node.left.split and cur_node.right.split:
                if x[cur_node.feature]<=cur_node.s:
                    cur_node=cur_node.left
                else:
                    cur_node=cur_node.right
                queue.append(cur_node)
            inter=pd.Series(cur_node.value)
            value=inter[inter==inter.max()].index
            output.append(value)
        return np.array(output)



w=pd.read_csv('/Users/renjianmei/Downloads/深度学习/Bidding.csv')

X=w.iloc[:,3:-1]
y=w['Class'].astype('category')
tree=ClassifierTree()
tree.fit(X,y)
tree=ClassifierTree()
tree.fit(X,y)

在这里插入图片描述

四、交叉验证知识点的补充

(一)交叉验证介绍

在这里插入图片描述

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值