データセットの分布に見る機械学習の性能と改善

## はじめに

どれだけ、どのようにデータセットを準備すれば「実際に役に立つ」機械学習システムができるのでしょうか。
そのシステムの使われ方によるのは勿論ですが、日々新しい方法論が提案されていることもあり、答えは自分で立てるしかないようです。

そこでこの記事では、データセットの分布の大きさを比較した実験を通じて、その性能への影響を確認してみます。また初歩的な話ですが、データのleakがもたらす影響も併せて見てみます。

この記事は、[githubのdaisukelab/ml-urban-sound](https://github.com/daisukelab/ml-urban-sound)に公開しています。今回記事だけでわかるようにしたつもりですので、コードについては後日の解説とします。

## 例題の設定

### 取り扱う問題 〜 Urban Sound Classification

環境音の種類を分類する[Urban Sound Dataset](https://serv.cusp.nyu.edu/projects/urbansounddataset/)から、よりハンディな[UrbanSound8k](https://serv.cusp.nyu.edu/projects/urbansounddataset/urbansound8k.html)を使い、10クラスの分類問題を解きます。

この時に与える学習&検証データを様々なバリエーションにして、学習時の性能指標acc/lossがどうなるかを評価します。

### データセット[UrbanSound8k](https://serv.cusp.nyu.edu/projects/urbansounddataset/urbansound8k.html)について

[Urban Sound Dataset](https://serv.cusp.nyu.edu/projects/urbansounddataset/)は[Freesound](https://freesound.org)というサイトから許諾を得て加工されています。このサイトに世界中から投稿されていて、wavデータ一つが一つのIDを表しているようです(Freesound IDまたはfsID)。

(Freesoundホームページより)
<img width="410" alt="1.png" src="https://qiita-image-store.s3.amazonaws.com/0/99911/96c0e531-9b3f-79f7-713d-f9ce8188891a.png">

今回使う[UrbanSound8k](https://serv.cusp.nyu.edu/projects/urbansounddataset/urbansound8k.html)のデータは、元の音データから最長4秒ずつに切り出されています。

★音データにはありがちなようですが、ここに、はまりやすい問題があります。
いわゆる「[Data Leakage](https://tjo.hatenablog.com/entry/2016/01/27/235620)」ですが、このデータセットは、普通に`train_test_split`すると漏れます。

各データが元のwavデータから切り出されたということは、元のwavデータが同じなら、特徴量がほとんど同じになります。ですから、同じ元wavデータ由来のデータは、全てtrainかvalidどちらかに割り当てないといけません。混ざると、成績が非常に良くなってしまいます。

そのためこのデータセットはデータソース別に10個のフォルダに分割されていて、例えばfold1〜8までを学習用、fold9と10を評価用、という形で安全に用途を分けることができます。(実は厳密にはわずかに混ざっていました。詳しくはノートブックでそのあたりを検証しています)

### 学習データのバリエーション

バリエーションはこの通りです。

| バリエーション | 解説 |
|--------------|-----|
| T4Leak | 元データセットそのままだがfsIDを混ぜる(leak) |
| T4NoAug | 元データセットそのまま、train/valid/testのデータを混ぜない |
| T3Aug1 | KerasのImageDataGeneratorでAugment (ランダム左右シフト・反転) |
| T2Aug2 | T3Aug1 + [強力なmixup](https://qiita.com/yu4u/items/70aa007346ec73b7ff05)を追加 |
| T1AugAll | T2Aug2 + データのランダム消去を追加 |

つまりAugmentationを使い、データセットの分布を __ある程度__ 定量的に大きさ別で用意してみました。
下に行くほど分布が大きくなるように意図しています。

### 学習モデルその他詳細

音データは一度16kHzに統一して変換し、その後40要素の[melspectrogram](https://librosa.github.io/librosa/generated/librosa.feature.melspectrogram.html)で10msごとに変換、1データが(40, 552)の二次元画像的配列にしました。一般的な音声認識の扱い方に準じています。

<img width="390" alt="mels.png" src="https://qiita-image-store.s3.amazonaws.com/0/99911/a6b213a6-2d0f-2c4c-9f93-19a40d112567.png">

このデータに対して、CNNを使ったモデルを学習します。


## 学習の実験と結果の比較

各データのバリエーションを、一から初期化して、同じCNNのモデルで学習します。
すべてのバリエーションの結果を比較してみましょう。

### accの観察 (学習時の正答率)

正答率は、やはり2つのグループに別れました。Augmentationしているグループと、そうでないグループ。

<img width="503" alt="1acc.png" src="https://qiita-image-store.s3.amazonaws.com/0/99911/b3989340-e05f-c6f5-74bc-519657947f71.png">

…ですが、__予想に反してT3Aug1「KerasのImageDataGeneratorでAugment (ランダム左右シフト・反転)」しているはずのデータも、accが高いのです__。つまり、Augmentationになっていなかったようです。CNNを使っているため、左右にシフトする程度ではCNNが同じとみなすのに得意とするデータだと気付かされました。

### lossの観察 (学習時のloss)

accの裏返し、同じ結論となりました。

<img width="512" alt="2loss.png" src="https://qiita-image-store.s3.amazonaws.com/0/99911/be0f9204-8b22-ac07-e217-82c02821f38c.png">

### val_accの観察 (検証時の正答率)

分かりやすい結果になりました。

<img width="508" alt="3val_acc.png" src="https://qiita-image-store.s3.amazonaws.com/0/99911/3c18798c-c924-121b-d758-09fe910bc9a2.png">

- T4Leakは、飛び抜けて良い制度になっていて「OK!」と一見安心させる結果です。※リークしていますので。
- [mixup](https://qiita.com/yu4u/items/70aa007346ec73b7ff05)を使うことで、10%性能が向上していることがわかります(T2Aug2, T1AugAll)。
- オーギュメントなし(T4NoAug)、あるいは効果なし(T3Aug1)では約60%の性能、十分Augmentされた場合に比べ10%性能が出ない。

### val_lossの観察 (検証時のloss)

lossを見ると、過学習していてデータのバリエーションが取れていないのが分かりやすく確認できます。またリークがどれだけ罪なのか、良く学習できているように見えており、検証の結果で判別できないこともわかります。

<img width="502" alt="4val_loss.png" src="https://qiita-image-store.s3.amazonaws.com/0/99911/5458d904-207b-5e4c-ddbf-13e021e7cdb3.png">

- オーギュメントなし(T4NoAug)、あるいは効果なし(T3Aug1)は、val_lossが時と共に悪化しています。これは分かりやすい過学習で、データまたはモデルに改善の余地が大きいことを表してくれます。
- [mixup](https://qiita.com/yu4u/items/70aa007346ec73b7ff05)を使ったモデルはだいたい同じ結果です(T2Aug2, T1AugAll)。学習すればまだ性能が良くなりそうです。
- リークさせたもの(T4Leak)は、悪魔のような結果です。val_accも良い、val_lossも良い。ハッピー??<br>
開発完了と叫んで即飲みに行きたくなる結果です。「実際のデータでなぜ性能が出ないのだ…」と苦しむことになります。

## まとめ

- データの分布を増やすことがやはり基本、ということだと思います。今回は実験ですので、Augmentationを使って擬似的にデータ分布を広げました。そもそも、元のデータ分布が広いことが必要です。第一に、なんとかデータを取れないかということにコストをかけるべきなのでしょう。Augmentationはその次です。
- acc/loss/val_acc/val_lossの学習曲線、この指標は取り急ぎの学習データ分布が十分かを追っていくために有効でした。リークしない限りは…
- Augmentしたつもりが、ランダムに左右シフト・反転する程度では全然効果がない例もありました。つまりモデルが同じように扱えるように加工したのでは意味が無く、一見手を打っているようにみえるのが良くない点です。<br>
∴ Augmentationでは、Model視点で確実に別物に見えるように加工すること。
- あなたのデータ、本当にリークしていませんか? 今回のデータセットをよく考えていくと、混ざっていることが分かりにくいケースは実データでは結構ありえるのではないでしょうか。<br>
データ分布の大切さとともに、その由来には本当に気をつけたいと思った実験でした。