Tensorflow Speech Recognition Competition 参加記
はじめまして。Maxwell氏の大学の後輩、Ireko8です。今回、tensorflow speech recognition competitionに参加して72位になり銅メダルを取得しました。Titanicをのぞけば初めてのKaggle competitionでしかもはじめて自分がまともにディープのモデルを使うコンペでした。
経緯
Maxwell氏の会社のもとでインターンをしていたとき、Google Speech APIなどを用いたSpeech to Textの簡単なシステムを作ったりしていたので、今回のコンペで音声認識の勉強になると思い参加しました。
コンペ概要
- タスク
約1秒の音声が指定された十種類のコマンド("on"や"left", "stop"など)+silence(音声が入っていない)+unknown(音声は入っているが指定されたコマンドではない)のどれにあたるかを分類します。
- Rasberry Piで実行できるモデルで一番精度のよかったものをつくるPrizeもあったのですが割愛します。
ルール
- External Dataは禁止。Pretrained Modelの使用も禁止。
データ
訓練データ
30種類程度の単語(分類対象のコマンドを含む)を録音した音声データおよそ6万件。ファイル名にはその音声を録音した人のidがついています。
- 同一の人物が発した音声は似通っているため、学習の際に訓練データと検証データの両方に同じ人物の発した音声が含まれているとleakになるので、データはidが重複しないように分割する必要があります。
訓練データにはsilenceと明確にラベリングされたデータはありません。silenceのデータは自分で生成する必要があります。ただし、訓練データを作成するときに用いられたバックグラウンドノイズ(ホワイトノイズなど)があり、External Data禁止なので基本的にこれを用いることになります。
- テストデータ 音声データ16万件弱。訓練データより多い上に、訓練データには含まれていない単語の音声(Unknown Unknowns)が存在します。
手法
- モデル スペクトログラムを入力とするVGG-likeな二次元CNNとwaveデータをそのまま入力とするVGG-likeな一次元CNNの二つを作りました。
- augmentation
リアルタイムに行ったaugmentationは、音声をフリップする(wavデータを-1倍する、波形の正負を反転する)のとノイズを付加する、ボリュームを変える、一秒ではない音声についてはランダムで切り出すor前後に無を追加の3つです。ピッチを半音の範囲でずらす、スピードを変えるといったaugmentationもofflineで行いました。librosaが重すぎて、ピッチシフトとスピードチェンジはリアルタイムにできませんでした。
- 音声フリップに関しては実は効果がなかったのでは、という疑いがあります。
- silence data 訓練データのバックグラウンドノイズから切り出したwavデータにランダムにボリュームを変えた20000件を用いました。いま考えると、バックグラウンドノイズを連結させたwavからEpochごとに必要なぶんだけ切り出したほうがよかった気がします。
- Pseudo-Labeling & Ensemble
5foldで学習させたあと、
CrossPseudo-Labelingを行い、入力データやaugmentationを変えたモデルをaveragingしました。- Pseudo-Labelingとはテストデータの予測ラベルのうち予測スコアが高いデータを訓練データに追加することでデータ数を増やす半教師あり学習です。このままですと自分自身の出力を訓練データに含めていることになるため、若干leakyになってしまいます。そこでまず独立に学習させた複数のモデルからテストデータの予測スコアを計算し、特定のモデルのpseudo-labelを作る際はそのモデル以外のモデルから計算された予測スコアをensembleした予測に基いてラベルを作ります。
これをCross Pseudo-Labelingと呼ぶそうです。たとえば5foldで独立に学習させたモデルがあった際、1fold目のモデルのためのpseudo-labelを作る際には2~5fold目のモデルの予測を用いる、といった具合です。詳細はiwiwi氏のstatefarm解説スライドを参照してください。修正(2018/2/1)statefarmの解説を読み直したら、自分の手法はCross Pseudo-Labelingではないですね……。
- Pseudo-Labelingとはテストデータの予測ラベルのうち予測スコアが高いデータを訓練データに追加することでデータ数を増やす半教師あり学習です。このままですと自分自身の出力を訓練データに含めていることになるため、若干leakyになってしまいます。そこでまず独立に学習させた複数のモデルからテストデータの予測スコアを計算し、特定のモデルのpseudo-labelを作る際はそのモデル以外のモデルから計算された予測スコアをensembleした予測に基いてラベルを作ります。
敗因
僕の用意したモデルはpublic LB 0.85強 ~ 0.86程度の性能だったのですが、終盤3日くらいで上位陣がsingle 0.87 ~ 0.88の性能のモデルを使っていることがわかったので、単体の性能で負けました。完全に見込みが甘かったとしかいえないです。
また、締め切り一週間前にトップがpublic LB 0.86のモデルを3つdiscussionに投下してきたのですが、そのコードが手元で再現できなかったのも痛いです。今回、discussionで上げられていた一次元CNNのモデルは手元で全く再現できませんでした。
最後の一週間くらい、一次元CNNのほうが二次元CNNより性能が悪かったので、そっちの性能向上に注力したのですが、うまくいかなくて諦めたのですが、二次元CNNのほうが性能向上は簡単だったので、これも誤算だったとしかいえません。
細かいところだと、
無音データの生成が足りなかった
テストデータに異常な数の無音が含まれていることには気づいていて、silenceデータ生成のときにも20件くらい無音を混ぜたのですが、もっとたくさん生成してもよかったと反省しています。ただ、結論からいうと、privateに含まれるsilenceやunknownの割合は10%弱で、このデータを大量に生成するとoverfitする可能性はあった気がします。(しかし、3%上がったと言っている方がいるので実際はわかりません)
ミスラベルを直さなかった
rightのデータがsilenceだった、などラベルミスがあるのは気づいてましたが手作業で取り除くのは負担が大きく、10~20%くらいミスラベルあってもokらしいので無視しました。しかし上位陣のソリューションではミスラベルを直すと精度が上がったらしいので、やるべきでした。
validationがとれなかった
訓練データに含まれていない単語がテストデータに含まれるというデータの性質上、正しくvalidationするのが困難でした。
shakeupに怯えすぎた
訓練データにはない音声がテストデータに入っている点、Public LBのラベルの分布とsubmitのラベルの分布が全く異なる点から、big shakeがあるのではとささやかれていました。一方でコンペのdescriptionにあるとおり、全てのデータがLBに用いられるわけではなく、さらにCVとLBに相関がとれていたので、そこまで心配する必要もないという気持ちがありました。蓋を開けてみれば、ほとんどのデータがダミーだったので杞憂だったわけで、minor shakeupで済みました。
Mel系の特徴量を使わなかった
MFCCなどの特徴量を上位陣は使っていました。MFCCへの変換が重すぎるので早々に見切ってしまったのは完全に誤りでした。
- ensembleのweightの決め方がわからなかった
上位陣のsolution
基本的にたくさんモデルを作ってensembleするのが主流でした。
-
raw wave・melspectrogram・MFCCの三種類のモデル(single public LB 0.86)をpseudo-labelingしてensembleというオーソドックスな手法でした。ただし、他のチームメンバーはsingle LB 0.88程度の強いモデルを持っていたみたいです。
-
log-mel filterbankを使った特徴量でCNNでした。この方はsingleでprivate 90.9%の精度を出すというすごいことをやっていました。wavを20~50のchunkに切り出しstandizationする、VLTPを使う、というaugmentationが大変有効だったそうです。
-
pseudo-labelingに加えて、pseudo-labelだけでまず学習させてその重みを初期値としてtraining dataでfine-tuningするという手法を使っていました。ねじゅみさんのメモにもある手法ですね。
-
stacking。silenceに関しては、confidence scoreの低いものでpowerが小さいものをtest dataから取ってきたみたいですね。
-
ベータ関数からサンプリングしたweightで二つのデータを混ぜるmixupが有効だったみたいです。このチームではシンプルにwavを混ぜていたみたいです。
使わなかった手法
RNN
16000Hzでサンプリングされた約1秒のデータということもあってRNNは使いませんでした。実際、上位陣の多くはCNNで畳み込んでいました。
silenceとそれ以外のclassifierを作る
テストデータの分布が訓練データの分布が異なりunknownやsilenceに偏っていたため、silenceやunknownに特化したclassifierとそれ以外のclassifierの二つを作る、というアイデアは考えてました。しかし、特定のlabelにoverfitするかも、と日和って採用しませんでした。今回一位のHeng氏も同様のアイデア(https://www.kaggle.com/c/tensorflow-speech-recognition-challenge/discussion/47083#latest-268685)を出していましたが実装はしていなかったみたいです。
test time augmentation
一位のHeng氏が使って(ピッチシフトなど)有効だったみたいです。
今回参考にしたサイト
-
Kaggleのテクニックについて簡潔にまとまっている素晴らしいメモです。
-
人物でvalidationを分けるなど、今回のコンペで類似するところが多々ありました。ただ各々のテストデータが似通っているわけではなかったので、test dataに対するunsupervisedは行いませんでした。
- kaggler-jaのログ
装備
GTX 1080とTesla K80(コンペで配られたGCPの500ドルクレジットを使いました)
終わりに
今回はいろいろ見込みが甘かったので、次はもっと上位に行きたいです。