统计学习方法第五章:决策树(decision tree),CART算法,剪枝及python实现

统计学习方法第二章:感知机(perceptron)算法及python实现
统计学习方法第三章:k近邻法(k-NN),kd树及python实现
统计学习方法第四章:朴素贝叶斯法(naive Bayes),贝叶斯估计及python实现
统计学习方法第五章:决策树(decision tree),CART算法,剪枝及python实现
统计学习方法第五章:决策树(decision tree),ID3算法,C4.5算法及python实现

欢迎关注公众号:常失眠少年,大学生的修炼手册!

完整代码:
https://github.com/xjwhhh/LearningML/tree/master/StatisticalLearningMethod
欢迎follow和star

决策树(decision tree)是一种基本的分类与回归方法。决策树模型呈树状结构,在分类问题中,表示基于特征对实例进行分类的过程。

它可以认为是if-then规则的集合,也可以认为是定义在特征空间与类空间上的条件概率分布。

其主要优点是模型具有可读性,分类速度快。

学习时,利用训练数据,根据损失函数最小化的原则建立决策树模型。预测时,对新的数据,利用决策树模型进行分类。

决策树学习通常包括3个步骤:特征选择,决策树的生成和决策树的修剪

主要的决策树算法包括ID3,C4.5,CART。我主要实现的是CART算法,其余两个算法也有所实现,但正确率不高,先不予分享,以免误人子弟。

以下是CART生成算法:

CART生成算法

以下是CART剪枝算法:

CART剪枝算法

每次剪枝剪的是某个内部结点的子结点,也就是将某个内部结点的所有子结点回退到这个内部结点里,并将这个内部结点作为叶子结点。因此在计算损失函数时,这个内部结点之外的值都没变,所以我们只需计算内部结点剪枝前和剪枝后的损失函数

问题1:为什么剪去g(t)最小的T?
个人认为是为了获得更多的区间和子树,便于比较

问题2:为什么不直接减去C(t)+\alpha最小的T?
个人认为是整体损失函数=内部结点损失函数+其他结点损失函数和。如果直接剪去这个T,使得内部结点损失函数最小,但其他结点损失函数和不一定,所以整体不一定。换句话说就是局部最优与整体最优的关系。而采用损失函数减小率来进行调整,剪枝幅度更小,能产生更多树,可能其中某个树就是整体损失函数最小

我实现的CART算法并没有剪枝,剪枝的代码会在之后做分享:

import cv2
import time
import logging
import numpy as np
import pandas as pd

from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

total_class = 10


# 这里选用了一个比较小的数据集,因为过大的数据集会导致栈溢出


def log(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        logging.debug('start %s()' % func.__name__)
        ret = func(*args, **kwargs)

        end_time = time.time()
        logging.debug('end %s(), cost %s seconds' % (func.__name__, end_time - start_time))

        return ret

    return wrapper


# 二值化
def binaryzation(img):
    cv_img = img.astype(np.uint8)
    cv2.threshold(cv_img, 50, 1, cv2.THRESH_BINARY_INV, cv_img)
    return cv_img


@log
def binaryzation_features(trainset):
    features = []

    for img in trainset:
        img = np.reshape(img, (28, 28))
        cv_img = img.astype(np.uint8)

        img_b = binaryzation(cv_img)
        features.append(img_b)

    features = np.array(features)
    features = np.reshape(features, (-1, 784))

    return features


class TreeNode(object):
    """决策树节点"""

    def __init__(self, **kwargs):
        '''
        attr_index: 属性编号
        attr: 属性值
        label: 类别(y)
        left_chuld: 左子结点
        right_child: 右子节点
        '''
        self.attr_index = kwargs.get('attr_index')
        self.attr = kwargs.get('attr')
        self.label = kwargs.get('label')
        self.left_child = kwargs.get('left_child')
        self.right_child = kwargs.get('right_child')


# 计算数据集的基尼指数
def gini_train_set(train_label):
    train_label_value = set(train_label)
    gini = 0.0
    for i in train_label_value:
        train_label_temp = train_label[train_label == i]
        pk = float(len(train_label_temp)) / len(train_label)
        gini += pk * (1 - pk)
    return gini


# 计算一个特征不同切分点的基尼指数,并返回最小的
def gini_feature(train_feature, train_label):
    train_feature_value = set(train_feature)
    min_gini = float('inf')
    return_feature_value = 0
    for i in train_feature_value:
        train_feature_class1 = train_feature[train_feature == i]
        label_class1 = train_label[train_feature == i]
        # train_feature_class2 = train_feature[train_feature != i]
        label_class2 = train_label[train_feature != i]
        D1 = float(len(train_feature_class1)) / len(train_feature)
        D2 = 1 - D1
        if (len(label_class1) == 0):
            p1 = 0
        else:
            p1 = float(len(label_class1[label_class1 == label_class1[0]])) / len(label_class1)
        if (len(label_class2) == 0):
            p2 = 0
        else:
            p2 = float(len(label_class2[label_class2 == label_class2[0]])) / len(label_class2)
        gini = D1 * 2 * p1 * (1 - p1) + D2 * 2 * p2 * (1 - p2)
        if min_gini > gini:
            min_gini = gini
            return_feature_value = i
    return min_gini, return_feature_value


def get_best_index(train_set, train_label, feature_indexes):
    '''
    :param train_set: 给定数据集
    :param train_label: 数据集对应的标记
    :return: 最佳切分点,最佳切分变量
    求给定切分点集合中的最佳切分点和其对应的最佳切分变量
    '''
    min_gini = float('inf')
    feature_index = 0
    return_feature_value = 0
    for i in range(len(train_set[0])):
        if i in feature_indexes:
            train_feature = train_set[:, i]
            gini, feature_value = gini_feature(train_feature, train_label)
            if gini < min_gini:
                min_gini = gini
                feature_index = i
                return_feature_value = feature_value
    return feature_index, return_feature_value


# 根据最有特征和最优切分点划分数据集
def divide_train_set(train_set, train_label, feature_index, feature_value):
    left = []
    right = []
    left_label = []
    right_label = []
    for i in range(len(train_set)):
        line = train_set[i]
        if line[feature_index] == feature_value:
            left.append(line)
            left_label.append(train_label[i])
        else:
            right.append(line)
            right_label.append(train_label[i])
    return np.array(left), np.array(right), np.array(left_label), np.array(right_label)


@log
def build_tree(train_set, train_label, feature_indexes):
    # 查看是否满足停止条件
    train_label_value = set(train_label)
    if len(train_label_value) == 1:
        print("a")
        return TreeNode(label=train_label[0])

    if feature_indexes is None:
        print("b")
        return TreeNode(label=train_label[0])

    if len(feature_indexes) == 0:
        print("c")
        return TreeNode(label=train_label[0])

    feature_index, feature_value = get_best_index(train_set, train_label, feature_indexes)
    # print("feature_index",feature_index)

    left, right, left_label, right_label = divide_train_set(train_set, train_label, feature_index, feature_value)

    feature_indexes.remove(feature_index)
    # print("feature_indexes",feature_indexes)

    left_branch = build_tree(left, left_label, feature_indexes)
    right_branch = build_tree(right, right_label, feature_indexes)
    return TreeNode(left_child=left_branch,
                    right_child=right_branch,
                    attr_index=feature_index,
                    attr=feature_value)

# @log
# def prune(tree):


def predict_one(node, test):
    while node.label is None:
        if test[node.attr_index] == node.attr:
            node = node.left_child
        else:
            node = node.right_child
    return node.label


@log
def predict(tree, test_set):
    result = []
    for test in test_set:
        label = predict_one(tree, test)
        result.append(label)
    return result


if __name__ == '__main__':
    logger = logging.getLogger()
    logger.setLevel(logging.DEBUG)

    raw_data = pd.read_csv('../data/train_binary1.csv', header=0)
    data = raw_data.values

    imgs = data[0:, 1:]
    labels = data[:, 0]

    print(imgs.shape)

    # 图片二值化
    # features = binaryzation_features(imgs)

    # 选取 2/3 数据作为训练集, 1/3 数据作为测试集
    train_features, test_features, train_labels, test_labels = train_test_split(imgs, labels, test_size=0.33,random_state=1)

    print(type(train_features))
    tree = build_tree(train_features, train_labels, [i for i in range(784)])
    test_predict = predict(tree, test_features)
    score = accuracy_score(test_labels, test_predict)

    print("The accuracy score is ", score)

运行结果如下:

运行结果

水平有限,如有错误,希望指出

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • 1、模型原理 (一)原理 1、原理:引入信息熵(不确定程度)的概念,通过计算各属性下的信息增益程度(信息增益越大,...
    Python_Franklin阅读 12,208评论 0 17
  • 决策树理论在决策树理论中,有这样一句话,“用较少的东西,照样可以做很好的事情。越是小的决策树,越优于大的决策树”。...
    制杖灶灶阅读 5,756评论 0 25
  • 决策树 决策树模型与学习 特征选择 决策树的生成 决策树的剪枝 CART 算法 决策树模型实现 决策树模型呈树形结...
    千与千与阅读 685评论 1 1
  • 生意人的账簿记录收入与支出,两数相减,便是盈利,人生账簿,记录爱与被爱,两数相加,就是成功。
    谢vV阅读 165评论 0 1
  • 日有一新浪微博名为@洁洁良 的账号公开发布辱华言论。 事件起源是在某活动现场,观众被批在离开后留下大量垃圾,@洁洁...
    汪不器阅读 697评论 0 0