【机器学习基础】数学推导+纯Python实现机器学习算法26:随机森林
Python机器学习算法实现
Author:louwill
Machine Learning Lab
自从第14篇文章结束,所有的单模型基本就讲完了。而后我们进入了集成学习的系列,整整花了5篇文章的篇幅来介绍集成学习中最具代表性的Boosting框架。从AdaBoost到GBDT系列,对XGBoost、LightGBM和CatBoost作了较为详细的了解。本文作为集成学习的最后一篇文章,来介绍与Boosting框架有所不同的Bagging框架。
Bagging与随机森林
Bagging是并行式集成学习方法最典型的代表框架。其核心概念在于自助采样(Bootstrap Sampling),给定包含m个样本的数据集,有放回的随机抽取一个样本放入采样集中,经过m次采样,可得到一个和原始数据集一样大小的采样集。我们可以采样得到T个包含m个样本的采样集,然后基于每个采样集训练出一个基学习器,最后将这些基学习器进行组合。这便是Bagging的主要思想。Bagging与Boosting图示如下:
可以清楚的看到,Bagging是并行的框架,而Boosting则是序列框架(但也可以实现并行)。
有了之前多篇关于决策树的基础以及前述关于Bagging基本思想的阐述,随机森林(Random Forest)就没有太多难以理解的地方了。所谓随机森林,就是有很多棵决策树构建起来的森林,因为构建过程中的随机性,故而称之为随机森林。随机森林算法是Bagging框架的一个典型代表。
关于构建决策树的过程,可以参考前述第4~5篇,这里不做重复阐述。因为基础的推导工作都是前述章节都已完成,这里我们可以直接阐述随机森林的算法过程,简单来说就是两个随机性。具体如下:
- 假设有M个样本,有放回的随机选择M个样本(每次随机选择一个放回后继续选)。
- 假设样本有N个特征,在决策时的每个节点需要分裂时,随机地从这N个特征中选取n个特征,满足n<<N,从这n个特征中选择特征进行节点分裂。
- 基于抽样的M个样本n个特征按照节点分裂的方式构建决策树。
- 按照1~3步构建大量决策树组成随机森林,然后将每棵树的结果进行综合(分类使用投票法,回归可使用均值法)。
所以,当我们熟悉了Bagging的基本思想和决策树构建的过程后,随机森林就很好理解了。
随机森林算法实现
本文我们使用numpy来手动实现一个随机森林算法。随机森林算法本身是实现思路我们是非常清晰的,但其原始构建需要大量搭建决策树的工作,比如定义树节点、构建基本决策树、在基本决策树基础上构建分类树和回归树等。这些笔者在前述章节均已实现过,这里不再重复。
在此基础上,随机森林算法的构建主要包括随机选取样本、随机选取特征、构造森林并拟合其中的每棵树、基于每棵树的预测结果给出随机森林的预测结果。
导入相关模块并生成模拟数据集。
import numpy as np# 该模块为自定义模块,封装了构建决策树的基本方法from ClassificationTree import *from sklearn.datasets import make_classificationfrom sklearn.model_selection import train_test_split# 树的棵数n_estimators = 10# 列抽样最大特征数max_features = 15# 生成模拟二分类数据集X, y = make_classification(n_samples=1000, n_features=20, n_redundant=0, n_informative=2, random_state=1, n_clusters_per_class=1)rng = np.random.RandomState(2)X += 2 * rng.uniform(size=X.shape)# 划分数据集X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)print(X_train.shape, y_train.shape, X_test.shape, y_test.shape)
定义第一个随机性,行抽样选取样本:
# 自助抽样选择训练数据子集def bootstrap_sampling(X, y): X_y = np.concatenate([X, y.reshape(-1,1)], axis=1) np.random.shuffle(X_y) n_samples = X.shape[0] sampling_subsets = []
for _ in range(n_estimators): # 第一个随机性,行抽样 idx1 = np.random.choice(n_samples, n_samples, replace=True) bootstrap_Xy = X_y[idx1, :] bootstrap_X = bootstrap_Xy[:, :-1] bootstrap_y = bootstrap_Xy[:, -1] sampling_subsets.append([bootstrap_X, bootstrap_y]) return sampling_subsets
然后基于分类树构建随机森林:
trees = []# 基于决策树构建森林for _ in range(n_estimators): tree = ClassificationTree(min_samples_split=2, min_impurity=0, max_depth=3) trees.append(tree)
定义训练函数,对随机森林中每棵树进行拟合。
# 随机森林训练def fit(X, y): # 对森林中每棵树训练一个双随机抽样子集 n_features = X.shape[1] sub_sets = bootstrap_sampling(X, y) for i in range(n_estimators): sub_X, sub_y = sub_sets[i] # 第二个随机性,列抽样 idx2 = np.random.choice(n_features, max_features, replace=True) sub_X = sub_X[:, idx2] trees[i].fit(sub_X, sub_y) trees[i].feature_indices = idx2 print('The {}th tree is trained done...'.format(i+1))
我们将上述过程进行封装,分别定义自助抽样方法、随机森林训练方法和预测方法。完整代码如下:
class RandomForest(): def __init__(self, n_estimators=100, min_samples_split=2, min_gain=0, max_depth=float("inf"), max_features=None): # 树的棵树 self.n_estimators = n_estimators # 树最小分裂样本数 self.min_samples_split = min_samples_split # 最小增益 self.min_gain = min_gain # 树最大深度 self.max_depth = max_depth # 所使用最大特征数 self.max_features = max_features
self.trees = [] # 基于决策树构建森林 for _ in range(self.n_estimators): tree = ClassificationTree(min_samples_split=self.min_samples_split, min_impurity=self.min_gain, max_depth=self.max_depth) self.trees.append(tree) # 自助抽样 def bootstrap_sampling(self, X, y): X_y = np.concatenate([X, y.reshape(-1,1)], axis=1) np.random.shuffle(X_y) n_samples = X.shape[0] sampling_subsets = []
for _ in range(self.n_estimators): # 第一个随机性,行抽样 idx1 = np.random.choice(n_samples, n_samples, replace=True) bootstrap_Xy = X_y[idx1, :] bootstrap_X = bootstrap_Xy[:, :-1] bootstrap_y = bootstrap_Xy[:, -1] sampling_subsets.append([bootstrap_X, bootstrap_y]) return sampling_subsets # 随机森林训练 def fit(self, X, y): # 对森林中每棵树训练一个双随机抽样子集 sub_sets = self.bootstrap_sampling(X, y) n_features = X.shape[1] # 设置max_feature if self.max_features == None: self.max_features = int(np.sqrt(n_features)) for i in range(self.n_estimators): # 第二个随机性,列抽样 sub_X, sub_y = sub_sets[i] idx2 = np.random.choice(n_features, self.max_features, replace=True) sub_X = sub_X[:, idx2] self.trees[i].fit(sub_X, sub_y) # 保存每次列抽样的列索引,方便预测时每棵树调用 self.trees[i].feature_indices = idx2 print('The {}th tree is trained done...'.format(i+1)) # 随机森林预测 def predict(self, X): y_preds = [] for i in range(self.n_estimators): idx = self.trees[i].feature_indices sub_X = X[:, idx] y_pred = self.trees[i].predict(sub_X) y_preds.append(y_pred) y_preds = np.array(y_preds).T res = [] for j in y_preds: res.append(np.bincount(j.astype('int')).argmax()) return res
基于上述随机森林算法封装来对模拟数据集进行训练并验证:
rf = RandomForest(n_estimators=10, max_features=15)rf.fit(X_train, y_train)y_pred = rf.predict(X_test)print(accuracy_score(y_test, y_pred))
0.78
sklearn也为我们提供了随机森林算法的api,我们也尝试一下与numpy手写的进行效果对比:
from sklearn.ensemble import RandomForestClassifierclf = RandomForestClassifier(max_depth=3, random_state=0)clf.fit(X_train, y_train)y_pred = clf.predict(X_test)print(accuracy_score(y_test, y_pred))
0.8
可以看到sklearn的预测结果要略高于我们手写的结果。当然我们的训练结果还可以经过调参进一步提高。随机森林调参可参考sklearn官方文档,这里略过。
参考资料:
机器学习 周志华
- 用一个命令使用 Docker Compose 安装Mesos
- C语言/C加加新手入门学习经验资料分享,基础知识大汇总!
- 云数据服务蜂拥而至...好难选呀
- ASP.NET MVC的Razor引擎:View编译原理
- ASP.NET Core中的依赖注入(4): 构造函数的选择与服务生命周期管理
- 通过Knockout.js + ASP.NET Web API构建一个简单的CRUD应用
- 自动驾驶车辆在2017年的七大光辉历史时刻
- 简单的实现Javascript的MVC
- 谷歌再出黑科技!人工智能模拟的人声和真人几乎难以分辨
- 【Scikit-Learn 中文文档】高斯混合模型 - 无监督学习 - 用户指南 | ApacheCN
- “E+单拼”组合域名eqi.com小六位结拍
- ASP.NET Core中的依赖注入(3): 服务的注册与提供
- 【Scikit-Learn 中文文档】使用 scikit-learn 介绍机器学习 | ApacheCN
- 哈萨比斯:2018年AI最大突破在生物或化学领域
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- python学生管理系统的实现
- 详解Android使用Handler造成内存泄露的分析及解决方法
- android使用flutter的ListView实现滚动列表的示例代码
- 实验7 3D机器人
- EasyValidate优雅地校验提交数据完整性
- 实验8 OpenGL太阳系动画
- Django实现列表页商品数据返回教程
- 实验9 OpenGL光照
- Python实现多线程下载脚本的示例代码
- Android自定义控件仿iOS滑块SwitchButton
- kotlin gson反序列化默认值失效深入讲解
- Android使用Volley实现上传文件功能
- 工作中使用jasmine遇到的一个html element和Component绑定属性失去同步的问题
- Android使用Volley框架定制PostUploadRequest上传文件
- Android实现横向滑动卡片效果