SIGNATE Student Cup 2020 [予測部門] 参加記

はじめに

去年に引き続きSIGNATE学生コンペに出てました。
今年はNLPコンペです。
NLPか〜考察ゲーってよりはSOTAな手法をガチャガチャするゲームなりそうで辛いな〜と思いつつ参加しました。
案の定SOTAな手法をガチャガチャして、闇の中を彷徨う苦行をしてたら終わったんですが、NLP初心者だったのでそれでも学びが多くて良かったです。

結果

21位でした!public40位くらいからのShake Upだったので満足です。
ちなみにBERT-base-uncasedに分類ヘッド付けてBERTごとFinetuningしたものと,CountVector+LightGBMとSWEM-max+LightGBMの6:2:2のblendでした.
後述しますが不均衡なデータだったので、
BERTモデルはUnder-sampleしてrandom seed average(n=6),
その他のLightGBM系モデルはimbalanced-learnのBalancedBaggingClassifier(n=30)
を使ってます。LightGBMは初期パラメータです。

例のLB Hackは使ってないです。(ただのargmaxです)

f:id:MosaasoM:20200827000533p:plain

タスク概要。

英語で職募集文が書いてあるので、それが

  1. データサイエンティスト
  2. 機械学習エンジニア
  3. ソフトウェアエンジニア
  4. コンサルタント

のどの職種か当てる問題。明確に判別が難しい領域が多めの問題設定。またターゲットに結構偏りがある(最多数ラベルと最少数ラベルで4〜5倍くらい離れてた。

ざっくり学び

  • まずはCountVectorやTf-Idf + LightGBMを使う。trainとtestで使われている単語が同じようなときは多分それだけでかなりスコアが出る。
  • 不均衡データはまずはimbalanced-learnのBalancedBaggingClassifierを使う。class weightによるチューニングは調整が難しく、過度なチューニングはShakeの可能性も上がりうるなどあんまり得しないと思う。
  • imblanced-learnを使うのが面倒なニューラルネット系の手法とかはUnderSamplingをrandom seed averageでひとまずいい気がする。学習も早くなるし。
  • under-samplingによる確率のバイアス修正は、testデータの分布がtrainと同一か不明なときにはやらなくていいと思った。(結局偽陽性とかとのトレードオフなので。)分布がわかってるときはやったほうがいいかもだけど。
  • 未知語が多いなら、次に試すのはSWEM-max(word2vecのMaxpooling)。
  • それでも駄目だったら仕方ないのでBERTなどのPretrained Modelを使う。未知語には間違いなく強い。
  • Transformer使うならとりあえずHuggingFaceのtransformersでよし。
  • でもtransformersのモデルたちは中の層の一部をいじったりできないので、それがしたいときは公式Githubとかから気合でやるしかない(今回はやってない)。Tensorflow1.xの実装とかも結構残ってるので、そこら辺をうまいことできるライブラリなりなんなり整備したほうがいいかも。
  • BERTは全層FineTuningしたほうが精度が出ることはある。コストは重いがfreezeは必ずしも最適ではない。(それはそう)
  • 今回はとくに元のBERTのタスクでは分類が難しい物同士の分類だったので、全層学習が良かったんだと思う。(例えば、He design (Server/Model) Architecture in google. みたいな文では()内のどちらも元もBERTのタスクだと大差なく扱われると思う。ここを補正してあげないとうまく行かない?)
  • BERT族のファインチューニングは学習率がAdamなら1e-5とかそのあたりが良かった。大きいと全然駄目。
  • BERTの他に文書分類ならとりあえずXLNet,ALBERT,RoBERTa,DistliBERT,DistliRoBERTaあたりがよし。XLNetはBERTと割と違うのでブレンドにはいいかも?やってないけど。
  • BERT以降のマイナーチェンジっぽいモデル(ALBERTとか)はGLUEに過学習してる雰囲気がなくはないので、心配だったら無難にBERTがいい気がする。
  • 再翻訳によるDataAugmentationならTransformersのMarianMTにいっぱいモデルがある。欧州諸語は結構精度出るが、逆に言うと再翻訳しても変わらないのも多いので、それを取り除く処理は必要かも。ちなみに日本語翻訳の精度は使えないくらいに低い…
  • 有償API使えるならDeepLでいいんですけどね。
  • MarianMT,結構処理時間かかるので(確か最後にビームサーチとかするのでGPUでもそんなに高速化しない)、ジェネレーターの中で呼び出すよりは予め水増しデータ作っちゃうほうがいいと思った。
  • 再翻訳によるDataAugmentationをするときにはCVの切り方に注意。水増し元のデータがtrainで、水増しデータがtestとかに行っちゃうと当然だけどLeakしてる。
  • 再翻訳によるDataAugmentationは効かないこともある。(今回は実際効いてなかった)
  • CountVector系手法とSWEM手法とBERT系手法のblendingは結構いい感じだった。多分全部結構違う方向性をもってるからだと思う。

細かいあれこれ

CountVector,Tf-Idf

  • まずはskleranのCountVectorizerとかTfIdfVectorizerでよし。
  • データ数が少ない時、CountVectorizerはbinary=Trueでいい気はする。(オーバーフィットの原因になりそう。未検証)
  • trainとtestの語彙に差があるときは、testの方でvectorizerをfitするのも手(testデータがすべてわかってればだけど)(今回は大差なかった)
  • stemmingとか品詞判定はとりあえずspacyが準備も利用も精度的にも楽だと思う。
  • 品詞でしぼるのもいい(名詞と形容詞だけとか)。実際副詞とかpunctuation系はノイズになることが多いと思う。

SWEM

  • とりあえずword2vecでやるのが楽。GloveとかFastTextはベクトルのファイルサイズが大きすぎて辛かった。
  • Mean,Max,Mean+Max,Hierarchical Maxとあるが、Maxが割と安定していた。
  • CountVectorとかに比べるとまだ未知語には強いがとはいえそこまでといった感じ。語彙に差が大きいときは微妙。
  • こっちもCountVectorとかと同じく、testにある単語だけとか品詞絞りとかもあると思う。(こっちも同じく大差なくて使ってない)
  • CountVectorと比較して次元数がめちゃくちゃ低いのはいいところ。(CountVectorは103〜104とかになりうるが、SWEMは300次元)

BERT系

  • とりあえずBERT-base-uncasedのfreezeと全層学習両方試すところから入るのがいいと思う。BERTはcased-uncasedとかバリエーションが多いのも良い。
  • ALBERTは自分の試した範囲では今回微妙だった(どうやっても精度があがらず、コンペ最初の方の結構な時間を溶かした)。GLUEにオーバーフィットしてるだけのモデルなのかもしれない?わからんけど。GLUEに近いタスクにはいいのかも?
  • RoBERTaは無難に強い(CVでは強かった)。PublicLBでは微妙だったが果たして…→実際微妙でした。
  • XLNetはBERTとちょっと学習方法とかが違うので多様性出すなら多分良い(今回は最終subに混ぜてないけど、予測)。ただ、BERTより学習が割と重いので実験とかがしんどい。
  • 今回は使わなかったけど、 BERTの精度を向上させる手法10選 - Qiitaこちらかなり勉強になった。
  • BERT-largeくらいのパラメータ数(330M)ならギリギリGoogle Colabで行けなくもないけど、batchサイズがかなり小さくなる(16とか8とか)。それ以上は無理。
  • transformersの機構を時系列予測っぽく使う(要はpretrainedは使わず、しかもBERT likeなタスクじゃなくて、Transformerの最終層から直接ターゲット値へのDenseにつないじゃうような使い方)は、未知語に弱くなるのでなんとも…とはいえ語彙がtrainとtestで同じようなときはCountVector系手法の上位互換になるかもしれない。
  • BERTで言うpooled outputは[CLS]トークン部分の最終層の出力のことらしい。用語が紛らわしすぎ。lambda layerとかで抜き出すと割と柔軟にいろいろできる気がする。
  • BERT側をfreezeする場合は、pretrained BERTの出力をニューラルネットじゃなくて、lightGBMとかSVM系の手法につなぐとニューラルネットよりいい精度が出ることが稀にある。今回は出なかったけど。
  • soft labeling([0,1,0]じゃなくて、[0.1,0.8,0.1]みたいな感じでラベルを与える)のも場合によってはいいかも。特にBERT本体ごと再学習するときは過学習を抑えやすくなる気がする(多分)。今回はあんまり関係なかったけど…今回みたいな明確な分離が難しいタスクだといい感じになるかなぁと思ったのに…

試せなかった手法とか。

  • dependency-parseの結果を利用するとか。
  • dependencyベースのword2vecを使ってみるとか。
  • bertの公式実装をちゃんと借りてきて一部だけfreezeしての学習とか。
  • SCDV。計算の重さの割に微妙と聞きかじったので試さなかった。
  • Doc2Vec。同上。
  • T5みたいなsentence-sentence系のモデルは分類問題でどう使うかわからなくて使えなかった。

その他

  • tfa(tensorflow add-on)はなんかmetricとか色々あって便利。
  • 辞書型っぽい書き方でDataFrame作れるの知らんかった…pd.DataFrame({"id":hoge,"predict":fuga})みたいな。
  • sklearnのOneVersusRestClassifierがSVMとかロジスティック回帰を多クラス拡張するときは便利。
  • SVM系手法はpredict_probaがあんまり当てにならないので、blend方式によっては使いにくい。(Votingならいいけど)
  • データとラベルの対応関係を保ったままシャッフルするときは zip()を使って複数配列(array, list)をshuffleする-python - ろぐれこーどこちらの方法が楽で良い。

感想。

  • 生コンペでGPU前提みたいな問題設計は割と辛いと思いました。
  • でも日本語じゃなくて英語なところに優しさを感じました。
  • 期間が短い…
  • フォーラムが盛り上がっててすごいなぁと思いました。(貢献できず申し訳無さを感じている)
  • フォーラムでも議論になってましたが、publicとprivateはrandom splitなのか、時系列splitなのか、何%のデータがpublicに当てられてるのかくらいの情報は欲しいですね…