学習する天然ニューラルネット

主に機械学習に関する覚書や情報の整理

imbalanced-learnの機能の紹介

はじめに

imbalanced-learnとは

scikit-learnと共同で利用できるPythonモジュールの一つである。不均衡データに対処する様々なテクニックが実装されている。不均衡データについてはこの記事にたどり着いた方に説明するまでもないでしょう。

クラス分類でデータが不均衡だと決定境界が下図のようにめちゃくちゃになってしまう。

http://contrib.scikit-learn.org/imbalanced-learn/stable/_images/sphx_glr_plot_comparison_over_sampling_0011.png

動機

imbalanced-learn(imblearn)を紹介してる日本語記事が少なかった。また、ドキュメントをまとめた記事は存在しなかった。そこで、imblearnでよく使うだろう機能と、そのアイデアを紹介する。

やること

  • imbalanced-learn documentationを簡単に要約する。

Welcome to imbalanced-learn documentation! — imbalanced-learn 0.3.0 documentation

  • よく使いそうなものだけを紹介する。
  • 番号はimbalanced-learn documentationに合わせる。ただし見出しのインデントは編集の都合上自分に都合の良い用に変えている
  • 実装は別の記事で行った。

aotamasaki.hatenablog.com

参考

他の日本語記事

インストールから簡単なコードの実装まで

tekenuko.hatenablog.com

こちらも似たような流れだが under-samplingとover-samplingの組み合わせまで実装している。

ohke.hateblo.jp

機能の紹介

インストール

日本語記事でも紹介されているので略

2.2.1 サンプルのでっち上げ(オーバーサンプリング)

imlearnはオーバーサンプリングの一つの手法として、SMOTEを提供している。これはある点とk-近傍の点の内分点に新たなサンプル  x_{new} を生成するものである。イメージとしては次の図。

http://contrib.scikit-learn.org/imbalanced-learn/stable/_images/sphx_glr_plot_illustration_generation_sample_0011.png

ただし、SMOTEにも亜種が存在する。インスタンスを生成する際に引数を指定することで亜種を選択できる。亜種はborderlineとSVMがあり、違いは、下図を見ていただければなんとなくわかるだろう。

http://contrib.scikit-learn.org/imbalanced-learn/stable/_images/sphx_glr_plot_comparison_over_sampling_0061.png

普通のSMOTE

亜種でないSMOTEを用いたいとき、regularとすれば、良い。

smote = SMOTE(kind='regular')
## あとはsmote.fit_sample(引数)してください。

ボーダーラインSMOTE

これはある点 x_iに対して、(1)ノイズか(2)危険か(3)安全に分類する。

  1.  x_iに対してすべての近傍が違うクラスであるとき、ノイズと判断。
  2.  x_iに対して少なくとも半分の近傍が同じクラスであるとき、危険と判断。
  3.  x_iに対してすべての近傍が同じクラスであるとき、安全と判断。

このとき、危険と判断したサンプル$x{i}$を使用して、新しいサンプルを生成する。Borderline1は安全側の点を[tex:x{zi}]として、その内分点に新たなデータを生成する。Borderline2は特にx_{zi}について考慮しない。

smote = SMOTE(kind='borderline1')
## borderline2も指定可能

SVM SMOTE

サポートベクターを見つけてそれを考慮して、新たなサンプルを作る方法。"C"のパラメーターをいじると、サポートベクターの数も変化する。

ボーダーラインとSVMでは"m_neighbors"というパラメーターがあり、サンプルが危険か安全かノイズかの判断強度を変更できる。

smote = SMOTE(kind='svm')

ADASYN

SMOTE以外にもADASYNという手法がある。これはkNN(k-最近傍法)を使用して、間違って分類されたサンプルがあったら、その近くにサンプルを合成するという手法。検出したい少数クラスが多数クラスに完全に埋もれてしまっている場合に有効だと推測される。"n_neighbor"というパラメーターが存在する。

adasyn = ADASYN()

3.2.2 クリーニングアンダーサンプリングテクニック(データの削除)

正確にはこのTomek's linkを削除するのに焦点を当てる。Tomek's linkとは、異なるクラスに属するサンプルx, yが合ったとき、以下の式を満たす任意のサンプルzが存在しないときのx, yの組み合わせのこと。(ドキュメントの数式と違いますがおそらくあっちが誤植です。) $$d(x, z) < d(x, y) \text{ or } d(y, z) < d(x, y)$$ 式だとわかりにくいが、要は、違うクラスの二点がお互いに近くに合ったときに、それをTomek's linkと言う。イメージ図は以下。

http://contrib.scikit-learn.org/imbalanced-learn/stable/_images/sphx_glr_plot_illustration_tomek_links_0011.png

デフォルト(ratio='auto')では、Tomek's linkのうち、多数のクラスのほうだけ削除する。"ratio='all'"とすると、Tomek's linkを成すサンプルを多数少数にかかわらず、すべて削除する。

tomek = TomekLinks(ratio='auto')

3.2.2.2. 近傍を用いたデータの編集

EditedNearestNeighboursは近傍法を、データを"編集"するのに用いる。近傍に"必要なだけの数量"がないと判断されたサンプルを削除する。とドキュメントに書いてあり、なにを言いたいのかさっぱりわからんと感じたが、図を見て一発でわかった。

http://contrib.scikit-learn.org/imbalanced-learn/stable/_images/sphx_glr_plot_comparison_under_sampling_0041.png

要は、重なり合って違うクラスが入り込んでいる場合に、取り除こうというアイデアらしい。違うクラスに入り込んでるかの判断には近傍法を用いている。

引数の説明、"kind_sel='mode'"を指定すると、検査点の近傍の大部分が同じクラスなら除去しない。"kind_sel='all'"を指定すると、検査点の近傍のすべてが同じクラスなら除去しない。 "n_neighbors"は、言うまでもなく近傍数。説明文のKNeighborMixinの下りが正直良くわからなかったが

enn = EditedNearestNeighbours(kind_sel='mode')

以上を繰り返し実行するRepeatedEditedNearestNeighboursやAllKNNもあるが、ここでは省略する。また、何らかの判別器を用いるInstance hardness thresholdという手法もあるが、これも省略。

4. オーバーサンプリングとアンダーサンプリングの組み合わせ

いままで、おもにSMOTE、Tomek's link、EditedNearestNeighbours(ENN)に注目して説明したのは、このためである。実際にインバランスデータを判別器に学習させるときにはここで紹介する関数を使うと予想する。

SMOTEをした後Tomek's linkの削除はSMOTETomekで、SMOTEをした後ENNを施すのはSMOTEENNで簡単に行うことができる。

smote_tomek = SMOTETomek()
##パラメーターはAPIドキュメントを見てください

http://contrib.scikit-learn.org/imbalanced-learn/stable/_images/sphx_glr_plot_comparison_combine_0011.png

この写真では、SMOTEENNの方が良いように見える(本文にもこれのほうが良いと書いてあった)。しかし、異常データが完全に正常データに埋もれてしまっている、なんて状況のときにはうまくいかないことが予想されるので、扱っているデータを見極めて使うべきだと個人的にはそう思う。

5.2. サンプラーと推定器のアンサンブル

ブートストラップサンプリングする際に正常データと異常データを同数抽出して、アンサンブル学習させるという発想。sklearnではBaggingClassifierが実装されているが、インバランスを考慮することはできない。imlearnではインバランスを考慮したBalancedBaggingClassifierが存在する。これは便利そうですね。場合によってはこの機能だけで済むこともあるかも。

bbc = BalancedBaggingClassifier(base_estimator=DecisionTreeClassifier(),
...                                 ratio='auto',
...                                 replacement=False,
...                                 random_state=0)
### これは公式からのコピペです

さいごに

今回は紹介に終わったが、次回の記事で実際にデータの素性を考慮して実装してみたいと思う。ここでは紹介していないがimblearnもpipelineの機能を持つ。これによって一連の操作をまとめて、交差検証をすることができる。実装してみるので、 そちらも参考になると嬉しい。

追記、実装しました

aotamasaki.hatenablog.com