机器学习算法——分类决策树

决策树的著名算法CART,它解决了ID3算法的2个不足,既能用于分类问题,又能用于回归问题
CART算法的主体结构和ID3算法基本是相同的,只是在以下几点有所改变:

  1. 选择划分特征时,ID3使用信息熵量化数据集的混乱程度;CART使用基尼指数和均方误差量化数据集的混乱程度。
  2. 选定某切分特征时,ID3算法使用该特征所有的取值进行切分,例如一个特征有k个取值,数据集则被切分成K份,创建k个子树;CART算法使用某一阈值进行二元切分,即在特征值的取值范围区间内选择一个阈值t,将数据集切分成两份,然后用一个子集(大于t)构造左子树,使用另一个数据子集(小于t)构造右子树,因此CART算法创建的就是二叉树。
  3. 对于以用于创建内部节点的特征,在后续运算中(创建子树中的节点时),ID3算法不会再次使用它作为内部节点;CART算法可能会再次使用它创建其他内部节点

CART分类树:
CART分类树的构造算法为递归算法,流程如下:
1.如果当前数据集D中样本数量小于“最小切分数”,或D的基尼指数小于“基尼指数阈值”:

  •     创建叶节点,节点的值为数据集D中出现最多的类标记

2. 否则

  •     创建内部节点
  •     使用每一个布尔值特征将数据集切分成D1,D2,并计算切分后基尼指数降低的值,从而找到最佳切分特征
  •     使用由最佳切分特征切分得到的D1,D2,递归调用CART分类树构造算法,创建左右子树
  •     将当前内部节点作为两颗子树的父节点

 

基尼指数:

假设数据集D中有K个分类,其样本总数为|D|,每个分类放入样本个数分别为|D1|,|D2|,...|Dk|,则一个样本属于第i类的概率为:

Pi= \frac{|Di|}{|D|}


数据集D的基尼指数定义如下:

Gini\left ( D \right )= 1-\sum_{i=1}^{k}pi^{2}

类似条件熵,也可以定义在特征A条件下D的基尼指数。CART算法总会动态的吧所有特征都转换为布尔值特征,因此数据集D总可以根据某一布尔特征A切分成D1和D2两个子集,在特征A条件下D的基尼指数定义如下:

Gini( D,A)= \frac{|D1|}{D}Gini\left ( D1 \right ) +\frac{|D2|}{|D|}Gini(D2)

CAR分类树构造算法在选择切分特征时,只需计算每一个特征条件下的基尼指数,然后选择其中使得基尼指数最小的特征。

 

代码如下:

import numpy as np
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score


class CartClassificationTree:
    class Node:
        '''树节点类'''

        def __init__(self):
            self.value = None

            # 内部叶节点属性
            self.feature_index = None
            self.feature_value = None
            self.left = None
            self.right = None

        def __str__(self):
            if self.left:
                s = '内部节点<%s>:\n' % self.feature_index
                ss = '[ >%s]-> %s' % (self.feature_value, self.left)
                s += '\t' + ss.replace('\n', '\n\t') + '\n'
                ss = '[<=%s]-> %s' % (self.feature_value, self.right)
                s += '\t' + ss.replace('\n', '\n\t')
            else:
                s = '叶节点(%s)' % self.value
            return s

    def __init__(self, gini_threshold=0.01, gini_dec_threshold=0., min_samples_split=2):
        '''构造器函数'''
        # 基尼系数的阈值
        self.gini_threshold = gini_threshold
        # 基尼系数降低的阈值
        self.gini_dec_threshold = gini_dec_threshold
        # 数据集还可继续分割的最小样本数量
        self.min_samples_split = min_samples_split

    def _gini(self, y):
        '''计算基尼指数'''
        values = np.unique(y)

        s = 0.
        for v in values:
            y_sub = y[y == v]
            s += (y_sub.size / y.size) ** 2

        return 1 - s

    def _gini_split(self, y, feature, value):
        '''计算根据特征切分后的基尼指数'''
        # 根据特征的值将数据集拆分成两个子集
        indices = feature > value
        y1 = y[indices]
        y2 = y[~indices]

        # 分别计算两个子集的基尼系数
        gini1 = self._gini(y1)
        gini2 = self._gini(y2)

        # 计算分割后的基尼系数
        # gini(y, feature) = (|y1| * gini(y1) + |y2| * gini(y2)) / |y|
        gini = (y1.size * gini1 + y2.size * gini2) / y.size

        return gini

    def _get_split_points(self, feature):
        '''获得一个连续值特征的所有分割点'''
        # 获得一个特征所有出现过的值, 并排序.
        values = np.unique(feature)
        # 分割点为values中相邻两个点的中点.
        split_points = [(v1 + v2) / 2 for v1, v2 in zip(values[:-1], values[1:])]

        return split_points

    def _select_feature(self, X, y):
        '''选择划分特征'''
        # 最佳分割特征的index
        best_feature_index = None
        # 最佳分割点
        best_split_value = None

        min_gini = np.inf
        _, n = X.shape
        for feature_index in range(n):
            # 迭代每一个特征
            feature = X[:, feature_index]
            # 获得一个特征的所有分割点
            split_points = self._get_split_points(feature)
            for value in split_points:
                # 迭代每一个分割点value, 计算使用value分割后的数据集基尼系数.
                gini = self._gini_split(y, feature, value)
                # 找到更小的gini, 则更新分割特征和.
                if gini < min_gini:
                    min_gini = gini 
                    best_feature_index = feature_index
                    best_split_value = value

        # 判断分割后基尼系数的降低是否超过阈值
        if self._gini(y) - min_gini < self.gini_dec_threshold:
            best_feature_index = None
            best_split_value = None

        return best_feature_index, best_split_value, min_gini

    def _node_value(self, y):
        '''计算节点的值'''
        # 统计数据集中样本类标记的个数
        labels_count = np.bincount(y)
        # 节点值等于数据集中样本最多的类标记.
        return np.argmax(np.bincount(y))

    def _create_tree(self, X, y):
        '''生成树递归算法'''
        # 创建节点
        node = self.Node()
        # 计算节点的值, 等于y的均值.
        node.value = self._node_value(y)

        # 若当前数据集样本数量小于最小分割数量min_samples_split, 则返回叶节点.
        if y.size < self.min_samples_split:
            return node

        # 若当前数据集的基尼系数小于阈值gini_threshold, 则返回叶节点.
        if self._gini(y) < self.gini_threshold:
            return node

        # 选择最佳分割特征
        feature_index, feature_value, min_gini = self._select_feature(X, y)
        if feature_index is not None:
            # 如果存在适合分割特征, 当前节点为内部节点.
            node.feature_index = feature_index
            node.feature_value = feature_value

            # 根据已选特征及分割点将数据集划分成两个子集.
            feature = X[:, feature_index]
            indices = feature > feature_value
            X1, y1 = X[indices], y[indices]
            X2, y2 = X[~indices], y[~indices]

            # 使用数据子集创建左右子树.
            node.left = self._create_tree(X1, y1)
            node.right = self._create_tree(X2, y2)

        return node

    def _predict_one(self, x_test):
        '''对单个样本进行预测'''
        # 爬树一直爬到某叶节点为止, 返回叶节点的值.
        node = self.tree_
        while node.left:
            if x_test[node.feature_index] > node.feature_value:
                node = node.left
            else:
                node = node.right

        return node.value

    def train(self, X_train, y_train):
        '''训练决策树'''
        self.tree_ = self._create_tree(X_train, y_train)

    def predict(self, X_test):
        '''对测试集进行预测'''
        # 对每一个测试样本, 调用_predict_one, 将收集到的结果数组返回.
        return np.apply_along_axis(self._predict_one, axis=1, arr=X_test)


if __name__ == '__main__':
    # 数据集url:https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data
    x = np.genfromtxt("iris.data", delimiter=",", usecols=range(4),dtype=np.float)
    # print(x)
    y = np.genfromtxt("iris.data", delimiter=",", usecols=4, dtype=str)
    # print(y)
    le = LabelEncoder()
    y = le.fit_transform(y)
    # print(y)
    cct = CartClassificationTree()
    x_train,x_test,y_train,y_test = train_test_split(x,y)
    cct.train(x_train,y_train)
    y_pred = cct.predict(x_test)
    score = accuracy_score(y_test,y_pred)
    print(score)

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值