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 相关文章推荐
Linux下用Python脚本监控目录变化代码分享
May 21 Python
浅谈python函数之作用域(python3.5)
Oct 27 Python
解决python使用open打开文件中文乱码的问题
Dec 29 Python
数组保存为txt, npy, csv 文件, 数组遍历enumerate的方法
Jul 09 Python
python多线程http压力测试脚本
Jun 25 Python
python3 常见解密加密算法实例分析【base64、MD5等】
Dec 19 Python
解决Python logging模块无法正常输出日志的问题
Feb 21 Python
利用PyQt5+Matplotlib 绘制静态/动态图的实现代码
Jul 13 Python
Python selenium如何打包静态网页并下载
Aug 12 Python
Python Pygame实现俄罗斯方块
Feb 19 Python
解决pytorch 的state_dict()拷贝问题
Mar 03 Python
Python正则表达式中flags参数的实例详解
Apr 01 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反射使用实例和PHP反射API的中文说明
2014/07/02 PHP
php实现的zip文件内容比较类
2014/09/24 PHP
PHP使用strstr()函数获取指定字符串后所有字符的方法
2016/01/07 PHP
PHP使用观察者模式处理异常信息的方法详解
2019/09/24 PHP
关于php开启错误提示的总结
2019/09/24 PHP
PHP开发API接口签名生成及验证操作示例
2020/05/27 PHP
js 数组的for循环到底应该怎么写?
2010/05/31 Javascript
JavaScript 盒模型 尺寸深入理解
2012/12/31 Javascript
JavaScript的漂亮的代码片段
2013/06/05 Javascript
JS中图片缓冲loading技术的实例代码
2013/08/29 Javascript
js自动生成的元素与页面原有元素发生堆叠的解决方法
2013/10/24 Javascript
javascript ajax的5种状态介绍
2014/08/18 Javascript
node.js中的path.dirname方法使用说明
2014/12/09 Javascript
jQuery实现强制cookie过期方法汇总
2015/05/22 Javascript
jQuery简易时光轴实现方法示例
2017/03/13 Javascript
Angular ElementRef简介及其使用
2018/10/01 Javascript
nodejs通过钉钉群机器人推送消息的实现代码
2019/05/05 NodeJs
layui 数据表格+分页+搜索+checkbox+缓存选中项数据的方法
2019/09/21 Javascript
vue实践---vue不依赖外部资源实现简单多语操作
2020/09/21 Javascript
[50:58]2018DOTA2亚洲邀请赛3月29日 小组赛A组OpTic VS Newbee
2018/03/30 DOTA
Python 实现 贪吃蛇大作战 代码分享
2016/09/07 Python
Python遍历文件夹和读写文件的实现方法
2017/05/10 Python
Python装饰器的执行过程实例分析
2018/06/04 Python
详解opencv中画圆circle函数和椭圆ellipse函数
2019/12/27 Python
HTML5中5个简单实用的API(第二篇,含全屏、可见性、拍照、预加载、电池状态)
2014/05/07 HTML / CSS
英国豪华真皮和布艺沙发销售网站:Darlings of Chelsea
2018/01/05 全球购物
酒店餐厅2014重阳节活动策划方案
2014/09/16 职场文书
2014领导干部学习焦裕禄同志先进事迹思想汇报
2014/09/19 职场文书
专题组织生活会发言材料
2014/10/17 职场文书
消费者投诉书范文
2015/07/02 职场文书
2016年社区文体活动总结
2016/04/06 职场文书
Django使用echarts进行可视化展示的实践
2021/06/10 Python
详解Spring事件发布与监听机制
2021/06/30 Java/Android
mysql的数据压缩性能对比详情
2021/11/07 MySQL
Win10 和 Win11可以共存吗? win10/11产品生命周期/服务更新介绍
2021/11/21 数码科技
通过T-SQL语句创建游标与实现数据库加解密功能
2022/03/16 SQL Server