こんにちは、スタッフエンジニアの @kenkoooo です!
先日開催されたデータサイエンスコンペ「第1回 国土交通省 地理空間情報データチャレンジ ~国土数値情報編~ モデリング部門」で優勝しました!(最終順位表)
この記事では1位解法を解説しています。不動産データ分析の雰囲気が伝われば幸いです。
第1回 国土交通省 地理空間情報データチャレンジ ~国土数値情報編~ モデリング部門とは
国土交通省が主催する、賃貸住宅の賃料を予測するコンペです。株式会社LIFULLが提供する全国の賃貸マンション・アパートの賃料と物件情報を元に、与えられた賃貸物件の賃料を予測します。
上記データの他に、国土交通省が提供する「国土数値情報」というデータを活用できます。また、株式会社ゼンリンが提供する ZENRIN Maps API や、Snowflake合同会社が提供する Snowflake 環境なども利用できます。
1位解法
LightGBM を使う
LightGBM を素のまま使いました。パラメータチューニングも行いませんでしたが、seed を変えたモデルをいくつか用意し、それらの予測値の平均をとりました。
PARAMS = { "objective": "regression", "metric": "l1", "seed": 42, "learning_rate": 0.1, } lgb_train = lgb.Dataset(X_train, y_train) lgb_eval = lgb.Dataset(X_eval, y_eval, reference=lgb_train) model = lgb.train( PARAMS, lgb_train, valid_sets=lgb_eval, num_boost_round=100000, callbacks=[ lgb.early_stopping(stopping_rounds=100), lgb.log_evaluation(100), ], )
平米単価の誤差を最小化するようにする
コンペの評価指標は賃料の予測値の RMSE となっているので、最初はこれに従い、賃料の RMSE を最小化するように学習させていました。しかし、手元の Cross Validation を見ると Fold ごとに大きくスコアが異なることに気づきました。Fold 内の予測値を見てみると、賃料が50万円を超えるような少数の外れ値に振り回されている雰囲気を感じました。
estie でよく扱うオフィス賃貸の物件では、賃料総額よりも単位面積あたりの賃料(オフィスの場合は坪単価)に注目することが多いです。これは、契約前から間取りや面積が固定されている住宅と異なり、オフィスは何もないフロアに契約後に壁を立てて契約した広さの区画を作るため、契約時まで賃料総額が決まらないからです。これをヒントに、今回のコンペでも賃料総額ではなく、平米単価の誤差を最小化することにしました。
賃料総額が非常に高い募集では、平米単価が高いのもさることながら、専有面積も大きいことが多いです。高い平米単価 × 大きい専有面積 = 非常に高い賃料、となっているわけです。賃料総額ではなく平米単価をターゲットにすることで、外れ値の外れ具合を緩和できた面もあります。
RMSE ではなく MAE を最小化するようにする
機械学習モデルを学習させる際の評価指標として RMSE ではなく MAE を採用しました。RMSE は誤差を二乗して平均を取るため、10万円の誤差1件が1万円の誤差100件と同じ影響を与えることになります。そのため、外れ値に大きく引っ張られやすい指標です。このような性質から、高級賃貸物件に最適化され、大多数の通常物件に対する予測精度が低下する可能性がありました。
一方で、MAEは誤差を絶対値として評価するため、外れ値の影響を抑えることができます。通常の賃貸物件で適切な予測精度を維持しつつ、高級賃貸への依存度を低下させることを目的に、MAEを選択しました。テストデータに高級賃貸物件が多く含まれていると危ない気がしますが、RMSE で学習させたとしても精度高く当てにいくのは難しいと思い、割り切りました。
欠損している面積を埋める
先に述べたように平米単価を予測するようにしたため、専有面積が直接スコアに関わってくる重要な値となりましたが、与えられるデータの一部は専有面積が欠損していました。そこで、まず専有面積予測モデルを作り、これを使って専有面積を埋めてから、平米単価を予測するようにしました。専有面積予測モデルは平米単価予測モデルとほぼ同じ LightGBM のモデルです。
欠損している郵便番号を埋める
後述の通り郵便番号が非常に強い特徴量になっていましたが、与えられるデータの一部は郵便番号が欠損していたので、ゼンリン API を使って郵便番号を埋めました。
同一部屋の賃料から賃料を決め打ちする
test と同じ部屋の別の時期のデータが train に含まれているケースがありました。都心の賃料は上昇傾向にある一方で郊外は下落傾向にあったり、コロナで落ち込んでいる時期があったり、同じ部屋でも時期によって賃料は異なります。募集時期を変数に線形回帰した賃料を使いました。
同一ビルの賃料から賃料を決め打ちする
test と同じ建物のデータが train にも含まれているケースが多数ありました。当初は、同じ物件なら平米単価も同じと考えて train のデータで test を埋めようとしましたが、train の中でも同一物件内で平米単価が大きく異なるケースがいくつか見つかりました。そこで、平米単価をそのまま使うのではなく、専有面積・階数・募集時期で線形回帰した値を使いました。
出力を見てみると極端な値がいくつかあったので、適当な閾値を超える値は使わないようにしました。
特徴量を作る
与えられるデータをそのまま使ったり、集計したりした他、国土数値情報やゼンリン API と組み合わせて427個の特徴量を作りました。RMSE での評価値を改善するため、外れ値である高級賃貸に特化した特徴量も多くあります。
与えられるデータから作る
与えられるデータをそのまま使ったものもありますが、その中でも次の特徴量は特に強かったです。
- 専有面積
- データをいくつか見てみたところ、郊外では専有面積が大きくなると平米単価は下がる傾向がある一方で、都心では専有面積が大きくなると平米単価も上がる傾向がありました。
- コンペ終了後に社内の有識者に聞いてみたところ、都心の広い賃貸マンションは富裕層からの需要が高くなるため、広い部屋ほど平米単価が上がる傾向があるとのことでした。
- 間取りを数値化したもの
- 1K < 1DK < 1LDK < 2K < … のように間取りに序列をつけて数値化したものです。
- 築年数
- 市区町村名
- 郵便番号
- 初期から非常に強い特徴量でしたが、なぜかは最後まで分かりませんでした。郵便番号で賃貸を選ぶことはないと思うので、裏に隠れた何らかの地域性が重要だとは思うのですが……
また、与えられるデータから新しく特徴量を作りました。その中でも特に強かった特徴量を紹介します。
- 同一郵便番号内での平米単価の平均値・分散
- 建物周辺の募集の平米単価の平均値を「相場」として使えるのではないかと考えました。
- 郵便番号でグルーピングしたのは、前述の通りなぜか郵便番号単体が非常に強い特徴量だったためです。
- 周囲 2 km 以内の平米単価の平均値・分散
- 建物周辺の募集の平米単価の平均値を「相場」として使っています。2 kmという閾値は適当で、地図を見たときに自分が散歩する範囲が自宅の半径 2 km 以内だったためです。
- 築年数が同一市区町村内の平均の何倍か
- 築年数は賃貸を選ぶときによく見る指標の一つですが、古い建物が多い地域での築浅の物件は価値が高くなると考え、特徴量にしました。
- 専有面積が同一市区町村内の平均の何倍か
- 都心で専有面積が大きい物件は希少価値が高いので、高級賃貸を当てる指標になるのではないかと思い、特徴量にしました。
- 町名
- 同じ港区でも住所が新橋か六本木かで賃料が変わりそうだと思ったので入れました。
国土数値情報
特徴量の作成に国土数値情報を活用しました。利用した国土数値情報は以下の10個です。
地価公示
a. 地価だけでも強い特徴量だけでしたが、直近のものだけでなく過去の年度の値もあるので、変動率のような特徴量も作ることができ、精度改善に繋がりました。都道府県地価調査
a. こちらも強い特徴量でした。地価公示とは調査主体が異なるので、地価のアンサンブルのような感じになったのではないでしょうか。小学校区
a. 都心では中学受験に備えて有名公立小学校に子どもを入れたいらしい、という話を聞いたので入れました。駅別乗降客数
a. 最寄り駅の乗降客数として入れることで、地域の性質を表現できるのではないかと考えました。
b. 駅自体の情報も入っているので、最寄り駅への距離を出すこともできました。
c. 乗降客数上位25の駅からの距離を出すことで、「都会度」を表現する特徴量も作りました。医療機関
a. 小児科が近い物件はファミリー需要から賃料も高くなるのではないかと考え、最寄りの小児科への距離を算出するのに使いました。1kmメッシュ別将来推計人口
a. 会社で事業開発の人が「人口は最も嘘をつかない指標」と言っていたのを聞いて入れました。
b. 全体的に強く、特に 35〜39 歳の人口、 45〜49 歳の人口、 5〜9 歳の人口の3つが強かったです。平年値(気候)メッシュ
a. 過ごしやすい場所ほど賃料が高いのではないかと考えました。
b. 日照時間や最高気温が特徴量として強かったです。用途地域
a. 第一種低層住居専用地域にはコンビニなどの店舗が作れないが、第二種低層住居専用地域には作れる、みたいな情報が効くのではないかと思い、入れました。そこそこ強い特徴量でした。人口集中地区データ
a. 地域の性質を表現できるのではないかと考えました。人口密度の値が効いていました。学校
a. 最寄りの保育園までの距離を入れるのに使いました。
ハザードマップとして用いられる洪水浸水想定区域データも使えれば良かったのですが、他のデータに比べてサイズが桁違いに大きかったので、後回しにしているうちに終わってしまいました。
ゼンリン API
ゼンリン API を用いて全国のコンビニ一覧を入手し、最寄りのコンビニまでの距離を特徴量として入れました。
1位解法を支える技術
個人的に、アルゴリズムやヒューリスティックの競技プログラミングは好きなのですが、データサイエンスのコンペには苦手意識がありました。過去に参加したコンペでは、notebook やスクリプトが散乱し、データの作成やモデルの学習が再現できなくなり、モチベーションを失っていくことが多々あったためです。
今回は苦手意識を払拭すべく、データや学習の再現性を担保して、継続的にモデルを改善できる開発環境を作ることに重点を置きました。このコンペでは国土数値情報やゼンリンなどの外部データを利用することができるため、モデルの設計を頑張るよりも、データエンジニアリングを頑張って良い特徴量を作ることが重要になると考えたのもあります。
Snowflake & dbt
estie の社内では大量のデータを加工してプロダクトへと届けるデータエンジニアリングの基盤として、Snowflake 上で dbt を使っています。以下の理由から、コンペで使う環境にも Snowflake と dbt を用いることにしました。
- Snowflake 合同会社がコンペに協賛していて、参加者は無料で Snowflake 環境を使うことができる。
- 社内に知見が溜まっていて、Snowflake のクエリの書き方や dbt 内部の実装について分からないことがあっても Ryosuke839 に聞けば教えてもらえる。
- 国土数値情報などのオープンデータを dbt で Snowflake に取り込む方法が社内で確立されている。
dbt は SQL や Python でテーブルを作るスクリプトを書くことができ、各スクリプトの依存関係を自動的に認識してくれます。全ての作成済みテーブルが消えてしまったとしても dbt build
とやるだけで自動的に DAG の上から実行して全てを再構築してくれます。
また、今回はデータ作成だけでなく、学習と予測も Snowflake & dbt で完結させたいと考えました。社内には Snowflake Model Registry を dbt モデルとして使えるようにするマクロがあったので、これを使ってパイプラインに機械学習モデルを含めることができるようになりました。
データ & 機械学習パイプライン
今回構築したデータ & 機械学習パイプラインは以下のようになっています(先ほどの DAG を簡略化したものです)。
dbt で構築したことでデータの追加が簡単にできるようになっているため、最初に使うデータを決めるのではなく、思い立ったときに都度データを追加して、インクリメンタルにモデルを改善していくことができました。郵便番号の欠損が多いことに気づいてゼンリンのデータを追加したり、直近以外の地価公示データも含めることにしたりしたのはコンペ終盤でしたが、データを簡単に追加できる環境を整備していたおかげで何とかなりました。
また、dbt で機械学習モデルをテーブルと同様に扱えるようになったことで、機械学習モデルをパイプラインの間に入れるのも簡単になりました。よく見る機械学習パイプラインは、前処理 → 学習 → 予測、というようにデータ加工と機械学習が分かれているものでした。今回は、
- 入力データを作る
- 作ったデータを使って面積予測モデルを学習させる
- 面積予測モデルを使って賃料予測モデル用のデータを作る
- 作ったデータを使って賃料予測モデルを学習させる
といったように、データ加工のステップに機械学習をはさむことができました。
まとめ
- 不動産ドメイン知識を活かして、賃料総額ではなく平米単価を当てにいくようにしました。
- 国土数値情報をフル活用して、特徴量を量産しました。
- Snowflake & dbt でデータエンジニアリングと機械学習の実装を効率化しました。
最後に
今回のコンペの最初のステップとして「賃料総額ではなく平米単価を見る」というのは結構重要だったと思いますが、estie が扱う商業用不動産では割と当たり前の所作なので、これが初手で出せて良かったです。 また、国土数値情報や Snowflake & dbt を使い込んでいる人が社内にいたのもラッキーでした。
estie のプロダクトでデータは非常に重要な役割を果たしていて、データ基盤・データエンジニアリング・データ分析など各所でエンジニアを募集しています。ぜひ入社してください。
全然関係ないですが、私がやっているプラットフォーム領域も仲間を募集中なので、こちらもお願いします。
追伸
「Kaggleで勝つデータ分析の技術」を著者の門脇さんにもらって「メッチャ宣伝しますよ!」と言いつつ全然してなかったのが5年間心残りだったので、ここで宣伝しときます!この本のおかげで優勝できました!最高!