EBM 内部机制 - 回归#
这是描述EBM内部结构及如何进行预测的三部分系列的第一部分。要查看第二部分,请点击这里。要查看第三部分,请点击这里。
在第一部分中,我们将介绍最简单的有用EBM:一个没有交互作用、缺失值或其他复杂性的回归模型。
本质上,EBMs是广义加性模型,其中来自各个特征和交互的分数贡献被加在一起以进行预测。每个单独的分数贡献是使用查找表确定的。在进行查找之前,我们首先需要将连续特征离散化并为分类特征分配分箱索引。
回归是EBM模型的最简单形式,因为最终的总和是实际的预测,不需要逆链接函数。
# boilerplate
from interpret import show
from interpret.glassbox import ExplainableBoostingRegressor
import numpy as np
from interpret import set_visualize_provider
from interpret.provider import InlineProvider
set_visualize_provider(InlineProvider())
# make a dataset composed of a nominal categorical, and a continuous feature
X = [["Peru", 7.0], ["Fiji", 8.0], ["Peru", 9.0]]
y = [450.0, 550.0, 350.0]
# Fit a regression EBM without interactions
# Eliminate the validation set to handle the small dataset
ebm = ExplainableBoostingRegressor(
interactions=0,
validation_size=0, outer_bags=1, min_samples_leaf=1, min_hessian=1e-9)
ebm.fit(X, y)
show(ebm.explain_global())
让我们来看看ExplainableBoostingRegressor的一些最重要的属性。
print(ebm.feature_types_in_)
['nominal', 'continuous']
由于我们没有向ExplainableBoostingRegressor的__init__函数传递feature_types参数,因此分配了一些合理的特征类型猜测。这些猜测被记录在ebm.feature_types_in_中。我们支持以下基本特征类型:‘continuous’(连续型)、‘nominal’(名义型)和‘ordinal’(有序型)。出于评估目的,‘nominal’和‘ordinal’可以视为相同,因为它们都是分类变量,并且在模型中以相同的方式表示。‘nominal’和‘ordinal’在训练过程中处理方式不同,但我们在这里只关注预测。
print(ebm.bins_)
[[{'Fiji': 1, 'Peru': 2}], [array([7.5, 8.5])]]
ebm.bins_ 定义了如何对分类(‘名义’和‘序数’)以及‘连续’特征进行分箱。
对于分类特征,我们使用一个字典将类别字符串映射到分箱索引。在这个例子中,“Fiji”被分配到分箱#1,“Peru”被分配到分箱#2。
连续特征分箱是通过一系列切点定义的,这些切点将连续范围划分为多个区域。在这个例子中,数据集在连续特征中有3个唯一值:7.0、8.0和9.0。为了将这3个值分成3个箱,我们需要2个切点。EBM选择了7.5和8.5作为切点,但它也可以选择7.0到8.0之间和8.0到9.0之间的任何切点值。
在进行涉及连续特征的预测时,我们接收到的特征值可以在负无穷到正无穷之间的连续范围内。因此,在这个例子中,我们的两个切点定义了3个分箱区域:分箱#1是[-inf, 7.5),分箱#2是[7.5, 8.5),分箱#3是[8.5, +inf]。
如果有任何特征值等于分箱切割值,它们将被放入上方的分箱选择中。要将连续特征转换为分箱,我们可以使用numpy.digitize函数,并进行一些微调,我们将在下面看到。
EBMs 还包括两个特殊的区间:缺失值区间和未见值区间。缺失值区间用于处理任何缺失的特征值,如 NaN 或 'None'。未见值区间用于在预测时遇到训练集中未出现的分类值。例如,如果我们的测试数据集中有分类值“Vietnam”或“Brazil”,那么在这个例子中我们将使用未见值区间,因为这些国家没有出现在训练集中。
缺失的箱子总是位于第0个索引,而未见过的箱子总是位于最后一个索引。
print(ebm.term_scores_[0])
[ 0. 84.55036163 -42.27518082 0. ]
ebm.term_scores_ 包含每个加法项的查找表。ebm.term_scores_[0] 是本例中第一个特征的查找表,该特征是包含国家字符串的分类特征。
由于第一个特征是分类的,我们使用来自ebm.bins_[0]的字典,即{'Fiji': 1, 'Peru': 2}来查找当这些字符串作为特征值出现时使用哪个分箱。如果我们接收到一个NaN的特征值,那么我们将使用索引0处的分数值。如果特征值是“Fiji”,我们将使用索引1处的分数值。如果特征值是“Peru”,我们将使用索引2处的分数值。如果特征值是其他任何值,我们将使用索引3处的分数值。
另一个需要注意的是,存储在ebm.term_scores_中的值与全局解释图中显示的值相同(见上文)。这对于分类特征和连续特征以及交互作用都是成立的。这种完全的模型透明度使得EBMs成为一个玻璃盒模型。
print(ebm.term_scores_[1])
[ 0. 42.27518082 15.44963837 -57.72481918 0. ]
ebm.term_scores_[1] 是我们数据集中连续特征的查找表。第0个索引再次保留给缺失值,最后一个索引再次保留给未见过的值。在连续特征的上下文中,未见的bin用于任何无法转换为浮点数的内容,因此如果我们收到字符串“BAD_VALUE”而不是数字,那么我们将使用最后一个bin。如果您希望出现错误条件,未见的bin的得分值可以选择设置为NaN。
中间的3个剩余分数对应于3个分箱区域,在我们的例子中是:分箱#1 [-numpy.inf, 7.5),分箱#2 [7.5, 8.5),和分箱#3 [8.5, +numpy.inf]。
再次,ebm.term_scores_[1]中的分数与特征_0001的全局解释图中显示的值相匹配(见上文)。
print(ebm.intercept_)
450.0
ebm.intercept_ 通常应该非常接近基础分数。在这个例子中,截距确实非常接近三个 'y' 值的平均值:numpy.average([450, 550, 350]) == 450。
在进行预测时,我们从截距值开始,并添加每个查找表的分数。
示例代码
最后,这里有一些代码将上述考虑整合到一个函数中,该函数可以为简化场景进行预测。此代码不处理诸如交互、缺失值、未见值或分类等问题。
如果你需要一个可以在所有EBM场景中使用的完整函数,请参见第3部分中的多类示例,该示例除了处理多类问题外,还处理回归和二元分类以及其他所有细节。
sample_scores = []
for sample in X:
# start from the intercept for each sample
score = ebm.intercept_
print("intercept: " + str(score))
# we have 2 features, so add their score contributions
for feature_idx, feature_val in enumerate(sample):
bins = ebm.bins_[feature_idx][0]
if isinstance(bins, dict):
# categorical feature
bin_idx = bins[feature_val]
else:
# continuous feature. bins is an array of cut points
# add 1 because the 0th bin is reserved for 'missing'
bin_idx = np.digitize(feature_val, bins) + 1
local_score = ebm.term_scores_[feature_idx][bin_idx]
# local_score is also the local feature importance (see plot below)
print(ebm.feature_names_in_[feature_idx] + ": " + str(local_score))
score += local_score
sample_scores.append(score)
print()
print("PREDICTIONS:")
print(ebm.predict(X))
print(np.array(sample_scores))
intercept: 450.0
feature_0000: -42.275180816747664
feature_0001: 42.27518081674757
intercept: 450.0
feature_0000: 84.55036163349533
feature_0001: 15.449638366504644
intercept: 450.0
feature_0000: -42.275180816747664
feature_0001: -57.72481918325221
PREDICTIONS:
[450. 550. 350.]
[450. 550. 350.]
预测结果与ExplainableBoostingRegressor的predict函数的预测结果一致。在这个例子中,EBM几乎完全恢复了原始的'y'值[450, 550, 350]。这并不令人惊讶,因为这个模型为了说明目的而极度过拟合。
另一个值得注意的有趣事情是,我们从查找表中检索到的值,这些值被分配给变量‘local_score’,与EBM局部解释中显示的值完全相同。
show(ebm.explain_local(X, y), 0)