はじめに
5/12~8/4の期間で開催されていた「Google Smartphone Decimeter Challenge」に参加したのでその参加レポートとなっております.
結果は金に0.01及ばずといった,悔しい思いをする結果となりましたが,自身の実力不足な面をしっかり受け止め,もっと確実に上位に入れるように成長したいと思いました.
この記事を作成した目的は,自身が後にこんなこともあったなと振り返れるようにもありますが,これからKaggleを始めようとしている人や,登録したもののハードルを感じて参加できずにいるような人に読んでもらえたらと思っています.
お恥ずかしながら,自身もKaggleに登録したものの1年くらいまともに手を出せずにいました.
これまでまともに参加できなかった理由は,まず仕組みがよくわからない,ディスカッションや公開コードが豊富だが,言語が英語であり,さらに数学的な知識が足りず理解ができないものが多い,そもそもの与えられたデータがよくわからない,データサイズ大き過ぎて...,などなど多くありました.
一応,大学院まで出ていますが,偏差値50程度の大学出身者としては,この記事を書いている今でもハードルが高いなと感じてはおります.
そんな自身ですが,今回のコンペはkaggleに登録して初めて真剣に取り組んだコンペであり,自身のように決して高度な数学に関する知識があるわけでもなく,英語が苦手でも,あと少しで金圏に入れるようなところまで戦うことができることもあることを知ってもらえたと思い記事を作成しました.
記事を読んだことで,kaggleに限らずコンペに参加するようになったよって人が一人でもいたら嬉しく思います.
コンペ概要
詳しくは直接ページを見に行ってもらいたいのですが,車の中にあるスマホの位置情報を推定するコンペでした.
indoorコンペと呼ばれる位置情報推定のコンペが終わった後に開催されたので,参加者からはoutdoorと呼ばれておりました.
データとしては衛星の情報やコンペのホストの方が計算した座標の推定値が与えられておりました(自身は最終的にこのデータと真の値しか使いませんでした).
序盤(5月)
とりあえず,参加申込をして取り組み始めました.
参加して最初に思ったことは,列の意味がよくわからないなってことでした(ドメイン&語学力不足).
公開notebookはカルマンフィルタ使っておりましたが,カルマンフィルタをよくわかっていなかったので少し勉強しましたが,しっかりとした知識のない中,今回のコンペ期間中にしっかり理解し,活かしきることができるかわからなかったので,まずはいったん使うのを辞めて,他の方法で何とかならないか試してみることにしました.
このあたりに関しては賛否両論あると思います.
こういったことを続けてしまっているので,未だにつよつよになれていないとも思いますので,実力をつけるという意味では,しっかり向き合うべきだなと思います.
ただ一方で,数式を理解できる能力や前提知識などには個人差があると思いますので,無理に公開されているものを全部理解しなくてはとなって,結果続かないよりは,多少のわりきりも必要なのかなと思います.
自身はLSTMのモデルをよく使用することもあり,それらのモデルを序盤は試しておりました.
朝からやったおかげで少し進捗出ました. #superintelligence? #kaggle https://t.co/Ln83szOKxz pic.twitter.com/9DyajM3bFS
— たかいと (@takaito0423) May 22, 2021
個人的にですが,コンペ序盤は運よく上位に入れたりするので,モチベーションを保つ上でも序盤に一度手を付けるのをオススメしたいです(序盤でLBの結果だとしても,やっぱり上位に名前があるのは嬉しいものです).
あとは無難な後処理として,前後の値で平均を取る移動平均をとったりすることで少しスコアを伸ばしていきました.
とりあえず,6.0の壁を越えられた.#kaggle https://t.co/Ln83szOKxz pic.twitter.com/dnwp8g2zKO
— たかいと (@takaito0423) May 26, 2021
5月には最初のチームを組むかどうかの葛藤がありました.
ものすごくチームを組んでみたいお気持ちがあるけど、今後どれくらいやり込めるか未知数なのと、まだkaggleに本気で取り組んだことがないので、時期尚早であるお気持ちが強い(´・ω・) https://t.co/b5GvHcBOoZ
— たかいと (@takaito0423) May 24, 2021
自身はソロ参加を選びましたが,チームを組んで参加するのも一つのモチベーションにもなると思います.
完全に余談ですが,ころんびあさん始め,優秀な学生の方が本当に多いので,自身ももっと頑張って実力をつけたいなとモチベに繋がっているので本当に感謝しています.
中盤(6月)
5月の終わりにいろいろ試した結果,あまりスコア改善しなかったので,上位のスコアを考えると,根本的に方法を切り替える必要があるなと思い始めました.
もう少しスコア改善するはずでしたが,そんなに甘くありませんでした.
— たかいと (@takaito0423) May 29, 2021
最終的に5.0を超えるのを目標に,きちんとデータと向き合います.#kaggle https://t.co/Ln83szOKxz pic.twitter.com/SiRxgVaHKF
ただ新卒で忙しいことなどもあり,少し目を離している間に順位がかなり落ちていました.
原因はスコアの良い公開notebookが公開されたのが理由だったので,それをとりあえず超えたいというモチベーションで取り組みました.
土日が待てずに手をつけたけど,金圏まで戻れず...
— たかいと (@takaito0423) June 7, 2021
上位のスコアを考えると,しっかりデータと向き合う時間を確保したいところ. #MachineLearning #kaggle - https://t.co/Ln83szOKxz pic.twitter.com/LG1FBnqfqw
ちょっとだけ進捗出た. pic.twitter.com/MPaw6DWH8E
— たかいと (@takaito0423) June 18, 2021
とりあえずスコアを更新したものの,上位とは未だ乖離がある状態でした.
このあたりの取り組みとしては,外れ値の除去やスタート地点とゴール地点を処理する方法をいろいろ試したり,ブレの大きい特定のエリア(学習データにおけるSJC)は,真の値で道がどのあたりにあるのかわかるので,その道の近くに補正するといった後処理を行っておりました.
ただ,根本的にこのままでは勝てないなと思い,いったん1から方法を考えなおすことにしました.
ちょっとだけ進捗出た.
— たかいと (@takaito0423) June 20, 2021
この土日でじっくりデータを見ることができ,いくつかスコア改善のアイデアもストックできたので,頑張って実装していきたい.#kaggle - https://t.co/Ln83szOKxz pic.twitter.com/tIPF8eCbIx
この土日で,もっとシンプルな方法でいろいろ試せることがあるなと思ったので,それらを手書きでノートにメモしておきました.
そこからは実装をコツコツ,平日の仕事終わりなどに行っていました.
終盤(7月)
ストックした方法をいろいろ試した結果,スコアを伸ばすことができました.
試したのは,まず速度を点と点の距離とそれにかかった時間から計算し,真の値の移動速度の最大値の1.5倍以上のものは外れ値判定をしました.
また,その前後3点も外れ値の近くで信頼できる情報か怪しいため外れ値判定をしました.
そして,それらの外れ値を除いた上で,残った点の中でぶれが大きそうな点の判定を行う関数を実装しました.
内容はいたってシンプルで,まずある点を含めて前後2点を用いて,時間を説明変数とし,各latDegとlngDegの値を目的変数とする単回帰を行い,その回帰直線との誤差をすべての点で計算して保存していきます.
これにより,スタート地点とゴール地点は3回分,それらの点の隣接点は4回分,他の全ての点は5回分の回帰の誤差があるため,それらの回数で誤差の合計を割ることで,その点の前後の点に対する当てはまりの良さを示す値を計算することができます.
この値が大きい値はすなわち誤差が大きい点であり,あまり信頼すべき点でないため,外れ値判定を行いました.
そしてこれら2つのステップで外した値を除いた上で,次の処理を行うことで,座標の修正と外れ値として外していた座標の値の推定を行いました.
まず,ある点を含めて前後5点を用いて,時間と時間の二乗を説明変数とし,各latDegとlngDegの値を目的変数とする重回帰を行い,その回帰曲線で各latDegとlngDegの値を推定します.
時間と時間の二乗を説明変数としているのは,正解データを確認した際に,直線よりも二次関数の方が当てはまりは良さそうな点が多いと思ったので,二次関数を利用しました.
以下に示した画像は,赤色の点がある時間での真の各座標の値を示しており,前後10か所の点を表示していますが,前後5点程度のさらに局所的な区間においては,二次関数で当てはめると良さげに見えるかなと思います.
また,この重回帰に使用した前後5点の間に外れ値として除かれているデータがある場合には,時間の情報を入力することで各latDegとlngDegの値を推定することができます.
これをすべての点で行うことで,各点に推定した値がいくつかある状態になります.
後はそれらを各点で平均をとることで,ブレが小さくなった座標を得ることができました.
ここから機械学習を使用していくのですが,ここで一つ気になったのが,学習データの'2020-05-29-US-MTV-2'の存在です.
このデータはもともと与えられた予測値では途中で停止しているのですが,真の値を見ると動き続けており,testデータでもしこういった箇所があったとしても,予測するのは不可能だなと思い,学習データの最初のスタート地点付近とゴール地点において,真の値との誤差が異常に大きい点が連続し続けるような点があるデータに限っては,真の値と予測値の値がある程度の誤差になるところまでデータを除去するようにしました('2020-05-29-US-MTV-2'のみでした).
下の画像の赤い部分が真の値ですが,黒い部分は途中で止まってしまっています.
学習データの異常値も削除できたので,ここから第一段階の機械学習利用です.
この段階では先ほどのスタート地点とゴール地点のブレが非常に気になったので,停止しているのかどうかを予測する二値分類を行い,スタート地点付近の動き出しまでの点,ゴール地点で停止を開始した点を特定し,停止している間の点の平均値を算出,元の値と0.999 : 0.001の割合で加重平均を取りました.
二値分類はLightGBMを使用し,特徴量は推定値から計算した速度,加速度,進行方向の変化(cos, sin),前後の座標情報を使用した主成分分析の第一主成分と第二主成分と第一主成分÷第二主成分,これらを前後30点ほど予測に使用できるようにshift特徴量を用いました.
まず大切なことは,先に外れ値の処理などをかけておくことで,そうしないと速度の情報などの質が落ちてしまいます.
今回のコンペでは失敗した多くの方法を含めると50以上の処理を作成しましたが,処理をかける順番はいろいろと試行錯誤しました.
特徴量に関しては,cosの値はけっこうこの後も重宝しました.
今回は車の移動ということで,急激な方向の転換はほとんど真の値に存在しなかったため,この推定座標のcosの値は0.1未満のものはたいてい何か問題が起こっているので,そういった点を調整するための関数を後半はいろいろ作成しました.
座標を主成分分析したものを入れたお気持ちは,スピードを出してまっすぐ進んでいる最中の点は第一主成分は大きく,第二主成分が小さくなり,停止している点は第一主成分も第二主成分も小さくその割り算した結果も1に近くなるなど,いろいろ特徴を捉えることができるような気がして入れました.
多くの参加者はstop meanで停止している地点で平均をとっていたようでしたが,自身はスタート地点とゴール地点のみにしておりました.
理由は停止中に一度ほんの少し進んでまた停止することとかもある気がし(道を曲がるときとか),そのあたりがうまく捉えられて二か所の地点でstop meanできればいいが,できるとも限らないため,そのままにしていました.
もちろんstop meanしない代わりに他の調整をかける関数をこの後たくさん実装してます.
この次は,学習データのSJCの地点はブレがとても大きいため,この真の値を利用して道のある位置を特定し,その道上の点と元の値とを0.999 : 0.001の割合で加重平均し補正する処理を行いました.
最後に,collectionごとをまとめて調整しました.
こちらも二次関数で調整することに自身はしました.
collectionごとの処理は三段階構成となっています.
まずcollectionごとのある時間から,その前後 t 秒の推定した点を用いて,時間と時間の二乗を説明変数とし,各latDegとlngDegの値を目的変数とする重回帰を行い,その回帰曲線で各latDegとlngDegの値の誤差を推定し,誤差の二乗が一番大きい点の除きました.
その上で,もう一度重回帰を行い,その回帰曲線で各latDegとlngDegの値の誤差の二乗を推定し,各点の誤差の二乗を保存していきます.
これらのすべての点で行い,各点の誤差の二乗の値の平均をとり,その誤差を用いてweightを作成しました.
最後にcollectionごとのある時間から,その前後 t 秒の推定した点を用いて,時間と時間の二乗を説明変数とし,先ほど求めたweightを利用して,各latDegとlngDegの値を目的変数とする重回帰を行い,その回帰曲線で各latDegとlngDegの値を推定しました.
このcollectionごとの処理がとても大事でこれにより,スコアを大きく伸ばすことができました.
とりあえずの目標には到達しましたが,あの頃とはLB状況がだいぶ異なるので,もう少し頑張ります!#kagglehttps://t.co/Ln83szOKxz https://t.co/08jBfsbRxo pic.twitter.com/5gJtqOm832
— たかいと (@takaito0423) July 6, 2021
ただ,この頃には5.0を切ってもまだまだ上に参加者がたくさんいたので,金圏に戻れるように残っていたアイデアを実装し続けていました.
そして,7月の後半に1mの改善を達成し,金圏に一度戻ることができました.
金圏に戻ってきました.
— たかいと (@takaito0423) July 25, 2021
この調子でCommonLitも何とかしたい.https://t.co/Ln83szOKxz pic.twitter.com/X5LleCG1C9
この結果の大きな要因は2つあります.
まず1つ目の要因はスタート地点とゴール地点の処理までを行った後に,stop meanの代わりに下の画像に示すような速度が極端に遅い点のブレを調整することでした.
本当は下のような箇所も調整できるようにしたかったのですが,この時点ではカーブを曲がっているような箇所にも影響を与えてしまう方法しか思いつかず,このような点は最後のギリギリで調整する関数を実装しました.
これまでは時間との関係を見てきましたが,X軸: lngDeg,Y軸: latDegで明らかに直線で動いているはずがブレてしまっている点を直線上に調整する必要があるため,これらの点の集合とこれらの点の集合の前後の点との関係性から,直線上に補正するかどうかを判定します.
先ほどのLightGBMモデルで停止判定されている箇所で調整すべき点の候補はわかっているため,修正すべき点の連続している部分とその前後の点を特定します.
わかりにくいですが,さきほどの画像の緑色の点が調整すべき連続した点と隣接している点であり,青色が調整すべき点の開始点と終了点となっています.
真ん中あたりの黒い点は修正対象の点であり,北と南の黒い点は緑の点の前と後の点となっています.
まずこの青い点を結んでできる直線と緑色の点を結んでできる直線の交点を求めます.
求めた交点は,先ほどの図の赤い点となっています.この赤い点が緑の2点によって作られる長方形の中にあれば,この調整すべき点を調整することにしました.
まず調整その1として,ばらついている点を青い点と青い点の間をゆっくり移動しているようにしたかったので,中心に点が集まるように青い2点の直線上に,配置します.
例えば,6点をこの直線上に配置するのは,直線を4:3:2:1:2:3:4に分ける点に調整します.
このような調整を行ったのが以下の図になります.
ここからさらに,止まっている点の情報自体があまり信用ならないので,緑の点の直線上に調整を行いました.
その調整を行ったのが以下の図です.
もちろん,この青い点も実はあまり正確でないことが多いため,後半ではもう少し長期的なスパンで,調整を行いましたが,この時点ではこの程度にとどめておきました.
2つ目の要因は,この段階でLightGBMやCatBoostを使用して,座標のずれを修正しました.
collectionをまとめて,二次関数で補正した後にこれらのモデルを使おうとすると,学習データに似たようなデータばかりになり,過学習しやすく精度を上げることができなかったので,あえてこの段階でこれらのモデルを用いて,真の値と現在の推定値の各lngDegとlatDegがどの程度ずれているかを目的変数として予測し,その予測した値を用いて,調整を行いました.
そしてその後は,これまで通り,SJCエリアの真の道への補完を行い,collectionをまとめて,二次関数で補正した結果,大きくスコアを伸ばすことができました.
この後,上位の豪華メンバーチームからチームマージのお誘いがあり,どうするべきかとても悩みました.
自身がこの先忙しく,どこまでやれるかわからないことや,豪華チームにマージしてもらったことで,外から自身がどのくらいチームに寄与しているのかが見えないとも思い,ソロで最後まで走り切ることを決意しました(もう少し期間が残っていて,自身に取り組む時間があれば成長のためにチームマージをさせてもらった可能性はありました).
チームマージはしませんでしたが,チームマージのお誘いは本当に嬉しくて,個人的には泣きそうでした.
実力がもっとついて,しっかりとコンペ終盤にもアイデアをどんどん出せるように成長した暁には,尊敬つよつよ勢の方ともチームを組んで一緒にコンペを闘い抜いてみたいと思っているので,頑張って成長できたらと思っています.
最終盤(8月)
ない時間を無理矢理作って,いろいろ試していました.
ただ,そこまで大きな影響があったものはあまりないので,本当にざっくり有効だったものを挙げます.
直線補完
先ほど示した以下のような箇所がcollectionごとに処理した後にも残っていたので,こういった箇所を直線で調整しました.
先ほどは調整すべき候補の点と隣接している二点の青い点のみを用いていましたが,そこから距離が r 離れている点までを確認し,一番離れた点同士の進行方向がまず一緒かどうかをコサイン類似度で確認しました.
これにより,カーブしている点の近くが大きく真の値から外れることがなくなります.
あとは,これらの調整すべき点をどこの点の情報を用いて補完するのかをいろいろ試しました.
真の座標を道の情報として,道の上に補正を行う
まずSJCエリアは,かなりブレが大きく,単純に道の上に補正するだけだと,本来右に曲がっているのに,一度左に曲がってその後Uターンして右に向かうみたいなことになってしまっていたので,どこからどこに向かっているのかの情報を加味した道の上への調整を行うようにした.
このどこからどこに向かっているのかを判定するために,雑なノードをまず推定しました.
これで真の道がどこからどこへ向かっているのかがわかり,その動きと同じ動きをしている点をその道の上に調整することができました.
あとは学習データの'2021-04-29-US-MTV-1'のエリアもエリアとの一致度を計算することで,'2021-04-28-US-MTV-1','2021-04-29-US-MTV-1','2021-03-16-US-RWC-2','2021-04-21-US-MTV-1','2021-04-28-US-MTV-2','2021-04-29-US-MTV-2'は学習データの真の座標を用いて,極端に離れている点以外は,道の上に補正を行いました.
他のエリアはCV,LBともに悪化するため,いろいろ工夫を行いましたが,ここでstop meanを行っていなかったことにより,点の進行方向が不安定な箇所が残っており,本来正しくない反対車線の真の道などに調整されてしまうなどの対策が間に合わず,使えませんでした(SJC同様,ノード作成すればうまくいった可能性もあるかもしれないです).
collectionごとの二次曲線補正にスマホの精度情報をweightとして加える
もともと与えられていた座標の値とcollectionごとの二次曲線補正を行う前の座標の値の誤差を計算し,この誤差が同一collectionで大きいものと,一番小さいものを比較して,その割り算の結果が大きいものは,もともとの情報が不確かなため,その割り算の結果を利用してweightがさらに小さくなるように調整を行った.
アンサンブル
collectionごとの二次曲線補正をするための使用するデータの時間のレンジをいろいろ変えたり,二次関数を三次関数に変更したものなども似たような精度を出していたので,いくつか試したものをアンサンブルしました.
本当はもっと序盤の処理などもいろいろ値を変えて試して,アンサンブルに利用したかったが間に合わず.
結果
Public 19 / Private12
仕事だったので遅くなりましたが,outdoorお疲れ様でした!
— たかいと (@takaito0423) August 5, 2021
最後なかなか時間が取れずオールで臨みましたが,一番ダメージを負う結果となりました.
金圏に一歩及ばずからの立ち直り方を誰か教えてください. pic.twitter.com/3WTFYI6MAA
最後の方にPublicの順位上がらず,有給休暇をとるのを辞めてしまったことは後悔しています.
部署の先輩方に有給休暇とって臨めばいいのにって言われてましたが,金圏から押し出されていたのでそこまでするのは流石にと思っていました.
たぶんこれがLBで12位なら有給休暇とっていましたが,過ぎたことを嘆いてもしょうがないので,もっと強くなれるように頑張ります.
感想
初メダルで喜べるはずが,金圏にわずかに届かなかったことによりしばらくはショックが上回ってしまいました.
ただ,明らかに自身の実力以上の結果ですので,ここで慢心せず,もっと実力をつけるように天から12位を与えられたような気がします(精進してもっと強くなるしか).
コンペを走り切った感想としては,ソロで取り組むには,少ししんどさがあるコンペだったかなと思います.
自身は結局,GNSS周りのデータは全く使わず前処理,後処理で戦うことを決めましたが,こちらも使うには自身の場合,少なくとももう一か月必要でした(それでもそっちで結果出せていたかは...).
ただ最終的には金圏にソロの方々がたくさん入っていたので,本当にすごいなと思います.
テストデータの推定値は可視化することができるので,それらのざっくりとしたチェックなどを行ったりもshakedown対策に必要でした.
特に深層学習モデルを学習させておくみたいなインターバルがないので,一生懸命コードを書いて実行を行うとすぐに結果が返ってくるため,それらの処理が有効そうかどうかのチェックなどを一息つく間もなく行い,次の処理を試すみたいな感じだったので,自身は大変でした.
上位の方々のように,ドメイン知識で取り組んで行けたら,また全然違った闘い方になると思います.
あとあまりにも最終盤時間がとれなかったため,最終日はオールで取り組んだのですが,あまりの眠さで=と==を書き間違えて,このミスに気がつくのに1時間以上かかってしまいました.
このあたりの時間のロスがなければ,もっと複数パターンの処理の組み合わせと,処理に使用するレンジを変えたものを試して,アンサンブルができたので,しっかり反省して次回は余裕をもった最終日を迎えられるようにできたらと思います.
おわりに
いろいろと書いてきましたが,この記事で伝えたいことは,コンペによっては,あまり難しいこと知らなくても,自身がちゃんと理解しているものをしっかり使えば戦えるし,ディスカッションに書いてあることを全部理解し実装する必要性も初心者のうちはないのかなってことです.
もちろん,上に書いたことができるに越したことはありませんが,誰もがそんなに最初から強いわけでもなく,それをハードルに感じてやめてしまうにはもったいないのかなと思います.
正直,お恥ずかしい話ですが,自身の最終的に提出したものは,回帰分析と主成分分析,コンペでは一般的なLightGBMくらいしか使用していませんし,ディスカッションも実はあまり追っていませんでした.
なので,難しいことを全部理解しないといけないのかとは思わず,自身が興味のあるものなどを中心に進めて行ければいいのかなと思います(流石に自身はそろそろ強くなるために,その段階を卒業すべきですが...).
そもそも,貴重な時間をコンペなどに割いている時点で十分すごいことだと思いますし,無理のない範囲でコンペに参加して楽しめたらいいのかなと思います.
いろいろあり,金圏に一時期は入れたりもしましたが,50位くらいに離されたり,最終盤LBスコア全然伸ばすことができなかったりと,挫けそうになる盤面も多かったですが,いろいろな方からの応援もあり,最後まで走り切ることができました.
コンペを一緒に走り切った方々,応援していただいた方々に感謝してこの記事を締めくくりたいと思います.
最後まで読んで頂きありがとうございました!