DeepLearning
音声認識
DataScience
Kaggle
SpeechRecognition

kaggle TensorFlow Speech Recognition Challengeの上位者のアプローチを紹介する(後編)

INTRODUCTION

前回に引き続き、kaggleのTensorflow Speech Recognition Challangeの上位者の
アプローチを紹介いたします。

これはこの記事の続きです。  
先にそちらをご覧ください。

今回は
1. Network Architecture
2. optimizer
3. resampling
4. normalization / standarization
5. data augmenation
6. silenceクラスへの対応
7. 未知のunknonwへの対応
8. 軽量化・高速化の工夫
9. LBのデータのトレーニングへの利用

について触れます。

nerwork architecture

CNNベースのものがほとんどで、DNN以外のアプローチはほぼ見られません。
2ndの人以外は画像に対しても利用されるオーソドックスなアーキテクチャーでした。

なお、以下はすべてアンサンブル前のレベル1のモデルのarchitectureを示しています。
またオリジナルから多少変更されている場合もあります。  

  1. Resnet (1st CherKeng, 3rd Little Boat, 4th P.Ostyakov, 10th tugstugi)

    • Little Boatは9 layer
    • tugstugiは32 layer
  2. VGG (1st CherKeng, 3rd LittleBoat, 4th P.Ostyakov, 4th feels_g00d_man, 7th JihaoLiu, 10th tugstugi)

    • feels_g00d_man, tugstugiはbatchnormalizationを加えたVGG19を使ったようです。
    • Little Boatは終わりがGMPとGAPのVGG
    • JihaoLiuはVGG11
  3. RNN (4th P.Ostyakov)

    • GRUかLSTMか不明
  4. Densenet((4th feels_g00d_man, 3rd LittleBoat, 10th tugstugi)

    • feels_g00d_manはDensenet201
    • LittleBoatはDensenet121
    • tugstugiはDenseNet 100, 190
  5. Senet (3rd Little Boat)

    • Little BoatはSenet18
  6. wide resnet (10th tugstugi)

    • tugstugiはwide resnet 28-10, ,28-10-dropout, 52-10
  7. Resnext (10th tugstugi)

    • tugstugiはResNext 29, 8x64
  8. Dual Path Network (10th tugstugi)

    • tugstugiはDPN 92
  9. "carefully designed" 多層CNN (2th O'Malley)

    おそらくアプローチを公開している中では、一番アーキテクチャーを工夫しており、
    single modelでprivate LB 90.9と群を抜いていました。しかも圧倒的にシンプルです。  
    きちんと一層一層意味を考えて、オリジナルのnetworkを設計しています。
    (本当に狙い通りに働いているかは分かりません。)
      以下に図示いたします。

    2D convolutionを使っていますが、最初のものを除き、
    一度にfrequencyの畳み込みとtimeの畳み込みを同時に行わないようにしています。

注:こちらの表は自分がkerasで再実装し、その出力を改変したものです。

2018/02/11 fix:
こちらは時間軸方向が16000となっていましたが、間違いです。
log mel filterbank energyに変換されていますので、
当然時間軸方向の次元も変わっています。
fftのwindowサイズとstepサイズが明記されていないため、
ここではtensorflowのspeech recognitionのtutorial内の
window size=30, step size=10に従っています。

_________________________________________________________________
Layer (type)                 Output Shape              Param #    O'Malleyの意図
=================================================================
input_1 (InputLayer)         (None, 1598, 120, 1)     0           1秒間16khz、120次元のlog mel filterbank energyの入力
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 1598, 120, 64)    1344        ノイズ軽減
(64(filter num), 
(7, 3)(kernel size))
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 1598, 40, 64)     0          よく使われる40次元に周波数の次元を圧縮
(1, 3)(pool size)
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 1598, 40, 128)    57344       周波数帯域の局所的パターンを見つける
(128, (1, 7))
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 1598, 10, 128)    0           スピーカーによるバリエーションを許容
(1, 4)
_________________________________________________________________ 
conv2d_3 (Conv2D)            (None, 1598, 1, 256)     327680      残っている周波数帯域の違いを扱えるようにしつつ、次元を圧縮。
                                                                  phonemeのような特徴が残ったと考えている。
(256, (1, 10), 
padding="valid")
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 1598, 1, 512)     917504      キーワードの中の時間的につながっているコンポーネントを探す
(512, (7, 1)) 
_________________________________________________________________
global_max_pooling2d_1 (Glob (None, 512)               0          すべてのコンポーネントを収集
_________________________________________________________________
dropout_1 (Dropout)          (None, 512)               0          
_________________________________________________________________
dense_1 (Dense)              (None, 256)               131328     なぜか性能が上がるためfully connected層を追加
_________________________________________________________________
dense_2 (Dense)              (None, 12)                3084       クラスの予測を出力
=================================================================
Total params: 1,438,284
Trainable params: 1,438,284
Non-trainable params: 0
_________________________________________________________________

optimizer

Adamを試みた人もいるようですが、結局はほとんどの人がSGDを使ったようです。

  1. SGD (4th feels_g00d_man, 10th tugstugi)  

    • feels_g00d_manの場合
      • learning rate = 1e-3
      • momentum 0.9
      • weight decay 0.0001
    • tugstugiの場合

      • learning rate 0.01
      • momentum 0.9
      • weight decay 0.01

      learning scheduler
      - patience 5
      - gamma 0.1
      - max-epochs 70

    どちらもプラトーになったら、学習率を減らしています。

resampling

  1. balanced class sampling (1st)
    トレーニング用のデータ内には他のクラスのサンプルに比べ、圧倒的にunknownクラスのものが多いです。
    oversamplingやundersamplingを用いて、バッチに含まれるクラスごとのサンプル数をバランスがとれた状態にすると有効でした。
    具体的にどちらを使ったのかは分かりませんが、
    同一クラス内のバリエーションが多いunknownのサンプル数をわざわざ減らすというのは考えにくいので、
    おそらくunknown以外をover-samplingをしたと推測されます。

normalization / standarization

  1. windowed peak volume standarization (2th O'Malley)
    一つのサンプルを波形のまま20~50のチャンクに区切り、それぞれのチャンク内で音量をstandarizeします。
    もし全体でstandarizeを行なうと、 単語の長さによって、平均が変わってしまうため、 区切ることで単語の長さの影響を和らげることができます。

    • LB scoreの改善
      • 2% (O'Malleyの場合)

data augmentation

前述または後述の通り、実は何種類かのノイズ用ファイル(1 min)が訓練用データセットに含まれていました。
順当に考えると、このノイズを他のファイルに合成してaugmentしたくなるところです。
しかし自分が調べた範囲では、意外なことに上位者はこのアプローチは使わなかったようです。トラップですな・・・

  1. time stretching (2th O'Malley, 4th P.Ostyakov)
    ピッチをそのままにテンポを変えます。元のものより話すスピードをゆっくりにしたり、速くしたりします。

    • LB scoreの改善
      • 1% (O'Malleyの場合)

  2. pitch shift (4th P.Ostyakov)
    テンポをそのままにピッチ(音の高低)を変えます

  3. time shift (4th P.Ostyakov)
    1秒間の音声の中で、単語が発声されているのは一部の部分だけです。
    しかし、人によって発声のタイミングにずれがあります。
    そこで発声している部分をずらすことで、対応できるようにします。

  4. random noise (4th P.Ostyakov)
    ピンクノイズやホワイトノイズといったランダムノイズを生成して、元の音声に合成します。ノイズの種類はわかりません。
    ちなみにSoxにはtype is one of sine, square, triangle, sawtooth, trapezium, exp, [white]noise, tpdfnoise, pinknoise, brownnoise, pluckなどがあります。

  5. VTLP (2th O'Malley)
    それぞれの周波数成分にスカラーのwarp factorをかけ、新しい周波数成分に変換します。
    例えばwarp factorが1.1なら、100hzの周波数成分の強度が変換後には110hzの強度となります。

    log-melと一緒に使う場合、mel filterbankに対してVTLPを適用し、そのfilterbankをspectrogramに適用します。

    詳しくはVTLPの導入論文または
    ASRに適用した論文を参照ください。

    • LB scoreの改善
      • 1% (O'Malleyの場合)

  6. mixup (7th JihaoLiu, 10th tugstugi)
    今回かなり効果があったようです。
    簡単に言うと、2つ以上のサンプルを線形に結合して人工的なサンプルを作ることで、
    サンプル間の空間にデータを補完しようという手法です。
    言葉で説明するより、以下の式を見たほうが早いかと思います。

       \tilde{x} = λx_i +(1−λ)x_j \\
       \tilde{y} = λy_i +(1−λ)y_j
    

    $\tilde{x}$, $\tilde{y}$は新しく生成されたサンプルの特徴量とラベル、$\lambda$はbeta分布からランダムにサンプリングされたものです。
    JihaoLiuの場合、beta分布の$\alpha$, $\beta$はともに2に設定したそうです。
    (xi, yi), (xj, yx)は両方とも元のトレーニングデータから選ばれたサンプルの特徴量とラベルです。
    詳しくは原著またはこのブログのポストをご覧ください。

    • LB scoreの改善
      • 2.9%の改善 (JihaoLiuの場合)
      • 2.03%の改善 (tugstugiの場合)

  7. testtime-augmenation (1st CherKeng)
    なんとtrainingデータではなく、testデータをaugmentします。
    最終的な予測値はaugmentationしたものから得られた予測値を平均します。
    意外なことに有効だったみたいです。

    CherKengはaugmentationの種類として、

    • time-shifting
    • 音量の増減
    • time stretching

    を使ったそうです。

silenceクラスへの対応

  1. power levelの小ささとsilence以外のクラスの事後確率をもとに判断 4th P.Ostyakov

    どのクラスにも判断しづらくかつspectrogramのpower levelが小さいものをsilenceとみなします。

    • 手順

      1. silence以外の分類器を作成します。
      2. その分類器にLBデータのサンプルを入力するとクラスの事後確率が得られます。
      3. サンプルごとに最大のクラスの事後確率を取得します。
      4. 最大のクラスの事後確率に基づいて、昇順にLBデータのサンプルをソート
      5. 上から(つまり低い順に)K番目までsilenceの候補を取得。
      6. 候補のデータから得られたspectrogramのpower levelがLより小さければsilenceとみなします

      Ostyakovの場合、K=10000, L=1を使ったそうです。

    • LB scoreの改善

      • 0.5% (Ostyakovの場合)

  2. pseudo-labeling
    後述

未知のunknonwへの対応

  1. "double words"(2つのunknownのサンプルを切ってつなげる) (4th feels_g00d_man)
    2つのunknonwのサンプルをそれぞれ振幅が最大のところで切って、つなげます。(コード)
    例えば"stop"と"dog"からは"stog"が作られます。
    実際に存在しない言葉でも有効なようです。

    LB scoreの改善

    • 0.24 %

  2. pseudo-labeling
    後述

軽量化・高速化の工夫

  1. model distillation(1st CherKeng, 7th JihaoLiu)
    高性能だが大きなモデルの知識を、より小さいモデルに移します (原著)
    詳しくは調査中です

LBのデータのトレーニングへの利用

LBのデータのトレーニングへの利用してさらにスコアをあげる方法です。
当然のことながら、手動でLBのデータをラベル付けすることは禁止されています。

そこでモデルの出力したクラスの確率など何らかの基準に基づいて、
確実だと思われるラベルを自動でつけます。

テストデータが未知のデータへの性能を測るためにあることを考えると、
実務ではまずできません。
(というかそれが許されるならちゃんとラベル付けしてtrainingデータに足しますよね)

こういう点はコンペだなあと思います。
最近のkaggleのコンペだと割とよく行われるようです。
さすがにここまでやるようになってくると、
DeepAnalyticsみたいに、最終的な評価用のデータは別にしたほうがいいんじゃないですかね...?

  1. pseudo labeling (1st Cherkeng, 3rd)

    性能が高いモデルが予測したラベルを正解ラベルとしてtrainingデータに加えます。

    新しい情報を学習すること以上に、主にLBデータの分布を学ぶことに使われたようです。

    加える方法はいくつかあります。

    • 予測されたクラスの確率が閾値より高いものだけをすべてトレーニングに加える。
    • epochごとに20%~35%を選んでトレーニングデータと加える。
    • トレーニングデータで訓練させた後、fine tuningをLBデータで行う。

    LB scoreの改善
    - 2% (Little Boatの場合)

outro

以上が今回の上位者のアプローチの概要でした。

アンサンブルの手法については、
特にこのコンペ特有というわけではなかったので、  
触れませんでした。

コンペの上位者のアプローチをちゃんと比較してまとめるのは初めてなのですが、
思った以上に戦略がばらけたようです。

また似ていても、結果に差がある参加者のアプローチと上位者のアプローチの
微細な違いも気になる点です。

余談

統計はありませんが、PyTorchを使っている参加者が目立ちました。
Kerasを使用していた人達がPyTorchに乗り換える流れが来ているのでしょうか・・・?