技术人生,  机器学习

决策树的ID3/C4.5算法

决策树是一种树形结构,在分类问题中,表示基于特征对实例进行分类的过程。

经典的决策树生成方法有ID3和C4.5算法,二者的生成过程大致相同,区别仅在于使用的对特征分类能力的评价标准不同。

ID3算法使用信息增益评估特征。信息增益即某一特征对分类结果的不确定性程度(即熵)的减少量。信息增益越大,某特征越能影响最终的分类结果,该特征应越位于决策树的顶端。而C4.5算法综合校正了选择取值较多的特征这一趋势,在信息增益的基础上除以某特征的熵,称为信息增益比。

由于计算信息增益需要频繁计算log函数,所以首先引入math库。

from math import log2

初始化节点类,每个节点有特征或类标签,子节点等属性。

class node:
    def __init__(self):
        #保存特征或类标记
        self.target = None
        #孩子字典,键为特征取值,值为子节点
        self.child = {}

下面开始树的生成,通过每一层传入的数据集计算出各个特征及类标签的取值。

    def generate(self, dataSet, features, method='ID3', e=0):
        '''生成树,输入参数分别为数据集,特征集,生成方法“ID3/C4.5”,阈值'''

        x_ranges = [set([i[j] for i in dataSet]) for j in range(len(dataSet[0]) - 1)]   #特征取值集合
        y_ranges = set([i[-1] for i in dataSet])                 #类标记取值集合

若所有实例属于同一类,则将该类作为类标记。

        #若所有实例属于同一类,则将该类作为类标记
        t = y_ranges
        if len(t) == 1:
            self.target = t.pop()
            return

若特征集为空,则将数据集中实例数最大的类作为类标记。

        #若特征集为空,则将数据集中实例数最大的类作为类标记
        if not features:
            t_dict = {}
            for x in y_ranges:
                t_dict[x] = len([i[-1] for i in dataSet if i[-1] == x])
            t_ls = list(t_dict.items())
            t_ls.sort(key=lambda a: a[1])
            self.target = t_ls[-1][0]
            return

计算经验熵与经验条件熵。经验熵即对整体分类标签不确定性的度量,经验条件熵是在已知某一特征的条件下计算得到的条件概率值。

        #计算经验熵
        sum0 = 0
        for i in y_ranges:
            ck_d = len([j[-1] for j in dataSet if j[-1] == i]) / len(dataSet)
            if ck_d == 0:
                continue
            sum0 -= ck_d * log2(ck_d)
        h_dataset = sum0
        #计算经验条件熵
        h_conditional = {}
        for i in range(len(features)):
            sum1 = 0
            for a in x_ranges[i]:
                sum2 = 0
                for b in y_ranges:
                    dik_di = len([c[-1] for c in dataSet if c[i] == a and c[-1] == b]) / len([c[-1] for c in dataSet if c[i] == a])
                    if dik_di == 0:
                        continue
                    sum2 += dik_di * log2(dik_di)
                sum1 -= (len([c[-1] for c in dataSet if c[i] == a]) / len(dataSet)) * sum2
            h_conditional[features[i]] = sum1

ID3算法可根据上述结果直接相减计算出各个特征的信息增益。

        if method == 'ID3':
            #计算信息增益
            information_gain = {}
            for i in features:
                information_gain[i] = h_dataset - h_conditional[i]

C4.5算法还需计算各个特征的熵,最后用信息增益比,即信息增益与各个特征的熵的比值来衡量。

        elif method == 'C4.5':
            #计算各个特征的熵
            h_feature = {}
            for n in range(len(features)):
                sum3 = 0
                for i in x_ranges[n]:
                    ck_d = len([j[n] for j in dataSet if j[n] == i]) / len(dataSet)
                    if ck_d == 0:
                        continue
                    sum3 -= ck_d * log2(ck_d)
                h_feature[features[n]] = sum3
            #计算信息增益比
            information_gain = {}
            for i in features:
                information_gain[i] = (h_dataset - h_conditional[i]) / h_feature[i]
        else:
            raise Exception

排序找到信息增益(比)最大的特征。

        #排序找到信息增益(比)最大的特征
        information_gain_ls = list(information_gain.items())
        information_gain_ls.sort(key=lambda x: x[1], reverse=True)
        feature_g = information_gain_ls[0]

若信息增益(比)小于阈值,则将数据集中实例数最大的类作为类标记。

        #若信息增益(比)小于阈值,则将数据集中实例数最大的类作为类标记
        if feature_g[1] < e:
            t_dict = {}
            for x in y_ranges:
                t_dict[x] = len([i[-1] for i in dataSet if i[-1] == x])
            t_ls = list(t_dict.items())
            t_ls.sort(key=lambda a: a[1])
            self.target = t_ls[-1][0]
            return

最后递归地对子节点进行生成构造,子节点的数据集必须是满足子节点的传入条件的数据实例,且传入的数据集与特征集都需要去掉已经用过的特征。

        #对特征的每一可能取值递归的构造子节点
        self.target = feature_g[0]
        f_index = features.index(self.target)
        f = features[:]
        f.remove(self.target)
        for i in x_ranges[f_index]:
            d = [x[:] for x in dataSet if x[f_index] == i]
            for j in range(len(d)):
                d[j].pop(f_index)
            self.child[i] = node()
            self.child[i].generate(d,f,method,e)

至此,整棵决策树生成完毕。下面是查询决策树。

查询方法十分简单,根据满足的特征条件自上而下查询至叶节点即可得出分类标签。

    def query(self, term):
        '''查找决策树,输入{'特征':'取值',...}形式的字典'''

        #判断是否查询到叶节点
        if not self.child:
            result = self.target
            return result
        else:
            result = self.child[term[self.target]].query(term)
            return result

输入数据生成决策树并验证。

if __name__ == "__main__":
    features = ['年龄','有工作','有自己的房子','信贷情况']        #特征集
    train_data = [['青年','否','否','一般','否'],                #训练数据集
                ['青年','否','否','好','否'],
                ['青年','是','否','好','是'],
                ['青年','是','是','一般','是'],
                ['青年','否','否','一般','否'],
                ['中年','否','否','一般','否'],
                ['中年','否','否','好','否'],
                ['中年','是','是','好','是'],
                ['中年','否','是','非常好','是'],
                ['中年','否','是','非常好','是'],
                ['老年','否','是','非常好','是'],
                ['老年','否','是','好','是'],
                ['老年','是','否','好','是'],
                ['老年','是','否','非常好','是'],
                ['老年','否','否','一般','否']]

    n = node()
    n.generate(train_data,features,method="ID3",e=0)

    z = {'有自己的房子':'否','有工作':'否','年龄':'青年','信贷情况':'一般'}
    print(n.query(z))

查询得到的结果为“否”。

A WindRunner. VoyagingOne

留言

您的邮箱地址不会被公开。 必填项已用 * 标注