如何在 TensorFlow 中训练提升树模型
文 / Chris Rawles、Natalia Ponomareva 和 Zhenyu Tan
## TL;DR:
# Train model.
est = tf.estimator.BoostedTreesClassifier(feature_columns, n_batches_per_layer)
est.train(train_input_fn)
# Per instance model interpretability:
pred_dict = est.experimental_predict_with_explanations(pred_input_fn)
# Global gain-based feature importances:
importances = est.experimental_feature_importances()
在处理结构化数据时,树集成方法(例如梯度提升决策树和随机森林)是使用最普遍,也是最有效的机器学习工具之一。树集成方法训练速度快,无需进行大量调整即可顺畅运行,并且无需使用大型数据集进行训练。
在 TensorFlow 中,可借助 tf.estimator API 使用梯度提升树。此外,tf.estimator API 还支持深度神经网络、宽度和深度模型等。在提升树中,支持使用预定义的均方误差损失函数执行回归 (BoostedTreesRegressor),以及使用交叉熵损失函数执行分类 (BoostedTreesClassifier)。用户还可以选择使用任何二次可微的自定义损失函数,但须将其提供给 BoostedTreesEstimator。
在本文中,我们会展示如何在 TensorFlow 中训练提升树模型,然后演示如何利用特征重要度解释已训练的模型,以及如何解释模型针对单个示例的预测。以下所有代码均可用于 TensorFlow 2.0(预制估算器可在 TensorFlow 2.0 中获得全面支持)。请点击 此处 1 和 此处 2,查看相关 TensorFlow 文档,了解本文中提及的所有代码。
注:此处 1 链接
https://github.com/tensorflow/docs/blob/master/site/en/r2/tutorials/estimators/boosted_trees.ipynb
此处 2 链接
https://github.com/tensorflow/docs/blob/master/site/en/r2/tutorials/estimators/boosted_trees_model_understanding.ipynb
生动呈现提升树模型的预测表面。梯度提升树是一种集成技术,可将多个(例如数十个、数百个,甚至数千个)树模型的预测结合到一起。增加树的数量通常会提升拟合质量。 如需试用完整示例,请点击 https://github.com/tensorflow/docs/blob/master/site/en/r2/tutorials/estimators/boosted_trees.ipynb
在 TensorFlow 中训练提升树模型
提升树估算器可为超出工作者内存空间的大型数据集提供支持,而且还可提供分布式训练。然而,出于演示目的,我们将在以下小型数据集上训练提升树模型:Titanic 数据集。这个(相当病态的)数据集的目标是根据乘客的特征(例如年龄、性别、船舱等级等),预测乘客在泰坦尼克号失事事件中的幸存率。
首先,我们导入必要的软件包并加载数据集。
import numpy as np
import pandas as pd
import tensorflow as tf
tf.enable_eager_execution()
# Load dataset.
dftrain = pd.read_csv('https://storage.googleapis.com/tf-datasets/titanic/train.csv')
dfeval = pd.read_csv('https://storage.googleapis.com/tf-datasets/titanic/eval.csv')
y_train = dftrain.pop('survived')
y_eval = dfeval.pop('survived')
然后,将 feature_columns 定义为与我们的 estimator 模型结合使用。特征列与所有 TensorFlow 估算器配合使用,旨在定义用于建模的特征。此外,特征列还提供一些特征工程功能,例如独热编码、归一化和桶化。CATEGORICAL_COLUMNS 字段的下方从分类列转换为独热编码列(指示器列):
fc = tf.feature_column
CATEGORICAL_COLUMNS = ['sex', 'n_siblings_spouses', 'parch', 'class', 'deck',
'embark_town', 'alone']
NUMERIC_COLUMNS = ['age', 'fare']
def one_hot_cat_column(feature_name, vocab):
return tf.feature_column.indicator_column(
tf.feature_column.categorical_column_with_vocabulary_list(feature_name,
vocab))
feature_columns = []
for feature_name in CATEGORICAL_COLUMNS:
# Need to one-hot encode categorical features.
vocabulary = dftrain[feature_name].unique()
feature_columns.append(one_hot_cat_column(feature_name, vocabulary))
for feature_name in NUMERIC_COLUMNS:
feature_columns.append(tf.feature_column.numeric_column(feature_name,
dtype=tf.float32))
您可以查看特征列生成的转换结果。例如,以下为在单一示例上使用 indicator_column 时的输出结果:
example = dict(dftrain.head(1))
class_fc = tf.feature_column.indicator_column(tf.feature_column.categorical_column_with_vocabulary_list('class', ('First', 'Second', 'Third')))
print('Feature value: "{}"'.format(example['class'].iloc[0]))
print('One-hot encoded: ', tf.keras.layers.DenseFeatures([class_fc])(example).numpy())
# Feature value: "Third"
# One-hot encoded: [[0. 0. 1.]]
接下来,您需要创建输入函数。这些函数会指定如何将数据读入我们的模型中,以进行训练和推理。您将在 tf.data API 中使用 from_tensor_slices 函数,以便直接从 Pandas 读入数据。该方法适合规模较小的内存数据集。对于较大的数据集,tf.data API 支持多种文件格式(包括 csv),以便您处理超出内存空间的数据集。
我们首先训练一个逻辑回归模型,以确定基准:
# Use entire batch since this is such a small dataset.
NUM_EXAMPLES = len(y_train)
def make_input_fn(X, y, n_epochs=None, shuffle=True):
def input_fn():
dataset = tf.data.Dataset.from_tensor_slices((dict(X), y))
if shuffle:
dataset = dataset.shuffle(NUM_EXAMPLES)
# For training, cycle thru dataset as many times as need (n_epochs=None).
dataset = dataset.repeat(n_epochs)
# In memory training doesn't use batching.
dataset = dataset.batch(NUM_EXAMPLES)
return dataset
return input_fn
# Training and evaluation input functions.
train_input_fn = make_input_fn(dftrain, y_train)
eval_input_fn = make_input_fn(dfeval, y_eval, shuffle=False, n_epochs=1)
然后训练一个包含上述相同过程的提升树模型:
linear_est = tf.estimator.LinearClassifier(feature_columns)
# Train model.
linear_est.train(train_input_fn, max_steps=100)
# Evaluation.
result = linear_est.evaluate(eval_input_fn)
模型理解
对许多最终用户而言,原因和方法的重要性通常不亚于预测。例如,最近出台的欧盟法规强调了用户的 “解释权”,规定对于会对用户产生重大影响的企业决策,用户应能得到相应的解释(资料来源 1)。此外,美国公平信用报告法要求机构披露 “所用模型中对消费者信用评分造成不利影响的所有关键因素,且这类关键因素总数不得超过四个”(资料来源 2)。
注:资料来源 1 链接
https://arxiv.org/pdf/1702.08608.pdf
资料来源 2 链接
https://consumercomplianceoutlook.org/2011/third-quarter/overview-of-the-credit-score/
模型可解释性还有助于机器学习 (ML) 从业者在模型开发阶段检测是否存在偏差。深入了解此类信息可以帮助 ML 从业者更好地调试和理解其模型。
模型可解释性通常分为两级:局部可解释性和全局可解释性。局部可解释性是指理解模型对单个示例的预测,而全局可解释性则是指理解整个模型。
可解释性技术通常因模型类型(例如,树方法、神经网络等)而异,而且会利用习得的参数。例如,基于增益的特征重要度是树方法专用的可解释性技术,而集成梯度技术则利用神经网络中的梯度。
与此相反的还有模型无关方法,例如模型无关的局部可解释性描述 (LIME)和 SHapley 加法解释 (SHAP)。LIME 的运作方法是为训练构建一个本地代理模型,以粗略估计底层黑盒模型的预测。SHAP 方法通过在以特征为条件的情况下,将每个特征归因于预期模型预测中的变化,进而将对策论与局部解释联系起来。
理解单个预测:定向特征贡献
我们已实施 Palczewska 等人,以及 Saabas 在解释随机森林项目中概述的局部特征贡献方法。您还可在用于 Scikit-learn 的 treeinterpreter 软件包中获得此方法。
简言之,此技术通过分析添加分割点时的预测变化,帮助人们理解模型如何对单个实例作出预测。从初始预测(通常称为偏差,一般定义为训练标签的平均值)开始,该技术遍历预测路径,并在分割特征后计算预测中的变化。对于每个分割点,我们将预测中的变化归因于分割点使用的特征。在所有分割点和树中,我们将这些归因相加,以指示每个特征的总贡献。
该方法会返回一个与每个特征相关联的数值。我们将这些值称为定向特征贡献 (DFC),用于区别评估特征影响所使用的其他方法,例如特征重要度(通常指全局特征重要度)。DFC 支持检查单个示例,并就模型 针对特定示例作出预测的原因提供数据分析。您可以使用此技术,创建如下图表:
Titanic 数据集中某个实例的 DFC。通俗来说,我们可以将此解释为 “adult_male 为 ‘真’ 对最终概率的 ‘贡献’ 约为 -0.11,而 Deck 为 B 对最终概率的贡献约为 +0.08 等等。”
DFC 的一个优点是可将每个特征的贡献相加,然后将总和计入实际预测。例如,如果模型中有五个特征,且在给定实例中,DFC 为
{sex_female: 0.2, age: 0.05, fare= -0.02, num_siblings_aboard=-0.1, fare: 0.09}
则预测概率为这些值的总和:0.22。
我们还可以在整个数据集中汇总 DFC,以深入了解整个模型,从而作出全局解释:
整个评估数据集中前几个 DFC 的平均绝对值
票价与贡献(LOWESS 拟合)。相比单个特征重要度指标,不同示例的贡献可以提供更精细的信息。一般来说,越高的票价特征会导致模型的预测越接近 1.0(提升幸存的可能性)
方法:TensorFlow 中的定向特征贡献
您可以在 提升树模型理解 笔记中找到以下所有代码。
注:提升树模型理解 链接
https://github.com/tensorflow/docs/blob/master/site/en/r2/tutorials/estimators/boosted_trees_model_understanding.ipynb
首先,您需要按照上述方法,使用 tf.estimator API 训练提升树估算器。
params = {
'n_trees': 50,
'max_depth': 3,
'n_batches_per_layer': 1,
# You must enable center_bias = True to get DFCs. This will force the model to
# make an initial prediction before using any features (e.g. use the mean of
# the training labels for regression or log odds for classification when
# using cross entropy loss).
'center_bias': True
}
est = tf.estimator.BoostedTreesClassifier(feature_columns, **params)
# Train model.
est.train(train_input_fn, max_steps=100)
# Evaluation.
results = est.evaluate(eval_input_fn)
完成模型训练后,我们可以使用 est.experimental_predict_with_explanations 检索模型解释。(请注意:由于我们可能会在删除实验前缀前修改此 API,于是将此函数命名为 experimental。)
您可以使用 Pandas 轻松查看 DFC:
# Make predictions.
pred_dicts = list(est.experimental_predict_with_explanations(eval_input_fn))
df_dfc = pd.DataFrame([pred['dfc'] for pred in pred_dicts])
# Plot results.
ID = 182
example = df_dfc.iloc[ID] # Choose ith example from evaluation set.
TOP_N = 8 # View top 8 features.
sorted_ix = example.abs().sort_values()[-TOP_N:].index
ax = example[sorted_ix].plot(kind='barh')
我们在 Colab 中加入了添加贡献分布的示例,以对比其余评估集,理解特定实例的 DFC:
单个示例的贡献(红色)。蓝色阴影部分展示特征贡献在整个验证集中的分布
我们还发现其他第三方模型无关可解释性方法可与 TensorFlow 搭配使用,例如 LIME 和 SHAP。请参阅下方的其他资源部分,获取更多相关链接。
模型级可解释性:基于增益和排列特征重要度
我们可以采用不同的方法,从模型层面理解提升树模型(即全局可解释性)。之前我们展示了如何在数据集中汇总 DFC,以进行全局解释。您也可以通过汇总其他局部解释值(例如通过 LIME 或 SHAP 生成的值)来作出全局解释(如上所述)。
下面我们将讨论其他两种技术,分别是基于增益的特征重要度和排列特征重要度。基于增益的特征重要度衡量的是分割特定特征时的损失变化,而排列特征重要度的计算方法是逐个重排每个特征并将模型性能的损失归因到重排的特征,从而根据评估集评估模型性能。排列特征重要度具有模型无关优势,但当潜在预测变量的衡量范围或分类数量不同时,这两种方法都不可靠(资料来源 https://bmcbioinformatics.biomedcentral.com/articles/10.1186/1471-2105-9-307)。
在 TensorFlow 的提升树估算器中,我们可以使用 est.experimental_feature_importances 检索基于增益的特征重要度。以下是完整示例及相应图表:
排列特征重要度的计算方法如下:
相关变量和其他考虑因素
当两个或多个特征相互关联时,许多模型解释工具会提供失真的特征影响视图。例如,如果您训练的集成树模型包含两个高度相关的特征,则这两个特征基于增益的特征重要度将低于只包含二者中任一特征时的重要度。
在 Titanic 数据集中,假设我们意外地以两个变量 class 和 pclass 的形式对某位乘客的船舱等级进行两次编码。在使用独热编码对这些分类特征进行编码,并对模型进行训练后,我们观察到三等舱的某位乘客拥有预测能力 — 我们观察到这种现象出现两次。
当我们删除一个特征 (pclass),并重新检查特征重要度后,三等舱乘客的重要度大约翻了一倍。
在这个案例中,两个特征完全相关,但即使特征仅部分相关,也会出现相同的现象(只不过发生的程度较小)。
因此,在使用我们上述讨论的技术时,建议移除高度相关的特征。这不仅有助于实现可解释性,还会加快模型训练速度。此外,维护较少的特征也比维护大量特征更轻松。
最后,我们注意到 Strobl 等人介绍的另一种名为条件变量重要度的技术。该技术利用特征排列,有助于在存在相关变量时,给出更真实的特征影响估计。如需了解更多信息,请查看 此论文(https://bmcbioinformatics.biomedcentral.com/articles/10.1186/1471-2105-9-307)。
结论
我们可以借助 tf.estimator API,在 TensorFlow 中使用梯度提升决策树,帮助用户快速试用不同的机器学习模式。对于梯度提升决策树,TensorFlow 提供局部模型可解释性(使用 Palczewska 等人,以及 Saabas(解释随机森林)列出的方法,通过 experimental_predict_with_explanations 实现针对每个实例的可解释性)和全局可解释性(基于增益和排列特征重要度)。这些方法可以帮助从业者更好地理解其模型。
TensorFlow 提升树的发布离不开许多人的努力,包括但不限于:Soroush Radpour、Younghee Kwon、Mustafa Ispir、Salem Haykal 和 Yan Facai。
其他资源
可与 TensorFlow 搭配使用的其他模型可解释性方法
LIME
(https://github.com/marcotcr/lime)
SHAP
(https://github.com/slundberg/shap)
集成梯度(仅适用于神经网络)
(https://github.com/ankurtaly/Integrated-Gradients)
SmoothGrad(仅适用于神经网络)
(https://github.com/pair-code/saliency)
TensorFlow 中的格子方法
(https://github.com/tensorflow/lattice)
排列特征重要度(Brieman,2001 年 )
(https://www.stat.berkeley.edu/~breiman/randomforest2001.pdf)
更多 AI 相关阅读: