scikit-learnのclass_weightパラメーターはどのように機能しますか?


115

class_weightscikit-learnのロジスティック回帰のパラメーターがどのように機能するかを理解するのに多くの問題を抱えています。

状況

ロジスティック回帰を使用して、非常に不均衡なデータセットでバイナリ分類を行いたいと思います。クラスには0(負)と1(正)のラベルが付けられ、観測されたデータは約19:1の比率で、サンプルの大部分が負の結果を示します。

最初の試み:手動でトレーニングデータを準備する

トレーニングとテストのために、持っていたデータをばらばらのセットに分割しました(約80/20)。次に、手動でトレーニングデータをランダムにサンプリングして、19:1とは異なる比率でトレーニングデータを取得しました。2:1-> 16:1から。

次に、これらの異なるトレーニングデータサブセットでロジスティック回帰をトレーニングし、異なるトレーニング比率の関数として再現率(= TP /(TP + FN))をプロットしました。もちろん、再現率は19:1の割合が観測されたバラバラのTESTサンプルで計算されました。注:異なるトレーニングデータで異なるモデルをトレーニングしましたが、同じ(互いに素な)テストデータですべてのモデルの再現率を計算しました。

結果は予想通りでした。リコールは2:1のトレーニング比率で約60%で、16:1になるまでにかなり速く落ちました。再現率がまともな5%を超える2:1-> 6:1の比率がいくつかありました。

2回目の試み:グリッド検索

次に、さまざまな正則化パラメーターをテストしたかったので、GridSearchCVを使用して、Cパラメーターとパラメーターのいくつかの値のグリッドを作成しましたclass_weight。n:mの比率のネガティブ:ポジティブトレーニングサンプルを次の辞書言語に翻訳するには、class_weightいくつかの辞書を次のように指定するだけだと考えました。

{ 0:0.67, 1:0.33 } #expected 2:1
{ 0:0.75, 1:0.25 } #expected 3:1
{ 0:0.8, 1:0.2 }   #expected 4:1

と私も含まれNoneていautoます。

今回は結果は完全に奇抜でした。すべての私のリコールは、class_weightexceptのすべての値に対して非常に小さい(<0.05)でしたauto。だから私は、class_weight辞書を設定する方法についての私の理解が間違っているとしか想定できません。興味深いことに、class_weightグリッド検索の「auto」の値はC、のすべての値で約59%であり、1:1にバランスが取れていると思いますか?

私の質問

  1. class_weight実際に提供するデータとは異なるトレーニングデータのバランスをとるために、どのように適切に使用しますか?具体的には、class_weightn:mの比率のネガティブ:ポジティブトレーニングサンプルを使用するためにどの辞書を渡しますか?

  2. さまざまなclass_weightディクショナリをGridSearchCVに渡すと、相互検証中に、ディクショナリに従ってトレーニングフォールドデータのバランスが再調整されますが、テストフォールドでのスコアリング関数の計算には、与えられた真のサンプル比率が使用されますか?メトリックは、それが観察された比率のデータからのものである場合にのみ私にとって有用であるため、これは重要です。

  3. auto値はclass_weightプロポーションまで何をしますか?私はドキュメントを読み、「データをその頻度に反比例してバランスさせる」とは、それが1:1になることを意味します。これは正しいです?そうでない場合、誰かが明確にできますか?


class_weightを使用すると、損失関数が変更されます。たとえば、クロスエントロピーの代わりに、それはweigtedクロスエントロピーになります。towardsdatascience.com/...
プラシャンス

回答:


123

まず、リコールだけで行くのは良くないかもしれません。すべてをポジティブクラスとして分類することで、100%の再現率を達成できます。通常、AUCを使用してパラメーターを選択し、次に関心のある操作点のしきい値(特定の精度レベルなど)を見つけることをお勧めします。

方法については、class_weightこれはサンプルでミスペナルティを課す:作品class[i]class_weight[i]はなく、1は上位クラス重量の手段だからあなたがクラスに重点を置きたいです。あなたが言うことから、クラス0はクラス1の19倍の頻度であると思われます。したがって、クラス0に比べてクラス1のを増やす必要があります。class_weightたとえば、{0:.1、1:.9}とします。class_weightが1にならない場合は、基本的に正則化パラメーターを変更します。

仕組みについてclass_weight="auto"は、このディスカッションをご覧くださいclass_weight="balanced"開発バージョンではを使用できます。これは理解が容易です。基本的には、大きいクラスと同じ数のサンプルが得られるまで、小さいクラスを複製しますが、暗黙的な方法です。


1
ありがとう!簡単な質問:明確にするためにリコールについて述べましたが、実際には、どのAUCを測定基準として使用するかを決定しようとしています。私の理解では、パラメーターを見つけるには、ROC曲線下の面積または再現率と精度曲線の面積を最大化する必要があります。この方法でパラメーターを選択した後、曲線に沿ってスライドして分類のしきい値を選択すると思います。これはあなたが意味したことですか?もしそうなら、私の目標ができるだけ多くのTPをキャプチャすることである場合、2つの曲線のどちらを見るのが最も理にかなっていますか?また、scikit-learnへの貢献と貢献に感謝します!!!
kilgoretrout 2015年

1
ROCを使用する方が標準的な方法だと思いますが、大きな違いはないと思います。ただし、曲線上の点を選択するための基準は必要です。
Andreas Mueller

3
@MiNdFrEaK私は、Andrewが推定器とは少数派クラスのサンプルを複製することを意味するので、異なるクラスのサンプルのバランスが取れていると思います。暗黙的にオーバーサンプリングしているだけです。
Shawn TIAN

8
@MiNdFrEaKおよびShawn Tian:SVベースの分類子、「バランス」を使用する場合、小さいクラスのサンプルをより多く生成しません。それは文字通り小さなクラスで行われた間違いを罰します。特に大規模なデータセットでは、サンプルをさらに作成する余裕がない場合、特にそうでないと言うのは間違いであり、誤解を招きます。この回答は編集する必要があります。
パブロ・リバス

4
scikit-learn.org/dev/glossary.html#term-class-weightクラスの重みは、アルゴリズムに応じて異なる方法で使用されます。線形モデル(線形SVMやロジスティック回帰など)の場合、クラスの重みは損失関数を次のように変更します各サンプルの損失をクラスの重みで重み付けします。ツリーベースのアルゴリズムの場合、クラスの重みは分割基準の再重み付けに使用されます。ただし、この再調整では、各クラスのサンプルの重みは考慮されないことに注意してください。
prashanth

2

最初の答えは、それがどのように機能するかを理解するのに適しています。しかし、私はそれを実際に使用する方法を理解したかったのです。

概要

  • ノイズのない適度に不均衡なデータの場合、クラスの重みの適用に大きな違いはありません
  • ノイズと強く不均衡のある適度に不均衡なデータの場合、クラスの重みを適用することをお勧めします
  • param class_weight="balanced"は、手動で最適化したくない場合に適切に機能します
  • class_weight="balanced"あなたがより真のイベント(高いTRUEリコール)をキャプチャするだけでなく、あなたが偽のアラートを取得する可能性が高くなります(TRUE精度を下げます)
    • その結果、すべての誤検知により、合計TRUEが実際よりも高くなる可能性があります。
    • 誤警報が問題である場合、AUCはここであなたを誤解するかもしれません
  • 決定しきい値を不均衡%に変更する必要はありません。強い不均衡であっても、0.5(または必要に応じて、その前後のどこか)を維持しても問題ありません。

NB

RFまたはGBMを使用すると、結果が異なる場合があります。GBMにはsklearnにはありませんが class_weight="balanced"lightgbmにはありますLGBMClassifier(is_unbalance=False)

コード

# scikit-learn==0.21.3
from sklearn import datasets
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score, classification_report
import numpy as np
import pandas as pd

# case: moderate imbalance
X, y = datasets.make_classification(n_samples=50*15, n_features=5, n_informative=2, n_redundant=0, random_state=1, weights=[0.8]) #,flip_y=0.1,class_sep=0.5)
np.mean(y) # 0.2

LogisticRegression(C=1e9).fit(X,y).predict(X).mean() # 0.184
(LogisticRegression(C=1e9).fit(X,y).predict_proba(X)[:,1]>0.5).mean() # 0.184 => same as first
LogisticRegression(C=1e9,class_weight={0:0.5,1:0.5}).fit(X,y).predict(X).mean() # 0.184 => same as first
LogisticRegression(C=1e9,class_weight={0:2,1:8}).fit(X,y).predict(X).mean() # 0.296 => seems to make things worse?
LogisticRegression(C=1e9,class_weight="balanced").fit(X,y).predict(X).mean() # 0.292 => seems to make things worse?

roc_auc_score(y,LogisticRegression(C=1e9).fit(X,y).predict(X)) # 0.83
roc_auc_score(y,LogisticRegression(C=1e9,class_weight={0:2,1:8}).fit(X,y).predict(X)) # 0.86 => about the same
roc_auc_score(y,LogisticRegression(C=1e9,class_weight="balanced").fit(X,y).predict(X)) # 0.86 => about the same

# case: strong imbalance
X, y = datasets.make_classification(n_samples=50*15, n_features=5, n_informative=2, n_redundant=0, random_state=1, weights=[0.95])
np.mean(y) # 0.06

LogisticRegression(C=1e9).fit(X,y).predict(X).mean() # 0.02
(LogisticRegression(C=1e9).fit(X,y).predict_proba(X)[:,1]>0.5).mean() # 0.02 => same as first
LogisticRegression(C=1e9,class_weight={0:0.5,1:0.5}).fit(X,y).predict(X).mean() # 0.02 => same as first
LogisticRegression(C=1e9,class_weight={0:1,1:20}).fit(X,y).predict(X).mean() # 0.25 => huh??
LogisticRegression(C=1e9,class_weight="balanced").fit(X,y).predict(X).mean() # 0.22 => huh??
(LogisticRegression(C=1e9,class_weight="balanced").fit(X,y).predict_proba(X)[:,1]>0.5).mean() # same as last

roc_auc_score(y,LogisticRegression(C=1e9).fit(X,y).predict(X)) # 0.64
roc_auc_score(y,LogisticRegression(C=1e9,class_weight={0:1,1:20}).fit(X,y).predict(X)) # 0.84 => much better
roc_auc_score(y,LogisticRegression(C=1e9,class_weight="balanced").fit(X,y).predict(X)) # 0.85 => similar to manual
roc_auc_score(y,(LogisticRegression(C=1e9,class_weight="balanced").fit(X,y).predict_proba(X)[:,1]>0.5).astype(int)) # same as last

print(classification_report(y,LogisticRegression(C=1e9).fit(X,y).predict(X)))
pd.crosstab(y,LogisticRegression(C=1e9).fit(X,y).predict(X),margins=True)
pd.crosstab(y,LogisticRegression(C=1e9).fit(X,y).predict(X),margins=True,normalize='index') # few prediced TRUE with only 28% TRUE recall and 86% TRUE precision so 6%*28%~=2%

print(classification_report(y,LogisticRegression(C=1e9,class_weight="balanced").fit(X,y).predict(X)))
pd.crosstab(y,LogisticRegression(C=1e9,class_weight="balanced").fit(X,y).predict(X),margins=True)
pd.crosstab(y,LogisticRegression(C=1e9,class_weight="balanced").fit(X,y).predict(X),margins=True,normalize='index') # 88% TRUE recall but also lot of false positives with only 23% TRUE precision, making total predicted % TRUE > actual % TRUE
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.