toxic text comment classificationの手法紹介

今更感がありますが、[kaggleのtoxic text comment classificationコンペ](https://www.kaggle.com/c/jigsaw-toxic-comment-classification-challenge)で使われた 上位者の手法を紹介します。 
ここでは、基礎レベルからコンペ特有のテクニックまで紹介いたします。  
今テキスト分類やるならどうするか、何を調べればいいか参考になれば幸いです。
それぞれのテクニックやモデルの使用者は複数いますが、そのうち代表者だけを参考として上げさせていただきます。

# コンペ・タスクの概要
## データ
wikipediaの編集ページのコメント欄

## タスク
マルチラベル  
有害なコメントに以下の6種類のラベルをつける  
1. toxic
2. severe_toxic
3. obscene
4. threat
5. insult
6. identity_hate

## タスクの困難な点
1. テキストに起因
  - 英語以外の言語がある
  - コーパスにないボキャブラリーが多い（検知を回避するために、わざと間違えたりしているようです。）
  - 表現が砕けている。
  - 英語圏の人じゃないと一見判断が難しい（遠回しな言い方）をしている
2. タスク設定に起因
  - マルチラベル
  - スコアがROC-AUCなので、正解率などのメトリックよりアンサンブルに工夫が必要

## コンペのサマリー
- 非常にスコアが近いところに集まっている(1位のprivate LBが0.9885で、
1000位のprivate LBが0.9861)、
- ベースラインとなるモデルと比べて、一つのアプローチがスコアを大きく引き上げることがなかった
- 上記の結果stacking/アンサンブルの元となるモデルのバリエーションをいかに増やすかが、最終的な汎化性能を上げる決め手になった。  


# 手法
## 参考にした上位者
1. [1st Chun Ming Lee](https://www.kaggle.com/c/jigsaw-toxic-comment-classification-challenge/discussion/52557)
2. [2nd neongen](https://www.kaggle.com/c/jigsaw-toxic-comment-classification-challenge/discussion/52612)
3. [3rd Burmistrov](https://www.kaggle.com/c/jigsaw-toxic-comment-classification-challenge/discussion/52644)
4. [3rd Tunguz](https://www.kaggle.com/c/jigsaw-toxic-comment-classification-challenge/discussion/52762)
5. [27th Yang and Souissi](https://www.kaggle.com/c/jigsaw-toxic-comment-classification-challenge/discussion/52719)
6. [5th Μαριος Μιχαηλιδης KazAnova](https://www.kaggle.com/c/jigsaw-toxic-comment-classification-challenge/discussion/52630)
7. [11th CPMP(チームメイトの規約違反のためランキングからは削除)](https://www.kaggle.com/c/jigsaw-toxic-comment-classification-challenge/discussion/52526)


※他にもアプローチを投稿されている上位者がいましたが、時間の都合上上記の投稿だけを参考にいたしました。

##  外部データ
今回は許可されていました。  
そもそも自然言語処理は大きなコーパスを元にした埋め込み表現などがないと厳しいかと。
なお、”Bad word list"などの外部の辞書を埋め込み以外で使っていた人もいましたが、上位者では行っている人はいなかったようです。

1. 学習済み埋め込み表現  
モデルの複雑性やアンサンブルのためのバリエーションを増やすために、  
複数の種類が必要でした。

- Glove Common Crawl (1st Lee)
- Glove Wikipedia (1st Lee)
- Glove Twitter (1st Lee)
- FastText Common Crawl (1st Lee)
- FastText Wikipedia (1st Lee)
- FastText Twitter (1st Lee)
- [BPEmb　200000 merge operations](https://github.com/bheinzerling/bpemb#how-to-use-bpemb) (2nd neongen)  
    英語, フランス語、ドイツ語のものが利用されました。neogenによると、単体では一番良かったようです。
- [LexVec](https://github.com/alexandres/lexvec) (2nd neongen)
- GoogleNews-vectors-negative300. (11th CPMP)
- numberbatch-en.txt (11th CPMP)

## 前処理
- しない　(2nd Computer says no) 
  下手に前処理をしないほうがよかったモデルもあったようです。

- 絵文字の文字での置き換え
- punctuationを残す (1st Lee) 
  fasttextのpretrained-embeddingはpunctuationを残してモデルを作成されているので、punctuationを扱えます。 
  これはlayer0のモデル同士でpunctuationを除いたモデルものとのvarianceを作るのに役に立ちます。  

- ASCII変換 (3rd Burmistrov) 
    すべてをASCIIに変換してからフィルタリングを行います。 
    これにより、例えば非英語で書かれた文字を誤って取り除いてしまうことを防ぎます。

- Twitter style character preprocessing (27th Souissi) 
    KerasのTokenizerなどでは取り除かれてしまう、"!"や"?"を一つの単語（token）として残します。顔文字なども一つのtokenとします。 
    URLなどのほぼユニークな表現は共通の表現に置き換えます。  

## 誤字・検知回避対策
埋め込み表現の辞書にない(out of vocaburary)単語をスペル修正により、 
埋め込み表現で扱えるようにします。  

1. スペル修正の対象  
    - fasttextにないものだけ  (3rd Burmistrov)
2. 辞書 
辞書は単純に埋め込み表現の辞書全部を使うのではなく、以下のような工夫が必要でした。  
    - 埋め込み表現が得られたトレーニングデータ内の単語
    - 通常のコメントより有害なコメントで頻度が多い単語のリスト  (3rd Burmistrov)
    - TextBlobの辞書 (3rd Burmistrov)<br>

2. スペル修正の方法
   - LinSpellまたはSymSpellなど編集距離に基づく方法 (3rd Burmistrov)  
   - TextBlobの辞書によるミススペルの修正(詳細不明) (3rd Burmistrov)
   - Fasttextの機能でword vectorを作り、ベクトルが近いものを探す (3rd Burmistrov)(厳密にはスペル修正ではない）
   - [spell checker]([https://www.kaggle.com/cpmpml/spell-checker-using-word2vec])  
   - fasttext subword information (5th KazAnova)  
     fasttextの機能です。未知語を部分から推測します。
    - byte pair encoding (5th KazAnova) (元の論文は[こちら](https://arxiv.org/abs/1508.07909))
    一つの単語を小さな単位に分解します。（例えばn-gram。単語の区切りは残します。）  
    単語内の連続した２つの部分のうち、テキスト全体で高頻出なものを未使用のシンボルで置き換えます。  
    この手順を繰り返すことで、高頻出語は最終的には1つの代わりのシンボルで置き換えられ、  
    低頻出語は他の単語と共通したシンボルとそうでない部分が残ります。  
    低頻出語はこの共通なシンボルから、意味を推測できるようになります。  
    元はデータ圧縮の方法です。

## 特徴量
- 埋め込み表現
    CNN/RNNのモデルではほぼこれがメインです。
    モデルの構造やpreprocessなどの要素より、ebmeddingほうがモデルの複雑性に対する影響が大きい(1st lee)そうです。

    - char embedding　(27th Yang)
    - word embedding (1st Lee)
    - 上記の結合 (3rd Burmistrov)
        ただし1stのLeeの場合これは逆効果だったようです。  
        同じようなモデルを使っていても有効性って結構変わるのですね。  
        埋め込みごとに別のGRUを使い、その後結合するのも有効だったよう (27th Souissi)  
        **2018/05/16追記**
[こちらの2018年4月の論文](https://arxiv.org/abs/1804.05262)にconcatとaddによる  
word analogy / semantic similarity taskの改善が示されています。  
              コンペ終了の後比較的近い時期に発表されたのがおもしろいですね。

    - pretrainedのものも、training/test dataそのものから学習したものも使われた。(3rd Tunguz)
    - pretrainedの場合はオーバーフィットするのでfreezeしてしまったほうがいい (3rd Burmistrov)

- TF-IDF (3rd Tunguz) 
    GBMやNaiveBayse-SVMのに使われました。
    1-6 charと1-4 wordのTFIDFが使われたようです。

- ユニークな単語の割合 (3rd Burmistrov)
- 単語が大文字だけかどうか (3rd Burmistrov)
- 品詞 (27th Yang) 
    word embeddingと結合して、RNNやCNNの入力に用いました。
- その他
 長さやFwordの有無、メールアドレスの有無など(27th Yang) 
 DNN以外のモデルでよく行われるように、様々なパターンを使って、自力でテキストの特徴量を抽出します。 
 ([詳しくはoliverによるカーネルを参照](https://www.kaggle.com/ogrellier/wordbatch-fm-ftrl-using-mse-lb-0-9804/code))

## モデルの種類・構造
- RNN
今回single modelでもっとも性能が良かった(3rd Burmistrov)。
    - 入力
        - 単位
            - 単語
            - character

        - 入力長  
          元の長さはコメントによって異なります。ほぼ全員が固定にして、paddingしていました。  
            - 900 (単語) (3rd Burmistrov)
            - 200-300 (文字) (1st Lee)
            - 20-50 (文字) (1st Lee）  
               最後のセンテンスだけでしか判断できないものもあったため、  
               アンサンブルに加えると0.0015スコアが上がったそうです。  
        
        ※ちなみに私が個人的に可変長でもやってみたのですが、スコアは悪化しました。  
        恐らく重みを更新する大きさを系列の長さに応じて調整していないため。

    - Bi-directional 
       全員Bidirectionalのようでした。
    - GRU (1st Lee)
        LSTMよりGRUの方が汎化性能が良かったようです。

    - LSTMの後にGRUを重ねる (3rd Burmistrov)  
    - 重ねた層にresnetのようにskip-connectionsを導入する (5th Souissi)

    - spatial dropout 
        埋め込み後の行列の要素をランダムにドロップするのではなく、
        同じ列の要素=同じ単語の成分を同時に選んでDropします。

    - concatation 
        GRUからの出力をmaximum poolingしたもの、 average poolingしたもの、 最後のhidden stateを結合します。 
        ここでユニークな単語の割合やすべて大文字の単語の割合など他の特徴量を
        追加することも有効でした。(3rd Burmistrov)

    - dense layerの追加 
        出力層以外にdense layerをpoolingの後に追加するとよかったようです。

- CNN
    - RNNを上回ることはなかった (ベストなものでもRNNより0.0015低い) (1st Lee)  
        文章は同じ単語でも、単語の順番が少し入れ替わっただけで意味が変わってしまいますが、  
        poolingだとそれが考慮できなかいためだそうです。 (1st Lee)
    - イメージのRGBのように、違ったタイプのembeddingを複数入力チャネルとする (Kuwatly 90th) 
    - k-max pooling (27th Souissi)  
        通常max poolingは最大の値だけを取得しますが、代わりに上位k個を取り出します。  
<br>
- Vowpal Wabbit (3rd Ryan adn Alex) 
    [Vowpal Wabbit](https://github.com/JohnLangford/vowpal_wabbit/wiki)という
    ライブラリを使用。アルゴリズムについては[説明はこちら](https://arxiv.org/pdf/1406.1837v2.pdf)
    高速なonline gradient descentの一種。詳細は調べ中。
   　
- Attention 
  系列の場合その位置によってある成分の注目度は違います。そのため今回もAttentionを組み込んだモデルが試されました。 
  ただ、単純なgru + poolingのモデルと比べると、ほぼ同じかわずかな改善にしかならなかったようです。  
    - aditive attention layer (3rd ryan and alex) 
    (参考[DeepMoji](https://github.com/bfelbo/DeepMoji)論文は[こちら](https://arxiv.org/pdf/1708.00524.pdf)) 
    rnn/cnnの後にweighted attentionを追加し、
    average/max poolingの代わりもしくはconcatする要素の一つとして用います。 
    3rdのryanはEmbedding layerの後にも追加しました。  <br>
    簡単に説明しますと、attendした結果として成分ごとの時間に対する重み付き平均を得ます。 
    そのための重みに、時間軸に対する成分の注目する度合の確率分布用います。 
    この確率分布を得るには　　

        - 1ステップの隠れ状態を入力とする1層だけのfeed-forward network  
         (すべてのタイムステップにおいて重みは共有します。)
        - その全てのタイムステップの出力を時間軸方向のソフトマックスへ入力  
        （この前にtanhなどactivationを適用することもあります）  
    
         なおDeepMojiの原著ではbiasはありませんし、Activationもありません。  <br><br>

    - hieralical attention network (27th Yang) 
    (元の論文は[こちら](http://www.cs.cmu.edu/~./hovy/papers/16HLT-hierarchical-attention-networks.pdf))  

        このモデルは文章のvectorを得るために２階層から成ります。
        - 単語レベルのRNN + attention
        - 文レベルのRNN + attention  

        文レベルのRNNは単語レベルのattentionからの出力を入力にします。 
        ただし、今回は単語レベルと文レベルの２階層の代わりに、 
        文字レベルと単語レベルの２階層を使った可能性があります。（[参考カーネル](https://www.kaggle.com/sermakarevich/hierarchical-attention-network)） 
        Yangがどちらを使ったかは分かりません。

        attentionは2のものと似ていますが、 
        feed-forward networkの後に共通のcontext vectorとの内積を計算し、 
        それをソフトマックスへの入力とするところが違います。  
        context vectorは重要な単語または文を取り出すためのクエリーのような働きをします。<br><br>   
        
    - transoformer (1st Lee) 
     (元は[Attention is All You Need](https://arxiv.org/abs/1706.03762)) 
    上記と違いRNNやCNNを用いません。もともとは翻訳のためのモデルで、decoderとencoderから成り立ちます。 
しかし分類問題ではdecorderへの入力に対応するものがないので、 
        self-attentionだけを使ったモデルをLeeなどはは使ったと推測されます。<br>
        
        transformerの詳細については、[こちらの原著者による解説](https://research.googleblog.com/2017/08/transformer-novel-neural-network.html)など
        多くの記事がありますので、そちらを参照ください。  
        こちらのattentionについては自分も別記事にまとめるつもりです。 
       シンプルなGRUモデルと同等の性能がでたそうです。(1st Lee)   <br><br>

- RNN + CNN  
    - GRUの後に1dのCNNをつなぐ(3rd Tunguz)<br><br>
- GBM(LGB, XGB) (2nd neongen) 
  単体の性能ではGBMはNeuralNetworkを超えるのは難しかったようですが、
  アンサンブルの際に多様性を確保するため使われました。<br><br>
- NaiveBayse-SVM (またはlogistic Regression) (27th Yang) 
    [jhowardのカーネル](https://www.kaggle.com/jhoward/nb-svm-strong-linear-baseline)で紹介されている手法です。[元の論文はこちら](https://nlp.stanford.edu/pubs/sidaw12_simple_sentiment.pdf)
- linear support vector machine (5th KazAnova)

## optimize
- 学習に応じてbatch sizeを増やす (27th Souissi)  
    epochごとにバッチサイズを大きくしていきます。

## data augmentation
  - 翻訳 (提案は[Pavel Ostyakovによる](https://www.kaggle.com/c/jigsaw-toxic-comment-classification-challenge/discussion/48038))  
    なんと一旦フランス語、ドイツ語、スペイン語に翻訳してから、英語に再翻訳し、データを増やします。  
    訓練だけではなく、予測にも使われました。
    予測に使う場合は、一つのサンプルから得られた各言語からの再翻訳を平均したものを予測値とします。  
    1stのLeeによるとこれを行ったチームと行わなかったチームでかなり差が付いたようです。  
    さらに面白いことに、英語じゃないコメントだけを訳した場合、逆効果だったようです。(1st Lee, 1457 th Ostyakov)

## ハイパーパラメータサーチ
- ベイズ最適化 (1st Lee)  
    スタッキングのlight-GBM(layer 1)のハイパーパラメータを最適化するために使用。
    ベイズ最適化が実践で使われているのが興味深いです。 

## バッチサイズ
- 大きいほうが結果が安定(3rd Burmistrov)

## semi-supervised
- pesudo labeling (1st Lee)  
Leeの場合、ベストなアンサンブルモデルによるテストデータの予測ラベルを
正解ラベルとして加えました。  
単純に識別能力を上げるというよりは、
トレーニングデータとテストデータのクラス分布が異なるので、  
テストデータのクラス分布を学習させるために行います。  
注意点として、overfitを避けるためにvalidation dataには含めないようにします。

## アンサンブル手法
- stacking (1st Lee) 
    各layer 0のモデルの予測値をlayer 1のモデルへ入力する特徴量とします。 
    ただし、注意しないと簡単にoverfitします。 
    それを防ぎながら汎化性能を上げる手法として1st Leeの例を挙げます。layer1にはlgbを使っています。<br><br>

    - lgbを使う場合(おそらくxgbも)、なるべく浅く、強いl1正則化があるものを選ぶ  
    - 以下を変えたモデルをブートストラップサンプリングしたtraining dataで学習させ、baggingを行う  
        - random シード
        - boosting type (DART/GBDT)  
    layer1のモデル自体にもvarianceがあり、それを小さくするためです。
    - stackingに加えてもCVのmetricとpublic LBを上げないlayer0のモデルは、overfitを避けるために除外<br><br>
- 異なるrandom seedを使った同一モデルを平均 (3rd Andre) 
    ランダム性による結果のブレを小さくします。<br><br>

- 異なるOOFの分け方のモデルを混ぜる (1st Lee)
    - 通常訓練データをn-foldに分けて、layer0のモデルを複数作成するときは分割の仕方をずっと固定します。  
    つまりどのサンプルがどのfoldに属しているかずっと固定させます。  
    その代わりにLeeは、いくつか異なる分け方をしたようです。  
    CVへのoverfitを避けることができるそうです。　<br><br>

- 英語以外のコメントに対するすべてのラベル確率を0にする (3rd Tunguz) 
  正確にはアンサンブル手法ではありませんが、 英語以外のコメントから無理にラベルの確率を予測するよりは、
  確率を全て0にしてしまったほうが少しスコアがよかったそうです。
  
# 結び
コアな手法で差がつきにくい中でも、上位者はランダム性を小さくしたり、correlationが小さいlayer0のモデルを増やすなど、
着実に結果につながることをしていました。  

テキスト分類の問題って、機械学習入門の一番最初に触れたりしますが、実際の問題でやると奥が深いですね。