u++の備忘録

signate「国立公園の観光宿泊者数予測」コンペで10位でした

本記事は、kaggle Advent Calendar 2018 その2の21日目の記事です。

qiita.com

はじめに

signateで開催されていた「国立公園の観光宿泊者数予測」コンペに参加し、10位でした。

signate.jp

参加者は471人、一度でも投稿した人が124人だったので、それなりの順位ではありますが、上位陣と大幅に差をつけられてしまい個人的には非常に残念な結果となりました。

本記事では、備忘録も兼ねて、私の解法をまとめておきたいと思います。

但し書き

本記事は、signate「国立公園の観光宿泊者数予測」の参加規約を遵守した範囲で作成しています。

https://signate.jp/competitions/141#terms

「第4条(秘密保持)」には、秘密情報には含まれないものに「開示されたいかなる情報にもよらずに独自に開発した情報(入賞対象者の評価対象提出物を除く)」とあります。私は2018年12月17日の「予測モデルソースコードの提出締切(※入賞候補連絡を受け取った方)」までに連絡を受け取っていないため「入賞対象者」になりえません。

これをもって、参加規約に反しない範囲で本記事を執筆します。

コンペの概要

本コンペでは、8つの国立公園周辺の観光宿泊者数を予測しました。

学習データの期間は「2015/1/1~2016/12/31」、テストデータの期間は「2017/1/1~2017/12/31」でした。

その他に以下のデータが提供されました。また外部データの利用も可能でした。

  • SNSデータ(株式会社ホットリンク)
  • ロケーション付SNSデータ(株式会社ナイトレイ)
  • メッシュ型流動人口データ(株式会社Agoop)
  • 公共交通検索ログデータ(ジョルダン株式会社)
  • 国別月別来訪者数集計データ(株式会社コロプラ
  • 気象データ(気象庁
  • 積雪気象観測データ(防災科学技術研究所(NIED))

時系列データの予測というタスクのため「前日以前のデータを元に翌日以降の宿泊者数が予測可能なモデルを作成する」ことが制約条件となっていました。

f:id:upura:20181220132715p:plain
出典:https://signate.jp/competitions/141#abstract

精度評価は「Mean Absolute Error(MAE)」でした。

f:id:upura:20181220140503p:plain

関連コンペ

直近の関連コンペとしては「Recruit Restaurant Visitor Forecasting」がありました。レストランの過去の来店者数・予約数・天気などの情報を基に、将来の来店者数を予測するコンペでした。

Recruit Restaurant Visitor Forecasting | Kaggle

コンペの構造が非常に似ているため、こちらのコンペの解法を読んで自分の方針を検討しました。

Kaggle Tokyo Meetup #4では、pocketさんが解法を発表しています。


特徴

作成した特徴の概要を以下に示します。

日付に関する特徴

  • 曜日
  • 祝日フラグ
  • 前日休みフラグ
  • 翌日休みフラグ
  • 六曜(大安とか仏滅とか)
  • 循環性をsin, cosに落とし込んだ値

祝日フラグは、手動で作成したので地味に辛かった思い出があります。

「循環性をsin, cosに落とし込んだ値」は、全特徴量の中でも一番効果を発揮していました。下図のように、1月1日から12月31日までで一周するような円を仮定し、それぞれの日付のsin, cosを特徴として抽出します。2年間の学習データが持つ循環性を良い具合に表現できたのではないかと思います。

f:id:upura:20181220140045p:plain

公共交通検索ログデータに関する特徴

このデータは「Recruit Restaurant Visitor Forecasting」における「予約」として見なせると考えました。具体的には、公園別・日別に、前日までに検索された回数を集計して特徴として加えました。

jorudan = pd.read_feather('./data/input/jorudan.feather')

jorudan = jorudan[
    jorudan['access_date'] < jorudan['departure_and_arrival_date']
]

jorudan = jorudan.groupby(
    ['departure_and_arrival_date', 'park']
).count()['access_date'].reset_index()

この特徴は「循環性をsin, cosに落とし込んだ値」に次いで効果を発揮しました。

カテゴリ変数

カテゴリ変数や、それらの組み合わせで「Target Encoding」をしました。カテゴリ変数の特徴量エンジニアリングについては、以下のスライドが参考になると思います。

気象データに関する特徴

前日までの情報しか特徴として使えなかったので、1日分スライドして特徴として採用しました。

使わなかったデータ

以下のデータは、一部試行錯誤したものもありますが、結局うまい具合に使いこなせず特徴には利用できませんでした。恐らく、この部分の特徴の作り込みで、最終的な順位の差が出たのだと考えています。

  • SNSデータ(株式会社ホットリンク)
  • ロケーション付SNSデータ(株式会社ナイトレイ)
  • メッシュ型流動人口データ(株式会社Agoop)
  • 国別月別来訪者数集計データ(株式会社コロプラ

モデル

それぞれの公園ごとに、sklearnのGradientBoostingRegressorを利用しました。データ数の問題か、LightGBMよりも良い結果を出していました。

全ての公園をまとめて学習する方法も試しましたが、私の場合は個別で学習させた方が精度が高かったです。まとめて学習させた際には、緯度経度の情報も特徴に加えましたが、特に効果は発揮しませんでした。

ちなみに「Recruit Restaurant Visitor Forecastingでは緯度と経度の四則演算が効果を発揮した」という謎の情報を得たので試しましたが、全く効果はありませんでした笑*1

時系列データということを意識し、交差検証に当たっては「TimeSeriesSplit」を利用しました。

sklearn.model_selection.TimeSeriesSplit — scikit-learn 0.20.2 documentation

upura.hatenablog.com

その他の試行錯誤

以下の2つは、理論的には非常に効果を発揮しそうですが、今回はうまく機能しませんでした。

  • 「Target Encoding」に当たって、学習データのうちテストデータに近いものをより重要視するような重み付け
  • ラグを特徴量に使うような実装

後者は、下記の記事のようなイメージです。

qiita.com

結果

f:id:upura:20181220150004j:plain

public LB20位くらいから、private LB10位になりました。時系列の検証をある程度意識したおかげか、private LBの順位が上がったのは朗報でした。

おわりに

本記事では「国立公園の観光宿泊者数予測」コンペの私の解法をまとめました。

おそらく上位陣は何か本コンペ特有のfindingsがあったのではないかと見込んでいます。可能ならば、何かしらの形で上位陣の解法をお聞きしたいです。

signateは今回が初参戦だったのですが、本コンペではKaggleのようなKernelやDiscussionがなく、他の参加者の取り組みを勉強できないのが心残りとなっています。Jリーグコンペのような公式のアフターイベントはなさそうなので、非公式な反省会を開催するのもありかもしれませんね。

[追記20181223]

僕の観測の範囲内で、解法を共有いただいた方のツイートをまとめました。

togetter.com

*1: