哪些特征对你的分类模型有害?

哪些特征对你的分类模型有害?

如何计算分类器特征的误差贡献,以理解和改进模型。

特征重要性是解释机器学习模型最常用的工具。它是如此受欢迎,以至于许多数据科学家最终认为特征的重要性等于特征的好坏。

事实并非如此。

当一个特征很重要时,它仅仅意味着模型发现它在训练集中很有用。然而,这并没有说明该特性对新数据进行泛化的能力!

为了解释这一点,我们需要区分两个概念:

  • 预测贡献:一个变量在模型做出的预测中所占的权重。这是由模型在训练集上发现的模式决定的,这相当于功能重要性。
  • 误差贡献:一个变量在模型对保留数据集的错误中所占的权重,这是新数据上功能性能的更好的指标。

在本文中,我将解释在分类模型上计算这两个量背后的逻辑。我还将展示一个例子,在这个例子中,与使用预测贡献相比,使用错误贡献进行特征选择会产生更好的结果。如果你想了解更多关于分类的相关内容,可以阅读以下这些文章:
机器学习中的文本分类是什么?
Classification Algorithm 101: 一小时学会机器学习的分类算法
红酒数据集Case Study:一个分类问题
如何在分类算法中使用逻辑回归

00#目录表

  1. 从一个玩具例子开始
  2. 对于分类模型,我们应该使用哪个“错误”?
  3. 我们应该如何管理分类模型中的SHAP值?
  4. 计算“预测贡献”
  5. 计算“误差贡献”
  6. 一个真实的数据集示例
  7. 证明它是有效的:递归特征消除与“错误贡献”
  8. 结论

01#从一个玩具例子开始

假设我们有一个分类问题,我们想要预测一个人的收入是低于还是高于10万美元。再想象一下,我们已经有了这个模型的预测:

基本事实和模型做出的预测。[作者图片]

预测和误差贡献的计算主要基于模型对每个个体的误差和每个个体的SHAP值。因此,我们必须花点时间讨论两个相关的问题:

  • 对于分类模型,我们应该使用哪个“error”?
  • 我们应该如何管理分类模型中的SHAP值?

我将在下两段中讨论这些要点。

02#对于分类模型,我们应该使用哪个“error”?

我们的主要目标是计算模型的每个特征的误差贡献。所以最重要的问题是:我们如何定义分类模型中的“error”?

请注意,我们需要一个可以在个体层面计算的误差,然后可以在整个样本上进行汇总,以获得“平均误差”(就像我们对回归模型的绝对误差所做的那样)。

分类模型中最常见的损失函数是对数损失(又称交叉熵)。看看是否适合我们。

对数损失的数学公式如下:

对数损失(又名交叉熵)。[作者图片]

对数丢失似乎是我们的完美选择,因为:

  • 公式的外面是一个简单的平均值
  • 顾名思义,这是一种“损失”,意思是越低越好(就像“error”)

让我们试着理解为什么我们可以把这个叫做”error”,为了简单起见,让我们将焦点放在求和符号内的数量上(这样我们就可以去掉下标了)

个体对数损失[作者图片]

这是单个个体对全局对数损失的贡献,因此我们可以称之为“个体对数损失”。

这个公式可能看起来仍然很吓人,但如果我们考虑到在一个二元分类问题中:y只能是0或1,我们可能会得到一个更简单的版本:

个体对数损失,备选版本(相当于前一个)[作者图片]

在可视化图形的帮助下,现在很容易理解对数损失背后的主要思想。

个人对数损失可视化[作者图片]

预测的概率离真实值(无论是0还是1)越远,损失就越大。此外,如果预测与事实相差甚远(例如,p=.2 y=1或者p=.8且y=0),则损失大于比值。现在应该更清楚了,为什么对数损失实际上是一种错误。

我们准备将单个log-loss的公式转换为Python函数。

为了避免处理无穷值(当y_pred恰好为0或1时),我们将应用一个小技巧:如果y_pred距离0或1的距离小于ε,我们将分别将其设置为ε或1-ε。对于ε,我们将使用1^-15(这也是Scikit-learn使用的默认值)

def individual_log_loss(y_true, y_pred, eps=1e-15):
  """Compute log-loss for each individual of the sample."""
  
  y_pred = np.clip(y_pred, eps, 1 - eps)
  return - y_true * np.log(y_pred) - (1 - y_true) * np.log(1 - y_pred)

我们可以使用这个函数来计算数据集每一行的log-loss:

目标变量、模型预测以及由此产生的单个对数损失[作者图片]

正如你所看到的,个体1和个体2的对数损失(或误差)非常小,因为预测值都非常接近实际观察值,而个体0的对数损失(或误差)更大。

03#我们应该如何管理分类模型中的SHAP值?

最流行的模型是基于树的,如XGBoost、LightGBM和Catboost。在数据集上获取基于树的分类器的SHAP值非常简单:

from shap import TreeExplainer

shap_explainer = TreeExplainer(model)
shap_values = shap_explainer.shap_values(X)

例如,假设我们计算游戏问题的SHAP值,得到以下结果:

我们的模型预测的SHAP值[作者图片]

或许你不知道SHAP值是如何运作的,但是就本文的目的而言,知道以下内容就足够了:

  • 一个正的SHAP值意味着:该特征导致该个体的概率增加
  • 一个负的SHAP值意味着:该特征导致该个体的概率减小

因此,应该清楚的是,在给定个体的SHAP值和模型所做的预测之间存在直接关系。

然而,由于SHAP值可以假设任何实数(正或负),我们不能期望它等于该个体的预测概率(这是0到1之间的数字)。那么SHAP和预测概率之间的关系是什么?

由于SHAP值可以假设为任何负值或正值,因此我们需要一个函数将SHAP和转换为概率。这个函数必须有两个属性:

  • 它应该“squeeze”任何实值到区间[0,1]
  • 它应该严格递增(因为较高的SHAP总和必须始终与较高的预测相关联)

满足这些要求的函数是sigmoid函数。因此,模型对给定行的预测概率等于该个体的SHAP值之和的s型。

从SHAP值到预测概率[作者图片]

这是sigmoid函数的样子:

Sigmoid函数:形状和与预测概率的关系[作者图片]

那么,让我们把这个公式转换成一个Python函数:

def shap_sum2proba(shap_sum):
  """Compute sigmoid function of the Shap sum to get predicted probability."""
  
  return 1 / (1 + np.exp(-shap_sum))

我们也可以用图形显示,看看我们的个体在曲线上的位置:

Sigmoid函数:形状和与预测概率的关系[作者图片]

现在我们已经看到了应该使用哪种错误以及如何在分类问题中处理SHAP值,我们准备好看看如何计算预测和错误贡献。

04#计算“预测贡献”

正如我们所看到的,当SHAP值高度正(高度负)时,预测将比没有该特性时高(低)得多。换句话说,如果SHAP值的绝对值很大,那么该特征对最终预测的影响很大。

这就是为什么我们可以通过取该特征的绝对SHAP值的平均值来测量特征的预测贡献。

prediction_contribution = shap_values.abs().mean()

根据我们的游戏数据集,这是我们得到的:

预测的贡献[作者图片]

因此,就特征重要性而言,工作是主要特征,其次是国籍,然后是年龄。

但是误差贡献呢?

05#计算“误差贡献”

误差贡献背后的思想是计算如果我们删除一个给定的特征,模型的误差会是多少。

由于有了SHAP值,这个问题很容易回答:如果我们从SHAP的和中排除一个特征,我们就得到了模型在不知道该特征的情况下会做出的预测。但这还不够:正如我们所看到的,为了获得预测的概率,我们首先需要应用s型函数。

因此,我们首先需要从SHAP的和中减去特征的SHAP值,然后我们必须应用sigmoid函数。这里是模型在不知道特征的情况下预测的概率。

在Python中,我们可以一次完成所有的特性:

y_pred_wo_feature = shap_values.apply(lambda feature: shap_values.sum(axis=1) - feature).applymap(shap_sum2proba)

这是我们数据集的结果:

如果我们去掉相应的特征,我们将得到的预测[作者图片]

这意味着,如果我们没有特征”job”,模型对第一个个体的预测概率为71%,对第二个个体的预测概率为62%,对第三个个体的预测概率为73%。相反,如果我们没有特征”nationality”,预测将分别为13%,95%和0%。

正如你所看到的,预测的概率根据我们移除的特征变化很大。因此,产生的错误(单个对数损失)将非常不同。

我们可以使用上面定义的函数(individual_log_loss)来计算在没有相应特性的情况下单个对数损失是多少:

ind_log_loss_wo_feature = y_pred_wo_feature.apply(lambda feature: individual_log_loss(y_true=y_true, y_pred=feature))

结果如下:

如果我们删除相应的特征,我们将获得的单个对数损失[作者图片]

例如,如果我们取第一行,我们可以看到,如果没有特征”job”,log-loss将是1.24,但是如果没有特征”nationality”,log-loss只有0.13。由于我们希望尽量减少损失,在这种情况下,最好删除特征”nationality”。

现在,为了知道模型是否有feature会更好,我们可以计算完整模型的个体log-loss与没有feature得到的个体log-loss之差:

ind_log_loss = individual_log_loss(y_true=y_true, y_pred=y_pred)
ind_log_loss_diff = ind_log_loss_wo_feature.apply(lambda feature: ind_log_loss - feature)

结果如下:

模型误差与没有特征时的误差之差[作者图片]

如果这个数字是:

  • 负的,那么特征的存在会导致预测误差的减少,所以特征对观察结果很有效。
  • 正的,那么特征的存在会导致预测误差的增加,所以这个特征对观察结果是不利的。

最后,我们可以按列计算每个特征的误差贡献作为这些值的平均值:

Error_contribution = ind_log_loss_diff.mean()

结果如下:

误差的贡献[作者图片]

一般来说,如果这个数字是负的,那么这个特征具有积极的作用,相反,如果这个数字是正的,那么这个特征对模型是有害的,因为它倾向于增加模型的平均误差。

在这种情况下,我们可以看到,模型中特征job的存在导致个体log-loss平均减少-0.897,而特征“nationality ”的存在导致个体log-loss平均增加0.049。因此,虽然“nationality ”是第二重要的特征,但它并不能很好地发挥作用,因为它使平均个人对数损失率增加了0.049。

让我们尝试将这些概念应用到一个真实的数据集。

06#一个真实的数据集示例

此后,我将使用来自Pycaret (MIT许可下的Python库)的数据集。这个数据集被称为“Gold”,它包含了一些时间序列的金融数据。

样本数据集。这些特征都以百分比表示,因此-4.07表示回报率为-4.07%[作者图片]

特征分别为观察时刻(“T-22”、“T-14”、“T-7”、“T-1”)前22、14、7、1天的金融资产收益。以下是所有用作预测特征的金融资产的详尽列表:

可用资产列表。在-22、-14、-7和-1次观察每个资产[作者图片]

我们总共有120个功能。

目标是预测未来22天黄金的回报率是否会超过5%。换句话说,目标变量是二进制的:

  • 0,如果黄金未来22天的回报率小于5%;
  • 1,如果黄金未来22天的收益率大于5%。
Gold的柱状图提前22天返回。红色标记的阈值用于定义我们的目标变量:收益率是否大于5%[作者图片]

一旦我加载了数据集,下面是我执行的步骤:

  1. 随机分割完整数据集:33%的行在训练数据集中,另外33%在验证数据集中,剩下的33%在测试数据集中。
  2. 在训练数据集上训练LightGBM分类器。
  3. 使用在前一步训练的模型对训练、验证和测试数据集进行预测。
  4. 使用Python库“SHAP”计算训练、验证和测试数据集的SHAP值。
  5. 使用我们在前一段中看到的代码,计算每个数据集(训练、验证和测试)上每个特征的预测贡献和错误贡献。

在这一点上,我们有预测和误差贡献,所以我们最终可以比较它们:

预测贡献vs.错误贡献(在验证数据集上)[作者图片]

看这张图可以让我们对模型有更深入的了解。

最重要的特征是T-22天的美国债券ETF,然而,它并没有带来如此强烈的误差减少。最好的功能是T-22的3M Libor,因为它最大程度地减少了误差。

玉米的价格很有意思。T-1和T-22的回报都是最重要的特征,然而,其中一个(T-1)是过度拟合(因为它加剧了预测的误差)

总的来说,我们可以观察到,误差贡献较大的特征都相对于T-1或T-14(观测时刻前1或14天),而误差贡献较小的特征都相对于T-22(观测时刻前22天)。这似乎表明,最近的特征倾向于过度拟合,而涉及较早回报的特征倾向于更好地概括。

除了深入了解模型之外,考虑使用错误贡献来执行特征选择是很自然的。这就是我们下一段要做的。

07#证明它是有效的:递归特征消除与“错误贡献”

递归特征消除(RFE)是从数据集中逐步去除特征的过程,目的是获得更好的模型。

RFE的算法非常简单:

  1. 初始化特性列表
  2. 在训练集上训练一个模型,使用当前的特征列表作为预测器
  3. 从功能列表中删除“最糟糕”的功能
  4. 转到步骤2(直到特性列表为空)

在传统方法中,“最差”= 最不重要。然而,基于我们所看到的,我们可能会反对先删除最有害的功能更有意义。

换句话说,

  • 传统RFE:首先去除最无用的特征(最无用=验证集中预测贡献最小)
  • 我们的RFE:首先删除最有害的特征(最有害=验证集上最大的错误贡献)

为了验证这种直觉是否正确,我使用这两种方法进行了模拟。

这是验证集上的对数损失结果:

验证集上两种策略的对数损失[作者图片]

由于log-loss是一个“越低越好”的度量,我们可以看到我们版本的RFE在验证数据集上明显优于经典的RFE。

但是,你可能会怀疑查看验证集是不公平的,因为错误贡献是在其上计算的。我们来看一下测试集。

两种策略在测试集上的对数损失[作者图片]

即使两种方法之间的差异现在变小了,但我们可以看到它仍然是巨大的,并且足以得出结论,在这个数据集上,基于误差贡献的RFE明显优于基于预测贡献的RFE。

除了log-loss之外,考虑一个更有实用价值的指标也会很有趣。例如,让我们看一下验证集上的平均精度:

两种策略在验证集上的平均精度[作者图片]

有趣的是,尽管贡献误差是基于对数损失的,但我们在平均精度上也得到了很好的结果。

如果我们想基于平均精度做出决策,那么我们将选择验证集中平均精度最高的模型。这意味着:

  • 基于误差贡献的RFE:包含19个特征的模型
  • 基于预测贡献的RFE:包含14个特征的模型

如果我们这样做,我们在新数据上会观察到什么表现?回答这个问题的最佳代理是测试集:

两种策略在验证集上的平均精度[作者图片]

同样,在这种情况下,基于误差贡献的RFE的性能通常优于基于预测贡献的RFE。特别是,根据我们之前的决定:

  • 基于误差贡献的RFE(包含19个特征的模型):平均精度72.8%;
  • 基于预测贡献(包含14个特征的模型)的RFE:平均精度65.6%。

因此,通过使用基于误差贡献的RFE,而不是传统的基于预测贡献的RFE,我们将获得平均精度增加7.2%的显著提高!

08#结论

特征重要性的概念在机器学习中起着至关重要的作用。然而,“importance”的概念经常被误认为是“goodness”。

为了区分这两个方面,我们引入了两个概念:预测贡献和误差贡献。这两个概念都基于验证数据集的SHAP值,在本文中我们已经看到了计算它们的Python代码。

我们还在一个真实的金融数据集上尝试了它们(其中的任务是预测黄金价格),并证明了基于误差贡献的递归特征消除与基于预测贡献的传统RFE相比,平均精度提高了7%。

感谢阅读!你还可以订阅我们的YouTube频道,观看大量大数据行业相关公开课:https://www.youtube.com/channel/UCa8NLpvi70mHVsW4J_x9OeQ;在LinkedIn上关注我们,扩展你的人际网络!https://www.linkedin.com/company/dataapplab/

原文作者:Samuele Mazzanti
翻译作者:过儿
美工编辑:过儿
校对审稿:Chuang
原文链接:https://towardsdatascience.com/which-features-are-harmful-for-your-classification-model-6227859a44a6