機械学習でクラスインバランスなデータを扱う - サンプリングの問題点と「目的」の設定

この記事は [ミクシィグループ Advent Calendar 2019](https://qiita.com/advent-calendar/2019/mixi) 16日目の記事です。

1ヶ月ほど Google にて機械学習のトレーニングを受けていたのですが、その中でクラスインバランスな問題への対処方法の参考文献として挙げられていた [Handling imbalanced datasets in machine learning](https://towardsdatascience.com/handling-imbalanced-datasets-in-machine-learning-7a0e84220f28) を読んで感動した halhorn が記事の内容をふんわり和訳したものです。

恥ずかしながらこの記事を読むまでは、クラスインバランスであればマイナーなクラスのデータをオーバーサンプリングするなりして頻度を調整すれば良い、程度の考えしか私にはありませんでした。
この記事ではオーバーサンプリングやダウンサンプリングを安易に行うことの理論的な問題や、そのような場合に必要な「ものの考え方」が書かれています。考えてみれば当たり前という話ですが私にはとても勉強になりました。
ぜひお読みください。


# イントロダクション
会社で働いていてこんなタスクを与えられたとしよう。
「様々な指標から、各製品が不良品かどうかを判定するモデルを作る。」
さっそくまずはお気に入りの識別器を使ってデータを学習させてみる。なんと君が育てた識別器は96.2%の精度(accuracy)を叩き出した！

上司はその性能に驚き、追加のテストをすること無しに君のモデルを使うことを決定した。数週間後、その上司は君の作ったモデルが全く使えないことに気づく。君の作ったモデルはどんな製品を入力してもそれは不良品ではないと答えたので、不良品はまったく検出されなかったのだ。

学習に使ったデータを調査してみたところ、不良品とラベリングされたデータは全体の3.8%しか無いことがわかった。そして君のモデルは常に「不良品ではない」と答えることで96.2%の精度をたたきだしてたのだ。
このような「ナイーブな」結果はクラスインバランスなデータセットに起因する。この記事のゴールはクラスインバランスなデータに使える、いくつかの種類の対応方法をレビューすることだ。

# アウトライン
- 「ナイーブな」ふるまいを検知できる、精度以外の評価指標
    - 再現率、適合率、ROCカーブ
- データセットのリサンプリングによる解決方法と、それらを使う危険性
    - アップサンプリング、ダウンサンプリング、データオーグメンテーション
- 解こうとしている問題の再定義の重要性
    - 問題を定義すること、コストに非対称性のある損失関数

# 「ナイーブな振る舞い」を検知する
この章では、先程の例のようにすべての商品に対して「不良品では無い」と返してしまうような「ナイーブな振る舞い」を検知できるような評価指標について考える。
例に見たように精度（accuracy）は重要な指標ではあるが、時としてミスリーディングになりうる指標だ。だから精度を使う場合は慎重に、そして他の指標と合わせて使わなければいない。では、他にどんな指標があるのかこれから見ていこう。

## 混同行列、適合率、再現率、そしてF1
混同行列（Confusion matrix）は識別問題で使われるシンプルで良い指標だ。この指標は学習させたモデルがどのくらいうまくやってるかの概要を説明してくれる。下の図は混同行列に関係するいろいろな指標を表している。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61079/0db308f8-012c-5433-5e38-3cad637c3ada.png)

- 訳注：あるクラスについて
    - 真陽性（True Positive: TP）: 予測がそのクラス（Positive）でそれが正解（True）
    - 真陰性(True Negative: TN): 予測がそのクラスではなく（Negative）それが正解（True）
    - 偽陽性（False Positive: FP）: 予測がそのクラス（Positive）だが不正解（False）
    - 偽陰性（False Negative: FN）: 予測がそのクラスではない（Negative）が不正解（False）

手短に説明しよう。モデルの精度（accuracy）は基本的には、すべての予測のうちの正しかった予測の割合だ。
クラスの適合率（precision）は、モデルがある入力がそのクラスに属していると言ったときにそれがどのくらい信頼できるかを表している。
クラスの再現率（recall）はモデルがそのクラスをどのくらい検出できるかを表している。（訳注：例で言えば本当に不良品だったもののうちどれだけを不良品としてモデルが検出できたかを表す。）
F1スコアは適合率と再現率の調和平均（$\frac{2*precision*recall}{precision+recall}$）で、適合率と再現率を一つの指標にまとめたものだ。

あるクラスに対して、再現率と適合率の各組み合わせは次のような意味を持つ。

- 高再現率＋高適合率：モデルはそのクラスを完璧にハンドリングしている
- 低再現率＋高適合率：モデルはそのクラスを検出できないことが多いが、検出できた場合にはその結果は信頼できる
- 高再現率＋低適合率：モデルはたくさんそのクラスを検出するが、その中には実際には違うクラスであるものも混じっている
- 低再現率＋低適合率：モデルはそのクラスをうまくハンドリングできていない

記事の冒頭のストーリーで1000個製品があったとして、混同行列を作ってみよう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61079/968bf304-5c2f-f55d-191d-d1302906f7b6.png)

精度は前に述べたように96.2%だ。「不良品ではない」クラスの適合率は96.2%だが、「不良品」クラスの適合率は0/0になり計算できない。
「不良品ではない」クラスの再現率は1.0でこれはパーフェクト（不良品ではないとラベルがついていたものすべてをモデルは検出できた）だ。しかし「不良品」クラスの再現率は0.0で、これは最悪の結果（不良品をモデルは何も検出できなかった）だ。このように、**「不良品」クラスに関しては**モデルはうまく動かなかった、と結論できる。
不良品に関してF1スコアは計算不能で、不良品ではない製品にかんしては0.981だ。
この例では混同行列を見ることによって、モデルもしくはゴールを再考する必要があることがわかる。（ゴールを再考するとは何のかは後のセクションで述べる。）これによって、使えないモデルを使ってしまうことを防ぐことができる。

## ROC と AUROC
もう一つの面白い指標は ROC (Receiver Operating Characteristic) カーブだ。 ROC はクラスごとに定義される。以下ではそのクラスを C と書くことにしよう。

ある(入力)点xに対して、その点がCに含まれる確率 P(C|x) を出力するモデルを考えてみよう。
この確率に基づいて点xがクラスCに含まれているかどうかを決定できる。つまり P(C|x) > T (Tはしきい値)の場合に x は C に含まれる。もししきい値を T=1.0 とするとそれはすなわちモデルが100%の確信をもってxがCに含まれると思う時のみxをCだとみなすということだ。逆にしきい値を T=0 とする場合、あらゆる点はCに含まれるとみなされる。

しきい値Tの値を1から0に変化させていくと、その各々のTで (x, y)=(偽陽性率, 真陽性率) の点を作れる。このようにしてできる点をつなげたのが ROC 曲線だ。この曲線は(0, 0)から始まり、(1, 1)へと増えていく。（訳注：T=1の場合すべてを陰性とするので、真陽性率も偽陽性率も0。T=0の場合すべてを陽性とするので真陽性率も偽陽性率も1。）
良いモデルでは、曲線は横軸を増やしていくと縦軸の値は素早く1に達する。これはつまり再現率を高める場合に適合率がほとんど犠牲にならないということだ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61079/cc6f75c6-dda4-3d99-2176-4486624f2e65.png)

ROC 曲線に基づいて、モデルの評価に使いやすい別の指標を作ることができる。それが AUROC (Area Under the ROC curve) だ。（訳注: つまり、 ROC 曲線の下側の面積。 AUC ともいう。）
AUROC は ROC 曲線の良さをスカラー値にしたものだ。 AUROC は最も良い場合1.0となり、最も悪い場合0.5となる。
まとめると、 AUROC スコアが高いということは次のことを意味している：モデルがあるクラスについて高い再現率を叩き出すために適合率を犠牲にしてはいない。

# 本当の問題は何なのか
クラスインバランス問題への対応のしかたを話す前に、クラスインバランスとは何なのかをもう少し考えてみよう。
そのために、2値分類の基本的な側面を考察しクラスインバランス問題の根本的な問題問題点を把握できるシンプルな例を見てみよう。この例はこのあとのセクションでも登場する。

## クラスインバランスの例
C0とC1の２つのクラスがあるとしよう。

- クラスC0から生成される点は平均0分散4の1次元正規分布に従う。
- クラスC1から生成される点は平均2分散1の1次元正規分布に従う。
- データセットの90%はクラス0の点で占められる（つまり残り10%がC1）

下の図では、50個の点を上記の分布に従ってサンプリングした結果だ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61079/ad8a9ff8-8ed6-2bb5-96d0-91d2bdf65645.png)

この例ではクラスC0の曲線は常にクラスC1の曲線の上にいることがわかる。それゆえにすべての点についてそれがクラスC0から生成された確率はC1から生成された確率より高くなってしまっている。数学的にはベイズの定理を使って次のようにかける。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61079/00d788ab-bf7b-97c3-5b7a-e609750f6e1c.png)

この式を見ると事前分布 $P(C1)$ がかけられていることによって、あるクラスが常に別のクラスより確率が大きくなる仕組みがわかる。

このことが表しているのは、完璧に理論的な見地に立ったとしても、このようなデータで学習を行ったときには **識別器は常にC0だと答えるときに精度(accuracy)が最大になる** ということだ。つまり、もし目的が可能な限り精度を上げることで使えるのがこれらの特徴量だったとしたら常にC0と答えることは問題ではなく最良であるということになる。我々はそれを受け入れなければならない。

## ２つのクラスを分離できるか
上に挙げた例では２つのクラスは分離できないものだった（２つのクラスは近すぎてお互いに混ざっている。）
しかしながら当然、クラスインバランスなデータを扱うということが常に上記のように「２つのクラスが分離不可能」で「少数派のクラスを識別機は分離できない」ことを意味しているわけではない。例えばこれまでと同じようにクラスC0が90%を占めていてC1が10%を占めている別の例を考えてみよう。C0に属するデータは平均0分散4の1次元正規分布に従い、C1に属するデータは平均10分散1の1次元正規分布に従うとする。前回と同じようにこのようなデータをプロットすると下のようになる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61079/9b757e1c-4880-7976-e5d3-fee14104b692.png)

この例では以前の例と違って、C0の曲線は常にC1の上にあるわけではない。故にC1から生成された確率のほうがC0から生成された確率よりも高くなるような点が存在する。この場合２つのクラスはインバランスさを補う程度には分離されている。つまり識別器はあらゆる点がC0だと予測する必要は無いのだ。

## 理論上の誤り確率の最小値
最後に、識別器には理論上の誤り確率（間違ったクラスだと判断する確率）の最小値があるということを覚えておかないといけない。
今回のような識別器（特徴量が1つ、クラスは2つ）の場合、理論上の誤り確率の最小値は、グラフ上の２つの曲線両方の下にある領域の面積になる。（図の灰色部分）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61079/3a3fc1f5-f342-e807-b758-9536bd06a22c.png)

この直感的な結果は数学的にも説明できる。理論的な観点から見ると、理想の識別器はある点xが与えられたときに2つのクラスのうち最も確率が高いクラスを選ぶ。これはつまり、ある点xが与えられたとき理論上の誤り確率の最小値はその2つのクラスのうち確率が低い方のクラスの確率となる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61079/f145687c-ee83-ac60-bbff-7923f0670640.png)

なので、全体での誤り確率は以下のようになる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61079/3f1b723c-a8a4-2566-f9f6-326a3057c492.png)

この式はつまり、上のグラフの２つのクラスの曲線のうち小さい方の下側の面積のことだ。




# データセットの修正がいつも解決法とは限らない
まずはじめに、クラスインバランスなデータセットと出会ったときに最初に考えるのは、そのデータは現実を反映していないのではないか？ということだ。つまり実際のデータはほぼどちらのクラスのデータもほぼ同じ数存在するが、（例えばデータ収集の方法かなにかのせいで）収集したデータには頻度の偏りが生じてしまったということだ。

では、もし実際に現実のデータが偏っているが故に収集したデータセットも偏っている場合には、何ができるだろうか。次の2つのサブセクションでは、クラスインバランスの対応方法としてよく紹介されている、データセットそのものを修正するようないくつかの手法について説明する。特にこの記事ではオーバーサンプリングやアンダーサンプリング、シンセティックデータの生成のリスクについてと、特徴量を増やすことのメリットについて説明する。

## アンダーサンプリング、オーバーサンプリング、シンセティックデータの生成
これらの方法は、識別器を学習させる前にデータをバランスさせる素晴らしい手法としてよく紹介されている。かんたんにいうとそれぞれ次のような方法でデータを修正する。

- アンダーサンプリング： 多数派のクラスからサンプリングを行い、多数派のクラスの一部だけを残し頻度を減らす。
- オーバーサンプリング： 少数派のクラスのデータを複製して頻度を増やす。
- シンセティックデータの生成： 少数派のクラスの新しいデータ点を人工的に作り出し少数派の頻度を増やす（例： SMOTE）

これらの手法はすべてデータセットに現れるクラスの頻度を（部分的にもしくは完全に）バランスさせることを目的としている。しかし、両方のクラスが同じ頻度で得られるようなバランスの修正を本当に行うべきだろうか？もしくは多数派のクラスは多数派のクラスは多数派にとどめておくべきだろうか？そうだとしたら、どの程度のバランシングをするべきだろうか。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61079/b0ae70ba-ee79-51ab-9a73-f1362caa01b6.png)

このようなリサンプリングの手法（例えばC0とC1を同じ数だけ撮ってくるような）をするときには、 **識別器に対して学習時に誤ったデータの頻度を見せている** ということを覚えておかないといけない。
このようにして学習した識別器は、もとのままのデータセットで学習した識別器に比べて、現実のテストデータで低い精度（accuracy）を出してしまう。つまり、各クラスの実際の頻度という情報は識別をする際には重要な情報で、リサンプリングをすることでその情報は落ちてしまうということだ。

なので、それらのリサンプル手法を使わざるを得ない場合には注意深く扱わないといけない。もし何らかの目的（この例は次のセクションで見ていく）を持ってクラスごとの頻度を変えているなら良いのだが、特に問題に対する考えなしに単にデータをリバランスさせるのはナンセンスだ。
このサブセクションの結論としては、「 **リサンプリングのような手法でデータセットを改変することは、現実（のデータ）を変えることになるので注意が必要** 」であってその識別器が出力したものの意味は何なのかということを心に留めておくことが必要だ。

## 追加の特徴量を入れる
前のサブセクションでは学習データセットをリサンプリング（つまりクラスの頻度を変える）することは、その識別器の目的によっては良い方法ではないということを見てきた。
これまでこの記事で扱ってきた例では、２つのクラスはインバランスで、そしてうまく分離ができず、我々の目的が最高の精度（accuracy）を出すことにあった。その場合には常に多数派のクラスを答えるような識別器ができてしまうことは必ずしも問題とは言えず、それは単にそれらの変数に対してはそれ以上に良い方法は無いというのが事実だった。

しかしながら、精度（accuracy）という観点でよりよい結果を得られる方法はまだある。それは追加の特徴量を入れることでデータセットの情報を増やすことだ。最初の例（２つのクラスが分離不可能なもの）に立ち戻ってみよう。もしかしたら、そのデータセットに追加の特徴量を導入することができ、その特徴量によって２つのクラスを分離できるかもしれない。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61079/f28f86d1-35ae-ae0b-b6d1-dbbd18308cde.png)

前のサブセクションで話したデータの中の現実を変えてしまうようなリサンプリング方法と比べて、データの中の現実からもっと情報を引き出してくるこの手法はもしそれができるなら遥かに良い方法だと言えるだろう。





# 問題点を考え直すことのほうが良い解決策だ
これまでのところ結論はちょっと残念な感じだ。
もし

- データセットが真のデータを表象していて（訳注：真のデータと分布が一致していて）
- 追加の特徴量は取れず
- 目的が可能な限り精度（accuracy）の高い識別器をつくる

ことであれば、「ナイーブな振る舞い」（常に多数派のクラスを出力する）をすることは必ずしも問題ではなく、事実として受け入れるほかないということだ。（もちろんナイーブな振る舞いが識別機のキャパシティ不足によるものではないことが前提だが）

では、もしこの結果に不満があるとしたらそれは何なのか？この場合それが意味しているのは、なにかしら **問題がうまく設定されていない** （さもなければ上記の結果を受け入れるしか無い）ということで、満足な結果が得られるよう問題の設定をどのようにするかを考えていく必要がある。例をみてみよう。

## コストベース識別
「結果が良くない」という感覚は目的関数がうまく定義されていないことからくる。
これまで我々が前提においてきたのは「目標は高い精度（accuracy）の識別器を作ること」で、そのことは同時に「二種類の誤り（偽陽性と偽陰性）がどちらも同じコストである」ということを前提にしている。例で言うと、「正解がC1のところをC0だと予測すること」と「正解がC0のところをC1と予測すること」はどちらも同程度に悪い、ということを前提としているということだ。２つの誤りに対称性を前提にしている。

この記事の最初の例、つまり不良品(C1)と不良品じゃないもの(C0)を識別する問題を考えてみよう。この場合、不良品を検出できないことのコストは、誤って不良品じゃないものを不良品と判定してしまうコストよりも大きいことが想像できるだろう。なぜなら不良品を検出できなければカスタマーサービスのコストや、もし危険な不良品ならさらに裁判のコストなどがかかってしまう。一方不良品じゃないものを不良品としてしまってもその１品のコストしかかからない。このように真のラベルがC1のところをC0と予測することがその逆よりはるかに悪いケースがある。この場合2つの誤りは非対称なのだ。

ここで誤りに関して以下のコストがかかる場合を考えよう

- 予測がC0だが真のラベルがC1だったときのコスト： P01
- 予測がC1だが真のラベルがC0だったときのコスト： P10 (0 < P10 << P01)

こうすることによって、目的関数を再定義できる。もはや最高の精度(accuracy)は目的ではなく、上記の予測コストを最小化することが目的となったのだ。


## 理論上の最小コスト
理論的には、以前定義した誤り確率を最小化するのではなく、下記の予測コストを最小化することになる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61079/6690b3fd-c26c-9b45-651b-959f0f971703.png)

ここで C(.) は識別器の関数を表している。なので予測コストを最小化したければ、理論上の理想の識別器は以下を最小化する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61079/6f7c3ce2-afa6-1c68-b98a-81504cf73622.png)

もしくは上記の式を x の確率密度で割ってやると、 C(.) は以下を最小化することになる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61079/52c5f833-e2e4-1d66-36e0-6940afda5031.png)

この目的関数を使うことで、理論上の理想の識別器は次のようなものになる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61079/a68ee306-c753-ac42-1b4c-177c980ad1a6.png)

もし２つのコストが同じだとしたら、上記の式は「古典的な」識別器（つまり精度(accuracy)を最大化するもの）になることが分かるだろう。

## 確率のしきい値
この「コスト」を識別器に導入するためにまず最初にできることは、学習後の調整だ。
このアイディアでは、まずは識別器を下記の確率を出力するように（とくにコストのことは考えず）普通に学習させる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61079/ba4f8a5c-56fe-97f9-7f30-a64ff5b8eed5.png)

次に、下記の式を満たす場合にクラスをC0と予測する（C1も同様に。）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61079/19a1de54-f9c9-15fe-4a63-3b8e6f27128b.png)

こうすることで、それが与えられた点の確率を出力をするものでさえあれば識別器は何でもかまわなくなる。例えばベイズ識別器をデータから学習させ、得られた確率に対して上記のコストで重み付けをしてやることができる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61079/c19b4a93-5a12-ffbf-5811-bae1d2d0c1d8.png)

## クラスリウェイト
クラスリウェイト（Classes reweight）ではコストの非対称性を直接学習時に組み込む。そうすることによって、各クラスの確率の出力は誤りのコストを含んだものが獲得されるので、識別時には単純に0.5でしきい値をきることで使うことができる。

いくつかのモデル(例えばニューラルネットワークの識別器)では、学習時にコストを組み込むことは目的関数を修正することで実現できる。識別器にはいままでどおり

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61079/1a91af30-2a4d-3dc6-15b6-dfa00e464054.png)

を出力させたいのだが、この場合下記のような目的関数を最小化するような学習を行う。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61079/5f9adcdf-9309-ca1f-605c-85492815d1c6.png)

その他のモデルのいくつか（例えばベイズ識別器）では、クラスごとの頻度をかえるために、クラスごとの頻度に誤りコストの情報を入れ込んだようなリサンプリングを行うことができる。もしP01とP10(P01>P10)のコストが有った時、つぎのどちらかを行う。

- 少数派のクラスを P01/P10 の割合でオーバーサンプリングする
- 多数派のクラスを P10/P01 の割合でアンダーサンプリングする

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61079/c80f332c-8974-a8f6-1ac1-c8a2aa75faae.png)

# まとめ
この記事で覚えておいてほしいことを列挙する。

- 機械学習のアルゴリズムを使う時はいつでも、評価指標は注意深く選ぶ必要がある。評価指標は、モデルが目的に沿ってどれだけうまくやっているかの全体像を把握させるようなものでなければならない。
- クラスインバランスなデータセットに対応する際に、もし各クラスが得られている特徴量ではうまく分離不可能でかつ目的が精度(accuracy)を上げることであるならば、理想の識別器は常に多数派のクラスを答える「ナイーブ」なものになってしまうかもしれない。
- リサンプリングは使える手法だが、注意深く扱わないといけない。それらは単独の解決法として使うべきではなく、目的を達するために解くべき問題の再定義と合わせて考えないといけない。
- 問題を再定義することはクラスインバランス問題に対応するときのもっとも良い方法だ。識別器としきい値はコストを最小化するようなよく吟味された目的とセットで考えないといけない

この記事では「Stratified sampling」のような、識別器をバッチ学習するときに便利なテクニックについては何もはなしていない。クラスインバランスの問題に対応するときにはそれらのテクニックは（バッチ内の頻度のぶれを削除することで）学習を安定させてくれる。

最後に、この記事の一番のキーワードは何かというと、「目的」だ。自分たちが何をやりたいのかを正確に知ることはクラスインバランスな問題への対応の助けになるだろう。目的を完璧に定義することはまず最初にやることであり、機械学習モデルを作るためのいろいろな選択をするときのスタートポイントとなるだろう。