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 相关文章推荐
下载糗事百科的内容_python版
Dec 07 Python
使用Python的Tornado框架实现一个Web端图书展示页面
Jul 11 Python
利用ctypes提高Python的执行速度
Sep 09 Python
Python使用requests及BeautifulSoup构建爬虫实例代码
Jan 24 Python
python实现简单的单变量线性回归方法
Nov 08 Python
Python语言快速上手学习方法
Dec 14 Python
python实发邮件实例详解
Nov 11 Python
python实现差分隐私Laplace机制详解
Nov 25 Python
使用pygame编写Flappy bird小游戏
Mar 14 Python
Python grequests模块使用场景及代码实例
Aug 10 Python
如何用用Python将地址标记在地图上
Feb 07 Python
python_tkinter弹出对话框创建
Mar 20 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防CC攻击实现代码
2011/12/29 PHP
WordPress导航菜单的滚动和淡入淡出效果的实现要点
2015/12/14 PHP
THREE.JS入门教程(4)创建粒子系统
2013/01/24 Javascript
深入理解JavaScript系列(39):设计模式之适配器模式详解
2015/03/04 Javascript
JS实现先显示大图后自动收起显示小图的广告代码
2015/09/04 Javascript
jquery实现简单的遮罩层
2016/01/08 Javascript
JavaScript判断浏览器及其版本信息
2017/01/20 Javascript
从零开始学习Node.js系列教程四:多页面实现数学运算的client端和server端示例
2017/04/13 Javascript
vue使用Axios做ajax请求详解
2017/06/07 Javascript
浅谈ECMAScript6新特性之let、const
2017/08/02 Javascript
详解vue-cli本地环境API代理设置和解决跨域
2017/09/05 Javascript
vue 2.0项目中如何引入element-ui详解
2017/09/06 Javascript
node+koa2+mysql+bootstrap搭建一个前端论坛
2018/05/06 Javascript
React之PureComponent的使用作用
2018/07/10 Javascript
详解react内联样式使用webpack将px转rem
2018/09/13 Javascript
vue中实现高德定位功能
2019/12/03 Javascript
[56:12]LGD vs Optic Supermajor小组赛D组胜者组决赛 BO3 第一场 6.3
2018/06/04 DOTA
python2 与python3的print区别小结
2018/01/16 Python
Python实现对一个函数应用多个装饰器的方法示例
2018/02/09 Python
Python多版本开发环境管理工具介绍
2019/07/03 Python
python shell命令行中import多层目录下的模块操作
2020/03/09 Python
详解Python中import机制
2020/09/11 Python
python基于selenium爬取斗鱼弹幕
2021/02/20 Python
米兰必去买手店排行榜首位:Antonioli
2016/09/11 全球购物
汉语言文学毕业生求职信
2013/10/01 职场文书
写自荐信有哪些不宜?
2013/10/17 职场文书
关于是否需要写商业计划书
2014/02/07 职场文书
《搭石》教学反思
2014/04/07 职场文书
大学生翘课检讨书范文
2014/10/06 职场文书
2014年计划生育协会工作总结
2014/11/14 职场文书
财务部岗位职责
2015/02/03 职场文书
消防演习通知
2015/04/25 职场文书
芙蓉镇观后感
2015/06/10 职场文书
四大名著读书笔记
2015/06/25 职场文书
《月球之谜》教学反思
2016/02/20 职场文书
2016年推广普通话宣传周活动总结
2016/04/06 职场文书