python 随机森林算法及其优化详解


Posted in Python onJuly 11, 2019

前言

优化随机森林算法,正确率提高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<<M)
  # print('特征值为 : ',n_features)
  oobs = {} # ----------------------
  for i in range(n_trees):   # 建立n_trees棵决策树
   sample = self.sample_split(train)  # 有放回的采样,创建数据子集
   oobs[i] = sample # ----------------
   tree = build_one_tree(sample, max_depth, min_size, n_features) # 建立决策树
   self.trees.append(tree)
   temp_flag += 1
   # print(i,tree)
  oob_score = self.OOB(oobs, train, self.trees) # oob准确率---------
  print("oob_score is ", oob_score) # 打印oob准确率---------
  return self.trees

 '''随机森林预测的多数表决'''
 def bagging_predict(self, onetestdata):
  predictions = [predict(tree, onetestdata) for tree in self.trees]
  return max(set(predictions), key=predictions.count)

 '''计算建立的森林的精确度'''
 def accuracy_metric(self, testdata):
  correct = 0
  for i in range(len(testdata)):
   predicted = self.bagging_predict(testdata[i])
   if testdata[i][-1] == predicted:
    correct += 1
  return correct / float(len(testdata)) * 100.0


# 数据处理
'''导入数据'''
def load_csv(filename):
 dataset = list()
 with open(filename, 'r') as file:
  csv_reader = reader(file)
  for row in csv_reader:
   if not row:
    continue
   # dataset.append(row)
   dataset.append(row[:-1])
 # return dataset
 return dataset[1:], dataset[0]

'''划分训练数据与测试数据'''
def split_train_test(dataset, ratio=0.3):
 #ratio = 0.2 # 取百分之二十的数据当做测试数据
 num = len(dataset)
 train_num = int((1-ratio) * num)
 dataset_copy = list(dataset)
 traindata = list()
 while len(traindata) < train_num:
  index = randint(0,len(dataset_copy)-1)
  traindata.append(dataset_copy.pop(index))
 testdata = dataset_copy
 return traindata, testdata

'''分析树,将向量内积写入list'''
def analyListTree(node, tag, result):
 # 叶子节点的父节点
 if (isinstance(node['left'], dict)):
  # 计算node与node[tag]的内积
  tag="left"
  re = Inner_product(node, tag)
  result.append(re)
  analyListTree(node['left'], 'left', result)
  return
 elif (isinstance(node['right'], dict)):
  # 计算node与node[tag]的内积
  tag = "right"
  re = Inner_product(node, tag)
  result.append(re)
  analyListTree(node['right'], 'right', result)
  return
 else:
  return

'''求向量内积'''
# 计算node与node[tag]的内积
def Inner_product(node ,tag):
 a = mat([[float(node['index'])], [float(node['value'])]])
 b = mat([[float(node[tag]['index'])], [float(node[tag]['value'])]])
 return (a.T * b)[0,0]

'''相似度优化'''
''' same_value = 20  # 向量内积的差(小于此值认为相似)
 same_rate = 0.63  # 树的相似度(大于此值认为相似)
 返回新的森林(已去掉相似度高的树)'''
def similarity_optimization(newforest, samevalue, samerate):
 res = list()    # 存储森林的内积
 result = list()    # 存储某棵树的内积
 i = 1
 for tree in newforest:
  # 分析树,将向量内积写入list
  # result 存储tree的内积
  analyListTree(tree, None, result)
  res.append(result)
  # print('第',i,'棵树:',len(result),result)
  result = []
 # print('res = ',len(res),res)
 # 取一棵树的单个向量内积与其他树的单个向量内积做完全对比(相似度)
 # 遍历列表的列
 for i in range(0, len(res) - 1):
  # 保证此列未被置空、
  if not newforest[i] == None:
   # 遍历做对比的树的列
   for k in range(i + 1, len(res)):
    if not newforest[k] == None:
     # time用于统计相似的次数,在每次更换对比树时重置为0
     time = 0
     # 遍历列表的当前行
     for j in range(0, len(res[i])):
      # 当前两颗树对比次数
      all_contrast = (res[ i].__len__() * res[k].__len__())
      # 遍历做对比的树的行
      for l in range(0, len(res[k])):
       # 如果向量的内积相等,计数器加一
       if res[i][j] - res[k][l] < samevalue:
        time = time + 1
      # 如果相似度大于设定值
     real_same_rate = time / all_contrast
     if (real_same_rate > samerate):
      # 将对比树置空
      newforest[k] = None
 result_forest = list()
 for i in range(0, newforest.__len__()):
  if not newforest[i] == None:
   result_forest.append(newforest[i])
 return result_forest


'''auc优化method'''
def auc_optimization(auclist,trees_num,trees):
 # 为auc排序,获取从大到小的与trees相对应的索引列表
 b = sorted(enumerate(auclist), key=lambda x: x[1], reverse=True)
 index_list = [x[0] for x in b]
 auc_num = int(trees_num * 2 / 3)
 # 取auc高的前auc_num个
 print('auc: ', auc_num, index_list)
 newTempForest = list()
 for i in range(auc_num):
  # myRF.trees.append(tempForest[i])
  # newTempForest.append(myRF.trees[index_list[i]])
  newTempForest.append(trees[index_list[i]])
 return newTempForest

'''得到森林中决策树的最佳深度'''
def getBestDepth(min_size,sample_ratio,trees_num,feature_ratio,traindata,testdata):
 max_depth = np.linspace(1, 15, 15, endpoint=True)
 # max_depth=[5,6,7,8,9,10,11,12,13,14,15]
 scores_final = []
 i=0
 for depth in max_depth:
  # 初始化随机森林
  # print('=========>',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)

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Eclipse + Python 的安装与配置流程
Mar 05 Python
深入理解 Python 中的多线程 新手必看
Nov 20 Python
Python基于列表模拟堆栈和队列功能示例
Jan 05 Python
python实现归并排序算法
Nov 22 Python
简单了解Python matplotlib线的属性
Jun 29 Python
pandas.DataFrame.drop_duplicates 用法介绍
Jul 06 Python
Python如何绘制日历图和热力图
Aug 07 Python
Django如何批量创建Model
Sep 01 Python
python中numpy数组与list相互转换实例方法
Jan 29 Python
python gui开发——制作抖音无水印视频下载工具(附源码)
Feb 07 Python
用python实现监控视频人数统计
May 21 Python
利用python调用摄像头的实例分析
Jun 07 Python
python实现从本地摄像头和网络摄像头截取图片功能
Jul 11 #Python
python常用库之NumPy和sklearn入门
Jul 11 #Python
python在新的图片窗口显示图片(图像)的方法
Jul 11 #Python
Python实现K折交叉验证法的方法步骤
Jul 11 #Python
Python获取命令实时输出-原样彩色输出并返回输出结果的示例
Jul 11 #Python
将python运行结果保存至本地文件中的示例讲解
Jul 11 #Python
详解python实现交叉验证法与留出法
Jul 11 #Python
You might like
PHP实现Socket服务器的代码
2008/04/03 PHP
discuz Passport 通行证 整合笔记
2008/06/30 PHP
PHP利用str_replace防注入的方法
2013/11/10 PHP
CodeIgniter配置之routes.php用法实例分析
2016/01/19 PHP
php远程下载类分享
2016/04/13 PHP
Yii2创建多界面主题(Theme)的方法
2016/10/08 PHP
JavaScript DOM 学习第五章 表单简介
2010/02/19 Javascript
取得窗口大小 兼容所有浏览器的js代码
2011/08/09 Javascript
jQuery手机浏览器中拖拽动作的艰难性分析
2015/02/04 Javascript
浅谈javascript基础之客户端事件驱动
2016/06/10 Javascript
使用JavaScript获取URL中的参数(两种方法)
2016/11/16 Javascript
Bootstrap导航条可点击和鼠标悬停显示下拉菜单
2016/11/25 Javascript
有趣的bootstrap走动进度条
2016/12/01 Javascript
easyui datebox 时间限制,datebox开始时间限制结束时间,datebox截止日期比起始日期大的实现代码
2017/01/12 Javascript
jquery表单验证实例仿Toast提示效果
2017/03/03 Javascript
Vue.js 中的 $watch使用方法
2017/05/25 Javascript
把JavaScript代码改成ES6语法不完全指南(分享)
2017/09/10 Javascript
JavaScript动态绑定详解
2017/09/14 Javascript
原生javascript实现的全屏滚动功能示例
2017/09/19 Javascript
Javascript中prototype与__proto__的关系详解
2018/03/11 Javascript
javacript replace 正则取字符串中的值并替换【推荐】
2018/09/13 Javascript
手把手教你 CKEDITOR 4 扩展插件制作
2019/06/18 Javascript
vue tab滚动到一定高度,固定在顶部,点击tab切换不同的内容操作
2020/07/22 Javascript
[03:20]2015国际邀请赛全明星表演赛
2015/08/08 DOTA
[00:32]2018DOTA2亚洲邀请赛EG出场
2018/04/03 DOTA
Python3.4学习笔记之常用操作符,条件分支和循环用法示例
2019/03/01 Python
Python玩转加密的技巧【推荐】
2019/05/13 Python
Python实现动态循环输出文字功能
2020/05/07 Python
序列化Python对象的方法
2020/08/01 Python
FC-Moto英国:欧洲最大的摩托车服装和头盔商店之一
2019/08/25 全球购物
软件设计的目标是什么
2016/12/04 面试题
学生自我评语
2015/01/04 职场文书
试用期自我评价怎么写
2015/03/10 职场文书
本科毕业论文致谢词
2015/05/14 职场文书
Python中文纠错的简单实现
2021/07/07 Python
解决Git推送错误non-fast-forward的方法
2022/06/25 Servers