【機械学習】scikit-learnで学ぶstacking
stackingはアンサンブル学習と呼ばれる機械学習の一種で、他の機械学習に基づく複数の予測モデルの出力を入力の一部として扱い、予測モデルを構築します。 単純なアルゴリズムであるのにもかかわらず、何かしらの分類器単体よりも高い予測精度を得やすく、予測精度を競うようなコンペにおいて良く用いられています。 本記事ではscikit-learnのバージョン0.22で導入されたStackingClassifierの使い方について紹介するとともに、学習時の挙動を紹介します。 本記事を読むことでscikit-learnでのstackingの学習の流れを理解できます。
目次
scikit-learn 0.22のインストール
最新版である0.22にするには以下のようにします。
pip install -U scikit-learn
StackingClassifierの利用例
scikit-learnのドキュメントにある例を以下に示します。 この例ではアヤメの分類問題に対して、ランダムフォレストとSVMの2つの分類器を使ってstackingによる分類器を構築しています。 stackingは、利用する分類器の学習と、それらの分類器の出力を使って最終的な分類をおこなう分類器の学習の2段階の学習が必要なのですが、ご覧いただいたとおり、ユーザはそれを意識する必要がないようにAPIがきれいに設計されています。
from sklearn.datasets import load_iris
from sklearn.svm import LinearSVC
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.ensemble import StackingClassifier
from sklearn.model_selection import train_test_split
X, y = load_iris(return_X_y=True)
# (str, estimator) のtupleを要素とするlist。stackingで利用する分類器。
estimators = [
('rf', RandomForestClassifier(n_estimators=10, random_state=42)),
('svr', make_pipeline(StandardScaler(),
LinearSVC(random_state=42)))
]
# estimatorsの予測結果を入力として最終的な予測結果を出力する分類器をfinal_estimatorに与える
clf = StackingClassifier(
estimators=estimators, final_estimator=LogisticRegression()
)
# 学習データと評価データに分ける
X_train, X_test, y_train, y_test = train_test_split(
X, y, stratify=y, random_state=42
)
# stackingによる分類器の学習および評価
clf.fit(X_train, y_train).score(X_test, y_test)
Stackingの概要
scikit-learnを使うと簡単にstackingを利用できることがわかりました。 ここからはstackingの内部を深堀りします。
二値分類問題を考えます。 Stackingによる分類器は事例$\mathbf{x}_{orig} \in \mathbb{R}^{K}$に対して$N$個の分類器の予測結果 $\mathbf{o} \in \mathbb{R}^{2 \times N}$ を結合し、$\mathbf{x}_{meta}$を作成します。 $\mathbf{x}_{meta}$に対して最終的な分類器が持つパラメータ$\mathbf{w} \in \mathbb{R}^{K + 2 \times N}$との内積を計算し、最終的な予測結果$y \in \{-1, 1\}$を得ます。
$$
\begin{eqnarray}
\mathbf{x}_{meta} = [\mathbf{x}_{orig}; \mathbf{o}] \\\
y = sign(\mathbf{w} \mathbf{x}_{meta})
\end{eqnarray}
$$
ただし$[;]$ は二つのベクトルの結合、$sign(z)$は$z>0$なら1、そうでなければ-1を返す関数とします。
Stackingの学習の流れ
stacking学習時の疑似コードを以下に示します。
estimators
として与えられた分類器は学習データすべてを利用します。
次にfinal_estimator
を学習する際は、学習事例に対して各分類器の予測結果が必要になります。
そのため、交差検定によって学習データをより小さな学習データとテストデータに分けます。
より小さな学習データで分類器を学習し、テストデータに対して分類器の予測結果を得ます。
このように交差検定を使って学習データに対して各分類器の予測結果を得て、final_estimator
の学習データの入力として与えます。
# init_estimators(): stackingで利用する分類器オブジェクトを初期化する関数とする。
# concatenate(): 2つのベクトルを結合する関数とする。
# 予測用にestimatorを学習する。このestimatorsはfinal_estimatorの学習には用いない。
estimators = init_estimators()
for estimator in estimators
estimator.fit(x_train, y_train)
# final_estimatorの学習用のestimatorを学習する。
estimators_for_train_final_estimator = init_estimators()
for estimator_for_train_final_est in estimators_for_train_final_est
predictions = []
# final_estimatorの学習用にestimatorを学習し、予測結果を得る
# estimatorの学習はN分割交差検定によって学習データをより小さな学習データとテストデータに分割する。
for n in N
# 交差検定によって学習データx_train_n, y_train_nとテストデータx_test_nを得る。
estimator_for_train_final_est.fit(x_train_n, y_train_n)
pred_test_n = estimator_for_train_final_est.predict(x_test_n)
# テストデータに対する予測結果を格納
predictions += pred_test_n
# estimatorの予測結果をもとの学習事例の素性ベクトルに追加
x_train = concatenate(x_train, predictions)
# estimatorの予測結果を追加した素性ベクトルを使ってfinal_estimatorを学習
final_estimator.fit(x_train, y_train)
なぜfinal_estimator
の学習時に利用する分類器を交差検定によって学習しているのでしょうか?
上記の擬似コードで言えば、なぜestimators
を使わずに、estimator_for_train_final_est
を別途学習して、それらの予測結果をfinal_estimator
へ与える入力としているのでしょうか?
final_estimator
の学習時に利用する分類器を、交差検定を使わずにすべての学習データを利用した場合を考えてみます。
この時、学習に使った学習事例に対する分類器の予測精度はほぼ100%に近づきます。
そのため、final_estimator
の学習において、分類器はほぼ正解を出力するため、final_estimator
の学習で与える入力の素性ベクトルに正解のラベルがある状態となります。
入力に正解のラベルが入っていれば、その値だけを重要視するように学習してしまいます。
実際の運用時においてラベルが未知の事例に対して予測するときには、分類器の予測結果が必ずしも正しいとは限らないため、学習時と予測時の乖離が生まれます。
このように学習時と予測時で条件が大きくずれていると、実際の予測精度が思ったよりも高くならないことが起こりえます。
final_estimator
の学習時に利用する分類器を交差検定によって学習するのは、こういった問題を避けるためです。
一方で、実際に予測で用いるestimators
がすべての学習データを利用しているのは、できるだけstackingによる分類器の予測精度を高めるためです。できるだけ多くの学習データを利用したほうがestimators
の予測精度改善が期待でき、結果としてfinal_estimator
の精度向上も期待できるからです。
まとめ
本記事ではscikit-learnのStackingClassifierを紹介するとともに、stacking学習時の挙動について紹介しました。 scikit-learnではAPIが上手に設計されているため、ユーザは意識する必要がありませんが、stackingで利用する分類器の学習で交差検定を用いることについて説明しました。