如何在预处理后删除特征名称后,在XGBoost特征重要性图中恢复原始特征名称?

24

在训练 XGBoost 模型之前,预处理训练数据(如居中或缩放)可能会导致特征名称丢失。大多数 Stack Overflow 上的答案建议以不丢失特征名称的方式训练模型(例如在数据框列上使用 pd.get_dummies)。

我已经使用经过预处理的数据(使用 MinMaxScaler 进行中心化和缩放)训练了 XGBoost 模型。因此,我面临着特征名称丢失的类似情况。

例如:

    scaler = MinMaxScaler(feature_range=(0, 1))
    X = scaler.fit_transform(X)
    my_model_name = XGBClassifier()
    my_model_name.fit(X,Y)` 

X和Y分别是训练数据和标签。上面的缩放返回一个二维NumPy数组,从而丢弃了来自pandas DataFrame的特征名称。

因此,当我尝试使用plot_importance(my_model_name)时,它会导致特征重要性的绘图,但仅使用诸如f0、f1、f2等特征名称,而不是原始数据集中的实际特征名称。 有没有办法将原始训练数据中的特征名称映射到生成的特征重要性图中,以便在图表中绘制原始特征名称?对此的任何帮助都将不胜感激。


1
也许这可以帮助 https://dev59.com/jlcP5IYBdhLWcg3wXI3Z - JChat
4个回答

43

您可以通过以下方法获取特征名称:

model.get_booster().feature_names


2
正如您在我的答案中所看到的(甚至在问题中),当您将numpy数组传递到fit方法中时,这不是正确的答案,因为您会丢失原始特征名称。 - Nerxis
这就是为什么你应该传递DataFrame而不是Numpy数组的原因。 - Binyamin Even
我不同意。是的,在大多数情况下,这可能是最好的方法。但在其他情况下(甚至例如在我的当前项目中),当您拥有复杂的数据准备过程并使用NumPy数组(出于不同的原因,例如性能等)时,传递此数组要容易得多。 - Nerxis
2
关于你的回答,你可以在回答中加上使用DataFrame而不是NumPy数组的注释,因为现在它并不能回答问题,因为用户正在使用NumPy数组,因此使用model.get_booster().feature_names对他没有用。 - Nerxis
3
如果使用save_model和load_model保存和加载模型,则此方法无效。 - Brady Gilg
值得一提的是,在某些情况下,传递DF不是一个选项,然后model.get_booster().feature_names返回None。结合@Nerxis的回复,我设法在save_model之前设置了特征名称,然后在load_model之后它们很容易就可以使用了。为了澄清:model.get_booster().feature_names = orig_feature_names是有效的。 - roy650

12
当你将NumPy数组传递给XGBoost的fit方法时,确实会丢失特征名称。这种情况下调用model.get_booster().feature_names是没有用的,因为返回的名称形式为[f0, f1, ..., fn],并且这些名称也显示在plot_importance方法的输出中。
但是有几种方法可以实现你想要的效果——假设你在某个地方存储了原始特征名称,例如orig_feature_names = ['f1_name', 'f2_name', ..., 'fn_name'],或者如果X是pandas DataFrame,则直接使用orig_feature_names = X.columns
然后你应该能够:
  • 更改存储的特征名称(model.get_booster().feature_names = orig_feature_names),然后使用plot_importance方法,它应该已经采用更新后的名称并在图表上显示
  • 或者由于该方法返回 matplotlib ax,你可以使用plot_importance(model).set_yticklabels(orig_feature_names) 修改标签(但必须设置正确的特征顺序)
  • 或者你可以自己将model.feature_importances_与你的原始特征名称相结合(即通过自己绘制进行绘制)
  • 类似地,你还可以使用model.get_booster().get_score()方法并将其与你的特征名称相结合
  • 或者你可以尝试使用 Learning API 与 xgboost的DMatrix,并在创建数据集(缩放后)时使用 train_data = xgb.DMatrix(X, label=Y, feature_names=orig_feature_names) 指定你的特征名称(但我没有太多使用此种训练方式的经验,因为我通常使用Scikit-Learn API)

编辑:

感谢@Noob Programmer(见下面的评论),根据使用不同的feature importance方法可能会有一些"不一致性"。以下是最重要的一些:

  • xgboost.plot_importance 默认使用"weight"作为重要性类型 (详见plot_importance)
  • model.get_booster().get_score() 也默认使用"weight"作为重要性类型 (详见get_score)
  • model.feature_importances_ 取决于importance_type参数 (model.importance_type),结果似乎被归一化到1的总和 (详见this comment)

有关此主题的更多信息,请参见How to get feature importance


model.feature_importance 和 plot_importance(model, type = "gain"),不要给出相同的特征,所以第三点不合法。像"f1001"后面的数字是数据框中特征的索引吗? - Noob Programmer
@NoobProgrammer:感谢您的评论,已经更新了答案。结果应该是相同的,不同之处在于规范化。如果您认为不够清晰,请随时更新答案。关于数字,是的,那些应该是数据框架(或numpy或任何输入数据)中特征的索引。这就是为什么您可以使用model.get_booster().feature_names = orig_feature_names。或者,您可以解析这些索引并直接在生成的字典上使用它们。 - Nerxis

0

我尝试了上面的答案,在训练后加载模型时没有起作用。 所以,对我来说可行的代码是:

model.feature_names

它返回一个特征名称列表。

0

我认为最好将numpy数组转换回pandas DataFrame。例如:

import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from xgboost import XGBClassifier


Y=label

X_df = pd.read_csv("train.csv")
orig_feature_names = list(X_df.columns)

scaler = MinMaxScaler(feature_range=(0, 1))
X_scaled_np = scaler.fit_transform(X_df)
X_scaled_df = pd.DataFrame(X_scaled_np, columns=orig_feature_names)

my_model_name = XGBClassifier(max_depth=2, n_estimators=2)
my_model_name.fit(X_scaled_df,Y)

xgb.plot_importance(my_model_name)
plt.show()

这将显示原始名称。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接