大数据,机器学习,人工智能机器学习和人工智能入门机器学习与数据挖掘

决策树(附代码实现)

2020-03-22  本文已影响0人  老居搞机

决策树

决策树其实跟人做决策过程的道理是比较相似的.

比如小明在想今天要不要出去玩这件事情,首先会看看今天是不是工作日,如果是工作日,好了还是安心去搬砖吧! 如果不是工作日那再看看当前新冠肺炎疫情情况,如果没好,那还是窝在家里比较安全,如果疫情好了那小明再去看看今天天气怎么样,如果刮风下雨温度太低什么的那也窝在家里吧,如果不下雨, ok 那小明就开开心心的出去浪了, 这个就是整个决策树的流程了.

如图:

这里有个关键问题是,小明要决策的条件很多(是不是刮风、是不是下雨、温度怎么样), 怎么判断先用哪个条件做决策再用哪个条件呢?换句话说这些条件的先后顺序怎么定?

image

在决策树中定义这些分裂条件优先级比较有名的算法有:信息增益(ID3)、信息增益率(ID4.5)、基尼系数(CART),下面分别介绍一下

信息增益(ID3)

ID3算法使用的是信息增益作为划分标准

总共14条数据,其中出去玩(Yes)的次数9, 那出去玩的概率是 P(Y) = 9/14

不出去玩(No)的次数5,那不出去玩的概率是 P(N) = 5/14

样本中共有4个属性(条件): Outlook, Temperature, Humidity, Wind

Outlook属性的信息熵

Sunny: 总数:5, Yes:2, No:3

Overcast: 总数:4, Yes:4, No:0

Rain: 总数:5, Yes:3, No:2

使用同样方法我们可以计算出其它属性的信息增益是:

所以Outlook的信息增益最高,所有我们切分决策树第一层的时候就选择使用使用天气属性(Outlook),下一层依然采用相同方法来寻找信息增益最大的属性

信息增益率(ID4.5)

ID3算法存在一个问题,就是偏向于多值属性(过拟合), 例如,如果存在唯一标识属性ID,则ID3会选择它作为分 裂属性,这样虽然使得划分充分纯净,但这种划分对分类几乎毫无用处

ID3的后继改进算法C4.5使用信息增益率(gain ratio),试图克服这个偏倚

基尼系数(CART)

CART算法在分类树使用基尼指数增益, 回归树使用最小化均方差

我们在候选属性集合A中,选择那个使划分后基尼指数最小的属性作为最优划分属性,即 a = argmin Gini(A)(D, a)

决策树剪枝

为了防止决策树的过拟合, 在决策树的构造中通常会做剪枝操作,剪枝分为预剪枝和后剪枝:

在构造过程中,当某个节点满足剪枝条件, 则直接停止 此分支的构造

先构造完完整的决策树, 在通过某些条件遍历树进行剪枝

附代码

# -*- encoding: utf8 -*-
from math import log
​
​
class DecisionTree:
    """
    ID3算法实现的决策树
    """
​
    def __init__(self):
​
        self.__feature_labels = dict()
        self.__tree = dict()
​
    def cul_entropy(self, data_set):
        """
        计算信息熵
        :param data_set:
        :return: float ent
        """
        total_len = len(data_set)
        label_counts = {}
​
        for item in data_set:
            label_counts[item[-1]] = label_counts.get(item[-1], 0) + 1
​
        ent = 0
        for key in label_counts:
            label_prob = float(label_counts[key]) / total_len
            ent -= label_prob * log(label_prob, 2)
​
        return ent
​
    def cul_information_gain_label(self, before_ent, data_set, axis):
        """
        计算信息增益
        :param before_ent:
        :param data_set:
        :param axis:
        :return: float information gain
        """
        total_len = len(data_set)
        feature_values = [item[axis] for item in data_set]
        unique_feature_values = set(feature_values)
​
        label_ent = 0
        for feature_value in unique_feature_values:
            label_data_set = self.split_data_lable(data_set, axis, feature_value)
            label_ent += float(len(label_data_set)) / total_len * self.cul_entropy(label_data_set)
​
        return before_ent - label_ent
​
    def get_best_feature(self, data_set):
        """
        获得信息增益最大的一个特征
        :param data_set:
        :return: feature axis
        """
        features_len = len(data_set[0][:-1])
        before_ent = self.cul_entropy(data_set)
​
        label_gain = {}
        for axis in range(features_len):
            label_gain[axis] = self.cul_information_gain_label(before_ent, data_set, axis)
​
        return max(label_gain.iterkeys(), key=lambda k: label_gain[k])
​
    def split_data_lable(self, data_set, axis, feature_value):
        label_data_set = []
        for item in data_set:
            if item[axis] == feature_value:
                item_vec = item[:axis]
                item_vec.extend(item[axis + 1:])
                label_data_set.append(item_vec)
​
        return label_data_set
​
    def create_tree_id3(self, data_set, labels):
        class_list = [item[-1] for item in data_set]
        # 预剪枝 - 所有数据属于同一类型停止分枝
        if class_list.count(class_list[0]) == len(class_list):
            return class_list[0]
​
        best_feature = self.get_best_feature(data_set)
        best_feature_label = labels[best_feature]
        del(labels[best_feature])
        my_tree = {best_feature_label: {}}
​
        feature_values = [item[best_feature] for item in data_set]
        unique_feature_values = set(feature_values)
        for feature_value in unique_feature_values:
            sub_labels = labels[:]
            my_tree[best_feature_label][feature_value] = self.create_tree_id3(self.split_data_lable(data_set, best_feature, feature_value), sub_labels)
​
        return my_tree
​
    def fit(self, data_set, labels):
        for axis in range(len(labels)):
            self.__feature_labels[labels[axis]] = axis
​
        self.__tree = self.create_tree_id3(data_set, labels)
        print "tree:", self.__tree
​
    def predict_tree(self, tree, product):
        for key in tree:
            feature_value = product[self.__feature_labels[key]]
            if isinstance(tree[key][feature_value], dict):
                return self.predict_tree(tree[key][feature_value], product)
            else:
                return tree[key][feature_value]
​
    def predict(self, products):
        return [self.predict_tree(self.__tree, product) for product in products]
​
​
def load_data():
    labels = ['Outlook', 'Temperature', 'Humidity', 'Wind', 'Play']
    data_set = [
        ['Sunny', 'Hot', 'High', 'Weak', 'No'],
        ['Sunny', 'Hot', 'High', 'Strong', 'No'],
        ['Overcast', 'Hot', 'High', 'Weak', 'Yes'],
        ['Rain', 'Mild', 'High', 'Weak', 'Yes'],
        ['Rain', 'Cool', 'Normal', 'Weak', 'Yes'],
        ['Rain', 'Cool', 'Normal', 'Strong', 'No'],
        ['Overcast', 'Cool', 'Normal', 'Strong', 'Yes'],
        ['Sunny', 'Mild', 'High', 'Weak', 'No'],
        ['Sunny', 'Cool', 'Normal', 'Weak', 'Yes'],
        ['Rain', 'Mild', 'Normal', 'Weak', 'Yes'],
        ['Sunny', 'Mild', 'Normal', 'Strong', 'Yes'],
        ['Overcast', 'Mild', 'High', 'Strong', 'Yes'],
        ['Overcast', 'Hot', 'Normal', 'Weak', 'Yes'],
        ['Rain', 'Mild', 'High', 'Strong', 'No']
    ]
​
    return data_set, labels
​
​
if __name__ == '__main__':
    data_set, labels = load_data()
    dt = DecisionTree()
    dt.fit(data_set, labels)
​
    products = [
                ['Rain', 'Mild', 'High', 'Strong'],
                ['Overcast', 'Hot', 'High', 'Strong'],
                ]
​
    probs = dt.predict(products)
    print "probs:", probs

参考

上一篇下一篇

猜你喜欢

热点阅读