どうも電電です.アドベントカレンダーの時期がやってきましたね.
この記事はCAMPHOR- Advent Calendar 2020 7日目の記事となっています.
ちなみに去年もアドベントカレンダー参加していました.
今回の記事は去年の記事に比べると,技術寄りなのでとっかかりにくいかもしれません.
内容としては,signateというところの画像コンペに2ヶ月位出てたのでそれについて書くという感じになっています.
目次
始めに
signateってなに?
日本版kaggleと思って大丈夫だと思います.
あるデータ(画像群だったり,動画だったり,表データだったり)が与えられて,それのデータに紐づくような値をできるだけ精度よく予測するモデルを作ってその精度を競い合うということをします.
データには訓練データという物とテストデータという物が存在していて訓練データは(データ,答え)がペアになっていますが,テストデータには(データ)しか与えられません.この時にこのテストデータの答えを予測します.
で,そのスコアがリーダーボードというページに乗るわけです.
今回のコンペについて
僕が今回出たのは, The 4th Tellus Satellite Challenge: Coastline Detection | SIGNATE - Data Science Competition というコンペで,衛星画像を使ったコンペです.衛星画像に関するコンペは今まで結構開かれていたらしく,今回のコンペで4回目なようです. 具体的なタスクはというと,海岸の衛星画像が与えられてそこから海岸線を推測するというものでした.
訓練データとして,海岸のグレイスケールの衛星画像とその海岸線の座標が与えられます.テストデータとして,異なる海岸の衛星画像が与えられるので,その画像の海岸線の位置を当てるという内容でした. (ルールとか評価関数とかが気になる方はsignateをみてみてください.)
どうしてこれに出ようと思ったのか?
まずkaggleでもなくどうしてsignateに出ようと思ったかですが,3つ大きな理由があります.
- このコンペが画像コンペであること(研究してる内容が画像なので画像コンペに出たかった)
- 機械学習やっているのに対外的になんもなかったから
- コンペの参入障壁が高めだった(気がする)
1.「このコンペが画像コンペであること(研究してる内容が画像なので画像コンペに出たかった)」
まず1つ目は自分と相方のドメイン知識が画像処理にあったので,それを生かすためにもテーブルデータではなく,画像を使った物で競いたかったというのがあります. kaggleもみてたんですけど良さそうな画像コンペがこの時なかった...結論からいうとこのコンペに出れたのはかなりよかったです.実際に自分たちが考えた施策や,学んだことが生きるのかどうか検証しながら進められるのはかなりいい経験でした.
2.「機械学習やっているのに対外的に言える成果がなんもなかったから」
これはシンプルですね.いやーなんだかんだプログラミング学びはじめのころから3,4年くらい機械学習とかでぃーぷらーにんぐっていうものと戦ってるわけなんですが,今んところ学会発表した位でいまいち業績という物がなかったんですよね.自分がどれくらいの位置にいけるのかみたいなのを見たくて,参加しました.
3.「コンペの参入障壁が高めだった(気がする)」
今回のsignateのコンペ内容は,直接的なセグメンテーションタスクじゃなかったので,工夫が必要故に参入するのが少しむずかしそうだったというのがあります.
通常のsegmentationタスクだと入力サイズなどをモデルに合わせて学習させ,訓練を行う工程の調整とか,モデルの工夫とかが大事だと思うんですけど,そこらへんで戦うと経験ある方との経験差がおおきいなあと思っていて,まだこういうタスクだったら足並みが揃いやすいかなと思いました.
今回は,アノテーションデータの作成から,データの選定,海岸の衛生画像からうまくデータセットを作成する必要あり,さらにそこからセグメントした画像から提出するのに必要な海岸の境界線を検出するなど必要な工程が比較的多かったので,これなら時間がある方が有利に働くと思い参加を決めました.
結果
パブリックリーダーボード(暫定順位みたいなもの)7位,プライベート(最終結果)13位でした. 自分たちのスコアはあまり変わらなかったんですが,他の方達のスコアが上がって,相対的に落ちた感じです.(10位までに残れるかな〜と思ってたので悔しい) 最後の方トップの方がずっとスコア伸ばしてて,何が足らないんだろうってずっと悩んでました. google mapみて海岸の特徴をみてたり,特徴的な海岸をみて回ったりしてたせいで日本の海岸線に詳しくなりました.
やったこと
このタスクをsegmentation taskとして扱っていました.
なので全体としては
前処理 -> segmentation -> 2値化 -> 輪郭検出 -> 急な変化のある部分を削除
という工程で行っていました.
訓練画像について直接海岸線をsegmentation modelで学習させようと思ったのですが,あまりうまくいかなかったので,海岸線の位置を示したデータから陸と海についてそれぞれ[0, 1]でアノテーションした画像を作成しました. 一部の海岸や,川については海岸線の情報がなかったので,そこの部分については最も近い点同士を直線で結んで穴埋めしました.(川の部分についてはあとで目視で削除しました.)
また,一つの画像サイズは大体4000 * 6000とかで大きすぎるので,画像全体を使うんではなく画像を224 * 224に切り取って小さい画像として扱っていきました.しかしながら,このままだと陸ばかり含んだデータや,海ばかり含んだデータが存在してしまうのと,海岸線について学習してくれればいいので,画像の陸と海の割合を検出して,海岸線が含まれている画像のみを訓練データとしました.
ただこれだけだと画像数が少なすぎる(せいぜい8000枚)とかなので画像の水増し(オーギュメンテーション)として,画像の±10°の回転,切り取り,左右反転,画像の一部分の白色化, ガウスノイズの追加をおこないました.*2 segmentationタスクでrandomerasing効くのか疑心暗鬼だったのですが,loss下がったので効くっぽいです.
あとは前処理として,訓練画像にわざとバイアスをかけました.与えられた海岸画像って方向と海の方向がばらばらで揃っていませんでした.(海が右側だったり,下側だったり)そこで,まず大体の海の方向を検出して,海の面が上に来るように画像の回転をおこなって学習を行いました.これにより,上から海,陸,川みたいなことがあっても,川がsegmentation時にうまく埋めらていました.もちろんこれは最後の輪郭検出時にもとに戻さないと行けないので位置を保持して,元に戻しています.
segmentationのモデルはdeep lab v3 + resnet 50 + sigmoid関数を用いました.SE layerとか足してみたんですが今回はあまり効かなかったっぽいです.(modelを細かくいじりだすと終わらないので今後はやらないようにしたい)
モデルとしては,U-net, deep lab v3+, backboneとしてresnet, mobilenetなどを使って,データを作成しました.
その後,得られた画像について,輪郭検出を行いました. (白)正解ラベル,(赤)セグメンテーション + 輪郭検出の結果
輪郭検出は2値化 -> cv2のcontours関数で行いました.*3
また,これで十分に海岸が検出できなかった画像については,cv2.Cannyを用いてエッジ検出を行いました.*4
その後,それらの輪郭部分について,連続する点についてベクトルを計算し,急激なベクトルの変化がある部分について削除しました. (ここら辺はチームメイトがやったのでそこまで詳しくない)
実験は後半になってから
これはもう少し工夫のしかたがあったような気がします.
最後に
いやー残念な結果に終わって悔しかったです.でもまあ,途中の考察がハマった瞬間とかとてもおもしろかったです. 結果が13位という悲しい結果だったのでいろいろと振り返る点はあるのですが,
- データ選択,前処理,後処理がめちゃくちゃ重要
- モデルは明確な意図がない限りいじらない
- 推測しない計測する(LBの値じゃなくてCVの値を信じる)
- 実験のイテレーションを早くする(前処理の効果を測定する時に使うモデルは小さい物で良さそう.)[今回で言えばmobilenetを使って何が効くのかを調べた上で大きいモデルを使えばよかった]
あたりを身をもってしれたので今後に生かしていきたいですね.
明日はgenya0407さんが担当です.お楽しみに !!! ほなまた〜(修論書かないと...)
*1:original data provided by JAXA
*2:ここら辺のaugmentationはalbumentationのライブラリをみるとわかりやすい.
*3:詳細http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_imgproc/py_thresholding/py_thresholding.html
*4:エッジ検出http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_imgproc/py_canny/py_canny.html