Kaggle奮闘記 〜塩コンペ編〜

【この記事は、「ミクシィ19新卒 Advent Calender 2018」の23日目です。】

はじめに

 はじめまして、phalanxと申します。今回はTGS Salt Identification Challengeに参加して優勝したので、その時の取り組みについて紹介していきます。チームマージを終了2週間前にしましたが、チームマージ後の取り組みには触れず、それまでの個人の取り組みのみを紹介します。チームのsolutionはこちらにあるのでどうぞ。チームマージ前のdata leak有りと無しの私のモデルのスコアを載せておきます。また、data leakに関しては特に触れません。それでは、よろしくお願いします。

  • add leak: publicLB 0.88833(publicLB1位) privateLB 0.896114(privateLB1位)
  • no leak: publicLB 0.878416(publicLB17位) privateLB 0.895344(privateLB4位)

f:id:phalanks:20181222011019p:plain

phalanxのスペック

 

機械学習経験:1年

深層学習経験:半年

kaggleの経験:

・Humpback Whale Identification Challenge

 ResNet50で適当に1サブミットして終了

・Santander Value Prediction Challenge

 24サブミットした時点でleak発覚。早々に撤退。

塩コンペまでに2回参加していますが、kaggleのことはあまり理解していませんでした。塩コンペで初めてkernelとdiscussionを知りました(あるのは知ってたけど見てなかった)。

開発環境 

Hardware
  •  Ubuntu18.04 (1TB boot disk)
  • 12 CPUs, 32 GB Memory
  • 1 x Geforce GTX 1080 Ti
Software
  • Python 3.6
  • CUDA 9.0
  • cudnn 7
  • pytorch 0.4.0

 コンペ概要

コンペティション開催の背景

 今回のコンペのホストであるTGS社は、石油やガスなどの地下資源探査に必要な地球科学データの収集・解析を行う企業です。地下資源探査において塩の固まりが埋まっている領域を採掘機で採掘することは危険であり、事前にどの領域に塩が埋まっているか把握しておく必要があります。そのため、TGS社は地震探査法と呼ばれる地下構造の性質を捉える手法で地質画像を取得し、その画像から専門家がどこが塩領域なのかを特定していました。しかし、同一の地質画像に対して専門家ごとに領域判定が異なることがあるため、専門家の判定は地下資源の採掘に潜在的な危険を及ぼします。そこで、TGS社は地質画像から塩領域を特定する画像セグメンテーション技術が必要であると考え、今回Kaggleにおいて本コンペティションを開催しました。

タスク

 タスクはセマンティックセグメンテーションになります。地質画像から塩の領域のセグメンテーションを行います。

データ 

 このコンペで使用するデータは下図のような画像になります。左が地震探査法で取得した地質画像、右がどの領域が塩なのかを表すmask画像となります。訓練画像4,000枚、テスト画像18,000枚が提供され、参加者はこれらの画像を用いて予測モデルを構築していきます。

f:id:phalanks:20181223193432p:plain

コンペの取り組み

このコンペは2018/07/19〜2018/10/19の期間で開催されました。以下では1ヶ月ごとの取り組みに分けて紹介していきます。

 今回のコンペは訓練画像4,000枚に対し、テスト画像18,000枚と、テスト画像が訓練画像の4.5倍あります。そのため、訓練画像だけでなく、テスト画像も活用してモデルの性能を向上させることを決め、コンペ初日に大雑把ですが、以下の計画を作りました。

  • 訓練画像のみを使用してモデルを構築 (開始〜終了1ヶ月前)
  • 訓練画像、テスト画像を使用してモデルを学習(終了1ヶ月前〜)

コンペ期間中はこの雑な計画に従い開発を進めていきます。

コンペ開始〜1ヶ月

ベースラインモデル作成

 まずモデルについてですが、セグメンテーションで代表的なU-Net[1]を使用しました。論文中の下図のモデルに対して、以下のようにモデルを変更します。

  • encoder: ResNet34
  • decoder: conv3x3, BatchNorm2d, ReLU -> transposed conv -> concat

 f:id:phalanks:20181222022037p:plain

 ResNet34ですが、今回のデータとImageNetでは画像ドメインが大きく異なるため、開始〜2週間までは事前学習済みのモデルを使っていませんでした。そこから試しに事前学習済みのモデルを使ってみると、大きく精度が上がりました(PublicLB +0.03)。また、image reconstructionでResNetのmax poolingを除いて精度が上がった経験があるため、segmentationでも効くだろうと思って外してみたら、こちらも精度が上がりました(PublicLB +0.01)。poolingを用いると精度が下がるのは、poolingで位置情報が失われるのが原因だと思われます。VGGよりもResNetがセグメンテーションのタスクで使われるのはこの点かな。

 このモデルを用いてaugmentation, loss, optimizer等を試行錯誤していきました。1ヶ月後の最終的な手法は以下になります。

  • model: resnet34(ImageNet pretrained) + u-net, remove max pooling
  • image size: 128x128 (101x101 + edge padding)
  • augmentation: horizontal flip, random shift/scale/rotate, random brightness/contrast
  • optimizer: sgd(lr:0.01, momentum:0.9, weight_decay:1e-4)
  • lr_scheduler: CosineAnnealingLR(T_max: 20, eta_min: 0.001)
  • loss: Binary Cross Entropy
  • TTA: horizontal flip

model, loss, image sizeは今後変更していきますが、それ以外は最終日まで固定です。この手法でcross validationを行っていきますが、訓練画像は塩領域の大きさにばらつきがある(全く塩がない、9割以上塩など)ため、塩領域の大きさごとに訓練画像を以下のように分割します。

  • salt_size == 0
  • 0 < salt_size <= 101*101 / 4
  • 101*101 / 4 < salt_size <= 101*101 / 2
  • 101*101 / 2 < salt_size <= 101*101*3 / 4
  • 101*101*3 / 4 < salt_size <= 101*101

上記の方法で訓練画像を分割し、cross validationを行います。この手法でpublicLB 0.833でした。(順位は忘れました)

論文サーベイ・実装

 セマンティックセグメンテーションが今回初めてだったので、2015年〜2018年あたりのトップカンファレンスにacceptされている論文を調査し、実装しました。だいたい2ヶ月で100本程度読んで実装してました。平日に1日1本、土日に10本くらいのペースですすめていき、2ヶ月で大体100本です。最初の1ヶ月で多くの論文を調査し、実装しましたが、上記のU-Netよりも性能のいいモデルはありませんでした。実装が間違えているかもしれませんが。

OsciiArt Kernel

 コンペ開始直後にOsciiArt氏がNO MASK predictionというカーネルを投稿しました。名前の通り、このカーネルは全テスト画像に対して塩領域なしとしたsubmission fileを作成します。このカーネルからpublicのテスト画像のうち38%が塩領域なしの画像であることがわかりました。これはかなり有益な情報でした。コンペ序盤はこの情報はあまり活用されませんでしたが、中盤辺りから参加者がpredictionの塩領域なしの予測が全体の38%になるように試行錯誤していきます。私も中盤辺りから参考にしましたが、既に塩領域なしの予測が38〜39%だったので、特に工夫はしませんでした。

1ヶ月〜2ヶ月

このあたりから、手法が大きく変わります。取り入れた手法は以下になります。

  • Spatial and Channel Squeeze and Channel Excination[2]

  • Feature Pyramid Attention for Semantic Segmentation[3]

  • Snapshot Ensemble[4]
  •  Lovasz Hinge Loss[5]

以上の手法を簡単に紹介します。

Spatial and Channel Squeeze and Channel Excination

 特徴マップをチャンネルごとに適応的に重み付けを行うSENet[6]がありますが、本手法はSENetを拡張し、空間方向のアテンションを加えました。下図が提案手法です。下図上部が今回提案された空間方向のアテンション機構であるsSE(spatial Squeeze and Excination)です。H\times W\times Cの特徴マップに対し1\times 1の畳み込みを適用(Squeeze)し、特徴マップの全体的な特徴が抽出されたH\times W\times 1テンソル出力を出力します。出力されたテンソルに対し、シグモイド関数が適用(Exicination)され、特徴マップのピクセルごとの重みが出力される。このピクセルごとの重みを用いて特徴マップをスケーリングする。下図下部のcSE(channel Squeeze and Excination)はSENetで提案されたチャンネル毎の重みを用いた特徴マップのスケーリングです。提案手法ではsSE, cSEをそれぞれ特徴マップに適用し、足し合わせることで空間・チャンネル両方向のアテンションを実現しています。

f:id:phalanks:20181222195824p:plain

Feature Pyramid Attention for Semantic Segmentation

 著者は従来のセグメンテーションモデルに対して、DeepLabのASPPモジュールはDilated convolutionにより局所的な情報が失われること、PSPNetのSpatial Pyramid Poolingはpoolingによりピクセルの位置情報が失われることを論文中で問題提起しています。この問題に対して、下図のようなFeature Pyramid Attentionを提案しました。32\times 32 \times Cの特徴マップに対して7\times 7, 5\times 5, 3\times 3の畳み込みを下図のようにピラミッド構造で適用し、足し合わせることで異なるスケールのコンテキストの情報を得られます。これを入力特徴マップに掛け合わせることでコンテキストに基づいた特徴選択を実現しています。

f:id:phalanks:20181222211145p:plain

FPAに加えて、Global Attention Upsample(GAU)を提案。下図のように、高レベルの特徴マップのコンテキストを用いて、低レベルの特徴マップに対してチャンネル毎に重み付けを行います。f:id:phalanks:20181222211632p:plain

Snapshot Ensemble

 複数のニューラルネットのアンサンブルは1つのニューラルネットよりも性能は向上しますが、計算コストが大きい問題があります。Snapshot Ensembleは1つのニューラルネットの学習において、複数の異なる局所解を得られるよう学習することで、計算コストを増やさずアンサンブルの効果を得られる手法です。

 下図右が提案手法です。学習率を減らして局所解に近づけた後に、急激に学習率を上げて局所解から抜け出し再度学習を行うことで、先ほど得た局所解とは異なる局所解が得られます。このサイクルを繰り返して複数の異なる局所解を得ることがこの手法の狙いです。

f:id:phalanks:20181223173307p:plain 

Lovasz Hinge Loss
モデル構築・学習

 この期間に様々なモデルを作りましたが、final submitに使用する3つのモデルを紹介します。

・モデル1

f:id:phalanks:20181223183808p:plain

開始〜1ヶ月で紹介したベースモデルを改良しています。encoderのResNet34の各レイヤのトップにscSE Moduleを置いています。また、decoder側ではhypercolumnsを使用しています。
 

・モデル2

f:id:phalanks:20181223184335p:plain

モデル1のcenter blockをFPAに変更しました。それだけです。
 

・モデル3 

f:id:phalanks:20181223184547p:plain

モデル2のdecoderをGAUに変更していますが、GAUを改良したGAUv2を使用しています。GAUv2は以下になります。

f:id:phalanks:20181223191503p:plain

チャンネル方向だけでなく空間方向のアテンションを取り入れました。

 以上の3つのモデルを学習していきます。学習の詳細は以下になります。1ヶ月前のベースラインモデルの学習方法との異なる部分は太字表記しています。

  • model-encoder: resnet34(ImageNet pretrained), remove max pooling
  • image size: 128x128 (101x101 + edge padding)
  • augmentation: horizontal flip, random shift/scale/rotate, random brightness/contrast
  • optimizer: sgd(lr:0.01, momentum:0.9, weight_decay:1e-4)
  • lr_scheduler: CosineAnnealingLR(T_max: 20, eta_min: 0.001)
  • loss: Lovasz hinge loss
  • 6 snapshot ensembling (50epoch per cycle)
  • TTA: horizontal flip

上記の方法で学習を行いました。学習結果は以下になります。

  • モデル1: publicLB 0.855
  • モデル2: publicLB 0.858
  • モデル3: publicLB 0.860
bestfitting降臨

 bestfitting氏が降臨しました。つまり、そういうことです。はい、その通りです。

2ヶ月〜最終日

 ついにコンペ終了まで1ヶ月です。計画していたとおり、テスト画像を活用して学習していきます。

multi step pseudo labeling

 pseudo labelingは反教師あり学習の1つで、テスト画像の予測結果のうち予測精度が高いテスト画像を訓練画像に追加する手法です。本来は予測精度が高いデータのみを使いますが、今回はすべてのデータを使用します。この手法はTensorFlow Speech Recognition ChallengeLittle Boat氏の3rd place solutionを参考にしました。手法としては以下になります。

  1. pseudo labelだけでモデルを事前学習
  2. 訓練画像を用いてモデルをfinetuning

上記のようにpseudo labelのみを用いて学習することで、セグメンテーションタスクにおける地質画像を用いた事前学習済みモデルが得られます。しかし、この状態はpseudo labelにoverfitしています。なので訓練画像を用いてfinetuningを行います。

 今回は上記の手法を2段階で行うmulti step pseudo labelingを行います。学習の流れは以下になります。

  1. 訓練画像のみを用いてmodel1を学習、pseudo label作成
  2. 1.で作成したpseudo label、訓練画像を用いてmodel2を学習、pseudo label作成
  3. 2.で作成したpseudo label、訓練画像を用いてmodel3を2つ学習

以上の流れで学習を行います。各学習を具体的に紹介します。述べてない部分はと同じです。

・1. の学習

    model: model1(encoder: ResNet34, remove maxpooling)

 image_size: 256x256(resize 101x101 -> 224x224, edge padding)

    loss: lovasz hinge loss

    6 snapshot ensembling (50 epoch per each cycle)

・2. の学習

    model: model2(encoder: ResNet34, remove maxpooling)

    image_size: 256x256(resize 101x101 -> 224x224, edge padding)

    loss: lovasz hinge loss

    pretrain with pseudo label: 150 epochs(50 epoch per each cycle), save best weight

    finetune with train data: 6 snapshot ensembling (50 epoch per each cycle)

・3. の学習(1つ目)   

    model: model3(encoder: ResNet18, remove maxpooling, conv7x7 -> conv3x3)

    image_size: 128x128(edge padding)

    loss: lovasz hinge loss

    pretrain with pseudo label: 150 epochs(50 epoch per each cycle), save best weight

    finetune with train data: 6 snapshot ensembling (50 epoch per each cycle)

・3. の学習(2つ目): 1つ目との差異は太字で表記

    model: model3(encoder: ResNet34, remove maxpooling, conv7x7 -> conv3x3)

    image_size: 128x128(resize 101x101 -> 128x128)

    loss: lovasz hinge loss

    pretrain with pseudo label: 150 epochs(50 epoch per each cycle), save best weight

    finetune with train data: 6 snapshot ensembling (50 epoch per each cycle) 

1.~3.の学習期間は約3週間です。この学習によるスコアは以下になります。

  • モデル1: publicLB 0.858499 privateLB 0.875374(200位)
  • モデル2: publicLB 0.873033 privateLB 0.890913(17位)
  • モデル3 : publicLB 0.878516 privateLB 0.894651(7位)
  • モデル2, モデル3のensemble: publicLB 0.878416 privateLB 0.895344(4位)

モデル2とモデル3のensembleを最終的な予測として使いました。

 1ヶ月前にこの学習を開始したので残りの期間はかなり暇でした。1日1回tensorboardを確認し、残りはキングダムハーツをしてました。

チームマージ、data leak

 モデル2の学習が終わったのが終了2週間前で、その時点での私の順位は7位でした。その時に当時6位のb.e.s.氏にチームマージを頼まれ、ノリでOKしました。記事のはじめにも書きましたが、チームマージ後の取り組みはこちらになります。興味のある方はどうぞ。

 

 また、チームマージ後にデータリークがあることに気づきます。b.e.s.氏にslackで「パズルやったらスコア上がった笑」みたいなメッセージが送られてきて、「パズルってなんぞ?」ってなりました。1ヶ月前からキングダムハーツに熱中し、コンペのカーネル、ディスカッションを見ていなかったので、パズルの存在にこの段階で気づきます。パズルに関してはこちらのディスカッションに詳しく書いてます。クソみたいなリークです。控えめに言って○ねです。しかし、このパズルを私のsubmissionに適用することで(気づいたらb.e.s.が適用してた)privateLBが0.895344(4位)→0.896114(1位)に上がりました。終盤は参加者全員がこのパズルを死ぬ気でやってました。

おわりに

 計画通りに進めて優勝出来たのは良かったが、取り組みの過程では詰めが甘い部分が多々あったのでその点は反省して次に活かしたい。今回は画像コンペに参加しましたが、テーブルデータや音声、自然言語などのコンペにも参加していく予定です。