M5 Forecasting - AccuracyであたりまえなShake upしました。
銀メダルでした。
在宅勤務しながら取り組めたので相対的にコミットする時間は作れたのかもしれません。
Rコンペ、Elo以来なので、1年ぶり(!)のメダルです。
一応、今年始めのShake downのリベンジが叶ったのでしょうか。
備忘録のためにやったことを書いておきます。
どんなタスク?そしてどんなデータ?
2011年〜2016年のWalmartの売上個数データ(店舗×商材×時系列ユニーク)を使って、それぞれ売上個数を予測するタスクでした。
店舗はアメリカの3つの州(テキサス・カリフォルニア・ウィスコンシン)にある店舗、
商材は食材(FOODS)、おもちゃ(HOBBIES)、日用品?(HOUSEHOLD)で分かれており、
各店舗の、商材別の売上個数予測(28日間)を行うという形でした。
また、M5オリジナルの要素として、コンペ終了1ヶ月前にpublic LBの正解が公開されるという形式となっており、
予想通り、ここ1ヶ月のpublic LBは地獄のようでした*1
指標は?
WRMSSE(重み付き標準化RMSE)。計算方法は下記を参照。
RMSEの変種ですが「重み」が本コンペの一番キツいところでした。
重みを計算する都合上、カスタム指標としてモデルに組み込むと計算が遅くなってしまい、
代替指標にRMSEを使っても、バリデーションの挙動が異なるため、
結果が全く読めないというところが厳しかったです。
お前の結果は?
public LBでは3379位、 private LBでは123位でした。
最後の1ヶ月ではほぼshake upするだろうと読んでいたのですが、銀圏に入れるほどのことはしていないので、
結構コンペの設計に助けられたな、と思っています*2。
参考にしたkernel
EDAは基本的に以下を参考にしていました。
aggregateされた結果からある程度の周期性が読めたので、おそらく移動平均系の特徴量でどうにかなること、
思ったよりもSNAP系は重要ではなさそう(特定の商材にしか通用しないのでは)という見立てを立てていました。
モデルのベースラインは以下です。
スコアでソートすると、これよりも良いkernelがあるのは確かなのですが、
高いスコアが出るロジック的に、private LBに対しては通用しないなあと思い、
まずは上記のシンプルな実装で、publicとprivate両方で使えそうなモデルにアレンジしています。
戦略は?
上のkernelをベースに、特徴量エンジニアリングをアレンジしたり、
回帰問題を poisson
や tweedie
に置き換えたりした1つのLightGBMモデルで構築しています。
具体的には28日の移動平均・標準偏差・最大最小値とか、28日以上のlag、diffなどをメインに作っています。
28日の理由は、public、privateの予測区間によります。
これより短いlagの場合、testデータに対しては何らかの形でうまく補完しないとLeakする上、
きぬいとはこれに対して頑健な策を持っていなかったので、 取れる最大限のlagでさばきました。
今回のタスクそのものはシンプルな売上個数の予測なので、下手にblendingやstackingをしてもうまく行かないんだろうなあと思っていました*3。
データ量も多かったため、1日単位のlagよりは7日〜1月単位でとらないとKernelのメモリに乗らないみたいな制約もありました。
また、今回はOptunaを使ったハイパーパラメータのチューニングを行いました。
実装が非常にシンプルで、使いやすかったです。ローカルマシンで1日くらいかかりましたが。
結果、同じ特徴量でもOptunaによるチューニング結果のほうがprivateは良く、チューニングは大事だなあと思いました。
CVについても、上のkernelでのCV(Customized Time Series Split, 3-fold)をそのまま使いました。
いろいろ議論はあったのですが、時系列に対してのCVとしてはこの形が頑健だろうと思って、これにこだわりました。
submitしたスコアは、一番新しい系列で学習した結果のみを使っています。
EDAの結果から、全体的な傾向にトレンドが強くあったようなので、
CVから平均を取ると、トレンドを過小評価するほうにバイアスすることを懸念していました。
このあたりの厳密な検証はできずじまいなので、追って検証できればなあと思います。
やらなかったことは?
いっぱい。
例えば今回は売上個数が0、みたいな領域の多いデータだったので、そこをうまく使えると嬉しかったなあとか思っています。
(売上0の日が続く最大日数とか)
あとはvalidationについても、予測期間の前年のところを使うことでもう少しうまくできたのでは?とか思ったり。
kaggle本にも類似の記述があったので、実装していたら変わったかなあ、という気持ちはありました。
マシンリソースの制約で、使わなかった特徴量とかいっぱいあるのですが、これはもう言おうと思えば山ほどあるので。
ともあれ
不確定性が多い*4コンペティションだったので、結構保守的なモデリングに終始し、
「上位20〜50%くらいに行ければ御の字だな」とか思っていたら、意外と結果が良かったです。
タスクとして非常に難しいコンペティションだったので、ドロップアウトする人も多かったのかな、と推察します。
撤退も一つの戦略なのでそれそのものは善悪がないのですが、最後まで取り組んだというところがこの結果に結びついたのであれば、
最後までとりあえずやってよかったな、と思っています。おわり。
今夜はゆっくり寝られそうです。
おわりに
できるならソロ金でmasterになりたい