🛹

Signate スケートボーダー重心位置予測チャレンジ 4位解法

2024/12/18に公開

はじめに

少し前にSignateのMotion Decoding Using Biosignals スケートボーダー重心位置予測チャレンジに参加し、4位に入ることができました。

Signateはコンペ終了後にKaggleと違って解法を記載する文化がないため(おそらく解法非公開のコンペが多いため)、こちらに解法を記載させていただきます。

問題設定

  • 図のようなハープパイプ形状の経路をスケートボードで滑走したときの皮膚表面から記録された生体信号データ(16チャンネル)を用いて、スケートボーダーのxyz空間での重心速度を予測するという問題です。
  • 信号データは2000Hzで取得され、学習対象路(青色の部分)のうち、各試行でランダムサンプリングされた0.5秒分、1000点が1回の試行データとして与えられます。ユーザは5名で、train,testで各320回試行を行います。
  • 同時に60Hzで重心速度が取得されており。予測するのは0.5秒間、各30点のxyzの速度情報になります。
  • 学習対象と予測対象の走行区間は異なります。
  • ボーダーは、ターンやポンピング等のトリックも行います。
  • 評価指標はRMSEです。

序盤で考えていたこと

問題設計的にリークが発生しやすそう(trainとtest、train同士やtest同士で時系列的なつながりがありそう)だったので、ルールベースやコサイン類似度を用いてtrainとtestで繋げるものがないかを探していましたが、結局見つけることはできませんでした。

副産物として各試行の開始時刻を調整してユーザ毎にxyzがどのように動くかのイメージが作成できたので、問題理解の役には立ちました。下記の図はuser5の各trialの位置データの開始時刻がそれらしくなるように調整したものです。

ここから下記がわかります

  • 評価値に与える影響はx軸(走行方向)の影響が最も大きい。
  • xyzの値はすべて時刻依存性が高い。
  • 例に用いたユーザは各試行の軌道が安定しているが、ユーザによってはブレが大きくなる

また、直近で終了したKaggleのHMSコンペが波形を扱っており役に立ちそうだったので、上位のソリューションは全て読みました。特に1st8thのソリューションは非常に参考になりました。

解法

解法図

  1. 筋電位のrawの波形を縦に並べて(3,224,224)の画像に変換します。
  2. 1の出力をefficieitnet_b0に入力し、1280次元の特徴を抽出します
  3. user_idを10次元にembeddingします
  4. 2と3をfc層で結合し、90点の予測値を出力します
  5. 画像を2通りの手法で生成。それぞれ5fold、2通りのseedで学習し20通りの予測値を出力します
  6. すべての予測値のmedianを最終的な予測値とします

工夫したポイント

画像の作り方

波形を画像化する方法には様々な方法がありますが、使用したのは下記2つの方法です。気持ちとしては、NNに必要な情報をできるだけ読み取ってほしいという感じですね。

  • スケールを-0.2~0.2に固定して全波形を描画(左図)
    • このスケールであればほとんどの場合枠内に収まる
    • スケールを固定することで波形間の大小関係を学習してくれるはず
  • スケールを色にもたせて全波形を描写(右図)
    • それぞれの波形についての情報を大きくもてる
    • 色情報により異なる波形間の大小関係も学習してくれるはず

見え方違うのでアンサンブルにも寄与しそう?

周波数を用いたアプローチ(fft,sftp,wavelet,バンドパスフィルタ)も試しましたが、いずれも精度向上には寄与しませんでした。そもそも筋電位は複数ある筋繊維の出力を足し合わせたものであるため、周波数的な特徴は出にくいのではないかと思っています。

例えば坂を登る前には膝を曲げるなど、筋肉を動かす位置はある程度固定化されているので、筋電位の大きさの情報を元にどの位置にいるかの推定を行うことはある程度可能であったと思います。

overfitの防止

学習データが2000弱と少ないため、overfitに気をつける必要がありました。

下記した対策としては下記になります。

  • 1モデルで90点すべての予測値の出力を行う
    • 個別に推論するよりも学習が安定、学習時間の節約にもなった
  • モデル構造のdropoutを多めに
    • drop_rate, drop_path_rate, dropoutを高めに設定
  • augmentation
    • 左右の入れ替え、強めにXYZmask
      • 特にXYZmaskの精度への寄与度は大きかったです。
  • 正則化
    • optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-4)
  • 外れ値の可能性が高いサンプルを学習から除外
    • trainでOOFの予測値が真値と一定以上乖離しているものは除外
      • 最終的に9サンプルのみ除外した
  • medianでアンサンブル
    • 予測値も外れ値が出ることがあるため、meanではなくmedianにすると精度向上

精度に寄与しなかったこと

  • 周波数の利用
    • 周期性よりもある時間帯における振幅情報が重要だったためと思われる
  • 複数の手法で作成した画像を結合させて入力
  • CNNやEEGNET等の画像ではなく波形をそのままNNに入力するアプローチ
  • 波形を特徴量化してlightgbm
  • efficientnet_b0以外のモデルの使用
  • 入力画像のサイズ変更
  • 学習に使用するデータの時間を短くしてtrainデータを増やす
    • 短くすると欲しいタイミングのデータがとれなくなり精度劣化

あとがき

コンペに通しで参加したのは久々でしたが結構楽しめました。
コンペ中にドメイン知識を得るためにスケボーを実際にやるか迷って結局行かなかったのが心残りです。行ってたらもう少しアイデアが出たかも…?

Discussion