Housing Prices Competition for Kaggle Learn Users: 限界突破の設計図

Housing Prices Competition for Kaggle Learn Users: 限界突破の設計図

今回はKaggle Getting Startedシリーズの「Housing Prices Competition for Kaggle Learn Users」に挑戦しました。今回はChatGPT 5.2と一緒にKaggle Notebook上で試行錯誤した過程を、失敗も含めてレポート形式でまとめてみます。

このコンペについて

アイオワ州エイムズの住宅データを使って、79個の説明変数から住宅の最終価格(SalePrice)を予測するコンペです。Kaggle Learnの機械学習コースを修了した人向けの「実践編」という位置づけで、練習すべきスキルとして次の2つが挙げられています。

  • クリエイティブな特徴量エンジニアリング
  • ランダムフォレストや勾配ブースティングなどの高度な回帰手法

評価指標は「予測値の対数と実際の販売価格との間のRMSE(二乗平均平方根誤差)」。対数を取ることで、高価な住宅と安価な住宅の予測誤差が結果に等しく影響するように設計されています。

データをざっと眺める

train.csvは1,460行 × 81列。数値特徴量が38、カテゴリ特徴量が43と、実はカテゴリ変数の方が多いデータセットです。目的変数はもちろんSalePrice。そして最大の特徴は、「欠損が意味を持つ列」と「単なる欠損」が混在していることでした。

欠損値は「ない」ことを意味する

欠損率を見ていくと、いきなり度肝を抜かれます。

特徴量欠損率解釈
PoolQC99.5%プールが「無い」家がほとんど
MiscFeature96.3%その他設備なし
Alley93.8%路地アクセスなし
Fence80.8%フェンスなし
MasVnrType59.7%化粧レンガなし
FireplaceQu47.3%暖炉なし
LotFrontage17.7%区画条件による欠損

PoolQCの欠損率99.5%を見て「使えない列だ」と切り捨てるのは早計でした。この欠損はデータの欠落ではなく、「その設備がそもそも存在しない」ことを表しています。上位解法はここを雑に扱わず、NaNを”None”や0、あるいは地域の中央値に置き換えて「意味を持たせて補完」しているそうです。

SalePriceの歪みをlog1pで直す

目的変数SalePriceの分布を見ると、強い右裾の長い分布(右に歪んだ形)になっていました。高価格帯の住宅が少数存在するせいです。ほぼ全ての上位解法が

SalePrice_log = np.log1p(SalePrice)

を使っているとのことで、これをやらないとRMSEが一気に悪化するそうです。numpy.log1p(x)eを底とした1 + xの対数で、真数が1に近い値でも高精度に計算できる関数とのこと。

効く特徴量を探す:品質・広さ・時間・立地

数値特徴量では OverallQual(総合品質)、GrLivArea(地上居住面積)、TotalBsmtSF(地下室面積)、GarageCars / GarageArea(ガレージ)、YearBuilt / YearRemodAdd(築年・改築年)あたりが単体でもSalePriceと強い相関を持つ定番特徴。ただしGrLivArea > 4000なのに価格が異常に低い家が外れ値として存在しており、これを除去するとCV・LBが改善しやすいとのことでした。

カテゴリ変数ではNeighborhood(地域)が最重要。同じ間取りの家でも地区によって価格が大きく変わるため、立地の価値を最もよく表す特徴だそうです。Tree系モデル(LightGBM・CatBoostなど)ならOne-Hotだけでも十分強く効きます。

整理すると、価格を決定づける柱は次の4つに集約できそうです。

派生特徴量を作る

単体の列をそのまま入れるよりも、組み合わせた派生特徴の方が効くというのがこのコンペの本質でした。実際に作ったのは次の4つです。

  • HouseAge = YrSold − YearBuilt(築年数)
  • RemodAge = YrSold − YearRemodAdd(改築後年数)
  • TotalSF = GrLivArea + TotalBsmtSF(総面積)
  • QualitySF = OverallQual × TotalSF(品質を考慮した面積)

「年」そのものより「経過年数」の方が効くというのは、言われてみれば納得です。

スコア感を掴む:目指すは12,000の壁

実際にコードを書く前に、このコンペのRMSEスコア感を整理しておきます。

スコア(RMSE)評価
30,000以上ほぼベースライン
20,000前後普通
17,000台良い(データ構造を理解できている)
15,000台上位寄り
12,000台かなり良い(今回の目標)

第1版:重要特徴だけのLightGBMベースライン(17,203)

まずは「重要そうな住宅の特徴だけ」に絞ったLightGBMベースラインから。OverallQual、GrLivArea、TotalBsmtSF、GarageCars、GarageArea、YearBuilt、YearRemodAdd、Neighborhood、ExterQual、KitchenQual、MasVnrAreaの11列と、先ほどの派生特徴だけを使い、欠損は0/Noneで手埋め、GrLivAreaの外れ値も除去。実行時間は数分。

結果はPublic RMSE 17,203。「無駄な特徴・過学習要素なし」「なぜ効くかが分かる構成」を優先した軽量版で、17,000台は「データ構造をちゃんと理解できている」ラインではあるものの、情報を捨てている分だけ伸びしろも大きい状態でした。

第2版:全列 + One-Hotで底上げ(14,638)

次に方針転換。目的変数はあえてlog1pせずそのまま(このコンペはドル単位のRMSEなので、logにすると逆にズレやすいという判断)、カテゴリも全部One-Hotで使い、LightGBM(LGBMRegressor)をKFoldで平均化。全特徴を使う正攻法の堅牢なパイプラインです。

結果は14,638。17,203から一気に改善し、「15,000台の壁」は突破しましたが、目標の12,000にはまだ遠い状態でした。

第3版:CatBoostに乗り換えて一気に改善(13,291)

ここで壁にぶつかります。One-Hotエンコーディングは Neighborhood のようなカテゴリを「独立したフラグ」として扱ってしまい、カテゴリ同士の関係性や順序を学習できません。そこで、カテゴリ変数をそのまま賢く扱えるCatBoostに乗り換えました。目的変数はlog1p、RMSE学習、5分割CVという構成です。

実行時間15分で、結果は13,291。「正攻法のCatBoost単体としては上位寄り」のスコアに一気に到達しました。ここまでの流れを俯瞰すると、スコアがどう乱高下してきたかがよく分かります。

罠1:ブレンド(アンサンブル)の幻想

13,291まで来て、次に試したのがCatBoostとLightGBMのブレンド(0.5:0.5の単純平均)でした。ブレンドは複数モデルの誤差を打ち消し合わせて精度を上げる定番テクニックのはずです。ところが結果は13,595と悪化。

ブレンドが効くのは「互いに違う場所で間違える」時だけです。今回はLightGBM(14,638)がCatBoost(13,291)と同じ方向で、しかもより大きく間違えていました。つまりCatBoostの方が明確に強く、LightGBMは「同じ所を、より大きく間違えている」状態。ここで0.5/0.5の平均を取ると、良い予測を悪い予測で薄めてしまう結果になったわけです。

罠2:MAE学習による予測の崩壊

「外れ値に引っ張られにくい学習をすればRMSEが下がるのでは」という仮説のもと、CatBoostのloss_functionをRMSEからMAEに変更し、さらにdepth=10、iterations=50000といったパラメータも一気に強めてみました。

結果は15,224。大幅に悪化です。原因ははっきりしていました。このコンペの評価指標RMSEは誤差を二乗するため、高額物件の大きな予測外しを強く罰します。一方MAE学習は外れ値を気にせず中央値に寄りやすい性質があり、結果として評価で重要な「高額物件」の予測を意図的に外しやすくなってしまいます。評価指標と学習ロスの不整合が、スコアを一気に崩壊させた形でした。

基本に立ち返ってdepthを調整(13,135 → 13,118)

ブレンドもMAEも空振りに終わったところで、原点に戻ります。loss_functionをRMSEに戻し、CatBoostのdepthだけを7・8・9・10と総当りで比較してみました。

depthCV RMSE
723,186.08(最良)
823,898.19
924,820.55
1024,696.64

depth=7が最良で、Public スコアは13,135まで改善。さらにdepth=6も試したところ13,118とわずかに上積みできました。複雑な手法に飛びつく前の「基本の徹底」が、結局いちばんスコアを動かしたという結果です。

この入門コンペでの現実的な上限

リークなし・正攻法(CatBoost + log1p + 定番の特徴量エンジニアリング)という条件下では、13,000 ± 200あたりが現実的な上限帯のようです。13,118はほぼその天井ゾーンに入っており、これ以上詰めても数十〜100程度の改善で、乱数や分割の運に左右される領域に入ります。

ここまでの試行錯誤を一覧にまとめると、こうなります。

Public RMSEモデル / 方針ひとことで言うと
baseline17,203LightGBM + 重要特徴のみ + log1p「少数強特徴」縛りの軽量版
baseline214,638LightGBM + 全列 + One-Hot情報を全部使う正攻法LGBM
catboost13,291CatBoost(RMSE)+ log1pカテゴリを直接扱えるモデルに乗り換え
catboost_lgbm13,595CatBoost + LGBM 0.5/0.5ブレンド良い予測を悪い予測で薄めて悪化
catboost_mae15,224CatBoost(loss=MAE)+ depth10評価RMSEと学習MAEの不整合で崩壊
catboost_depth=7-1013,135CatBoost(RMSE)+ depth探索depthを選ぶ段階に入って詰めた
catboost_depth=6(最終)13,118CatBoost(RMSE)+ depth=6深さを1つ浅くして過学習を抑制

次にやるなら:Neighborhoodのターゲットエンコーディング

13,000の壁をさらに切り崩すとしたら、唯一12,000に近づく可能性がある手として挙がったのが、NeighborhoodのTarget Encoding(CV内で地区ごとの平均価格を特徴量として与える手法)です。単なるカテゴリラベルではなく、具体的な「価格の相場感」を直接学習させることで精度が一段階引き上がるとのこと。ただし必ずCV(交差検証)の中でエンコードし、過学習(データリーク)を防ぐ必要があるやや上級のテクニックなので、今回は次回への宿題として持ち越しました。

まとめ:Ames攻略の4つの黄金則

今回の試行錯誤を通して見えてきたのは、次の4つのルールです。

  • 第1条:目的変数を変換せよ — SalePriceは必ずlog1pで対数化する。
  • 第2条:欠損値を「翻訳」せよ — 欠損値(NaN)は「存在しない」という強力な情報。意味を持たせて補完する。
  • 第3条:ブレンドに気をつけよ — 劣ったモデルとの単純なブレンドは、優れた予測を汚染する。
  • 第4条:評価指標を尊重せよ — RMSE評価のコンペでは、高額物件の誤差を無視するMAE学習は命取りになる。

「重要特徴だけ」→「全特徴+One-Hot」→「CatBoost」→「ブレンド失敗」→「MAE失敗」→「depthチューニング」という順番で回り道をしましたが、失敗も含めてどこで何が効いて何が効かなかったのかがクリアになったのは収穫でした。ChatGPT 5.2と一緒にKaggle環境で実行しています。コードとCSVはhouse-code-csv、スライドはhouse-learn-users-v02・house-learn-users_Ames_Housingとしてこの記事の下に添付しています。

後から、レポートブログ化のため、notebook lmでスライド作成して追加しました。

ML/AI/Kaggleカテゴリの最新記事