静かなる名辞

pythonとプログラミングのこと


【python】sklearnのclass_weightの挙動

はじめに

 先に断っておくと、class_weightの挙動はモデルによって異なる可能性が十分ある。今回はsklearn.svm.SVCとsklearn.ensemble.RandomForestClassifierのドキュメントを参照して、一応基本的に共通する部分を抜き出した。

 class_weightを調整する必要が出てきたときは、自分が使うモデルで確認してください。

 参考:
3.2.4.3.1. sklearn.ensemble.RandomForestClassifier — scikit-learn 0.20.1 documentation
sklearn.svm.SVC — scikit-learn 0.20.1 documentation

解説

 与えられる引数はNone(デフォルト)、辞書、"balanced"という文字列の3種類というのが基本だと思う*1

 Noneを渡した場合、class_weightはすべてのクラスに対して1であると仮定される。これは悩むような話ではない。

 辞書を渡す場合、キーはクラスラベル、値は重みになる。すべてのクラスラベルと対応する重みを辞書の要素に与えなくても動くようだが、まあ与えた方が無難。詳しく検証はしていない。

 重みは大きいほどそのクラスを重視する意味になる。なので、たとえばクラス0に100件、クラス1に1000件のデータがあったとしたら、何もしないとクラス1が重視されすぎてしまう懸念がある。なので、クラス1を1/10してやるか、クラス0を10倍してやれば同じ比率になるだろう、という気がする。

{0:1000/100,1:1}
{0:1, 1:100/1000}

 直感的にはこんな感じで良い。

 "balanced"を指定すると似たような演算をしてくれる。ただし、ちょっとやっていることが異なる。公式によると、この演算が行われるらしい。

n_samples / (n_classes * np.bincount(y))

 n_samplesは全サンプル数の合計(恐らくfit時のサンプルである)。n_classesはクラス数だが、np.bincountというのが見慣れない処理である。でもやることは簡単。

>>> import numpy as np
>>> np.bincount([0,0,0,1,1,1,1,2,2,2,2,3,4,5,6,5,4,4,4,3,2,1])
array([3, 5, 5, 2, 4, 2, 1])

 つまりクラスラベルごとに出現回数を数えている(index=0の要素が0の出現回数・・・という仕組み)。これの逆数を取って、あとは定数をかけているだけの式と解釈できる。そこそこ無難そうな式ではある。

まとめ

 普通は意識しないと思います・・・。

 クラスごとのサンプル数の偏りが無視できそうな程度に小さかったり、無視できそうな程度にサンプルが大量にある場合はNone(デフォルトのまま)、

 偏っている場合でも特殊なケース以外は"balanced"で良いと思う。

 これをパラメタチューニングしてどうこうとかは、考えない方が良いんじゃないかなぁ・・・。

*1:ランダムフォレストは複数値への分類に対応しているのでlistも受け取るようだ。また、ランダムフォレストはブートストラップの絡みで“balanced_subsample”というのも指定できるようだ。これらは特殊だと思うので説明しない(ドキュメントに書いてあるし)