機械学習には、アンサンブル学習という手法があります。なんだか楽器の演奏に使われそうなネーミングですよね。アンサンブル学習は、やや上級者向けの学習方法であり、複数の弱学習器から強力な一つの学習器(強学習器)を作成することです。この記事では、まずアンサンブル学習の基礎を説明し、後半でプログラミング言語Pythonによる実装コード例を紹介します。
目次
弱学習器と強学習器
機械学習には、サポートベクターマシンや決定木などさまざまなアルゴリズムがあります。そうしたアルゴリズムにより学習すると一つのAIができるのですが、それを弱学習器と呼びます。そして、弱学習器を束ねたものを強学習器と呼ぶのです。まとめると以下のようになります。
・弱学習器→一つのアルゴリズムで学習したAI
・強学習器→複数の弱学習器の結果を組み合わせたAI
前回の記事で決定木というアルゴリズムを紹介したのですが、複数の決定木(弱学習器)を用いた強学習器をランダムフォレストといいます。
・決定木→弱学習器
・ランダムフォレスト→強学習器
モデルの誤差
ランダムフォレストは、多数の決定木を並列的に作成するバギングというアンサンブル学習です。機械学習モデルの誤差は、
誤差=バイアス(モデル選択から生じる誤差)+バリアンス(訓練データに由来する誤差)+ノイズ(消せない)
と表せます。決定木とランダムフォレストは非線形モデルであり、非線形モデルの誤差の特徴は
バイアス→小
バリアンス→大
となります。非線形モデルは訓練データにしっかりと対応できるよう学習するものの、訓練データに過剰適合しやすい側面があり、訓練データが少し変わるだけで予測結果が大きく変わってしまいます(バリアンスが大きい)。
ちなみに、線形モデルの誤差の特徴は、
バイアス→大
バリアンス→小
です。非線形モデルとは逆に、予測の精度自体は非線形モデルに比べて高くないのですが、訓練データに過剰適合しにくく安定的なモデルとなります。
バギング、ブースティング、スタッキングの説明
そして、アンサンブル学習には、主にバギング、ブースティング、スタッキングという3つの種類があります。
バギング→訓練データの一部だけを用いて学習する弱学習器を多数作ることで、訓練データへの過剰適合を防ぎ、バリアンスを低下させる
ブースティング→一つの弱学習器の誤差に注目し、その誤差を少なくするようさらに学習を行う。どんどんと誤差が小さくなるので、バイアスが低下する。バギングと異なり、並列的に弱学習器を複数作るのではなく、逐次的に学習を行っていく。
決定木のバギングはランダムフォレスト、決定木のブースティングはGBDT(Gradient Boosting Decision Tree)と呼ばれ、近年では特に後者のGBDTの精度が高くなるとして注目されています。XGBoostやLightGBM、Catboostが有名かつ人気です。
最後に、アンサンブル学習の3つ目の手法であるスタッキングを紹介します。文字通り、学習器をスタック(積み重ねる)という意味です。決定木やサポートベクターマシン、ニューラルネットワークなどさまざまなアルゴリズムがありますが、それらを用いて予測値を算出し、その予測値を新たな「特徴量」として再び学習を行うことです。したがって、全体として非常に複雑なモデルとなります。
ランダムフォレストによるバギングのPythonコード
ここからは、プログラミング言語Pythonによるランダムフォレスト(バギング)のコード例を紹介します。Pythonには、データサイエンス用の便利なライブラリーであるscikit-learnというものがあります。この中に、乳がんに関するデータがありますので、そちらを利用します。今回は、しこりが良性か悪性かを判断するAIを作成します。しこりの半径や表面の凹凸、手触りや滑らかさから良性・悪性を見分けられるようにします。サンプル数(データ数)は569です。
また、今回使用するデータはあらかじめある程度きれいに整理されているので、前処理をせずにそのままトレーニング(学習)を行います。また、データはトレーニングデータとテストデータに分割します。トレーニングデータで学習を行い、テストデータで精度の確認を行います。
下図のコードを実行すると、96.5%の精度で乳がんを判定するAIとなりました。
#必要なモジュール(便利な機能)をインポート from sklearn import ensemble from sklearn.datasets import load_breast_cancer from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score #scikit-learn内にある乳がんに関するデータをcancerという変数に代入。 cancer = load_breast_cancer() #Xに乳がんの特徴量を代入。特徴量は、半径や表面の凹凸、なめらかさなど「しこり」の属性データのこと。 X=cancer.data #yに乳がんの目的変数を代入。目的変数は、しこりが良性か悪性か。 y=cancer.target #トレーニングデータとテストデータに分割。 #トレーニングデータで学習を行い、テストデータでAIが実際に使えるかの精度検証。テストデータは全体の3割に設定。 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0) #ランダムフォレストをインスタンス化(実体化)。n_estimatorsは100本の決定木を用いるというハイパーパラメータ。 clf = ensemble.RandomForestClassifier(n_estimators=100, random_state=1) #学習(トレーニング)を実行 clf.fit(X_train, y_train) #テストデータを用いてpredict(予測)を実行 y_pred = clf.predict(X_test) #正解率の算出。予測データと正解データを比較してAIの精度検証を行う。 accuracy_score(y_test,y_pred) #予測精度は約96.5%となった。つまり、作成したAIは96.5%の精度で正解と一致していた。
XGBoostによるブースティングのPythonコード
次に、XGBoost(GBDT)によるブースティングのコード例を紹介します。偶然ですが、今回も正解率は96.5%となりました。ただ、ランダムフォレストとXGBoostの中身を覗いてみると、予測結果は若干異なっていました(つまり、XGBoostが良性と判断したものをランダムフォレストが悪性と判断しているものがありました)。
#必要なモジュール(便利な機能)をインポート from xgboost import XGBClassifier from sklearn.datasets import load_breast_cancer from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score #scikit-learn内にある乳がんに関するデータをcancerという変数に代入。 cancer = load_breast_cancer() #Xに乳がんの特徴量を代入。特徴量は、半径や表面の凹凸、なめらかさなど「しこり」の属性データのこと。 X=cancer.data #yに乳がんの目的変数を代入。目的変数は、しこりが良性か悪性か。 y=cancer.target #トレーニングデータとテストデータに分割。 #トレーニングデータで学習を行い、テストデータでAIが実際に使えるかの精度検証。テストデータは全体の3割に設定。 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0) #XGBoostをインスタンス化(実体化)。 clf = XGBClassifier() #学習(トレーニング)を実行 clf = clf.fit(X_train, y_train) #テストデータを用いてpredict(予測)を実行 y_pred = clf.predict(X_test) #正解率の算出。予測データと正解データを比較してAIの精度検証を行う。 accuracy_score(y_test,y_pred) #予測精度は約96.5%となった。つまり、作成したAIは96.5%の精度で正解と一致していた。
スタッキングのPythonコード
まず、サポート・ベクターマシン、KNN(K近傍法)、ロジスティック回帰の3つの方法で学習を行います。そして、その予測結果を特徴量として、ランダムフォレストで予測を行います。
ランダムフォレストとXGBoostではデータをトレーニングデータとテストデータに分割しましたが、スタッキングではデータをトレーニングデータ、検証データ、テストデータと3分割します。
まず、トレーニングデータで学習を行いサポートベクターマシンとKNN、ロジスティク回帰の学習を行います。
次に、それらの予測結果を特徴量として、検証データでランダムフォレストの学習を行います。
最後に、テストデータで精度を確認します。
3分割する理由は、いわゆるデータのリーク(leakage)を防ぐためです。データを2分割した状態でスタッキングを行うと、精度を確かめるためのテストデータが未知データではなくなってしまいます。完全に未知のデータを残すために、3分割を行うのです。
また、本来は標準化などの前処理を行うべきなのですが、議論をわかりやすくするため、これまで同様標準化などの前処理を行わずにトレーニングを実行します。
#必要なモジュール(便利な機能)をインポート from sklearn.svm import SVC from sklearn.neighbors import KNeighborsClassifier from sklearn.linear_model import LogisticRegression from sklearn import ensemble from sklearn.datasets import load_breast_cancer from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score import numpy as np #scikit-learn内にある乳がんに関するデータをcancerという変数に代入。 cancer = load_breast_cancer() #Xに乳がんの特徴量を代入。特徴量は、半径や表面の凹凸、なめらかさなど「しこり」の属性データのこと。 X=cancer.data #yに乳がんの目的変数を代入。目的変数は、しこりが良性か悪性か。 y=cancer.target #トレーニングデータ、検証データ、テストデータとデータを3分割する。3分割の手順は以下。 #まず、データをtrain_validとtrain_testに分割し、さらにtrain_validを_trainとtrain_validに分割。 X_train_valid, X_test, y_train_valid, y_test = train_test_split(X, y, test_size=0.2, random_state=0) X_train, X_valid, y_train, y_valid = train_test_split(X_train_valid, y_train_valid, test_size=0.2, random_state=0) #_trainのデータで学習を行いサポートベクターマシンとKNN、ロジスティク回帰の学習を行う。 #次に、それらの予測結果を特徴量として、train_validでランダムフォレストの学習を行う。 #最後に、train_testで精度を確認する。 #サポートベクターマシン、KNN、ロジスティック回帰をインスタンス化。 clf_s = SVC() clf_k = KNeighborsClassifier() clf_l = LogisticRegression() #それぞれでトレーニングを実行 clf_s.fit(X_train, y_train) clf_k.fit(X_train, y_train) clf_l.fit(X_train, y_train) #それぞれで予測を実行 y_pred_s = clf_s.predict(X_test) y_pred_k = clf_k.predict(X_test) y_pred_l = clf_l.predict(X_test) #テストデータで各モデルの精度を確認 print(accuracy_score(y_test,y_pred_s)) print(accuracy_score(y_test,y_pred_k)) print(accuracy_score(y_test,y_pred_l)) #それぞれ、91.2%、95.6%、94.7%の精度となった。 #サポートベクターマシン、KNN、ロジスティック回帰に検証データを入力し、それら予測結果を新たな特徴量とする。 #これらがランダムフォレストの新たなトレーニングデータとなる。 train_s = clf_s.predict(X_valid) train_k = clf_k.predict(X_valid) train_l = clf_l.predict(X_valid) # それぞれのデータを列に追加し、3列のデータにする。 stack_train = np.column_stack((train_s, train_k, train_l)) # ランダムフォレストで再びトレーニングを行う。 stack_clf = ensemble.RandomForestClassifier(n_estimators=100, random_state=0) stack_clf.fit(stack_train, y_valid) # 最終的な精度の確認を行うテストデータの準備。 stack_last_train = np.column_stack((y_pred_s, y_pred_k, y_pred_l)) #ランダムフォレストによる予測を実行 stack_pred = stack_clf.predict(stack_last_train) #予測精度の確認 print(accuracy_score(y_test,stack_pred)) #スタッキングの精度は95.6%となった。
各モデルの精度が91.2%、95.6%、94.7%で、最終的なスタッキングの精度も95.6%でした。理想をいえば各アルゴリズムよりもスタッキングの精度の方が高くなってほしかったです。前処理をしたりハイパーパラメータを最適化したりすれば精度は出せると思いますが、話がややこしくなるので今回はここまでとします。
スタッキングの概要をつかんでいただけたのではないでしょうか?
アンサンブル学習は精度を高めるための常套手段
最も有名な機械学習コンペである『Kaggle』では、アンアンブル学習が頻繁に用いられています。ぜひ皆さんも取り組んでみてください。