机器学习之决策树模型(1)

机器学习之决策树模型(1)

简介和原理

决策树学习本质上是从训练数据中归纳出一组分类规则。假设给定一个训练数据集:

D = { ( x 1 , y x ) , ( x 2 , y 2 ) , . . . , ( x N , y N ) } D = \{(x_1, y_x), (x_2, y_2), ..., (x_N, y_N)\} D={ (x1,yx),(x2,y2),...,(xN,yN)}

其中, x i = ( x i ( 1 ) , x i ( 2 ) , . . . , x i ( n ) ) x_i = (x_i^{(1)}, x_i^{(2)}, ..., x_i^{(n)}) xi=(xi(1),xi(2),...,xi(n)),n为特征的个数,
y i ∈ { 1 , 2 , . . . , k } y_i \in \{1, 2, ..., k\} yi{ 1,2,...,k}是类标记, i = 1 , 2 , . . . , N i = 1, 2, ..., N i=1,2,...,N,N是样本容量,
学习的目的是根据给定的训练数据集构建一颗树,非叶子结点代表分类的标准,叶子结点
表示分类后的类别。举个栗子,比如有这么一个数据集:

我们构建这么一棵决策树:

输入一个实例 x ′ = ( x ′ ( 1 ) , x ′ ( 2 ) ) , . . . , x ′ ( n ) x' = (x'^{(1)}, x'^{(2)}), ..., x'^{(n)} x=(x(1),x(2)),...,x(n),便可以根据这
个决策树来决定输出的类别。
显而易见,构建一课分类效果好的决策树关键在于非叶子结点的确定,即我们用什么特征
来构造决策树。然后对于每一个叶子结点用多数表决的方式来决定它的类别。

如何选择结点特征:信息增益

信息增益是用来衡量某一个特征对于分类效果的好坏的度量。如果一个特征对于数据集的分类
效果比较好,那么这个特征的信息增益便会比较大。如何衡量特征对于数据集的分类效果,
可以用熵和条件熵来度量。

如果 X X X是一个取有限个值的离散型变量,其概率分布为:

P ( X = x i ) = p i , i = 1 , 2 , . . . , n P(X=x_i) = p_i, i=1, 2, ..., n P(X=xi)=pi,i=1,2,...,n

那么随机变量 X X X的熵定义为:

H ( X ) = − ∑ i = 1 n p i l o g p i H(X) = -\sum_{i=1}^np_ilogp_i H(X)=i=1npilogpi

(由于熵只依赖于X的分布而不依赖于X的取值,所以也可以记为 H ( p ) H(p) H(p)
从定义可以证明: 0 ≤ H ( p ) ≤ l o g n 0≤H(p)≤logn 0H(p)logn,熵是衡量一个随机变量的不确定性,举个栗子,一个
二分变量取0和1的概率为 1 10 \frac{1}{10} 101 9 10 \frac{9}{10} 109的熵要比概率为 1 2 \frac{1}{2} 21
1 2 \frac{1}{2} 21的熵要小,代表着前者的不确定性更小,后者的不确定性更大。

条件熵

设随机变量 ( X , Y ) (X, Y) (X,Y),其联合概率分布为

P ( X = x i , Y = y j ) = p i j , i = 1 , 2 , . . . , n ; j = 1 , 2 , . . . , m P(X=x_i, Y=y_j)=p_{ij}, i=1, 2, ..., n; j = 1, 2, ..., m P(X=xi,Y=yj)=pij,i=1,2,...,n;j=1,2,...,m

则在随机变量X给定的条件下随机变量Y的条件熵 H ( Y ∣ X ) H(Y|X) H(YX)定义为X给定条件下Y的条件分布
概率的熵对X的数学期望(有点绕):

H ( Y ∣ X ) = ∑ i = 1 n p i H ( Y ∣ X = x i ) H(Y|X) = \sum_{i=1}^np_iH(Y|X=x_i) H(YX)=i=1npiH(YX=xi)

其实是根据X把变量分成n个集合,然后对每一个集合计算Y的熵然后加和,就是X对Y的条件熵,
条件熵 H ( Y ∣ X ) H(Y|X) H(YX)表示在已知随机变量X的条件下随机变量Y的不确定性。

信息增益

有了熵和条件熵的,我们便可以定义信息增益了:特征A对训练数据集D的信息增益 g ( D , A ) g(D, A) g(D,A)
,定义为集合D的经验熵 H ( D ) H(D) H(D)与在特征A给定的条件下D的经验条件熵 H ( D ∣ A ) H(D|A) H(DA)之差,即

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

直观上,特征A对训练数据集D的信息增益 g ( D , A ) g(D, A) g(D,A)代表着由于特征A而使得对数据集D的分类
的不确定性减少的程度。因此我们倾向于选择对数据集D信息增益大的特征去构建决策树,这样使得
训练出来的模型有着比较好的决策能力。

信息增益的算法

输入:训练数据集D和特征A
输出:特征A对于训练数据集D的信息增益 g ( D , A ) g(D, A) g(D,A)

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

H ( D ) = − ∑ k = 1 K ∣ C K ∣ ∣ D ∣ l o g ∣ C K ∣ ∣ D ∣ H(D) = -\sum_{k=1}^K\frac{|C_K|}{|D|}log\frac{|C_K|}{|D|} H(D)=k=1KDCKlogDCK

  • 计算特征A对训练数据集D的条件熵 H ( D ∣ A ) H(D|A) H(DA):

H ( D ∣ A ) = ∑ i = 1 n ∣ D i ∣ ∣ D ∣ H ( D i ) ∣ H(D|A) = \sum_{i=1}^n\frac{|D_i|}{|D|}H(D_i)| H(DA)=i=1nDDiH(Di)

  • 计算信息增益

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

下面是信息增益的python代码

class information_gain:
    def get_entropy(self, X):
        num = X.shape[0]
        dic = {}
        for d in X:
            if d in dic:
                dic[d] += 1
            else:
                dic[d] = 1
        # print(dic)
        H_X = 0
        for key, val in dic.items():
            H_X -= val/num * np.log(val/num)
        return H_X  

    def get_condition_entropy(self, Y_X, X):#Y_X是一个2xn的矩阵
        num = X.shape[0]                    #Y_X中第一行存的是特征Y的特征值,第二行存的是特征X的特征值
        dic = {}
        for d in X:
            if d in dic:
                dic[d] += 1
            else:
                dic[d] = 1
        condition_entrop = 0
        for key, val in dic.items():
            Y_Xi = [Y_X[0][i] for i in range(num) if Y_X[1][i] == key]
            condition_entrop += val/num * self.get_entropy(np.array(Y_Xi))
        return condition_entrop
    def get_information_gain(self, D, A):
        D_A = np.vstack((D, A))
        H_D = self.get_entropy(D) #特征D的经验熵
        H_D_A =  self.get_condition_entropy(D_A, A) #特征A对特征D的条件熵
        ig = H_D - H_D_A #信息增益
        ig_rate = ig / H_D #信息增益比
        return ig, ig_rate

信息增益比

还有个衡量指标叫信息增益比,是特征选择的另一准则,这里不详细解释,只给出相关定义:
(信息增益比)特征A对训练数据集D的信息增益比g_R(D, A)定义为其信息增益g(D, A)
与训练数据集D关于特征A的值的熵H_A(D)之比,即:

g R ( D , A ) = g ( D , A ) H A ( D ) 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 ∣ l o g D i D H_A(D) = -\sum_{i=1}^n\frac{|D_i|}{|D|}log\frac{D_i}{D} HA(D)=i=1nDDilogDDi

n是特征A取值的个数。

构建决策树(ID3算法)

这一节主要介绍如何用ID3算法来构建决策树。ID3算法从直观上是递归地用对训练数据集
信息增益最大的特征来构建分类结点。

ID3算法

输入:训练数据集D,特征集A,阀值 ϵ \epsilon ϵ
输出:决策树T

  • 若D中所有实例属于同一类 C k C_k Ck,则T为单节点树,并将类 C k C_k Ck作为该结点的类标记
    ,返回T
  • 若A为空集,则T为单节点树,并将D中实例最大的类 C k C_k Ck来作为该结点的类标记,返回T
  • 否则按照信息增益算法计算特征集A中个特征对D的信息增益,选择信息增益最大的特征 A g A_g Ag
  • 如果特征 A g A_g Ag的信息增益小于阀值 ϵ \epsilon ϵ,则T为单节点树,并将D中实例数目最大
    的类 C k C_k Ck作为该点的类标记,返回T
  • 否则对 A g A_g Ag的每一可能的取值 a i a_i ai,按照 A g = a i A_g=a_i Ag=ai将D分割为若干个非空子集 D i D_i Di,
    D i D_i Di中实例树最大的类作为标记,构建子结点,由结点及其子结点构成树T,返回T
  • 对第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

ID3构建决策树的python代码:

class Dicision_tree:
    def ID3(self, Data_set, e, label, list_A):
        D = Data_set.loc[:, ['class']] #类集
        A = Data_set.loc[:, list_A] #特征集
        num = D.shape[0]
        class_D = self.hash(D.values.reshape((1, num))[0])
        if len(class_D) <= 1: #如果类别只有一种,返回单节点树,标签为这个类别
            return node("class", 2, list(class_D.keys())[0])

        if len(list_A) <= 0: #如果特征都分完了,返回单节点树,标签为类集中数量最多的那个类别
            c_k = max(class_D.items(), key=operator.itemgetter(1))[0]
            return node("class", 2, c_k)
        engein_information_gain = {}
        ig = information_gain()

        for c in A.columns:#得到每个特征对类别的信息增益
            engein_information_gain[c] = ig.get_information_gain(D.values.reshape((1, num))[0], A[c].values)[0]
        max_ig = max(engein_information_gain.items(), key=operator.itemgetter(1))#获得信息增益最大的那个
        if max_ig[1] < e:#如果这个信息增益小于阀值e,返回单节点树,标签为类集中数量最大的类别
            # print("less than e")
            c_k = max(class_D.items(), key=operator.itemgetter(1))[0]
            return node("class", 2, c_k)
        dic_max_ig = self.hash(A[max_ig[0]].values)
        T = node(max_ig[0], len(dic_max_ig), label)
        list_A.remove(max_ig[0])
        for key, value in dic_max_ig.items():#根据信息增益最大的特征对类集进行划分
            tmp = pd.DataFrame(columns=(list_A)+["class"])
            for i in range(num):
                if Data_set.loc[i, [max_ig[0]]].values[0] == key:
                    tmp = tmp.append(Data_set.loc[i, list_A + ["class"]], ignore_index=True)    
            t_dic = self.hash(tmp["class"].values)
            c_k = max(t_dic.items(), key=operator.itemgetter(1))[0]
            T.children[key] = self.ID3(tmp, e, c_k, list_A)
        return T

C4.5算法

C4.5算法和ID3算法相似,只不过前者用的特征选择的标准是信息增益比,后者是信息增益,这里不过多阐述。

决策树的剪枝

为了防止得到的决策树模型过拟合,提高模型的泛化能力,我们需要对得到的决策树进行剪枝。
设树T的叶结点个数为 ∣ T ∣ |T| T,t是树T的叶结点,该结点上有 N t N_t Nt个样本点,其中k类的样本点有
N t k N_{tk} Ntk个, k = 1 , 2 , . . . , K k = 1, 2, ..., K k=1,2,...,K H t ( T ) H_t(T) Ht(T)为叶结点t上的经验熵, α ≥ 0 \alpha ≥ 0 α0为平衡
预测误差和模型复杂度的一个参数,决策树学习的损失函数可以定义为:

C α = ∑ t = 1 ∣ T ∣ N t H t ( T ) + α ∣ T ∣ C_{\alpha} = \sum_{t=1}^{|T|}N_tH_t(T) + \alpha |T| Cα=t=1TNtHt(T)+αT

其中经验熵为:

H t ( T ) = − ∑ N t k N t l o g N t k N t H_t(T) = -\sum\frac{N_{tk}}{N_t}log\frac{N_{tk}}{N_t} Ht(T)=NtNtklogNtNtk

将定义式的第一项计为:

C ( T ) = ∑ t = 1 ∣ T ∣ N t H t ( T ) = − ∑ t = 1 ∣ T ∣ ∑ k = 1 K N t k l o g N t k N t C(T) = \sum_{t=1}^{|T|}N_tH_t(T) = -\sum_{t=1}^{|T|}\sum_{k=1}^KN_{tk}log\frac{N_{tk}}{N_t} C(T)=t=1TNtHt(T)=t=1Tk=1KNtklogNtNtk

这时有:

C α ( T ) = C ( T ) + α ∣ T ∣ C_{\alpha}(T) = C(T) + \alpha |T| Cα(T)=C(T)+αT

第一项 C ( T ) C(T) C(T)代表着模型的预测误差, ∣ T ∣ |T| T时叶结点的数量,也代表着模型的复杂度, α \alpha α
是平衡这两者的一个参数。

剪枝算法

输入:生成算法得到的整个树T,参数 α \alpha α
输出:剪枝之后的子树 T α T_{\alpha} Tα

  • 计算每个结点的经验熵
  • 递归地从子树的叶结点向上回缩,设一组叶结点回缩到其父结点之前于之后的整体树分别为 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 ) C_{\alpha}(T_A) ≤ C_{\alpha}(T_B) Cα(TA)Cα(TB),则进行剪枝(剪掉这个父结点所有的子树),即将父结点变为新的叶结点。
  • 返回(2),直至回溯到根节点,得到损失函数最小的子树 T α T_{\alpha} Tα

剪枝算法的python代码:

def forecast_err(self, T, Data_set):#得到预测误差
        if T == None:
            return 0
        if T.engein == "class":
            dic = self.hash(Data_set["class"].values)
            # print(dic)
            s = sum(dic.values())
            H = 0
            for key, val in dic.items():
                H -= val/s * np.log(val/s)
            return H
        err = 0
        for key, val in T.children.items():
            tmp = pd.DataFrame(columns=Data_set.columns)
            for i in range(Data_set.shape[0]):
                if Data_set.loc[i, T.engein] == key:
                    tmp = tmp.append(Data_set.loc[i, :], ignore_index=True)
            err += self.forecast_err(val, tmp)
        return err

    def get_loss(self, T, alpha, Data_set):#得到损失函数
        leaves = self.get_leave(T)
        return self.forecast_err(T, Data_set) + alpha * len(leaves)

    def pruning(self, T, root, Data_set):#剪枝函数
        if T == None or T.engein == "class":
            return
        for key, val in T.children.items():#对当前节点剪枝前先递归地对子节点剪枝
            self.pruning(val, root, Data_set)
        before_loss = self.get_loss(root, 0.1, Data_set)
        print("before loss", before_loss)
        before_engein = T.engein
        T.engein = "class" #将标签设置为class做懒惰处理,
        after_loss = self.get_loss(root, 0.1, Data_set)
        print("after loss", after_loss)
        if after_loss < before_loss:
            for key in T.children:
                tmp = T.children[key]
                T.children[key] = None
                del tmp
        else:
            T.engein = before_engein

总结

关于CART(回归和分类树)在下一节

猜你喜欢

转载自blog.csdn.net/qq_44026293/article/details/104687274