サマリ
AdaBoostについて勉強しています。本記事では、AdaBoostを実装する際に躓いたポイントを備忘録的に残しておこうと思います。本記事のサマリは下記です。
- 二値分類問題用のトイデータを作成した。
- スクラッチ実装したAdaBoostを使い、上記のトイデータにおける二値分類問題を解いてみた。スクラッチ実装したAdaBoostのクラスは、
AdaBoostHandmade
とした。- 当初、全ての弱分類器が同じになってしまうという課題が発生した。原因は、弱分類器として
scikit-learn
のDecisionTreeClassifier
のインスタンスをAdaBoostHandmade
に渡していることにあった。 DecisionTreeClassifier
のクラスと、DecisionTreeClassifier
の引数を可変長引数としてAdaBoostHandmade
に渡すことで、正しく動くようになった。
- 当初、全ての弱分類器が同じになってしまうという課題が発生した。原因は、弱分類器として
本記事では、まず最初にAdaBoostのアルゴリズムの概要を説明し、次にトイデータの設定を説明します。その後、弱分類器が全て同じになってしまったAdaBoostHandmade
を提示し、どこに課題があったのか、どのように修正したのかについて説明します。
AdaBoostの概要
AdaBoostとは、二値分類問題を解くためのアルゴリズムの一つです。図1にアルゴリズムの内容を示します。
詳細については、下記記事に書いてあります。興味があったらご覧ください。 aisinkakura-datascientist.hatenablog.com
aisinkakura-datascientist.hatenablog.com
トイデータの 作成
下記の設定でトイデータを作成しました。
- 特徴量
- 標準正規分布にしたがう10個の独立な変数。つまり、一つのサンプルに対して、とする。
- 目的変数
- 各特徴量を二乗して和を取ったのち、自由度10のカイ二乗分布の中央値より大きい場合1、小さい場合0とした。つまり、ならば、、 ならば、とする。
- サンプルサイズ
- 2000個とした。
以上の条件でPythonを用いてトイデータを作成すると、下記のようになります。
AdaBoostの実装
弱分類器が全て同じになってしまった悪い例
一番最初に実装したスクリプトが下記です。
上記スクリプトのIn[3]の出力結果をご覧ください。[1, 0, 0, ..., 0,0,1]が並んでいます。これらは、100個の弱分類器で2000個のデータの予測をした結果です。[1, 0, 0, ..., 0,0,1]が並んでいるということは、これら100個の弱分類器が同じ予測をしてしまっていることを示しています。これでは、アンサンブル学習をした意味がありません。
なぜこのようなことになってしまったのか?理由はシンプルで、for文
内で.fit
をしても適切にインスタンスが更新されていないからです。具体的には、下記の箇所に問題があります。
# モデル作成するためのメソッド def fit(self, X, y): # 省略 # 弱分類器を連続して作成する for m in range(self.n_estimators): # 弱分類器の学習 self.weak_learner.fit(X, y, sample_weight=weight)
weak_learner
は、DecisionTreeClassifier(max_depth=1, random_state=100)
(=インスタンス!)を指しています。for文
の中で毎回sample_weight
の値を変えて.fit
をしたかったのですが、ここが上手くいきませんでした。詳細はわからないのですが、一度.fit
したインスタンスは、再度.fit
してもインスタンスの中身が変わらないようです。したがって、for文
内でn_estimators
回の.fit
をしても、結局1回目に.fit
した結果が保存されてしまいます。そのため、弱分類器が全て同じになってしまいました。
ハードコーディングをして一旦解消
上記のように、一度インスタンスを作成し.fit
をしてしまうとfor文
内でインスタンスが更新されません。ならば、for文
内でDecisionTreeClassifier
のインスタンスを毎回作成するという解決策を思いつきました。
具体的には、下記のように変更します。
# モデル作成するためのメソッド def fit(self, X, y): # 省略 # 弱分類器を連続して作成する for m in range(self.n_estimators): # 弱分類器の学習 weak_learner = DecisionTreeClassifier(max_depth=1) # 追加した行!! self.weak_learner.fit(X, y, sample_weight=weight)
これにより、複数の異なる弱分類器を作成できるようになりました。実際に動かしてみた結果は下記です。predictの結果が同じになっていないですし、accuracyも0.5を上回っているので成功です。
ハードコーディングを避ける
ハードコーディングをすることで、正しく動くようにはなりました。しかし、DecisionTreeClassifier
を弱分類器としてハードコーディングしているので、柔軟性がありません。弱分類器をもっと深い決定木に変えたり、ロジスティック回帰にするためには、面倒な書き換えが必要です。
そこで、AdaBoostHandmadeに対して、DecisionTreeClassifier
の(インスタンスではなく)クラスと引数を渡すことにしました。その結果、下記のようなスクリプトになりました。
class AdaboostHandmade: ''' n_estimetors:学習する弱分類器の個数 weak_learner:弱分類器(DecisionTreeClassifier)を想定 params:weak_learnerのパラメータ ''' def __init__(self, n_estimators, WeakLearner, **params): self._n_estimators = n_estimators self._WeakLearner = WeakLearner self._params = params def fit(self, X, y): # 省略 # 弱分類器を連続して作成する for m in range(self.n_estimators): # 弱分類器のインスタンスを作成する weak_learner = self._WeakLearner() weak_learner.set_params(**self._params)
これにより、AdaBoostHandmade内に弱分類器をハードコーディングすることなく、異なる複数の弱分類器を作れるようになりました。全体としては、下記のようなスクリプトになります。
学習・テストデータを分割していないので、いい加減なacurracyですが、一応0.843
にまで上げることができました。
次回の記事のトピック
ここまでで、一応動くAdaBoostのスクリプトを書くことができました。次回は、下記の二冊を参考にスクリプトをキレイ化していこうと思います。