CalibratedClassifierCV で XGBoost の学習済みモデルが ValueError: feature_names mismatch となる

はじめに

CalibratedClassifierCV に XGBoost の学習済みモデルを指定して、fit すると ValueError: feature_names mismatch となる場合があります。
これは XGBoost の学習時には pandas の column 名を変数とし、CalibratedClassifierCV では numpy.ndarray に変換されるため、説明変数名の不一致が起きてしまうことに起因しています。
XGBoost の学習済みモデルを用いて補正する場合に限定されますが、それを回避する方法を記載します。

Probability Calibration とは

Probability Calibration(確率補正)は、モデルによって予測された確率を本来の確率に補正して近づける手法です。
詳細は別途機会があれば整理したいと思いますが、とても簡単に言うと機械学習のモデルの推定する値は確率とは異なる場合があります(確率を推定するモデル以外は)。

「分類器としての性能と,確率を正しく推定できるかは分けて考えないといけないのだろうな.」https://twitter.com/nakamichi/status/797361213185335296

その推定値を確率として補正する、ということだろうと思います。

tjo.hatenablog.com

CalibratedClassifierCV で XGBoost 学習済みモデルを補正

上述のように、CalibratedClassifierCV に XGBoost の学習済みモデルを指定して、fit すると ValueError: feature_names mismatch となる場合があります。

ValueError: feature_names mismatch: ['a', 'b', ...] ['f0', 'f1', ']
expected f0 f1 .
training data did not have the following fields: a b ...

これは簡単に言うと、学習時のデータが pandas.DataFrame であった場合、CalibratedClassifierCV では numpy.ndarray に変換されるため、説明変数名の不一致が起きてしまうことに起因しています。

sklearn の XGBClassifier は pandas.DataFrame を学習データとして投入された時、feature names を column 名として保持します。
それに対して、CalibratedClassifierCV は fit 関数内部で pandas.DataFrame を numpy.ndarray に変換して学習済みの XGBClassifier Object にわたすため、feature names の不一致が起きてしまい ValueError となってしまうようです。
一方、 CalibratedClassifierCV で学習も同時に実行する場合は、XGBClassifier では numpy.ndarray が内部的に DMatrix 形式に変換され、feature names は ['f0', 'f1', '] のように設定されるため、このような不一致は起きなくなります。

これについての報告や詳細な考察は以下にあります。

github.com

calibration.py の class CalibratedClassifierCV.fit をたどると、さまざまな validation を通り抜けて、最終的に cv='prefit' (既に学習済みのモデル)である場合、_CaliratedClassifier.fit で確率補正学習します。
その vaidation の中で、dataframe のX,y は numpy.array に変換されます。
具体的には validation.py の indexable という関数にて、データ数のチェックや sparse 行列の csr 変換や non-iterable object を配列に変換する処理をしている箇所です。

def indexable(*iterables):
    """Make arrays indexable for cross-validation.

    Checks consistent length, passes through None, and ensures that everything
    can be indexed by converting sparse matrices to csr and converting
    non-interable objects to arrays.

こちらから validation 済みのデータを渡すのであれば、特にここを通す必要はなく、更に学習済みモデルを補正する場合は直接 _CalibratedClassifier で fit すればよいです(L.155のあたり)。
(一方、CrossValidation を行う場合は L.162 の交差検証実行されます。)

# L.155
calibrated_classifier = _CalibratedClassifier(base_estimator, method=self.method)
    
if sample_weight is not None:
    calibrated_classifier.fit(X, y, sample_weight)
else:
    calibrated_classifier.fit(X, y)

実行結果として適切なものがなくてコードだけになってしまいますが、動作を確認したものを簡単に記載します。

import xgboost as xgb
from _calibration import _CalibratedClassifier

#
# model は xgb.XGBClassifier の学習済みモデル
# X_test, y_test は model のテストデータ
#

sig_clf = _CalibratedClassifier(model, method='sigmoid')
sig_clf.fit(X_test, y_test)

# 補正後の値
y_prob_sig = sig_clf.predict_proba(X_test)[:, 0]

# 補正前の価
y_prob = model.predict_proba(X_test)[:, 0]

# plot ...