💡機械学習の学びメモ
『クロスバリデーションと欠損値処理』
概要
-
目的:
- クロスバリデーションがなぜ必要で、どのようにモデル評価の信頼性を高めるのかを理解する。
- 欠損値処理の主要な手法と、それぞれの使い分けを学ぶ。
- 学んだ内容を具体的なPythonコードで実装し、実践力を養う。
- Nishikaマンション価格予測コンペティションへの応用イメージを掴む。
資料
1. はじめに
「データの前処理」と「モデルの評価方法」について学んでいきます。
具体的には、
- クロスバリデーション: モデルの「実力」を正しく測る方法
- 欠損値処理: データにある「穴」をどう埋めるか
2. 機械学習における「モデルの評価」ってなぜ大事?
モデルを作ったら、その実力をちゃんと測る必要がありますよね。これがモデルの評価です。
なぜ評価が大事なのでしょうか?
-
① モデルが良いか悪いかを知るため:
作ったモデルが、新しいデータに対してどれくらいの精度で予測できるのかを知るために必要です。 -
② 「過学習」と「未学習」を防ぐため:
- 過学習 (Overfitting): 訓練データ(学習に使ったデータ)には完璧に合うけど、新しいデータには全然合わない状態。まるで「過去問は満点だけど、本番のテストはボロボロ」な状態です。
-
未学習 (Underfitting): 訓練データにも新しいデータにも、どっちにも合わない状態。これは「そもそも勉強不足」な状態ですね。
私たちの目標は、未知のデータに対しても高い精度で予測できる「汎化性能の高い」モデルを作ることです。
-
③ データの分割:
モデルの汎化性能を測るために、通常データは3つに分けます。- 訓練データ (Training Data): モデルを学習させるためのデータ。
- 検証データ (Validation Data): 学習中にモデルの性能を評価し、ハイパーパラメータ(モデルの調整設定)を決めるためのデータ。
- テストデータ (Test Data): 最後にモデルの最終的な性能を評価するためのデータ。Nishikaコンペで提出するデータがこれにあたります。
3. クロスバリデーションの構築:モデルの「本当の実力」を測る
「検証データ」を使ってモデルの評価をするって言いましたよね。でも、もし運悪く、その検証データがたまたまモデルにとって「得意な」データだったらどうでしょう?実際のモデルの性能よりも高く評価されてしまうかもしれません。
そこで登場するのが、クロスバリデーション(交差検定)です!
-
クロスバリデーションとは?:
限られたデータでも、より信頼性の高いモデル評価を行うための、賢いデータの使い方です。データを何回かに分けて「訓練」と「検証」を繰り返すことで、評価のばらつきを抑え、モデルの「本当の実力」を測ります。 -
なぜ必要?:
- 特定のデータセットでの評価に偏るリスクを減らせます。
- ハイパーパラメータ(モデルの細かい設定)を調整する際に、過学習を防ぎながら最適な設定を見つけやすくなります。
-
K分割交差検定 (K-Fold Cross-Validation) のしくみ:
一番よく使われるのが「K分割交差検定」です。- まず、学習用のデータ全体をK個の同じくらいの大きさのグループ(「Fold」と呼びます)に分割します。
- そして、K回「学習と評価」を繰り返します。
- 1回目の学習では、1つのFoldを「検証データ」として使い、残りのK-1個のFoldを「訓練データ」として使います。
- 2回目では、別のFoldを「検証データ」に、残りを「訓練データ」にします。
- これをK回繰り返します。
- 最後に、K回分の評価結果の平均を取ります。この平均値が、モデルのより信頼性の高い評価値となるわけです。
-
すべてのデータが公平に「検証データ」として使われるので、評価の信頼性が高まります。
🧪 クロスバリデーションの基本をおさらい(分類問題での例)
まずは、シンプルな分類タスクでクロスバリデーションの基本を理解しましょう。今回は分類問題の代表例「Iris(アヤメ)データセット」で説明します。
🧩 実装例(ロジスティック回帰モデル × KFold)
from sklearn.model_selection import KFold, cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_iris
# データの読み込み(4つの特徴量と3つのクラス)
X, y = load_iris(return_X_y=True)
# モデル(ロジスティック回帰)を定義
model = LogisticRegression(max_iter=1000)
# KFoldで5分割(シャッフルあり→分類クラスが偏らないようにする)
kf = KFold(n_splits=5, shuffle=True, random_state=42)
# クロスバリデーションの実行
scores = cross_val_score(model, X, y, cv=kf, scoring='accuracy')
# 結果表示
print("各Foldのスコア:", scores)
print("平均スコア:", scores.mean())
✅ 出力例
各Foldのスコア: [0.967 1.000 0.967 0.933 1.000]
平均スコア: 0.973
🧠 この結果から何が言える?
内容 | 解説 |
---|---|
平均スコア = 0.973 | モデルの精度は約97.3%。つまり、分類の成功率がとても高い。 |
スコアのばらつきが小さい | 最低でも 93.3%、最高で100%。各Foldで精度が安定しており、「モデルの再現性が高い」と判断できる。 |
KFoldの意義が見える | 1回のtrain/test分割では見逃す可能性のあるバラつきや過学習の傾向を、5回の検証で把握できる。 |
🎓 まとめ
- クロスバリデーションは「モデルの評価精度」と「安定性(ばらつき)」の両方を見られる
-
cross_val_score()
を使えば、少ないコードで繰り返し検証ができる - 次のマンション価格予測では、回帰タスクでのクロスバリデーションに挑戦していきます
🔄 Irisの事例とマンション価格予測の違いを整理しよう
観点 | Iris事例(分類) | マンション価格予測(回帰) |
---|---|---|
問題のタイプ | 分類(クラス予測) | 回帰(数値予測) |
目的変数(予測対象) | 花の種類(0, 1, 2) | マンション価格(例:4980万円) |
モデルの例 |
LogisticRegression (分類モデル) |
LightGBMRegressor (回帰モデル) |
評価指標 |
accuracy (正答率) |
RMSE (予測誤差) |
CVの目的 | 各クラスをしっかり学習できているか? | 高い価格や安い価格にも対応できているか?誤差が小さいか? |
解釈の仕方 | 平均 accuracy が高ければ「よく分類できている」 | 平均 RMSE が小さく、標準偏差が小さければ「安定して正確に予測できている」 |
💡 なぜこの違いが大事なのか?
- 分類では「正しく当てたか」が大事 → → 正解/不正解の判定がはっきりしている
- 回帰では「どれだけ近い値を予測できたか」が大事 → → 誤差の平均やばらつきを重視
🧭 学習の流れのイメージ
Irisで学ぶ(分類モデルのCV)
↓
クロスバリデーションの仕組みと意味を理解
↓
マンション価格のような「数値予測」に応用
📝 ポイント
- クロスバリデーションは「モデルの評価方法」として分類でも回帰でも使える
- ただし、評価指標(accuracyかRMSEか)や解釈の視点が違うことに注意!
回帰事例 マンション価格 × LightGBM × KFold
# --- 必要なライブラリをインポート ---
import pandas as pd # データ操作のためのライブラリ
from sklearn.model_selection import KFold # K分割交差検定のためのツール(データを分割する道具)
from sklearn.linear_model import LinearRegression # 今回はシンプルに線形回帰モデルを使います
from sklearn.metrics import mean_squared_error # 評価指標としてRMSE(二乗平均平方根誤差)を計算します
import numpy as np # 数値計算のためのライブラリ
# --- 1. サンプルデータの準備 ---
# まずは練習用の小さなデータを作ってみましょう。
# 広さ、築年数、駅からの距離で価格を予測するイメージです。
np.random.seed(42) # 乱数の種を設定。これを固定すると、何度実行しても同じ結果になります。
data = {
'広さ_m2': np.random.rand(100) * 50 + 30, # 広さデータ (30平米〜80平米)
'築年数': np.random.randint(0, 50, 100), # 築年数データ (0年〜49年)
'駅からの距離_km': np.random.rand(100) * 10 + 1, # 駅からの距離 (1km〜11km)
'価格_万円': (np.random.rand(100) * 1000 + 2000 # 価格データ (2000万円〜3000万円をベースに)
+ np.random.rand(100) * 500 * (np.random.rand(100) > 0.5) # ちょっとしたばらつきを加える
)
}
df = pd.DataFrame(data)
print("--- 作成したサンプルデータ サイズ(行,列) ---")
print(df.shape)
print("--- 作成したサンプルデータ (最初の5行) ---")
print(df.head()) # データの最初の5行を表示
print("\n")
# 特徴量(予測に使うデータ)と目的変数(予測したいデータ)に分ける
X = df[['広さ_m2', '築年数', '駅からの距離_km']] # 特徴量 (説明変数とも言います)
y = df['価格_万円'] # 目的変数 (予測したい値)
# --- 2. K-Fold Cross-Validation の設定と実行 ---
# KFoldクラスを使って、データを5分割(n_splits=5)する準備をします。
# shuffle=Trueでデータをシャッフルしてから分割します(偏りをなくすため)。
# random_stateも固定することで、何度実行しても同じ分割になります(結果の再現性のため)。
kf = KFold(n_splits=5, shuffle=True, random_state=42)
# 各Fold(分割)で計算されたRMSEスコアを保存するためのリスト
rmse_scores = []
print("--- K-Fold Cross-Validationを開始します ---")
# kf.split(X) は、Xのデータをどのように分割するか教えてくれます。
# 具体的には、各Foldの「訓練データ」と「検証データ」の「行番号」をペアで返してくれます。
# enumerate()を使うと、何回目のFold(foldという変数に0, 1, 2...と入る)かを一緒に取得できます。
for fold, (train_index, val_index) in enumerate(kf.split(X)):
print(f"\n--- Fold {fold+1} がスタート ---") # 現在何回目の分割かを表示 (foldは0から始まるので+1します)
# train_indexとval_index(行番号のリスト)を使って、実際のデータを訓練用と検証用に分けます。
# .iloc[インデックス] は、Pandas DataFrameの指定した行番号のデータを抽出する便利な方法です。
X_train, X_val = X.iloc[train_index], X.iloc[val_index]
y_train, y_val = y.iloc[train_index], y.iloc[val_index]
print(f"訓練データのサイズ: {X_train.shape}, 検証データのサイズ: {X_val.shape}")
# 例: (80, 3) は「80行、3列のデータ」という意味です。合計100行なので、80行が訓練、20行が検証に使われます。
# --- 3. モデルの訓練と予測 ---
model = LinearRegression() # 線形回帰モデルのインスタンス(「新しいモデル」)を作成
model.fit(X_train, y_train) # 訓練データを使って、モデルを学習させます
# 検証データで予測を行います
y_pred = model.predict(X_val)
# --- 4. 評価指標(RMSE)の計算 ---
# RMSEを計算します。
# mean_squared_error は「二乗誤差の平均」を計算します。
# np.sqrt は「平方根(ルート)」を計算します。
rmse = np.sqrt(mean_squared_error(y_val, y_pred))
rmse_scores.append(rmse) # 計算したRMSEをリストに追加
print(f"Fold {fold+1} のRMSE: {rmse:.2f}") # 小数点以下2桁まで表示して、各Foldの結果を確認
print("\n--- クロスバリデーションの結果 ---")
print(f"各FoldのRMSEスコア: {rmse_scores}") # 各Foldで計算されたRMSEのリスト
print(f"平均RMSE: {np.mean(rmse_scores):.2f}") # 全てのFoldのRMSEの平均値
print(f"RMSEの標準偏差: {np.std(rmse_scores):.2f}") # RMSEのばらつき具合(小さいほど安定)
🔎 クロスバリデーション結果の読み解き(実価格ベースの具体例)
以下は実際にクロスバリデーション(KFold)を行った際の結果例です。
--- クロスバリデーションの結果 ---
各FoldのRMSEスコア: [233.86, 314.10, 359.81, 281.76, 334.83]
平均RMSE: 304.87 万円
RMSEの標準偏差: 43.77 万円
✅ どう読み解くか?
項目 | 解説 |
---|---|
平均RMSE ≒ 305万円 | 5回の検証の平均誤差が305万円。物件価格が3000~6000万円だと誤差率は約5~10%。まずまずの精度。 |
標準偏差 ≒ 44万円 | Foldによって最大120万円以上のスコアの差があり、モデルの安定性に少し課題がある可能性あり。 |
Fold 3のRMSE ≒ 360万円 | 他Foldに比べ大きく悪化している。データの偏りや外れ値が影響しているかもしれない。 |
🧭 次に何を考えるか?
① 特徴量の見直し
- 特定Foldだけ悪化するなら、「そのFoldだけ他と違う特徴」が含まれていないか?
- 例:都心 vs 地方、広さが極端など
② 欠損値処理の見直し
- 欠損値が集中していたFoldがあるか? その処理方法は適切か?
③ log変換を試す
# 目的変数をlogに変換してモデルに使うことで、価格のスケールを抑え誤差を小さくできる場合がある
y_train = np.log1p(y_train)
④ 外れ値を確認する
- 価格が極端に高い(1億円超えなど)データがあれば、RMSEが大きく引っ張られている可能性。
🔚 結論まとめ
項目 | 状況 | コメント |
---|---|---|
スコア水準 | 実用レベルに近い(305万円) | 入門段階として十分合格点 |
モデルの安定性 | やや不安あり(ばらつき大) | 精度の再検証・工夫が必要 |
改善方針 | 特徴量・目的変数の変換・外れ値確認 | 次の一歩として効果的 |
4. 欠損値処理:データにある「穴」をどう埋める?
データを使っていると、「データが抜けている(欠損している)」ことがあります。これを欠損値 (Missing Values) と呼びます。Pandasでは NaN
(Not a Number) と表示されることが多いです。
-
欠損値って何?なぜ発生するの?:
- 欠損値とは: データが存在しない、または記録されていない値のことです。
- 発生原因の例: アンケートの未回答、センサーの故障、データ入力ミス、データ統合時の不整合など、様々な理由で発生します。
-
なぜ処理が必要?:
- ほとんどの機械学習モデルは、欠損値を含むデータをそのままでは処理できません。エラーになったり、モデルの性能が著しく低下したりします。
- 欠損値を適切に処理しないと、誤った分析結果や予測につながる可能性があります。
-
欠損値の確認方法
まずは、データにどれくらいの欠損値があるのかを確認しましょう。
# --- 必要なライブラリをインポート ---
import pandas as pd
import numpy as np # np.nanを使うために必要です
# --- 1. サンプルデータの準備(欠損値を含む) ---
data_with_nan = {
'広さ_m2': [50, 60, np.nan, 75, 40], # 広さのデータが1つ欠損
'築年数': [10, 20, 5, np.nan, 30], # 築年数のデータが1つ欠損
'駅からの距離_km': [2, 1, 3, 0.5, np.nan], # 駅からの距離が1つ欠損
'価格_万円': [3000, 4000, 2500, 4500, 2000] # 価格には欠損なし
}
df_nan = pd.DataFrame(data_with_nan)
print("--- 欠損値を含む元のデータフレーム ---")
print(df_nan)
print("\n")
# --- 2. 欠損値の確認 ---
# isna() または isnull() を使うと、各セルが欠損値かどうかをTrue/Falseで返します。
# Trueの場所が欠損値です。
print("--- 欠損値の有無 (True:欠損あり) ---")
print(df_nan.isna()) # または df_nan.isnull()
print("\n")
# 各列にいくつ欠損値があるかを確認します。sum()を使うとTrue(1)の数を数えられます。
print("--- 各列の欠損値の数 ---")
print(df_nan.isna().sum())
print("\n")
# データフレーム全体の欠損値の合計数
print("--- データフレーム全体の欠損値の合計数 ---")
print(f"合計欠損数: {df_nan.isna().sum().sum()}")
-
主要な欠損値処理手法と具体的なコード例
欠損値の処理方法はいくつかあります。データの特性や欠損の理由によって、最適な方法を選びましょう。
-
欠損値のある行/列を削除 (Drop)
- どんな時に使う?: 欠損値がごく一部で、削除しても情報があまり失われない場合や、逆に欠損値が非常に多く、他の方法で補完するのが難しい場合。
- メリット: シンプルで簡単。
- デメリット: 貴重な情報が失われる可能性があり、データ量が減ってしまう。データが偏る原因になることも。
-
欠損値のある行/列を削除 (Drop)
# 元のデータフレームをコピーして作業します(元のデータを変更しないため)
df_nan_copy = df_nan.copy()
# 行を削除: how='any'で、どれか一つでも欠損値がある行を削除します。
# how='all'にすると、すべての列がNaNの行だけを削除します。
df_dropped_rows = df_nan_copy.dropna(how='any')
print("--- 1. 欠損値のある行を削除したデータフレーム ---")
print(df_dropped_rows)
print(f"元の行数: {len(df_nan_copy)}, 削除後の行数: {len(df_dropped_rows)}\n")
# 列を削除: axis=1を指定すると列を削除します。
# 今回の例だとすべての列に欠損値があるので、すべての列が削除されてしまいますが、
# 実際には、特定の列に極端に欠損値が多い場合に使うことがあります。
# 例: 欠損値が80%以上の列を削除する、といった判断をします。
# df_dropped_cols = df_nan_copy.dropna(axis=1, how='any')
# print("--- 欠損値のある列を削除したデータフレーム ---")
# print(df_dropped_cols)
- 統計量による補完 (Imputation)
欠損値を、その列の平均値、中央値、最頻値などで埋める方法です。
-
どんな時に使う?: 欠損値の数がそれなりにあり、削除したくない場合。
-
メリット: データ量を維持できる。
-
デメリット: 補完された値が実際の値と異なる可能性がある。外れ値(極端な値)に影響される場合がある(平均値)。変数の関係性を無視してしまう。
-
使い分け:
- 平均値 (Mean): 数値データで、分布が正規分布に近い場合。外れ値の影響を受けやすい。
- 中央値 (Median): 数値データで、外れ値がある場合や分布が歪んでいる場合。
- 最頻値 (Mode): カテゴリカルデータ(性別、色など)の場合や、数値データでも値が離散的(1, 2, 3...のように飛び飛び)な場合。
-
使い分け:
# 元のデータフレームをコピーして作業
df_nan_copy = df_nan.copy()
# --- 平均値で補完 (fillna() を使用) ---
# '広さ_m2'列の欠損値を、その列の平均値で埋める
df_mean_imputed = df_nan_copy.copy() # さらにコピー
df_mean_imputed['広さ_m2'] = df_mean_imputed['広さ_m2'].fillna(df_mean_imputed['広さ_m2'].mean())
print("--- 2-1. 広さ_m2列を平均値で補完したデータフレーム ---")
print(df_mean_imputed)
print(f"広さ_m2の平均値: {df_nan_copy['広さ_m2'].mean():.2f}\n")
# --- 中央値で補完 ---
# '築年数'列の欠損値を、その列の中央値で埋める
df_median_imputed = df_nan_copy.copy()
df_median_imputed['築年数'] = df_median_imputed['築年数'].fillna(df_median_imputed['築年数'].median())
print("--- 2-2. 築年数列を中央値で補完したデータフレーム ---")
print(df_median_imputed)
print(f"築年数の中央値: {df_nan_copy['築年数'].median():.2f}\n")
# --- 最頻値で補完 (例: カテゴリカルデータの場合) ---
# 今回の数値データに無理やり適用しますが、本来はカテゴリデータに使います。
# 例として'駅からの距離_km'の最頻値で埋めてみます。
df_mode_imputed = df_nan_copy.copy()
# mode()[0]とするのは、mode()がシリーズ(Pandasの1列データ)を返すため、
# その中の最初の値(つまり一番多い値)を取得するためです。
df_mode_imputed['駅からの距離_km'] = df_mode_imputed['駅からの距離_km'].fillna(df_mode_imputed['駅からの距離_km'].mode()[0])
print("--- 2-3. 駅からの距離_km列を最頻値で補完したデータフレーム ---")
print(df_mode_imputed)
print(f"駅からの距離_kmの最頻値: {df_nan_copy['駅からの距離_km'].mode()[0]:.2f}\n")
# --- scikit-learnのSimpleImputerを使うと便利 ---
# 複数の列をまとめて、より汎用的に補完できます。
from sklearn.impute import SimpleImputer
# 平均値で補完するImputerを作成
# strategyには 'mean'(平均値)、'median'(中央値)、'most_frequent'(最頻値)、
# 'constant'(指定した定数)を選ぶことができます。
imputer_mean = SimpleImputer(strategy='mean')
# '広さ_m2', '築年数', '駅からの距離_km'の3つの列に適用
# fit_transform() は、
# 1. fit: 欠損値を埋めるためのルール(例: 各列の平均値)をデータから「学習」し、
# 2. transform: そのルールを使って、実際の欠損値を「補完」する
# この2つのステップを一気に行います。
# 結果はNumpy配列になるので、DataFrameに戻します。
df_imputed_sk = df_nan_copy.copy()
df_imputed_sk[['広さ_m2', '築年数', '駅からの距離_km']] = imputer_mean.fit_transform(df_imputed_sk[['広さ_m2', '築年数', '駅からの距離_km']])
print("--- 2-4. SimpleImputer (平均値) でまとめて補完したデータフレーム ---")
print(df_imputed_sk)
print("これでNaNが全てなくなりました!\n")
3.定数で補完 (Constant)
欠損値を特定の意味を持つ定数(例: 0, -1, 'Unknown'など)で埋める方法です。
- どんな時に使う?: 欠損値自体に意味がある場合(例: 「情報なし」を意味する)や、カテゴリカルデータで新しいカテゴリを追加したい場合。
- メリット: シンプル。
- デメリット: モデルに誤った情報を与える可能性があるので注意が必要。
df_nan_copy = df_nan.copy()
# '築年数'の欠損を -999 で補完する例(「築年数不明」という特別な意味を持たせる場合など)
df_fill_constant = df_nan_copy.copy()
df_fill_constant['築年数'] = df_fill_constant['築年数'].fillna(-999)
print("--- 3. 定数 (-999) で築年数列を補完したデータフレーム ---")
print(df_fill_constant)
4.予測モデルによる補完 (Model-based Imputation)
欠損値がある列を目的変数として、他の列を使って欠損値を予測し、埋める方法です。
-
どんな時に使う?: データの関係性を保ちながら、より精度の高い補完をしたい場合。
-
メリット: より複雑な関係性を考慮した補完ができる。
-
デメリット: 計算コストが高い。過学習のリスクがある。
-
(今回は概念のみの説明に留めますが、
IterativeImputer
などが該当します。) -
Nishikaコンペへの応用:
-
Nishikaコンペの
train.csv
やtest.csv
を読み込んだら、まずはdf.isnull().sum()
でどこに、どれくらい欠損値があるかを確認しましょう。 -
各列のデータがどんな意味を持つか(例:
部屋数
の欠損は「部屋なし」を意味するのか?築年数
の欠損は「不明」なのか?)をよく考えて、最適な補完戦略を選びましょう。 -
一般的には、数値データには平均値や中央値、カテゴリデータには最頻値や新しいカテゴリを追加する方法がよく使われます。
-
超重要ポイント: 訓練データで欠損値処理のルール(平均値など)を「学習」したら、その同じルールをテストデータにも適用してください。テストデータのデータを使って平均値を計算してしまうと、公平な評価ができなくなります(これをデータリーケージ (Data Leakage) と呼びます)。
5. まとめ
-
重要ポイント:
-
クロスバリデーション: モデルの「本当の実力」を、データ全体を賢く使うことで、より信頼性高く測るための評価手法です。これで「たまたま」良い結果が出た、という勘違いを防げます。
-
欠損値処理: データにある「穴(NaN)」を放置すると、多くの機械学習モデルが動かなくなったり、間違った予測をしてしまったりします。欠損値の確認から、削除、統計量での補完、定数での補完など、様々な方法があることを学びました。
-
Nishikaコンペでの実践に向けて:
- まずはコンペのデータをよく観察し (
.info()
,.describe()
,.isnull().sum()
)、データの特徴や欠損値の状況を把握しましょう。 - 様々な前処理手法を試行錯誤してみてください。どれが最適解かは、実際に試してみないと分からないことが多いです。
- クロスバリデーションを組み込んで、評価指標の安定性を確認しながら、モデルを改善していくことが大切です。
- まずはコンペのデータをよく観察し (
-
今後の学習の方向性:
- 今日はデータの前処理の一部でしたが、他にも特徴量エンジニアリング(既存のデータから新しい意味のある特徴量を作り出すこと)も非常に重要です。
- 様々な機械学習モデル(決定木、ランダムフォレスト、勾配ブースティングなど)を学習して、それぞれの得意なことを知るのも良いでしょう。
- モデルの性能をさらに高めるためのハイパーパラメータチューニングも、ぜひ学んでみてください。
FAQ:よくある質問とその回答
Q1: クロスバリデーションの n_splits
(分割数) はいくつにすればいいですか?
A1: 一般的には 5分割 (n_splits=5) や 10分割 (n_splits=10) がよく使われます。分割数を増やすと評価の信頼性は上がりますが、その分、計算時間も長くなります。データ量や使える計算リソース(PCの性能など)を考慮して選びましょう。データが非常に少ない場合は、より多くの分割を行う方法(例: Leave-One-Out Cross-Validation)もありますが、計算コストが非常に高くなるので注意が必要です。
Q2: 欠損値はデータの中にあったら、全部埋めないといけないですか?
A2: 基本的には、ほとんどの機械学習モデルは欠損値をそのままでは処理できないため、**何らかの方法で欠損値を処理する必要があります。**ただし、最近のいくつかの高性能なモデル(例: LightGBMやXGBoostなど)は、デフォルトで欠損値を内部的に処理できる機能を持っています。もしあなたがそういったモデルを使うのであれば、必ずしも事前に全てを埋める必要はありません。しかし、欠損値があること自体が情報になる場合もあるので、その場合はあえて埋めずにモデルに渡すことも検討できます(ただし、モデルが対応している場合に限ります)。
Q3: 欠損値の補完方法で、どれを選べばいいか迷います。
A3: これは機械学習で一番悩むポイントの一つです!「これが絶対的な正解」というものはありません。
- まず、その列のデータが何を意味しているか(数値データか、カテゴリデータか、連続した値か離散した値かなど)をよく考えましょう。
- データの分布を確認するのも重要です(ヒストグラムなどを書いてみて、値がどのように散らばっているか見てみましょう)。
- いくつか試してみて、クロスバリデーションでのモデルの性能が一番良くなる方法を選ぶのが実践的です。
- もし「欠損値が存在すること」自体に意味がある場合(例:「情報がない」という状態が意味を持つ場合)は、
'Unknown'
のような新しいカテゴリとして扱うのも有効な手です。
Q4: 訓練データとテストデータで、欠損値の補完方法を変えてもいいですか?
A4: 絶対に同じ方法を使うべきです! 例えば、訓練データの「広さ」の平均値で欠損値を補完したら、テストデータも同じ訓練データの平均値で補完しなければなりません。もしテストデータの平均値を使ってしまうと、まだ見ていない「未来のデータ(テストデータ)」の情報を学習時に使ってしまっていることになり、公平な評価ができなくなります(これをデータリーケージ (Data Leakage) と呼び、モデルの評価を歪めてしまう非常に危険な行為です)。
Q5: Nishikaコンペのデータで、具体的な欠損値処理の例を教えてください。
A5: Nishikaコンペのデータセットを確認すると、例えばland_area
(土地面積)やbuilding_area
(建物面積)のような数値データに欠損値があるかもしれません。これらは数値データなので、まずは中央値で補完を試すのが良いでしょう。なぜ中央値かというと、面積データはたまに極端な値(外れ値)がある可能性があるため、平均値よりも中央値の方が頑健(外れ値に強い)だからです。
また、もしstructure_type
(建物の構造)のようなカテゴリカルデータに欠損があった場合、その欠損値を'Unknown'
という新しいカテゴリとして補完することも有効です。これにより、「この物件の構造は不明」という情報をモデルに伝えることができます。
Q6: クロスバリデーションを実行すると、モデルの学習が何度も行われるので時間がかかります。どうすればいいですか?
A6: はい、その通りです。特にデータ量が多い場合や、複雑なモデルを使う場合は時間がかかります。
対策としては:
- まずは分割数を減らす(例: 10分割から5分割に)ことを検討します。これにより、学習・評価の回数が減り、時間が短縮されます。
- Google Colab Proなどの高性能なクラウド環境を利用することで、計算処理を速めることができます。
- コードのデバッグや初期の試行錯誤段階では、データの一部だけを使って小さいデータセットでテスト的に動かしてみて、コードに問題がないか確認してから、大規模なデータで実行するのが効率的です。
Q7: クロスバリデーションのRMSEの結果「334.83」は何を意味しますか?
A7: RMSEはRoot Mean Squared Errorの略で、回帰問題(数値予測)で使われる評価指標です。簡単に言うと、「モデルの予測値が、実際の値から平均してどれくらいずれているか」を示す数値です。
あなたの「Fold 5 のRMSE: 334.83」という結果は、Fold 5 の検証データにおいて、平均して約334.83万円の予測誤差があったことを意味します。例えば、もし実際のマンション価格が3000万円だった場合、あなたのモデルは平均して2665.17万円〜3334.83万円くらいの範囲で予測していた、というイメージです。
RMSEは数値が小さいほど、モデルの予測精度が高いことを意味します。0に近ければ近いほど、予測は完璧に近いです。
各FoldでRMSEが異なるのは、データの分割によって検証データが異なるためです。クロスバリデーションの目的は、これらのFoldごとのRMSEを平均することで、より安定した、モデルの「本当の実力」を評価することです。