1.背景介绍
从目前来看深度学习大多建立在多层的神经网络基础上,也即一些参数化的多层可微的非线性模块,这样就可以通过后向传播去训练,Zhi-Hua Zhou和Ji Feng在Deep Forest论文中基于不可微的模块建立深度模块,这就是gcForest。
传统的深度学习有一定的弊端:
● 超参数个数较多,训练前需要大量初始化,主要靠经验调整,使得DNN更像一门艺术而非科学;● DNN的训练要求大量的训练数据,数据的标注成本太高;
● DNN是一个黑盒子,训练的结果很难去解释描述,并且学习行为很难用理论去分析解释;
● DNN在训练之前就得确定具体的网络结构,模型的复杂度也是提前设定好的,虽然有一些dropout等的技术。
但是有一点是我们相信的,在处理更复杂的学习问题时,算法的学习模块应该要变的更深,现在的深层网络结构依旧依赖于神经网络(我们这里把像CNN,RNN的基本结构单元也归结为神经网络单元), 周志华团队考虑深度学习的结构可不可以用其他不可微的模块组成:
Can deep learning be realized with non-differentiable modules?
这个问题会使我们想到一些其他问题:
● 深度学习是不是就等同于DNN(深度模型是不是只能通过可微的函数结构去构建?)?● 是不是可以不通过后向传播训练深度模型?
● 是不是使得在一些RandomForest,XGBoost,lightGBM成功的学习任务上,深度模型能够战胜它们?
下面我们就来看一下,周志华团队是如何从传统的机器学习和深度学习中获得灵感,构建gcForest的。
2.gcForest模型灵感来源
我们本节首先介绍gcForest模型构建的灵感来源,论文提到了两个方面,一个是在DNN中,一个是在集成学习(Ensemble Learning)。
(1) DNN中的灵感来源
深度学习在一些学习任务上之所以成功,关键在于特征的表示学习上(representation learning,不知道翻译的对不对 :)),如下图所示,随着一层一层的处理(layer-by-layer processing),马的图像数据的特征被高度抽象出来用于动物分类任务。
也就是说DNN在做分类任务时最终使用的是生成抽象出来的关键高层特征,而决策树,Boosting等传统机器学习模型只是使用初始的特征表示去学习,换句话说并没有做模型内部的特征变换(in_model feature transformation)。
除此之外,DNN可以设计成任意的复杂度,而决策树和Boosting模型的复杂度是有限的,充足的模型复杂度虽然不是DNN必要的取胜法宝,但是也是非常重要的。
总结在设计gcForest来自DNN的灵感:
● layer-by-layer processing● in_model feature transformation
● suffcient model complexity
(2) 集成学习中的灵感来源
众所周知,集成学习通常比起单模型往往会有一个比较不错的表现,在构建一个好的集成模型时,要同时考虑子模型预测的准确度和子模型之间的多样性,尽量做到模型之间的互补,即理论上来说,子模型的准确度越高且子模型之间差异性越大(多样性),集成的效果越好。
但是,what is diversity? 怎样去度量模型之间的多样性?
在实践中,多样性增强的基本策略是在训练过程中引入基于启发式的随机性。粗略的说有4个机制:
● 数据样本机制: 它通过产生不同的数据样本来训练子模型;● 输入特征机制:它通过选取不同特征子空间来训练子模型;
● 学习参数机制:他通过设定不同参数来使得子模型具有多样性;
● 输出表示机制:通过不同的输出表示增加子模型的多样性;
下一节通过本节的一些灵感构建gcForest模型。
3.gcForest模型
(1) 级联森林结构
基于上一节中的一些灵感,gcForest被构造成如下图所示的级联结构
解释:
● 每一个Level包含若干个集成学习的分类器(这里是决策树森林,你可以换成XGBoost,lightGBM等),这是一种集成中的集成的结构;● 为了体现多样性,黑色和蓝色的Forest代表了若干不同的集成学习器,为了说明这里用了两种,黑色的是完全随机森林:由500棵决策树组成,每棵树随机选取一个特征作为分裂树的分裂节点,然后一直生成长,直到每个叶节点细分到只有一个类或者不多于10个样本,蓝色的表示普通的随机森林:由500棵决策树组成,每棵树通过随机选取sqrt(d)(d表示输入特征的维度)个候选特征然后通过gini系数筛选分裂节点;
● gcForest采用了DNN中的layer-by-layer结构,从前一层输入的数据和输出结果数据做concat作为下一层的输入;
● 为了防止过拟合的情况出现,每个森林的训练都使用了k-折交叉验证,也即每一个训练样本在Forest中都被使用k-1次,产生k-1个类别列表,取平均作为下一个Level级联的输入的一部分;
● 当级联扩展到新的Level后,之前所有级联结构的表现通过验证集去评估,如果评估结果没有太大的改变或提升则训练过程结束,因此级联结构的Level的个数被训练过程决定;
● 这里要注注意的是,当我们想控制训练过程的loss或限制计算资源的时候,我们也可以使用训练误差,而不是交叉验证的误差同样能够用于控制级联的增长;
● 以上这一点就是gcForest和神经网络的区别,gcForest可以通过训练自适应的调整模型的复杂度,并且在恰当的时候停止增加我们的Level;
(2) 多粒度扫描
为了增强级联森林结构我们增加了多粒度扫描,这实际上来源于深度网络中强大的处理特征之间关系能力,多粒度扫描的结构如下图所示
其过程可描述如下:
先输入一个完整的p维样本(p是样本特征的维度),然后通过一个长度为k的采样窗口进行滑动采样,得到s=(p-k)/1+1个k维特征子样本向量(这个过程就类似于CNN中的卷积核的操作,这里假设窗口移动的步长为1);接着每个子样本都用于完全随机森林和普通随机森林的训练,并在每个森林都获得一个长度为c的概率向量(c是分类类别个数,上图中c=3),这样每个森林都产生一个s*c的表征向量,最后把每层的F个森林的结果拼接在一起,得到样本输出。
上图是一个滑动窗口的简单情形,下图我们展示了多滑动窗口的过程:
这里的过程和上图中的过程是一致的,这里要特别注意在每个Level特征维度的变化,并且上图中的结构可以称为级联中的级联,也即每个级联结构中有多个级联子结构。
除了这种结构,我们的多粒度扫描的结构还可以有其他的形式,可以入下图所示:
这也是一种多粒度扫描结构,被称为gcForest_conc结构,即在多粒度扫描后进行了特征的concatenate,然后再去做级联结构,从实验结果上来看,该结构要稍微逊色于第一种结构。
(3) 小结
以上就是gcForset的模型结构和构建过程,我们可以清晰的看到gcForest的主要超参数:
级联结构中的超参数:
● 级联的每层的森林数● 每个森林的决策树的数量
● 树停止生长的规则
多粒度扫描中的超参数:
● 多粒度扫描的森林数● 每个森林的决策树数
● 树的停止生长规则
● 滑动窗口的数量和大小
其优点如下:
● 计算开销小,单机就相当于DNN中使用GPU(如果你没有钱购买GPU,可以考虑使用gcForest)● 模型效果好(周老师的papar中实验对比了不同模型的效果,gcForest在传统的机器学习,图像,文本,语音上都表现不俗)
● 超参数少,模型对超参数不敏感,一套超参数可以用到不同的数据集
● 可以适用于不同大小的数据集,模型复杂度可自适用的伸缩
● 每个级联生成使用交叉验证,避免过拟合
● 相对于DNN,gcForest更容易进行理论分析
4.gcForest模型的Python实现
GitHub上有两个star比较多的gcForest项目,在参考文献中我们已经列出来了,下面我们就使用这两个gcForest的Python模块去尝试使用gcForest模型去解决一些问题,这里要说明的是其中参考文献3是官方提供(由gcForest的作者之一Ji Feng维护)的一个Python版本。目前gcForest并没有相对好用的R语言接口,我们在近期将会推出R语言的gcForest包,敬请期待。这里我们仅介绍参考文献4中的gcForest模块,如果感兴趣,可以自行研究官方提供的gcForest模块(官方模块,集成模型的选择除了随机森林外还可以选择其他的结构像XGBoost,ExtraTreesClassifierLogisticRegression,SGDClassifier等,因此建议使用该模块)。这里就介绍参考文献4中gcForest的模块使用。
Example1: iris数据集预测
from GCForest import gcForest
from sklearn.datasets import load_iris, load_digits
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import pandas as pd
import numpy as np
iris = load_iris()
X = iris.data
y = iris.target
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.33)
print(pd.DataFrame(X_tr).head())
# 注意里边的参数设置,详细的参数意义
# 可以参考源码中的参数解释
gcf = gcForest(shape_1X=4, window=2, tolerance=0.0)
gcf.fit(X_tr, y_tr)
# 类别预测
pred_X = gcf.predict(X_te)
print(pred_X)
pred_X_prob = gcf.predict_proba(X_te)
print(pred_X_prob)
# 模型评估
accuracy = accuracy_score(y_true=y_te, y_pred=pred_X)
print('gcForest accuracy : {}'.format(accuracy))
# 模型保存
from sklearn.externals import joblib
joblib.dump(gcf, 'my_iris_model.sav')
# 模型加载
# joblib.load('my_iris_model.sav')
Example2:Digits数据集预测
from GCForest import gcForest
from sklearn.datasets import load_iris, load_digits
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import pandas as pd
import numpy as np
digits = load_digits()
X = digits.data
y = digits.target
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.4)
# 注意参数设定
gcf = gcForest(shape_1X=[8,8], window=[4,6], tolerance=0.0, min_samples_mgs=10, min_samples_cascade=7)
gcf.fit(X_tr, y_tr)
# 类别预测
pred_X = gcf.predict(X_te)
print(pred_X)
# 模型评估
accuracy = accuracy_score(y_true=y_te, y_pred=pred_X)
print('gcForest accuracy : {}'.format(accuracy))
Example3:多粒度扫描和级联结构分离
因为多粒度扫描和级联结构是相对独立的模块,因此是可以分开的,如果代码中提供了参数y,则会自动去做训练,如果参数y是None,代码会回调最后训练的随机森林进行数据切片。
# 模型根据y_tr进行训练
gcf = gcForest(shape_1X=[8,8], window=5, min_samples_mgs=10, min_samples_cascade=7)
X_tr_mgs = gcf.mg_scanning(X_tr, y_tr)
# 回调最后训练的随机森林模型
X_te_mgs = gcf.mg_scanning(X_te)
# 使用多粒度扫描的输出作为级联结构的输入,这里要注意
# 级联结构不能直接返回预测,而是最后一层级联Level的结果
# 因此,需要求平均并且取做大作为预测值
gcf = gcForest(tolerance=0.0, min_samples_mgs=10, min_samples_cascade=7)
_ = gcf.cascade_forest(X_tr_mgs, y_tr)
pred_proba = gcf.cascade_forest(X_te_mgs)
tmp = np.mean(pred_proba, axis=0)
preds = np.argmax(tmp, axis=1)
accuracy_score(y_true=y_te, y_pred=preds)
Example4:去除多粒度扫描的级联结构
我们也可以使用最原始的不带多粒度扫描的级联结构进行预测
gcf = gcForest(tolerance=0.0, min_samples_cascade=20)
_ = gcf.cascade_forest(X_tr, y_tr)
pred_proba = gcf.cascade_forest(X_te)
tmp = np.mean(pred_proba, axis=0)
preds = np.argmax(tmp, axis=1)
accuracy_score(y_true=y_te, y_pred=preds)
感兴趣的小伙伴可以研究一下,官方提供(由gcForest的作者之一 Ji Feng维护的)的模块,其实都差不多,如果感兴趣,我们后期也会做相关的介绍。
5.小结
文中我们介绍了gcForest的模型算法和Python代码的实现,目前gcForest算法的官方Python包[参考文献3]并未托管在Pypi,但v1.1.1支持Python3.5,也有大牛实现基于Python3.x的gcForest version0.1.6版本(上一节中已经演示),但其功能要相对弱于参考文献3;如果你喜欢用R语言,我们会在近期推出R语言的gcForest的R包,供R语言用户调用。