【机器学习】随机森林(基于CART分类树,简化版本)—— python3 实现方案

 随机森林的原理相对来说简单多了,准确率提升,又因为“双随机”预防了过拟合,无需再对元决策树剪枝,真是太棒了!

以下是在CART分类树的基础上构建的随机森林,主要是增加了2个参数,以及对整体准确率的计算。

import numpy as np
from collections import Counter

class RF:
    def __init__(self, T=10, k=1):
        '''
        定义参数
        :param T: 元决策树的个数
        :param k: 随机选取的特征的个数
        '''
        self.T = T
        self.k = k

    def baggingDataSet(self, dataSet):
        '''
        从原始训练集使用bagging方法选取等数量训练集,和未被选择的数据集(作为验证集)
        :return: 训练集,验证集,验证集对应的索引值
        '''
        m = len(dataSet)
        trainDataSet, validDataSet, selectedm = [], [], []
        for i in range(m):
            k = np.random.randint(0, m)
            selectedm.append(k)
            trainDataSet.append(dataSet[k])
        unselectedm = list(set(range(0, m)) - set(selectedm))  # 验证集对应的索引值
        for i in unselectedm:
            validDataSet.append(dataSet[i])
        return trainDataSet, validDataSet, unselectedm

    def caclGini(self, dataSet):
        '''
        计算基尼指数
        :param dataSet: 包含标签类的数据集 m*(n+1)。 离散特征用int数据类型,连续特征用float数据类型。 如果包含离散特征,则必须是二维列表类型。(因为array的数据类型都是一样的)
        :return: 数据集的基尼指数
        '''
        targets = [example[-1] for example in dataSet]  # 获取标签列表
        d = {}  # 存储标签及其相应的数量
        gini = 1
        for target in targets:
            if target not in d:
                d[target] = 0
            d[target] += 1
        for target in d.keys():
            prob = d[target] / len(dataSet)
            gini -= prob**2  # 计算基尼指数
        return gini

    def splitDataSet(self, dataSet, feature, value):
        '''
        按照给定的特征和特征值切分数据集。这里根据是否连续型数据或离散型数据,采用不同的二分法
        :param dataSet: 同上
        :param feature: 待切分的特征
        :param value: 选取的特征值
        :return: 二分切割后的2个子数据集
        '''
        left, right = [], []
        if isinstance(value, int):  # 离散型
            for i in range(len(dataSet)):
                reduceSet = dataSet[i][: feature]
                reduceSet.extend(dataSet[i][feature + 1:])
                if dataSet[i][feature] == value:  # 离散型数据根据值是否等于给定值划分数据集
                    left.append(reduceSet)
                if dataSet[i][feature] != value:
                    right.append(reduceSet)

        if isinstance(value, float):  # 连续型
            for i in range(len(dataSet)):
                reduceSet = dataSet[i][: feature]
                reduceSet.extend(dataSet[i][feature + 1:])
                if dataSet[i][feature] <= value:  # 连续型数据根据值是否小于等于给定值划分数据集
                    left.append(reduceSet)
                if dataSet[i][feature] > value:
                    right.append(reduceSet)

        return left, right

    def chooseBestFeature(self, dataSet):
        '''
        选出使基尼指数最小的特征 与 对应的特征值
        :param dataSet: 同上
        :return: 最佳分类特征与特征值
        '''
        n = len(dataSet[0]) - 1  # 获取特征的数量
        splitGini, bestFeatrue, bestValue = None, None, None  # 声明变量,防止“本地变量调用前被引用”的错误
        minGini = np.inf  # 一个无限大的正数

        randFeature = []
        if self.k < n:  # 如果特征个数大于指定特征个数k,那么从[0,n)随机选择k个不同的数字作为索引
            index, i = None, 0
            while i < self.k:
                index = np.random.randint(0, n)
                if index not in randFeature:
                    randFeature.append(index)
                    i += 1
        else:  # 如果特征个数少了,那么按一般规则处理
            randFeature = range(n)

        for featureIndex in randFeature:
            values = set([example[featureIndex] for example in dataSet])  # 获取所有可能的特征取值
            for value in values:
                left, right = self.splitDataSet(dataSet, featureIndex, value)
                leftGini = self.caclGini(left)  # 左子树
                rightGini = self.caclGini(right)  # 右子树
                splitGini = (len(left) / len(dataSet)) * leftGini + (len(right) / len(dataSet)) * rightGini  # 分割后的基尼指数
                # print('特征索引:', featureIndex, '特征值:', value, '基尼指数:', splitGini)
                if splitGini < minGini:
                    minGini = splitGini
                    bestFeatrue = featureIndex
                    bestValue = value

        return bestFeatrue, bestValue

    def classtarget(self, targets):
        '''
        辅助函数
        :param targets: 类别列表
        :return: 返回最终叶子中,数量最多的列表
        '''
        d = {}
        for target in targets:
            if target not in d:
                d[target] = 0
            d[target] += 1
        return max(d, key=lambda x: d[x])  # 获取值最大的键

    def createTree(self, dataSet, featureLabel):
        '''
        创建决策树
        :param dataSet: 同上
        :param featureLabel: 特征索引-特征含义 对照表
        :return: 二叉决策树
        '''
        if not dataSet:
            return None
        targets = [example[-1] for example in dataSet]
        if len(set(targets)) == 1:  # 只包含一个类别
            return targets[0]
        if (len(dataSet[0]) - 1) < self.k:  # 没有可分的特征
            return self.classtarget(targets)

        bestFeatureIndex, bestValue = self.chooseBestFeature(dataSet)
        featureLable_copy = featureLabel.copy()  # 避免对源数据的修改
        bestFeatureLabel = featureLable_copy[bestFeatureIndex]
        if isinstance(bestValue, int):  # 如果是离散型数据,删除对照表中的对应的索引,因为不会再用到。 如果是连续型的数据,则保留,因为可能再用到
            del featureLable_copy[bestFeatureIndex]

        mytree = {}
        mytree['FeatLabel'] = bestFeatureLabel
        mytree['FeatValue'] = bestValue
        lSet, rSet = self.splitDataSet(dataSet, bestFeatureIndex, bestValue)
        mytree['left'] = self.createTree(lSet, featureLable_copy)
        mytree['right'] = self.createTree(rSet, featureLable_copy)
        return mytree

    def predict(self, tree, featureLabel, testvec):
        '''
        根据训练好的决策树,输出单个待测试样本的类别
        :param tree: 训练好的决策树
        :param featureLabel: 同上
        :param testvec: 待测试的样本(不含标签)
        :return: 预测类别
        '''
        if not isinstance(tree, dict):
            return tree
        bestFeatureIndex = featureLabel.index(tree['FeatLabel'])
        value = testvec[bestFeatureIndex]
        if isinstance(value, int):
            if value == tree['FeatValue']:
                return self.predict(tree['left'], featureLabel, testvec)
            if value != tree['FeatValue']:
                return self.predict(tree['right'], featureLabel, testvec)
        if isinstance(value, float):
            if value <= tree['FeatValue']:
                return self.predict(tree['left'], featureLabel, testvec)
            if value > tree['FeatValue']:
                return self.predict(tree['right'], featureLabel, testvec)

    def predictall(self, tree, featureLabel, testvecall):
        '''
        根据训练好的决策树,输出所有待测试样本的类别
        :param tree: 同上
        :param featureLabel: 同上
        :param testvecall: 所有待测试样本(不含标签) 格式是二维列表
        :return:
        '''
        predict = []
        for testvec in testvecall:
            predict.append(self.predict(tree, featureLabel, testvec))
        return predict

    def creataRF(self, dataSet, featureLabel):
        '''
        创建随机森林
        :param dataSet:
        :param featureLabel:
        :return: self.T颗决策树组成的列表, 和 随机森林对验证集的准确率
        '''
        forest = []
        dictIndex_to_predict = {}  # 索引值对应的预测值,预测值存储在列表中,最终生成 每棵树对 所有未训练样本的预测值 的集合
        for t in range(self.T):
            trainDataSet, validDataSet, unselectm = self.baggingDataSet(dataSet)
            tree = self.createTree(trainDataSet, featureLabel)
            forest.append(tree)  # 把树添加到森林

            testvecall = [example[: -1] for example in validDataSet]  # 获取这棵树未训练样本的特征数据
            predictions = self.predictall(tree, featureLabel, testvecall)  # 预测所有的特征数据
            for index, predict in zip(unselectm, predictions):  # 把预测值添加到预测字典中
                if index not in dictIndex_to_predict:
                    dictIndex_to_predict[index] = [predict]  # 索引作为键, 预测值的列表 作为值
                else:
                    dictIndex_to_predict[index].append(predict)

        count = 0
        for index in dictIndex_to_predict.keys():
            predict = dictIndex_to_predict[index]
            dictIndex_to_predict[index] = Counter(predict).most_common(1)[0][0]  # 统计所有树对 同一样本的预测值,选出得票数最多的作为 森林的预测值
            if dictIndex_to_predict[index] == dataSet[index][-1]:
                count += 1
        accuracy = count / len(dictIndex_to_predict)  # 计算准确率

        return forest, accuracy


# 测试数据,因为数据量少,且是人造的,测试结果不准确。 只为测试能否跑通程序
dataSet = [[0, 1, 0.15, 'yes'],
           [1, 1, 0.25, 'yes'],
           [1, 0, 0.21, 'no'],
           [0, 1, 0.45, 'no'],
           [0, 1, 0.55, 'no']]
feature_label = ['No Surfacing', 'Flippers', 'other']
cart = RF(k=2)
mytree = cart.createTree(dataSet, feature_label)
rf, accuracy = cart.creataRF(dataSet, feature_label)
print(accuracy)

猜你喜欢

转载自blog.csdn.net/zhenghaitian/article/details/81086596