やりたいこと
自作のfeature selectorをパイプラインに組み込み、前処理から学習を一括管理したい。
その際、削除された変数を確認したいので、データフレームで出力できるようにする。
最低限の実装
今回は練習用に、同じ値の要素が閾値以上の列を削除するfeature selectorを実装してみる。
似たような機能を持つsklearnのVarianceThreshold
1のソースコード2を参考に作成した。
データフレームでの出力方法以外は、すでに解説記事3があるのでそちらを参照して頂きたい。
まずは最低限の実装を示す。
こちらの実装では、fitメソッドの引数Xはデータフレームでなければならない。
from scipy.stats import mode
from sklearn.base import BaseEstimator
from sklearn.feature_selection import SelectorMixin
class MySelector(SelectorMixin, BaseEstimator):
def __init__(self, threshold=1):
self.threshold = threshold
def fit(self, X, y=None):
self.feature_names_in_ = X.columns
_, counts = mode(X, axis=0)
self.freq_ratios = (counts / X.shape[0]).flatten()
return self
def _get_support_mask(self):
return self.freq_ratios < self.threshold
_get_support_mask
メソッドを実装すれば、SelectorMixin
からtransform
メソッドとinverse_transform
メソッドが実装される。
またBaseEstimator
を継承しているので、インスタンス変数feature_names_in_
をセットすることで、次のようにset_output
メソッドにtransform="pandas"
を渡すことで出力をデータフレームにできる。
selector = MySelector(threshold=1).set_output(transform="pandas")
selector.fit(X)
X_transformed = selector.transform(X)
ひとまず目的は達成されたが、fit
の引数としてデータフレームしか受け取れない問題が残っている。
(他にも色々あると思われるが・・・)
これは型チェック用の関数を導入することで解決できる。
型チェックを加えた実装
fit
メソッドの中で_validate_data
メソッドを呼び出せば良い。内部ではcheck_array
関数によって入力データが2次元配列であることをチェックしつつ、_check_feature_names
メソッドが呼び出され、インスタンスのfeature_names_in_
属性に値をセットしてくれる。
また、check_array
に渡すキーワード引数を_validate_data
メソッドに渡しておくことで、入力データの様々な型チェックが可能4。今回は出力のデータ型を維持するようにdtype=None
を渡しておく。
ついでに、_get_support_mask
メソッドの中でcheck_is_fitted
メソッドを呼び出すことで、fit
メソッドの実行前にtransform
メソッドを実行しようとしたときに、 "fitメソッドが実行されていないよ" というエラーメッセージを返してくれる。
以上を加えた実装はこちら。
from scipy.stats import mode
from sklearn.base import BaseEstimator
from sklearn.feature_selection import SelectorMixin
from sklearn.utils.validation import check_is_fitted
class MySelector(SelectorMixin, BaseEstimator):
def __init__(self, threshold=1):
self.threshold = threshold
def fit(self, X, y=None):
X = self._validate_data(X, dtype=None)
# self.feature_names_in_ = X.columns
_, counts = mode(X, axis=0)
self.freq_ratios = (counts / X.shape[0]).flatten()
return self
def _get_support_mask(self):
check_is_fitted(self)
return self.freq_ratios < self.threshold
パイプライン化
上記のように実装したクラスはsklearnのパイプラインに組み込むことができる。
またパイプラインでは出力をデータフレームにする設定をまとめて行うことができる5。
以下は、自作feature selectorで全て同じ値の変数を削除した後に標準化を行う例。
pipeline = Pipeline(
[("my_selector", MySelector(threshold=1)), ("standard_scaler", StandardScaler())]
)
pipeline.set_output(transform="pandas")
pipeline.fit(X)
X_transformed = pipeline.transform(X)
-
https://scikit-learn.org/stable/modules/generated/sklearn.feature_selection.VarianceThreshold.html ↩
-
https://github.com/scikit-learn/scikit-learn/blob/364c77e04/sklearn/feature_selection/_variance_threshold.py#L13 ↩
-
https://scikit-learn.org/stable/modules/generated/sklearn.utils.check_array.html ↩
-
https://scikit-learn.org/stable/auto_examples/miscellaneous/plot_set_output.html ↩