深度森林gcForest模型

Python实现gcForest模型

1.介绍

gcForest v1.1.1是gcForest的一个官方托管在GitHub上的版本,是由Ji Feng(Deep Forest的paper的作者之一)维护和开发,该版本支持Python3.5,且有类似于Scikit-Learn的API接口风格,在该项目中提供了一些调用例子,目前支持的基分类器有RandomForestClassifier,XGBClassifer,ExtraTreesClassifier,LogisticRegression,SGDClassifier如果采用XGBoost的基分类器还可以使用GPU,如果想增加其他基分类器,可以在模块中的lib/gcforest/estimators/__init__.py中添加,使用该模块需要依赖安装如下模块:

  • argparse
  • joblib
  • keras
  • psutil
  • scikit-learn>=0.18.1
  • scipy
  • simplejson
  • tensorflow
  • xgboost

2.API调用样例

这里先列出gcForest提供的API接口:

  • fit_tranform(X_train,y_train) 是gcForest模型最后一层每个估计器预测的概率concatenated的结果

  • fit_transform(X_train,y_train,X_test=x_test,y_test=y_test) 测试数据的准确率在训练的过程中也会被记录下来

  • set_keep_model_mem(False) 如果你的缓存不够,把该参数设置成False(默认为True),如果设置成False,你需要使用fit_transform(X_train,y_train,X_test=x_test,y_test=y_test)来评估你的模型

  • predict(X_test) # 模型预测

  • transform(X_test)

最简单的调用gcForest的方式如下:


# 导入必要的模块
from gcforest.gcforest import GCForest

# 初始化一个gcForest对象
gc = GCForest(config) # config是一个字典结构

# gcForest模型最后一层每个估计器预测的概率concatenated的结果
X_train_enc = gc.fit_transform(X_train,y_train)

# 测试集的预测
y_pred = gc.predict(X_test)

下面我们使用MNIST数据集来演示gcForest的使用及代码的详细说明:

# 导入必要的模块

import argparse # 命令行参数调用模块
import numpy as np 
import sys
from keras.datasets import mnist # MNIST数据集
import pickle
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
sys.path.insert(0, "lib")

from gcforest.gcforest import GCForest
from gcforest.utils.config_utils import load_json


def parse_args():
	'''
	解析终端命令行参数(model)
	'''
    parser = argparse.ArgumentParser()
    parser.add_argument("--model", dest="model", type=str, default=None, 
	help="gcfoest Net Model File")
    args = parser.parse_args()
    return args


def get_toy_config():
	'''
	生成级联结构的相关结构
	'''
    config = {}
    ca_config = {}
    ca_config["random_state"] = 0
    ca_config["max_layers"] = 100
    ca_config["early_stopping_rounds"] = 3
    ca_config["n_classes"] = 10
    ca_config["estimators"] = []
    ca_config["estimators"].append(
            {"n_folds": 5, "type": "XGBClassifier", "n_estimators": 10, 
		"max_depth": 5,"objective": "multi:softprob", "silent": 
		True, "nthread": -1, "learning_rate": 0.1} )
    ca_config["estimators"].append({"n_folds": 5, "type": "RandomForestClassifier", 
	"n_estimators": 10, "max_depth": None, "n_jobs": -1})
    ca_config["estimators"].append({"n_folds": 5, "type": "ExtraTreesClassifier",
	 "n_estimators": 10, "max_depth": None, "n_jobs": -1})
    ca_config["estimators"].append({"n_folds": 5, "type": "LogisticRegression"})
    config["cascade"] = ca_config
    return config

# get_toy_config()生成的结构,如下所示:

'''
{
"cascade": {
    "random_state": 0,
    "max_layers": 100,
    "early_stopping_rounds": 3,
    "n_classes": 10,
    "estimators": [
        {"n_folds":5,"type":"XGBClassifier","n_estimators":10,"max_depth":5,
		"objective":"multi:softprob", "silent":true, 
		"nthread":-1, "learning_rate":0.1},
        {"n_folds":5,"type":"RandomForestClassifier","n_estimators":10,
		"max_depth":null,"n_jobs":-1},
        {"n_folds":5,"type":"ExtraTreesClassifier","n_estimators":10,
		"max_depth":null,"n_jobs":-1},
        {"n_folds":5,"type":"LogisticRegression"}
    ]
}
}
'''

if __name__ == "__main__":
    args = parse_args()
    if args.model is None:
        config = get_toy_config()
    else:
        config = load_json(args.model)

    gc = GCForest(config)
    # 如果模型消耗太大内存,可以使用如下命令使得gcforest不保存在内存中
    # gc.set_keep_model_in_mem(False), 默认情况下是True.

    (X_train, y_train), (X_test, y_test) = mnist.load_data()
    # X_train, y_train = X_train[:2000], y_train[:2000]
    # np.newaxis相当于增加了一个维度
    X_train = X_train[:, np.newaxis, :, :]
    X_test = X_test[:, np.newaxis, :, :]


    X_train_enc = gc.fit_transform(X_train, y_train)
    # X_enc是gcForest模型最后一层每个估计器预测的概率concatenated的结果
    # X_enc.shape =
    #   (n_datas, n_estimators * n_classes): 如果是级联结构
    #   (n_datas, n_estimators * n_classes, dimX, dimY): 如果只有多粒度扫描结构

    # 可以在fit_transform方法中加入X_test,y_test,这样测试数据的准确率在训练的过程中
    # 也会被记录下来。
    # X_train_enc, X_test_enc = 
	gc.fit_transform(X_train, y_train, X_test=X_test, y_test=y_test)

    # 注意: 如果设置了gc.set_keep_model_in_mem(True),必须使用
    # gc.fit_transform(X_train, y_train, X_test=X_test, y_test=y_test)
    # 评估模型

    # 测试集预测与评估
    y_pred = gc.predict(X_test)
    acc = accuracy_score(y_test, y_pred)
    print("Test Accuracy of GcForest = {:.2f} %".format(acc * 100))

    # 可以使用gcForest得到的X_enc数据进行其他模型的训练比如xgboost/RF
    # 数据的concat
    X_test_enc = gc.transform(X_test)
    X_train_enc = X_train_enc.reshape((X_train_enc.shape[0], -1))
    X_test_enc = X_test_enc.reshape((X_test_enc.shape[0], -1))
    X_train_origin = X_train.reshape((X_train.shape[0], -1))
    X_test_origin = X_test.reshape((X_test.shape[0], -1))
    X_train_enc = np.hstack((X_train_origin, X_train_enc))
    X_test_enc = np.hstack((X_test_origin, X_test_enc))

    print("X_train_enc.shape={}, X_test_enc.shape={}".format(X_train_enc.shape,
	 X_test_enc.shape))

    # 训练一个RF
    clf = RandomForestClassifier(n_estimators=1000, max_depth=None, n_jobs=-1)
    clf.fit(X_train_enc, y_train)
    y_pred = clf.predict(X_test_enc)
    acc = accuracy_score(y_test, y_pred)
    print("Test Accuracy of Other classifier using 	gcforest's X_encode = {:.2f} %".format(acc * 100))

    # 模型写入pickle文件
    with open("test.pkl", "wb") as f:
        pickle.dump(gc, f, pickle.HIGHEST_PROTOCOL)

    # 加载训练的模型
    with open("test.pkl", "rb") as f:
        gc = pickle.load(f)
    y_pred = gc.predict(X_test)
    acc = accuracy_score(y_test, y_pred)
    print("Test Accuracy of GcForest (save and load) = {:.2f} %".format(acc * 100))

这里需要注意的是gcForest不但可以对传统的结构化的2维数据建模,还可以对非结构化的数据比如图像,序列化的文本数据,音频数据等进行建模,但要注意数据维度的设定:

  • 如果仅使用级联结构,X_train,X_test对于2-D数组其维度为(n_samples,n_features);3-D或4-D数组会自动reshape为2-D,例如MNIST数据(60000,28,28)会reshape为(60000,784),(60000,3,28,28)会reshape为(60000,2352)。

  • 如果使用多粒度扫描结构,X_train,X_test必须是4—D的数组,图像数据其维度是(n_samples,n_channels,n_height,n_width);序列数据其维度为(n_smaples,n_features,seq_len,1),例如对于IMDB数据,n_features为1,对于音频MFCC特征,其n_features可以为13,26等。

上述代码可以通过两种方式运行:

  • 一种方式是通过json文件定义模型结构,比如级联森林结构,只需要写一个json文件如代码中显示的结构,然后通过命令行运行python examples/demo_mnist.py --model examples/demo_mnist-gc.json就可以完成训练;如果既使用多粒度扫面又使用级联结构,那么需要同时把多粒度扫描的结构定义出来。
  • 定义好的json可以通过模块中的load_json()方法加载,然后作为参数初始化模型,如下:
config = load_json(your_json_file)
gc = GCForest(config) 
  • 另一种方式是直接通过Python代码定义模型结构,实际上模型结构就是一个字典数据结构,即是上述代码中的get_toy_config()方法。

3.小结

通过gcForest v1.1.1 Python模块,结合gcForest模型的结构,演示了如何使用gcForest模块构建Deep Forest模型。gcForest不论是处理传统的机器学习任务还是非结构化数据的挖掘都有不错的表现。关于gcForest模块的详细介绍和源码,读者可以参考参考文献中所列的项目地址进行详细的学习。

4.参考文献

[1].https://github.com/kingfengji/gcForest

 

 

1. 背景介绍

从目前来看深度学习大多建立在多层的神经网络基础上,即一些参数化的多层可微的非线性模块,这样就可以通过后向传播去训练,Zhi-Hua Zhou 和 Ji Feng 在 Deep Forest [1,2] 论文中基于不可微的模块建立深度模块,这就是 gcForest。

传统的深度学习有一定的弊端:

  • 超参数个数较多,训练前需要大量初始化,主要靠经验调整,使得 DNN 更像一门艺术而非科学(有人说深度学习就是在调参);
  • DNN 的训练要求大量的训练数据,数据的标注成本太高;
  • DNN 是一个黑盒子,训练的结果很难去解释描述,并且学习行为很难用理论去分析解释;
  • DNN 在训练之前就得确定具体的网络结构(虽然 dropout 等的技术可以增加网络结构的一些不确定性),模型的复杂度也是提前设定好的。

但是有一点是我们相信的,在处理更复杂的学习问题时,算法的学习模块应该要变的更深(论文 The Power of Depth for Feedforward Neural Networks 从理论上已经证明了加深网络比加宽网络更有效),现在的深层网络结构依旧依赖于神经网络(我们这里把像 CNN,RNN 的基本结构单元也归结为神经网络单元), 周志华团队考虑深度学习的结构可不可以用其他不可微的模块组成:

Can deep learning be realized with non-differentiable modules?

这个问题会使我们想到一些其他问题:

  • 深度学习是否就等同于 DNN(深度模型是不是只能通过可微的函数结构去构建)?
  • 是否可以不通过后向传播训练深度模型?
  • 在一些 RandomForest,XGBoost,lightGBM,catBoost 成功的学习任务上, 深度模型是否也能够战胜它们?

下面我们就来看一下,如何从传统的机器学习和深度学习中获得灵感,构建 gcForest Model。

2.gcForest 模型灵感来源

我们本节首先介绍 gcForest 模型构建的灵感来源,论文提到了两个方面,一个是在 DNN 中,一个是在集成学习(Ensemble Learning)中。

2.1 DNN 中的灵感来源

深度学习在一些学习任务上之所以成功,关键在于特征的表示学习上(representation learning),如下图所示,随着一层一层的处理(layer-by-layer processing),马的图像数据的特征被高度抽象出来用于动物分类任务。

pic1

也就是说 DNN 在做分类任务时最终使用的是生成抽象出来的关键高层特征,而决策树,Boosting,SVM 等传统机器学习模型只是使用初始的特征表示去学习,换句话说并没有做模型内部的特征变换(in-model feature transformation)。

除此之外,DNN 可以设计成任意的复杂度,而决策树和 Boosting 模型的复杂度是有限的,充足的模型复杂度虽然不是 DNN 必要的取胜法宝,但是也是非常重要的。

总结在设计 gcForest 来自 DNN 的灵感:

  • layer-by-layer processing
  • in-model feature transformation
  • suffcient model complexity

2.2 集成学习中的灵感来源

众所周知,集成学习通常比起单模型往往会有一个比较不错的表现,在构建一个好的集成模型时,要同时考虑子模型预测的准确度和子模型之间的多样性,尽量做到模型之间的互补,即理论上来说,子模型的准确度越高且子模型之间差异性越大(diversity),集成的效果越好。

但是,what is diversity? 怎样去度量模型之间的多样性?

在实践中,增强多样性的基本策略是在训练过程中引入基于启发式的随机性。粗略地说有 4 个机制:

  • 数据样本机制:通过产生不同的数据样本来训练子模型;
  • 输入特征机制:通过选取不同特征子空间来训练子模型;
  • 学习参数机制:通过设定不同参数来使得子模型具有多样性;
  • 输出表示机制:通过不同的输出表示增加子模型的多样性;

下一节通过本节的一些灵感构建 gcForest 模型。

3.gcForest 模型

3.1 级联森林结构

基于上一节中的一些灵感,gcForest 被构造成如下图所示的级联结构

pic2

解释:

  • 每一个 Level 包含若干个集成学习的分类器(这里是决策树森林,你可以换成 XGBoost,lightGBM,catBoost 等),这是一种集成中的集成的结构;
  • 为了体现多样性,黑色和蓝色的 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;

3.2 多粒度扫描

为了增强级联森林结构我们增加了多粒度扫描,这实际上来源于深度网络中强大的处理特征之间关系能力,多粒度扫描的结构如下图所示

pic3

其过程可描述如下:

  • 先输入一个完整的 p 维样本(p 是样本特征的维度),然后通过一个长度为 k 的采样窗口进行滑动采样,得到 s=(p-k)+1 个(这个过程就类似于 CNN 中的卷积核的滑动,这里假设窗口移动的步长为 1)k 维特征子样本向量;
  • 接着每个子样本都用于完全随机森林和普通随机森林的训练,并在每个森林都获得一个长度为 c 的概率向量(c 是分类类别个数,上图中 c=3),这样每个森林都产生一个 s*c 的表征向量,最后把每层的 F 个森林的结果拼接在一起,得到样本输出。

上图是一个滑动窗口的简单情形,下图我们展示了多滑动窗口的过程:

pic4

这里的过程和上图中的过程是一致的,这里要特别注意在每个 Level 特征维度的变化,并且上图中的结构可以称为级联中的级联,也即每个级联结构中有多个级联子结构。

除了这种结构,我们的多粒度扫描的结构还可以有其他的形式,可以入下图所示:

pic5

这也是一种多粒度扫描结构,被称为 gcForest_conc 结构,即在多粒度扫描后进行了特征的 concatenate, 然后再去做级联结构,从实验结果上来看,该结构要稍微逊色于第一种结构。

3.3 小结

以上就是 gcForset 的模型结构和构建过程,我们可以清晰的看到 gcForest 的主要超参数:

级联结构中的超参数:

  • 级联的每层的森林数
  • 每个森林的决策树的数量
  • 树停止生长的规则

多粒度扫描中的超参数:

  • 多粒度扫描的森林数
  • 每个森林的决策树数
  • 树的停止生长规则
  • 滑动窗口的数量和大小

其优点如下:

  • 计算开销小,单机就相当于 DNN 中使用 GPU(如果你没有钱购买 GPU, 可以考虑使用 gcForest)
  • 模型效果好(论文中 [1,2] 实验对比了不同模型的效果,gcForest 在传统的机器学习,图像,文本,语音上都表现不俗)
  • 超参数少,模型对超参数不敏感,一套超参数可以应用到不同的数据集
  • 可以适用于不同大小的数据集,模型复杂度可自适用的伸缩
  • 每个级联生成使用交叉验证,避免过拟合
  • 相对于 DNN,gcForest 更容易进行理论分析

但 Zhi-Hua Zhou 和 Ji Feng 在 Deep Forest [1,2] 论文中 CIFAR-10 数据集的训练上 gcForest 在简单的结构设定下显然逊色于 AlexNet 和 ResNet(可以通过增加网络结构的复杂度和增强特征表示来优化 gcForest),未来仍需要在特征表示和内存消耗上做进一步的优化。

4.gcForest 模型的 Python 实现

GitHub 上有两个 star 比较多的 gcForest 项目,在参考文献中已经列出,下面我们就使用这两个 gcForest 的 Python 模块去尝试使用 gcForest 模型去解决一些问题,这里要说明的是其中参考文献 [3] 是官方提供(由 gcForest 的作者之一 Ji Feng 维护)的一个 Python 版本。目前 gcForest 并没有相对好用的 R 语言接口,我们已经开发了基于参考文献 [4] 的 R 接口包(目前已托管在 GitHub 和 CRAN)。这里我们仅介绍参考文献 [4] 中的 gcForest 模块,如果感兴趣,可以自行研究官方提供的 gcForest 模块(官方模块,集成模型的选择除了随机森林外还可以选择其他的结构像 XGBoost,ExtraTreesClassifier LogisticRegression,SGDClassifier 等,因此建议使用该模块)。这里仅介绍参考文献 [4] 中 gcForest 的模块使用。

Example1: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))

Example2: 多粒度扫描和级联结构分离

因为多粒度扫描和级联结构是相对独立的模块,因此是可以分开的,如果代码中提供了参数 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)

Example3: 去除多粒度扫描的级联结构

我们也可以使用最原始的不带多粒度扫描的级联结构进行预测

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 模型的 R 实现

我们已经基于 [4] 开发了其 R 版本的 gcForest 包(该包已托管在 GitHub 和 CRAN)[5,6],如果你习惯用 R 语言,可以尝试该包,这里演示 gcForest 的 Cheat Sheet 和一些官方的 Demo。

gcforest_r

Example1:iris 数据集预测

# install from CRAN
# install.packages('gcForest')

# install from github
# install.packages("devtools")
# devtools::install_github('DataXujing/gcForest_r')

# 在运行gcForest包前,请确保系统中已经安装了Python3.X及相应的模块包括:
# Numpy >= 1.12.0
# Scikit-learn >= 0.18.1

# 如果你不想手动安装可以通过req_py()函数,进行Python环境的包的监测和安装:
# library(gcForest)
# req_py()

library(gcForest)

sk <- reticulate::import('sklearn')
train_test_split <- sk$model_selection$train_test_split

data <- sk$datasets$load_iris
iris <- data()
X = iris$data
y = iris$target
data_split = train_test_split(X, y, test_size=0.33)

X_tr <- data_split[[1]]
X_te <- data_split[[2]]
y_tr <- data_split[[3]]
y_te <- data_split[[4]]

gcforest_m <- gcforest(shape_1X=4L, window=2L, tolerance=0.0)
gcforest_m$fit(X_tr,y_tr)
gcf_model <- model_save(gcforest_m,'../gcforest_model.model')

gcf <- model_load('../gcforest_model.model')
gcf$predict(X_te)

Example2: 多粒度扫描和级联结构分离

# mg-scanning
gcforest_m <- gcForest(shape_1X=c(8L,8L), window=5L, min_samples_mgs=10L, min_samples_cascade=7L)
X_tr_mgs <- gcforest_m$mg_scanning(X_tr, y_tr)

X_te_mgs <- gcforest_m$mg_scanning(X_te)

# cascade_forest
gcforest_m <- gcForest(tolerance=0.0, min_samples_mgs=10L, min_samples_cascade=7L)
cf <- gcforest_m$cascade_forest(X_tr_mgs, y_tr)

pred_proba <- gcforest_m$cascade_forest(X_te_mgs)
pred_proba <- reticulate::py_to_r(pred_proba)

# then do mean and max

详细的 gcForest R 包文档可以参考 https://cran.r-project.org/web/packages/gcForest/vignettes/gcForest-docs.html

6. 小结

文中我们介绍了 gcForest 的模型算法和 Python 及 R 的实现,目前 gcForest 算法的官方 Python 包 [3] 并未托管在 Pypi,但 v1.1.1 支持 Python3.5,也有开源实现基于 Python3.x 的 gcForest version0.1.6 版本 (上一节中已经演示)[4],但其功能要相对弱于 [3]; 如果你是 R 语言的忠实用户,那么可以使用我们开发的 R 版本的 gcForest 包 [5,6]

7. 参考文献

[1]. Z.-H. Zhou and J. Feng. Deep Forest: Towards an Alternative to Deep Neural Networks. In IJCAI-2017. (https://arxiv.org/abs/1702.08835v2 )

[2]. Z.-H. Zhou and J. Feng. Deep Forest. In IJCAI-2017. (https://arxiv.org/abs/1702.08835 )

[3].gcForest v1.1.1(https://github.com/kingfengji/gcForest)

[4].gcForest version 0.1.6(https://github.com/pylablanche/gcForest)

[5].gcForestr(https://github.com/DataXujing/gcForestr)

[6].CRAN gcForest(https://CRAN.R-project.org/package=gcForest)

posted @ 2020-12-14 09:41  bonelee  阅读(1708)  评论(0编辑  收藏  举报