ptyhon 随机森林算法及其优化 RandomForest

优化随机森林算法,正确率提高1%~5%(已经有90%+的正确率,再调高会导致过拟合)

论文当然是参考的,毕竟出现早的算法都被人研究烂了,什么优化基本都做过。而人类最高明之处就是懂得利用前人总结的经验和制造的工具(说了这么多就是为偷懒找借口。hhhh)

优化思路

1. 计算传统模型准确率
2. 计算设定树木颗数时最佳树深度,以最佳深度重新生成随机森林
3. 计算新生成森林中每棵树的AUC,选取AUC靠前的一定百分比的树
4. 通过计算各个树的数据相似度,排除相似度超过设定值且AUC较小的树
5. 计算最终的准确率

主要代码粘贴如下(注释比较详细,就不介绍代码了)

#-*- coding: utf-8 -*-
import time
from csv import reader
from random import randint
from random import seed

import numpy as np
from numpy import mat

from group_11 import caculateAUC_1, plotTree

# 建立一棵CART树
'''试探分枝'''
def data_split(index, value, dataset):
    left, right = list(), list()
    for row in dataset:
        if row[index] < value:
            left.append(row)
        else:
            right.append(row)
    return left, right

'''计算基尼指数'''
def calc_gini(groups, class_values):
    gini = 0.0
    total_size = 0
    for group in groups:
        total_size += len(group)
    for group in groups:
        size = len(group)
        if size == 0:
            continue
        for class_value in class_values:
            proportion = [row[-1] for row in group].count(class_value) / float(size)
            gini += (size / float(total_size)) * (proportion * (1.0 - proportion))# 二分类执行两次,相当于*2
    return gini

'''找最佳分叉点'''
def get_split(dataset, n_features):
    class_values = list(set(row[-1] for row in dataset))# 类别标签集合
    b_index, b_value, b_score, b_groups = 999, 999, 999, None

    # 随机选取特征子集,包含n_features个特征
    features = list()
    while len(features) < n_features:
        # 随机选取特征
        # 特征索引
        index = randint(0, len(dataset[0]) - 2)  # 往features添加n_features个特征(n_feature等于特征数的根号),特征索引从dataset中随机取
        if index not in features:
            features.append(index)
    for index in features:      # 对每一个特征
        # 计算Gini指数
        for row in dataset:   # 按照每个记录的该特征的取值划分成两个子集,计算对于的Gini(D,A),取最小的
            groups = data_split(index, row[index], dataset)
            gini = calc_gini(groups, class_values)
            if gini < b_score:
                b_index, b_value, b_score, b_groups = index, row[index], gini, groups
    return {'index': b_index, 'value': b_value, 'groups': b_groups}  # 每个节点由字典组成

'''多数表决'''
def to_terminal(group):
    outcomes = [row[-1] for row in group]
    return max(set(outcomes), key=outcomes.count)

'''分枝'''
def split(node, max_depth, min_size, n_features, depth):
    left, right = node['groups']  # 自动分包/切片
    del (node['groups'])
    if not left or not right:    # left或者right为空时
        node['left'] = node['right'] = to_terminal(left + right)  # 叶节点不好理解
        return

    if depth >= max_depth:
        node['left'], node['right'] = to_terminal(left), to_terminal(right)
        return
    # 左子树
    if len(left) <= min_size:
        node['left'] = to_terminal(left)
    else:
        node['left'] = get_split(left, n_features)
        split(node['left'], max_depth, min_size, n_features, depth + 1)
    # 右子树
    if len(right) <= min_size:   # min_size最小的的分枝样本数
        node['right'] = to_terminal(right)
    else:
        node['right'] = get_split(right, n_features)
        split(node['right'], max_depth, min_size, n_features, depth + 1)

'''建立一棵树'''
def build_one_tree(train, max_depth, min_size, n_features):
    # 寻找最佳分裂点作为根节点
    root = get_split(train, n_features)
    split(root, max_depth, min_size, n_features, 1)
    return root

'''用森林里的一棵树来预测'''
def predict(node, row):
    if row[node['index']] < node['value']:
        if isinstance(node['left'], dict):
            return predict(node['left'], row)
        else:
            return node['left']
    else:
        if isinstance(node['right'], dict):
            return predict(node['right'], row)
        else:
            return node['right']


# 随机森林类
class randomForest:
    def __init__(self,trees_num, max_depth, leaf_min_size, sample_ratio, feature_ratio):
        self.trees_num = trees_num                # 森林的树的数目
        self.max_depth = max_depth                # 树深
        self.leaf_min_size = leaf_min_size        # 建立树时,停止的分枝样本最小数目
        self.samples_split_ratio = sample_ratio   # 采样,创建子集的比例(行采样)
        self.feature_ratio = feature_ratio        # 特征比例(列采样)
        self.trees = list()                       # 森林

    '''有放回的采样,创建数据子集'''
    def sample_split(self, dataset):
        sample = list()
        n_sample = round(len(dataset) * self.samples_split_ratio)  #每棵树的采样数
        while len(sample) < n_sample:
            index = randint(0, len(dataset) - 2)  #随机有放回的采样
            sample.append(dataset[index])
        return sample

    ##############***Out-of-Bag***################################
    # 进行袋外估计等相关函数的实现,需要注意并不是每个样本都可能出现在随机森林的袋外数据中
    # 因此进行oob估计时需要注意估计样本的数量
    def OOB(self, oobdata, train, trees):
        '''输入为:袋外数据dict,训练集,tree_list
        return oob准确率'''

        n_rows = []
        count = 0
        n_trees = len(trees)  # 森林中树的棵树

        for key, item in oobdata.items():
            n_rows.append(item)

        # print(len(n_rows))  # 所有trees中的oob数据的合集

        n_rows_list = sum(n_rows, [])

        unique_list = []
        for l1 in n_rows_list:  # 从oob合集中计算独立样本数量
            if l1 not in unique_list:
                unique_list.append(l1)

        n = len(unique_list)
        # print(n)

        # 对训练集中的每个数据,进行遍历,寻找其作为oob数据时的所有trees,并进行多数投票
        for row in train:
            pre = []
            for i in range(n_trees):
                if row not in oobdata[i]:
                    # print('row: ',row)
                    # print('trees[i]: ', trees[i])
                    pre.append(predict(trees[i], row))
            if len(pre) > 0:
                label = max(set(pre), key=pre.count)
                if label == row[-1]:
                    count += 1

        return (float(count) / n) * 100

    '''建立随机森林'''
    def build_randomforest(self, train):
        temp_flag = 0
        max_depth = self.max_depth           # 树深
        min_size = self.leaf_min_size        # 建立树时,停止的分枝样本最小数目
        n_trees = self.trees_num             # 森林的树的数目
        n_features = int(self.feature_ratio * (len(train[0])-1))    #列采样,从M个feature中,选择m个(m<',i,'<=============')
        myRF_ = randomForest(trees_num, depth, min_size, sample_ratio, feature_ratio)
        # 生成随机森林
        myRF_.build_randomforest(traindata)
        # 测试评估
        acc = myRF_.accuracy_metric(testdata[:-1])
        # print('模型准确率:', acc, '%')
        # scores_final.append(acc.mean())
        scores_final.append(acc*0.01)
        i=i+1
    # print('scores_final: ',scores_final)
    # 找到深度小且准确率高的值
    best_depth = 0
    temp_score = 0
    for i in range(len(scores_final)):
        if scores_final[i] > temp_score:
            temp_score = scores_final[i]
            best_depth = max_depth[i]
    # print('best_depth:',np.mean(scores_final),best_depth)
    # plt.plot(max_depth, scores_final, 'r-', lw=2)
    # # plt.plot(max_depth, list(range(0,max(scores_final))), 'r-', lw=2)
    # plt.xlabel('max_depth')
    # plt.ylabel('CV scores')
    # plt.ylim(bottom=0.0,top=1.0)
    # plt.grid()
    # plt.show()
    return best_depth


'''对比不同树个数时的模型正确率'''
def getMyRFAcclist(treenum_list):
    seed(1)  # 每一次执行本文件时都能产生同一个随机数
    filename = 'DataSet3.csv'            #SMOTE处理过的数据
    min_size = 1
    sample_ratio = 1
    feature_ratio = 0.3  # 尽可能小,但是要保证 int(self.feature_ratio * (len(train[0])-1)) 大于1
    same_value = 20  # 向量内积的差(小于此值认为相似)
    same_rate = 0.63  # 树的相似度(大于此值认为相似)

    # 加载数据
    dataset, features = load_csv(filename)
    traindata, testdata = split_train_test(dataset, feature_ratio)
    # 森林中不同树个数的对比
    # treenum_list = [20, 30, 40, 50, 60]
    acc_num_list = list()
    acc_list=list()
    for trees_num in treenum_list:
        # 优化1-获取最优深度
        max_depth = getBestDepth(min_size, sample_ratio, trees_num, feature_ratio, traindata, testdata)
        print('max_depth is ', max_depth)

        # 初始化随机森林
        myRF = randomForest(trees_num, max_depth, min_size, sample_ratio, feature_ratio)
        # 生成随机森林
        myRF.build_randomforest(traindata)

        print('Tree_number: ', myRF.trees.__len__())
        # 计算森林中每棵树的AUC
        auc_list = caculateAUC_1.caculateRFAUC(testdata, myRF.trees)
        # 选取AUC高的决策数形成新的森林(auc优化)
        newTempForest = auc_optimization(auc_list,trees_num,myRF.trees)
        # 相似度优化
        myRF.trees = similarity_optimization(newTempForest, same_value, same_rate)
        # 测试评估
        acc = myRF.accuracy_metric(testdata[:-1])
        print('myRF1_模型准确率:', acc, '%')
        acc_num_list.append([myRF.trees.__len__(), acc])
        acc_list.append(acc)
    print('trees_num from 20 to 60: ', acc_num_list)
    return acc_list


if __name__ == '__main__':
    start = time.clock()
    seed(1)  # 每一次执行本文件时都能产生同一个随机数
    filename = 'DataSet3.csv'       # 这里是已经利用SMOTE进行过预处理的数据集
    max_depth = 15  # 调参(自己修改) #决策树深度不能太深,不然容易导致过拟合
    min_size = 1
    sample_ratio = 1
    trees_num = 20

    feature_ratio = 0.3     # 尽可能小,但是要保证 int(self.feature_ratio * (len(train[0])-1)) 大于1
    same_value = 20        # 向量内积的差(小于此值认为相似)
    same_rate = 0.82     # 树的相似度(大于此值认为相似)
    # 加载数据
    dataset,features = load_csv(filename)
    traindata,testdata = split_train_test(dataset, feature_ratio)

    # 优化1-获取最优深度
    # max_depth = getBestDepth(min_size, sample_ratio, trees_num, feature_ratio, traindata, testdata)
    # print('max_depth is ',max_depth)

    # 初始化随机森林
    myRF = randomForest(trees_num, max_depth, min_size, sample_ratio, feature_ratio)
    # 生成随机森林
    myRF.build_randomforest(traindata)

    print('Tree_number: ', myRF.trees.__len__())
    acc = myRF.accuracy_metric(testdata[:-1])
    print('传统RF模型准确率:',acc,'%')

    # 画出某棵树用以可视化观察(这里是第一棵树)
    # plotTree.creatPlot(myRF.trees[0], features)
    # 计算森林中每棵树的AUC
    auc_list = caculateAUC_1.caculateRFAUC(testdata,myRF.trees)
    # 画出每棵树的auc——柱状图
    # plotTree.plotAUCbar(auc_list.__len__(),auc_list)

    # 选取AUC高的决策数形成新的森林(auc优化)
    newTempForest = auc_optimization(auc_list,trees_num,myRF.trees)
    # 相似度优化
    myRF.trees=similarity_optimization(newTempForest, same_value, same_rate)

    print('优化后Tree_number: ', myRF.trees.__len__())
    # 测试评估
    acc = myRF.accuracy_metric(testdata[:-1])
    # print('优化后模型准确率:', acc, '%')
    print('myRF1_模型准确率:', acc, '%')
    # 画出某棵树用以可视化观察(这里是第一棵树)
    # plotTree.creatPlot(myRF.trees[0], features)
    # 计算森林中每棵树的AUC
    auc_list = caculateAUC_1.caculateRFAUC(testdata, myRF.trees)
    # 画出每棵树的auc——柱状图
    plotTree.plotAUCbar(auc_list.__len__(), auc_list)
    end = time.clock()
    print('The end!')
    print(end-start)

你可能感兴趣的:(artificial,intelligence)