CART分类树算法与随机森林

来源:互联网 发布:河北体育学院网络教育 编辑:程序博客网 时间:2024/05/23 20:50

说明:本博客是学习《python机器学习算法》赵志勇著的学习笔记,其图片截取也来源本书。

CART分类树算法与随机森林

对于一个复杂的分类问题,训练一个复杂的分类模型通常比较耗时,同时为了能够提高对分类问题的预测准确性,通常可以选择训练多个分类模型,并将各自的预测结果结合起来,得到最终的预测。集成学习(Ensemble Learning)便是这样一种学习方法,集成学习是指将多种学习算法,通过适当的形式组合起来完成同一个任务。在集成学习中,主要分为Bagging算法和boosting算法。

随机森林(Random Forest)是bagging算法中最重要的一种算法,通过对数据集的采用生成多个不同的数据集,并在每一个数据集上训练一个分类树,最终结合每一棵分类树的预测结果作为随机森林的预测结果。

1、决策树分类器

决策树(Decision Tree)算法是一类常见的机器学习算法,在分类问题中,决策树算法通过样本中某一维属性的值,将样本划分到不同的类别中。以二分类为例,二分类的数据集如表1所示。
这里写图片描述
样本中的属性为”是否用鳃呼吸”和”有无鱼鳍”。

对于上面 的数据集,首先通过属性”是否使用鳃呼吸”判断是否为鱼,如图1所示

这里写图片描述

从图1可以看出,通过属性”是否用鳃呼吸”,已经将一部分样本区分开。接下来对”有无鱼鳍”进行划分。如图2所示。
这里写图片描述

在图2中,通过属性”有无鱼鳍”对剩余的样本进行划分,得到了最终的决策。

1.1、选择最佳划分标准

在选择特征对数据集进行划分数据时一定要优先选择具有区分性的特征。在决策树算法中,通常有这些标准:信息增益(Information Gain)、增益率(Gain Ratio)和基尼指数(Gini Index)。

熵(Entropy)是度量样本集合纯度最常用的一种指标,对于包含m个训练样本的数据集这里写图片描述,在数据集D中,第k类的样本所占的比例为Pk,则数据集D的信息熵为:
这里写图片描述
其中,K表示的是数据集D中的类别的个数。对于表1所示的数据集,其信息熵为:
这里写图片描述
数据集对特征A的值a划分为两个独立的子数据集D1和D2时,此时整个数据集D的熵,为两个独立数据集D1和D2的熵的加权和,即:
这里写图片描述
则图1所示,此时的数据集的信息熵为:
这里写图片描述
由上述的划分可以看出,在划分数据集D的信息熵减小了,对于给定的数据集,划分前后信息熵的减少的量称为信息增益(Information Gain),即:
这里写图片描述
信息熵表示的数据集中的不纯度,信息熵较小表明数据集纯度提升了。在选择数据集划分标准时,通常选择能够使得信息增益最大的划分。ID3就是以信息增益为划分标准的。

增益率(Gain Ratio)的计算方法:

这里写图片描述
其中,IV(A)被称为特征A的”固有值(Intrinsic Value)”,即:
这里写图片描述
C4.5就是以信息增益率为划分标准的。
基尼指数(Gini Index):对于数据集D,假设有K个分类,则样本属于k个分类的概率为Pk,则此时概率分布的基尼指数为:
这里写图片描述
对于数据集D,其基尼指数为:
这里写图片描述
其中,|Ck|表示在数据集D中,属于类别K的样本的个数。若此时根据特征A将数据集划分为两个子数据集D1和D2,基尼指数为:
这里写图片描述
如表1中的数据集D中,其基尼指数为:
这里写图片描述
利用特征”是否用鳃”将数据集D划分成独立的两个数据集D1和D2后,其基尼指数为:
这里写图片描述
在CART决策树中利用Gini指数作为划分数据集的方法。

2、CART分类树算法

from math import pow"""树的节点类"""class node:    def __init__(self,fea=-1,value=None,results=None,right=None,left=None):        self.fea = fea        self.value =value        self.results = results        self.right = right        self.left = left"""根据特征fea中的值value将数据集data划分成左右子树input:  data(list):数据集        fea(int):待分割特征的索引        value(float):待分割的特征的具体值output: (set1,set2)(tuple):分割后的左右子树"""def split_tree(data,fea,value):    set_1 = []    set_2 = []    for x in data:        if x[fea] >= value:  # 大于等于一般是在左子树            set_1.append(x)        else:            set_2.append(x)    return (set_1,set_2)"""统计数据集中不同的类标签label的个数input:  data(list):原始数据集output: label_uniq_cnt(int):样本中的标签的个数"""def label_uniq_cnt(data):    label_uniq_cnt = {}  # 字典    for x in data:        label = x[len(x) -1]          if label not in label_uniq_cnt:  # 取得每一个样本的类标签label            label_uniq_cnt[label] = 0        label_uniq_cnt[label] += 1    return label_uniq_cnt"""计算给定数据的gini指数    input:  data(list):树中    output: gini(float):Gini指数"""def cal_gini_index(data):    total_sample = len(data)  # 样本的总个数     if len(data) == 0:        return 0    label_counts = label_uniq_cnt(data)  # 统计数据集中不同标签的个数    # 计算数据集的Gini指数    gini = 0    for label in label_counts:        gini = gini + pow(label_counts[label],2)    gini = 1 - float(gini)/pow(total_sample,2)    return gini"""构建树    input:  data(list):训练样本    output: node:树的根结点"""def build_tree(data):    # 构建决策树,函数返回该决策树的根节点    if len(data) == 0:        return node()    # 1、计算当前的Gini指数    currentGini = cal_gini_index(data)    bestGain = 0.0    bestCriteria = None # 存储最佳切分属性以及最佳切分点    bestSets = None    feature_num = len(data[0]) -1  # 样本中特征的个数    # 2、找到最好的划分    for fea in range(0,feature_num):        # 2.1、取得fea特征处所有可能的取值        feature_values = {}  # 在fea位置处可能的取值        for sample in data:  # 对每一个样本            feature_values[sample[fea]] = 1  # 存储特征fea处所有可能的取值        # 2.2、针对每一个可能的取值,尝试将数据集划分,并计算Gini指数          for value in list(feature_values.keys()):  # 遍历该属性的所有切分点            # 2.2.1、 根据fea特征中的值value将数据集划分成左右子树            (set_1,set_2) = split_tree(data,fea,value)            # 2.2.2、计算当前的Gini指数            nowGini = float(len(set_1)*cal_gini_index(set_1)+len(set_2)*cal_gini_index(set_2))/len(data)            # 2.2.3、计算Gini指数的增加量            gain = currentGini - nowGini            # 2.2.4、判断此划分是否比当前的划分更好            if gain > bestGain and len(set_1) > 0 and len(set_2) > 0:                bestGain = gain                bestCriteria = (fea,value)                bestSets = (set_1,set_2)    # 3、判断划分是否结束    if bestGain > 0:        right = build_tree(bestSets[0])        left = build_tree(bestSets[1])        return node(fea=bestCriteria[0],value=bestCriteria[1],right=right,left=left)    else:        return node(results=label_uniq_cnt(data))  # 返回当前的类别标签作为最终的类别标签"""对每一个样本sample进行预测    input:  sample(list):需要预测的样本            tree(类):构建好的分类树    output: tree.results:所属的类别"""def predict(sample,tree):    # 1、只是树根    if tree.results != None:        return tree.results    else:        # 2、有左右子树        val_sample = sample[tree.fea]        branch = None        if val_sample >= tree.value:            branch = tree.right        else:            branch = tree.left        return predict(sample, branch)

3、集成学习

在集成学习方法中,其泛化能力比单个学习方法强。
Bagging(Bootstrap Aggregating)算法通过对训练样本有放回的抽取,由此产生多个训练集,并在每一个训练集的子集上训练一个分类器,最终结果是由多个分类结果投票而产生的。Bagging算法的整个过程如下图所示。
这里写图片描述
Boosting算法通过顺序地给训练集中的数据项重新加权创造不同的基础学习器。其核心思想是重复应用一个基础学习器来修改训练数据集。
这里写图片描述

4、随机森林算法模型

随机森林(Random Forest,RF)算法是一种基于Bagging的几成学习方法,可以用来做分类、回归等问题。这里主要介绍随机森林在分类问题中的应用。随机森林是由一系列的决策树组成,它通过自助(Bootstrap)重采样技术,从原始训练样本中有放回地重复随机抽取m个样本,生成新的训练样本集合,然后根据自助样本集合生成k个分类树组成的随机森林,新数据的分类结果按分类树投票多少形成的分数而定。其实质是对决策树算法的一种改进,将多个决策树合并在一起,每一棵树的建立依赖于一个独立抽取的样本,森林中的每一棵树具有相同的分布,分类误差取决于每一棵树的分类能力和它们之间的相关性。特征选择采用随机的方法分裂每一个节点,然后比较不同情况下产生的误差。能够检测到的内在估计误差、分类能力和相关性决定特征的数目。单棵树的分类能力可能很小,但在随机产生大量的决策树后,一个测试样品可以通过统计每一棵树的分类结果,从而选择最有可能的分类。

4.1、随机森林算法流程

随机森林算法是通过训练多个决策树,生成模型,然后综合利用多个决策树进行分类。随机森林算法只需要两个参数:构建的决策树的个数n,在决策树的每个节点进行分裂时需要考虑的输入的特征的个数k,通常k可以取log2(n),其中n表示的是元数据集中的个数。对于单棵决策树的构建,可以分为如下步骤:

(1)假设训练样本的个数为m,则对于每一个决策树的输入样本的个数都为m,且这个m个样本是通过从训练集中有放回地随机抽样得到的。

(2)假设训练样本特征的个数为n,对于每一棵决策树的样本特征是从该n个特征中随机挑选k个,然后从k个输入特征中选择最好的进行分裂。

(3)每棵树都一直这样分裂下去,直到该节点的所有训练样例都属于同一类。在决策树分裂过程中不需要剪枝。

import numpy as npimport random as rdfrom math import log# import pickle as p"""导入数据input:  file_name(string):训练数据保存的文件名output: data_train(list):训练数据"""        def load_data(file_name):    data_train = []    f = open(file_name)    for line in f.readlines():        lines = line.strip().split("\t")        data_tmp = []        for x in lines:            data_tmp.append(float(x))        data_train.append(data_tmp)    f.close()    return data_train"""input:  data(list):原始数据集        k(int):选择特征的个数output: data_samples(list):被选择出来的样本        feature(list):被选择的特征index"""def choose_sample(data,k):    m,n = np.shape(data) # 样本的个数和样本特征的个数    # 1、选择出k个特征的index    feature = []    for j in range(k):        feature.append(rd.randint(0,n-2))  # n-1列是标签    # 2、选择出m个样本的index    index = []    for i in range(m):        index.append(rd.randint(0,m-1))    # 3、从data中选择出m个样本的k个特征,组成数据集data_samples    data_samples = []    for i in range(m):        data_tmp = []        for fea in feature:            data_tmp.append(data[index[i]][fea])  # 添加i行fea列        data_tmp.append(data[index[i]][-1]) # 添加标签值        data_samples.append(data_tmp)    return data_samples,feature"""构建随机森林input:  data_train(list):训练数据        trees_num(int):分类树的个数output: trees_result(list):每一棵树的最好划分        trees_feature(list):每一棵树中对原始特征的选择"""def random_forest_training(data_train,tress_num):    trees_result = []  # 构建好每一棵树的最好划分    trees_feature = []    n = np.shape(data_train)[1] # 样本的维数    if n > 2:        k = int(log(n-1,2)) + 1   # 设置特征的个数    else:        k = 1    # 开始构建每一棵树    for i in range(tress_num):        # 1、随机选择m个样本, k个特征        data_samples,feature = choose_sample(data_train,k)        # 2、构建每一棵分类树        tree = build_tree(data_samples)        # 3、保存训练好的分类树        trees_result.append(tree)        # 4、保存好该分类树使用到的特征        trees_feature.append(feature)    return trees_result,trees_feature"""选择特征input:  data_train(list):训练数据集        feature(list):要选择的特征output: data(list):选择出来的数据集"""def split_data(data_train,feature):    m = np.shape(data_train)[0]    data = []    for i in range(m):        data_x_tmp = []        for x in feature:            data_x_tmp.append(data_train[i][x])        data_x_tmp.append(data_train[i][-1])        data.append(data_x_tmp)    return data"""trees_fiture:保存了每一颗分类树中随机选择的特征"""def get_predict(trees_result,trees_fiture,data_train):    m_tree = len(trees_result) # 有多少棵树    m = np.shape(data_train)[0]  # 训练集的数目    result = []    for i in range(m_tree):        clf = trees_result[i]        feature = trees_fiture[i]        data = split_data(data_train,feature)        result_i = []        for i in range(m):            result_i.append((list(predict(data[i][0:-1],clf).keys()))[0])        result.append(result_i)    final_predict = np.sum(result,axis=0)    return final_predictdef cal_correct_rate(data_train,final_predict):    m = len(final_predict)    corr = 0.0    for i in range(m):        if data_train[i][-1] * final_predict[i] > 0:            corr += 1    return corr/m

3.1、训练模型

# 1、导入数据print("----------- 1、load data -----------")data_train = load_data("C:\\Python-Machine-Learning-Algorithm-master\\Chapter_5 Random Forest\\data.txt")# 2、训练random_forest模型print("----------- 2、random forest training ------------")trees_result,trees_feature = random_forest_training(data_train,1)# 3、得到训练的准确性print("------------ 3、get prediction correct rate ------------")result = get_predict(trees_result,trees_feature,data_train)corr_rate = cal_correct_rate(data_train,result)print("\t------correct rate: ", corr_rate) 
----------- 1、load data ---------------------- 2、random forest training ------------------------ 3、get prediction correct rate ------------    ------correct rate:  0.99
trees_feature
[[0, 1]]

3.2、测试模型

# 加载测试数据print("----------- 1、load data -----------")data_test = load_data("C:\\Python-Machine-Learning-Algorithm-master\\Chapter_5 Random Forest\\test_data.txt")# 预测print("------------ 2、get prediction correct rate ------------")result = get_predict(trees_result,trees_feature,data_test)
----------- 1、load data ----------------------- 2、get prediction correct rate ------------
result
array([-1., -1., -1., ...,  1., -1.,  1.])

由于测试数据没有标签值,固没有办法计算出预测的准确度是多少。