欢迎大家前往云+社区,获取更多腾讯海量技术实践干货哦~

决策树可能会受到高度变异的影响,使得结果对所使用的特定测试数据而言变得脆弱。

根据您的测试数据样本构建多个模型(称为套袋)可以减少这种差异,但是树本身是高度相关的。

随机森林是套袋(方法)的延伸,除了基于多个测试数据样本构建树木之外,它还限制了可用于构建树木的特征,使得树木间具有差异。这反过来可以提升算法的表现。

在本教程中,您将了解如何在Python中从头开始实现随机森林算法。

完成本教程后,您将知道:

  • 套袋决策树和随机森林算法的区别。
  • 如何构造更多方差的袋装决策树。
  • 如何将随机森林算法应用于预测建模问题。

让我们开始吧。

  • 2017年1月更新:将cross_validation_split()中fold_size的计算更改为始终为整数。修复了Python 3的问题。
  • 2017年2月更新:修复了build_tree中的错误。
  • 2017年8月更新:修正了基尼计算中的一个错误,增加了群组大小(基于迈克尔!)。

如何在Python中从头开始实现随机森林 图片来自 InspireFate Photography,保留部分权利。

描述

本节简要介绍本教程中使用的随机森林算法和Sonar数据集。

随机森林算法

决策树涉及从数据集中(利用)贪婪选择选取最佳分割点过程中的每一步。

如果不精简(该算法),此算法容易使决策树出现高方差。这种高方差(结果)可以通过创建包含测试数据集中(多个)不同的实例(问题的不同观点)的多重树,接着将实例所有的可能结果结合,这种方法简称为bootstrap聚合或套袋。

套袋的局限性在于,它使用相同的贪婪算法来创建每棵树,这意味着在每棵树中可能会选择相同或非常相似的分割点,使得不同的树非常相似(树将被关联)。这反过来又使他们的预测相似,从而缩减了最初寻求的差异。

我们可以通过贪婪算法在创建树时在每个分割点评估的特征(行)来限制决策树不同。这被称为随机森林算法。

像装袋一样,测试数据集的多个样本在被采集后,接着在每个样本上训练不同的树。不同之处在于在每一点上,拆分是在数据中进行并添加到树中的,且只考虑固定的属性子集。

对于分类问题,我们将在本教程中讨论的问题的类型——分割中输入特点数的平方根值对为分割操作考虑的属性个数的限制。

num_features_for_split = sqrt(total_input_features)

这一小变化的结果是树之间变得更加不同(不关联),作为结果会有更加多样化的预测,这样的结果往往好于一个单独的树或者单独套袋得到的结果。

声纳数据集

我们将在本教程中使用的数据集是Sonar数据集。

这是一个描述声纳声音从不同曲面反弹后返回(数据)的数据集。输入的60个变量是声呐从不同角度返回的力度值。这是一个二元分类问题,需要一个模型来区分金属圆柱中的岩石。这里有208个观察对象。

这是一个很好理解的数据集。所有变量都是连续的且范围一般是0到1。输出变量是“Mine”字符串中的“M”和“rock”中的“R”,需要转换为整数1和0。

通过预测在数据集(“M”或“mines”)中观测数最多的类,零规则算法可以达到53%的准确度。

您可以在UCI Machine Learning repository了解关于此数据集的更多信息。

下载免费的数据集,并将其放置在工作目录中,文件名为sonar.all-data.csv。

教程

本教程分为2个步骤。

  1. 计算分割。
  2. 声纳数据集案例研究。

这些步骤为您需要将随机森林算法应用于自己的预测建模问题奠定了基础。

1.计算分割

在决策树中,通过利用最低成本找到指定属性和该属性的值方法来确定分割点。

对于分类问题,这个成本函数通常是基尼指数,它计算分割点创建的数据组的纯度。基尼指数为0是完美纯度,其中在两类分类问题的情况下,将类别值完全分成两组。

在决策树中找到最佳分割点涉及到为每个输入的变量评估训练数据集中每个值的成本。

对于装袋和随机森林,这个程序是在测试数据集的样本上执行的,并且是可替换的。更换取样意味着同一行(数据)会不止一次的被选择并将其添加到取样中。

我们可以优化随机森林的这个程序。我们可以创建一个输入属性样本来考虑,而不是在搜索中枚举输入属性的所有值。

这个输入属性的样本可以随机选择而不需要替换,这意味着每个输入属性在查找具有最低成本的分割点的过程中只被考虑一次。

下面是实现此过程的函数名称get_split()。它将数据集和固定数量的输入要素作为输入参数进行评估,此数据集可能是实际测试数据集的一个样本。

helper函数test_split()用于通过候选分割点拆分数据集,gini_index()用于根据创建的行组来计算给定拆分的花费。

我们可以看到,通过随机选择特征索引并将其添加到列表(称为特征)来创建特征列表,然后枚举该特征列表并且将测试数据集中的特定值评估作为分割点。

# Select the best split point for a dataset
def get_split(dataset, n_features):
class_values = list(set(row[-] for row in dataset))
b_index, b_value, b_score, b_groups = , , , None
features = list()
while len(features) < n_features:
index = randrange(len(dataset[])-)
if index not in features:
features.append(index)
for index in features:
for row in dataset:
groups = test_split(index, row[index], dataset)
gini = gini_index(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}

现在我们知道如何修改决策树算法以便与随机森林算法一起使用,我们可以将它与一个bagging实现一起使用,并将其应用于现实生活中的数据集。

2.声纳数据集案例研究

在本节中,我们将把随机森林算法应用到声纳数据集。

该示例假定数据集的CSV副本位于当前工作目录中,文件名为sonar.all-data.csv。

首先加载数据集,将字符串值转换为数字,并将输出列从字符串转换为0和1的整数值。这可以通过使用帮助器函数load_csv(),str_column_to_float()和str_column_to_int()来加载和预备数据集。

我们将使用k-fold交叉验证来估计未知数据的学习模型的性能。这意味着我们将构建和评估k个模型,并将性能估计为平均模型误差。分类准确性将用于评估每个模型。这些工具或是算法在cross_validation_split(),accuracy_metric()和evaluate_algorithm()辅助函数中提供。

我们也将使用适合套袋包括辅助功能分类和回归树(CART)算法的实现)test_split(拆分数据集分成组,gini_index()来评估分割点,我们修改get_split()函数中讨论在前一步中,to_terminal(),split()和build_tree()用于创建单个决策树,预测()使用决策树进行预测,subsample()创建训练数据集的子采样,以及bagging_predict()用决策树列表进行预测。

开发了一个新的函数名称random_forest(),首先根据训练数据集的子样本创建一个决策树列表,然后使用它们进行预测。

正如我们上面所说的,随机森林和袋装决策树之间的关键区别是对树的创建方式中的一个小的改变,这里是在get_split()函数中。

完整的例子如下所示。

# Random Forest Algorithm on Sonar Dataset
from random import seed
from random import randrange
from csv import reade
from math import sqrt # Load a CSV file
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)
return dataset # Convert string column to float
def str_column_to_float(dataset, column):
for row in dataset:
row[column] = float(row[column].strip()) # Convert string column to intege
def str_column_to_int(dataset, column):
class_values = [row[column] for row in dataset]
unique = set(class_values)
lookup = dict()
for i, value in enumerate(unique):
lookup[value] = i
for row in dataset:
row[column] = lookup[row[column]]
return lookup # Split a dataset into k folds
def cross_validation_split(dataset, n_folds):
dataset_split = list()
dataset_copy = list(dataset)
fold_size = int(len(dataset) / n_folds)
for i in range(n_folds):
fold = list()
while len(fold) < fold_size:
index = randrange(len(dataset_copy))
fold.append(dataset_copy.pop(index))
dataset_split.append(fold)
return dataset_split # Calculate accuracy percentage
def accuracy_metric(actual, predicted):
correct =
for i in range(len(actual)):
if actual[i] == predicted[i]:
correct +=
return correct / float(len(actual)) * 100.0 # Evaluate an algorithm using a cross validation split
def evaluate_algorithm(dataset, algorithm, n_folds, *args):
folds = cross_validation_split(dataset, n_folds)
scores = list()
for fold in folds:
train_set = list(folds)
train_set.remove(fold)
train_set = sum(train_set, [])
test_set = list()
for row in fold:
row_copy = list(row)
test_set.append(row_copy)
row_copy[-] = None
predicted = algorithm(train_set, test_set, *args)
actual = [row[-] for row in fold]
accuracy = accuracy_metric(actual, predicted)
scores.append(accuracy)
return scores # Split a dataset based on an attribute and an attribute value
def test_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 # Calculate the Gini index for a split dataset
def gini_index(groups, classes):
# count all samples at split point
n_instances = float(sum([len(group) for group in groups]))
# sum weighted Gini index for each group
gini = 0.0
for group in groups:
size = float(len(group))
# avoid divide by zero
if size == :
continue
score = 0.0
# score the group based on the score for each class
for class_val in classes:
p = [row[-] for row in group].count(class_val) / size
score += p * p
# weight the group score by its relative size
gini += (1.0 - score) * (size / n_instances)
return gini # Select the best split point for a dataset
def get_split(dataset, n_features):
class_values = list(set(row[-] for row in dataset))
b_index, b_value, b_score, b_groups = , , , None
features = list()
while len(features) < n_features:
index = randrange(len(dataset[])-)
if index not in features:
features.append(index)
for index in features:
for row in dataset:
groups = test_split(index, row[index], dataset)
gini = gini_index(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} # Create a terminal node value
def to_terminal(group):
outcomes = [row[-] for row in group]
return max(set(outcomes), key=outcomes.count) # Create child splits for a node or make terminal
def split(node, max_depth, min_size, n_features, depth):
left, right = node['groups']
del(node['groups'])
# check for a no split
if not left or not right:
node['left'] = node['right'] = to_terminal(left + right)
return
# check for max depth
if depth >= max_depth:
node['left'], node['right'] = to_terminal(left), to_terminal(right)
return
# process left child
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+)
# process right child
if len(right) <= 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+) # Build a decision tree
def build_tree(train, max_depth, min_size, n_features):
root = get_split(train, n_features)
split(root, max_depth, min_size, n_features, )
return root # Make a prediction with a decision tree
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'] # Create a random subsample from the dataset with replacement
def subsample(dataset, ratio):
sample = list()
n_sample = round(len(dataset) * ratio)
while len(sample) < n_sample:
index = randrange(len(dataset))
sample.append(dataset[index])
return sample # Make a prediction with a list of bagged trees
def bagging_predict(trees, row):
predictions = [predict(tree, row) for tree in trees]
return max(set(predictions), key=predictions.count) # Random Forest Algorithm
def random_forest(train, test, max_depth, min_size, sample_size, n_trees, n_features):
trees = list()
for i in range(n_trees):
sample = subsample(train, sample_size)
tree = build_tree(sample, max_depth, min_size, n_features)
trees.append(tree)
predictions = [bagging_predict(trees, row) for row in test]
return(predictions) # Test the random forest algorithm
seed()
# load and prepare data
filename = 'sonar.all-data.csv'
dataset = load_csv(filename)
# convert string attributes to integers
for i in range(, len(dataset[])-):
str_column_to_float(dataset, i)
# convert class column to integers
str_column_to_int(dataset, len(dataset[])-)
# evaluate algorithm
n_folds =
max_depth =
min_size =
sample_size = 1.0
n_features = int(sqrt(len(dataset[])-))
for n_trees in [, , ]:
scores = evaluate_algorithm(dataset, random_forest, n_folds, max_depth, min_size, sample_size, n_trees, n_features)
print('Trees: %d' % n_trees)
print('Scores: %s' % scores)
print('Mean Accuracy: %.3f%%' % (sum(scores)/float(len(scores))))

使用k值5进行交叉验证,给定每个倍数值为208/5 = 41.6或者在每次迭代中刚好超过40个记录被计算。

构建深度树的最大深度为10,每个节点的最小训练行数为1。训练数据集样本的创建大小与原始数据集相同,这是随机森林算法的默认期望值。

在每个分割点处考虑的特征的数量被设置为sqrt(num_features)或者sqrt(60)= 7.74被保留为7个特征。

对一套有着3种不同数量的树木(示例)进行评测在此过程中进行比较,结果表明随着更多树木的添加,(处理)技能也随之提升。

运行该示例将打印每个折叠的分数和每个配置的平均分数。

Trees:
Scores: [56.09756097560976, 63.41463414634146, 60.97560975609756, 58.536585365853654, 73.17073170731707]
Mean Accuracy: 62.439% Trees:
Scores: [70.73170731707317, 58.536585365853654, 85.36585365853658, 75.60975609756098, 63.41463414634146]
Mean Accuracy: 70.732% Trees:
Scores: [82.92682926829268, 75.60975609756098, 97.5609756097561, 80.48780487804879, 68.29268292682927]
Mean Accuracy: 80.976%

扩展

本节列出了您可能有兴趣探索的关于本教程的扩展。

  • 算法优化。发现教程中使用的配置有一些试验和错误,但没有进行优化。尝试更多的树木,不同数量的特征,甚至不同的树形配置来提高性能。
  • 更多的问题。将该技术应用于其他分类问题,甚至将其应用于回归,具有新的成本函数和结合树预测的新方法。

你有没有尝试这些扩展? 在下面的评论中分享你的经验。

评论

在本教程中,您了解了如何从头开始实现随机森林算法。

具体来说,你了解到:

  • 随机森林和Bagged决策树的区别。
  • 如何更新决策树的创建以适应随机森林过程。
  • 如何将随机森林算法应用于现实世界的预测建模问题。

翻译人:一只懒惰的小白,该成员来自云+社区翻译社原文链接:https://machinelearningmastery.com/implement-random-forest-scratch-python/
原文作者:Jason Brownlee

相关阅读

教程从头开始在Python中实现k最近邻居

Python NLTK 自然语言处理入门与例程

浅谈用Python计算文本BLEU分数


此文已由作者授权云加社区发布,转载请注明文章出处

如何在Python中从零开始实现随机森林的更多相关文章

  1. 面试官问我:如何在 Python 中解析和修改 XML

    摘要:我们经常需要解析用不同语言编写的数据.Python提供了许多库来解析或拆分用其他语言编写的数据.在此 Python XML 解析器教程中,您将学习如何使用 Python 解析 XML. 本文分享 ...

  2. 如何在Python中快速画图——使用Jupyter notebook的魔法函数(magic function)matplotlib inline

    如何在Python中快速画图--使用Jupyter notebook的魔法函数(magic function)matplotlib inline 先展示一段相关的代码: #we test the ac ...

  3. 如何在Python中使用Linux epoll

    如何在Python中使用Linux epoll 内容 介绍 阻塞套接字编程示例 异步套接字和Linux epoll的好处 epoll的异步套接字编程示例 性能考量 源代码 介绍 从2.6版开始,Pyt ...

  4. 如何在Python 中使用UTF-8 编码 && Python 使用 注释,Python ,UTF-8 编码 , Python 注释

    如何在Python 中使用UTF-8 编码 && Python 使用 注释,Python ,UTF-8 编码 , Python  注释 PIP $ pip install beauti ...

  5. 如何在Python中加速信号处理

    如何在Python中加速信号处理 This post is the eighth installment of the series of articles on the RAPIDS ecosyst ...

  6. Python多进程实现并行化随机森林

    文章目录 1. 前言 2. 随机森林原理 3.实现原理 3.1并行化训练 3.1.1训练函数 3.1.2 单进程训练函数 生成数据集模块--生成部分数据集 单进程训练函数代码 3.2 并行化预测 3. ...

  7. 如何在python中使用Elasticsearch

    什么是 Elasticsearch ​ 想查数据就免不了搜索,搜索就离不开搜索引擎,百度.谷歌都是一个非常庞大复杂的搜索引擎,他们几乎索引了互联网上开放的所有网页和数据.然而对于我们自己的业务数据来说 ...

  8. 如何在Python中处理不平衡数据

    Index1.到底什么是不平衡数据2.处理不平衡数据的理论方法3.Python里有什么包可以处理不平衡样本4.Python中具体如何处理失衡样本印象中很久之前有位朋友说要我写一篇如何处理不平衡数据的文 ...

  9. 如何在Python中实现这五类强大的概率分布

    R编程语言已经成为统计分析中的事实标准.但在这篇文章中,我将告诉你在Python中实现统计学概念会是如此容易.我要使用Python实现一些离散和连续的概率分布.虽然我不会讨论这些分布的数学细节,但我会 ...

随机推荐

  1. 【shell mysql 导出数据到csv脚本,完美解决乱码转义符等问题】-费元星

    #!/bin/bash#@author:feiyuanxing [既然笨到家,就要努力到家]#@date:2017-12-05#@E-Mail:feiyuanxing@gmail.com#@TARGE ...

  2. jsp页面固定页面为绝对路径

    1 <!-- 固定到绝对路径 --> 2 <base href="<%=request.getContextPath()%>/"/>

  3. 织梦dedecms中arclist标签下无法嵌套图片

    版权声明:本文为博主原创文章,未经博主允许不得转载. 错误代码: {dede:arclist row=10 orderby=click titlelen=35} [field:title/] {/de ...

  4. 织梦dedecsm系统"企业简介"类单栏目模版如何修改和调用

    2013-1-12 14:46 | 发布者: moke | 栏目:dedecms教程        我们的模版里应该都有article_article.htm这个模版,这个模版是文章内容页模板,也就是 ...

  5. 有经验JAVA程序员如何提升自己?

    作为一个具有一到五年开发经验程序员,需要学习内容很多: JVM/分布式/高并发/性能优化/Spring MVC/Spring Boot/Spring Cloud/MyBatis/Netty源码分析等等 ...

  6. 通过与Quickbuild和Mist.io的持续集成实现云管理和使用监控

    欢迎大家前往云+社区,获取更多腾讯海量技术实践干货哦~ 这篇文章由巴拉克·梅里莫维奇撰写. 总结我自己有关Openstack的各种骚操作先告一段落.这一次我想谈谈有关监控云服务的使用情况. 我个人使用 ...

  7. JVM之GC算法

  8. mybatis-pageHelper做分页

    Mybatis-PageHelpera是一个很好的第三方分页插件,支持很多数据库,几乎主流的数据库都支持 github地址:https://github.com/pagehelper/Mybatis- ...

  9. linux如何自动获取ip地址

    第一步:激活网卡 系统装好后默认的网卡是eth0,用下面的命令将这块网卡激活. # ifconfig eth0 up 第二步:设置网卡进入系统时启动 想要每次开机就可以自动获取IP地址上网,就要设置网 ...

  10. video.js不能控制本地视频或者音频播放时长

    问题: 把视频放到本地,然后对视频进行测试,想要控制视频或者音频的播放时长,没办法做到,每次拉动进度条,都会使得本地视频重新播放 原因: 所有浏览器默认js无法访问本地地址,也就是说js不能对本地文件 ...