身近なデータで試すPythonの機械学習! その2 お住まいの地域の不動産取引価格 続編 1 - LightGBMモデルの学習と保存-
こんにちは!前回の記事「お住まいの地域の不動産取引価格の予測モデル作成」では、不動産取引価格情報を使って、複数の機械学習モデルを試し、その性能を比較する基本的なワークフローをご紹介しました。様々なモデルを試す中で、どの特徴量が有効で、どの程度の精度が期待できそうかの感触を掴むことができました。しかし、前回の結果では、残念ながらテストデータでの評価値が非常に高いモデルは得られませんでした。
そこで今回は、前の記事の「まとめと今後のステップ」で触れた改善策のひとつとして、高性能で人気のあるLightGBMに焦点をあて、その学習プロセスをより詳細に見ていき、学習済みモデルを保存する方法について解説します。モデルを保存しておくことで、再学習の手間を省き、新しいデータで予測を行ったり、アプリケーションに組み込んだりする際に役立ちます。
今回のプロセスの流れ
1. データ読み込みと前処理(おさらいと改良): 前回と同様にCSVデータを読み込み、和暦の「建築年」を西暦へ変換、そして「築年数」を計算します。今回はこの処理を少し整理し、関数化しました。
2. 特徴量とターゲットの指定: 予測に使用する特徴量と目的変数を明確に定義します。
3. データ分割と標準化: データを学習用とテスト用に分割し、特徴量のスケールを標準化します。
4. LightGBMモデルの学習: LightGBMリグレッサー(LGBMRegressor)を使ってモデルを学習させます。
5. モデル評価: 学習済みモデルをテストデータで評価し、R2スコアとRMSE(二乗平均平方根誤差)を確認します。
6. モデルとスケーラーの保存: 学習済みのLightGBMモデルと、標準化に使用したスケーラーオブジェクトをファイルに保存します。
コード紹介
それでは、具体的にコードを見ていきましょう。前回のコードをベースに、今回の目的に合わせて整理・機能追加を行いました。
1. 設定とライブラリのインポート
まず、必要なライブラリをインポートし、ファイルパスや使用するカラム名などの基本的な設定を行います。
import pandas as pd
import numpy as np
import random
import joblib # モデル保存用
import pickle # モデル保存用 (別形式)
import os
from jeraconv import jeraconv # 和暦・西暦変換
from IPython.display import display
from lightgbm import LGBMRegressor # 今回使用するモデル
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler # データ標準化
from sklearn.metrics import mean_squared_error, r2_score # 評価指標
from typing import Tuple, Dict, Any # 型ヒント用
# --- 設定 ---
FILE_PATH = "############.csv" # ご自身のデータファイルパスに置き換えてください
TARGET_COLUMN = '取引価格(総額)'
FEATURE_COLUMNS = [
'最寄駅:距離(分)', '面積(㎡)', '間口',
'延床面積(㎡)', '前面道路:幅員(m)', '築年数' # '築年数'は後ほど生成
]
WAREKI_COLUMN = '建築年'
TRADE_TIMING_COLUMN = '取引時点'
NEW_SEIREKI_COLUMN = '建築年_西暦'
NEW_TRADE_YEAR_COLUMN = '取引年'
NEW_AGE_COLUMN = '築年数'
TEST_SIZE = 0.3
RANDOM_STATE_SPLIT = 34 # データ分割時の乱数シード (再現性のため固定)
# LightGBMのハイパーパラメータ (適宜標準範囲で設定)
LGBM_PARAMS = {
'boosting_type': 'gbdt',
'bagging_fraction': 0.6,
'reg_alpha': 0.01,
'reg_lambda': 0.002,
'num_leaves': 48,
'colsample_bytree': 0.6,
'subsample': 0.9,
'subsample_freq': 2,
'min_child_samples': 20,
'random_state': 34 # モデルの再現性のために追加
}
LGBM_PARAMS には、LightGBMのハイパーパラメータを辞書としてまとめています。これらのパラメータはモデルの挙動を細かく制御するもので、通常はハイパーパラメータチューニングというプロセスで見つけ出しますが、今回は標準範囲内で適宜指定した値を使用します。
2. データ読み込みと前処理関数
次に、データの読み込みと基本的な前処理(和暦変換、築年数計算、欠損値処理など)を行う関数 load_and_preprocess_data です。
def load_and_preprocess_data(filepath: str) -> pd.DataFrame:
"""
CSVファイルを読み込み、基本的な前処理を行います。
具体的には、和暦の建築年を西暦に変換し、取引年から築年数を計算します。
"""
print(f"'{filepath}' からデータを読み込んでいます...")
if not os.path.exists(filepath):
raise FileNotFoundError(f"エラー: ファイルが見つかりません: {filepath}")
try:
df = pd.read_csv(filepath, encoding='utf-8')
except UnicodeDecodeError:
print("UTF-8での読み込みに失敗。'cp932'で再試行します...")
df = pd.read_csv(filepath, encoding='cp932')
df_processed = df.copy()
j2w = jeraconv.J2W()
# 1. 和暦の建築年を西暦に変換 (エラー処理を少し強化)
def convert_wareki_to_seireki_val(wareki_val):
if pd.isna(wareki_val) or not isinstance(wareki_val, str):
return np.nan
try:
# "元年"を"1年"に置換するなど、jeraconvが受け付ける形式に近づける
if "元年" in wareki_val:
wareki_val = wareki_val.replace("元年", "1年")
# jeraconvは"平成1年"のような形式を期待するが、データが"平成1"のような場合もあるため、
# 数字で終わる場合は"年"を補うことを試みる (より堅牢なエラー処理が必要な場合もある)
if wareki_val[-1].isdigit() and not wareki_val.endswith("年"):
# 元号部分と年部分を分離しようと試みる
era_part = ""
year_part_str = ""
for char_idx, char_val in enumerate(wareki_val):
if char_val.isdigit():
era_part = wareki_val[:char_idx]
year_part_str = wareki_val[char_idx:]
break
if era_part and year_part_str:
wareki_val = era_part + year_part_str + "年"
return j2w.convert(wareki_val)
except Exception: # jeraconvが変換できない場合
return np.nan
if WAREKI_COLUMN in df_processed.columns:
df_processed[NEW_SEIREKI_COLUMN] = df_processed[WAREKI_COLUMN].apply(convert_wareki_to_seireki_val)
else:
print(f"警告: '{WAREKI_COLUMN}' カラムが見つかりません。")
df_processed[NEW_SEIREKI_COLUMN] = np.nan
# 2. 取引時点から取引年を抽出
if TRADE_TIMING_COLUMN in df_processed.columns:
df_processed[NEW_TRADE_YEAR_COLUMN] = df_processed[TRADE_TIMING_COLUMN].astype(str).str.extract(r'(\d{4})').astype(float)
else:
df_processed[NEW_TRADE_YEAR_COLUMN] = np.nan
# 3. 築年数を計算
valid_years_mask = df_processed[NEW_SEIREKI_COLUMN].notna() & df_processed[NEW_TRADE_YEAR_COLUMN].notna()
df_processed[NEW_AGE_COLUMN] = np.nan # まずNaNで初期化
df_processed.loc[valid_years_mask, NEW_AGE_COLUMN] = (
df_processed.loc[valid_years_mask, NEW_TRADE_YEAR_COLUMN] -
df_processed.loc[valid_years_mask, NEW_SEIREKI_COLUMN] + 1
)
df_processed.loc[df_processed[NEW_AGE_COLUMN] < 0, NEW_AGE_COLUMN] = 0 # 負の築年数を補正
# 4. 特徴量と目的変数に必要なカラムを数値型に変換
cols_to_convert = FEATURE_COLUMNS + [TARGET_COLUMN]
for col in cols_to_convert:
if col in df_processed.columns:
if df_processed[col].dtype == 'object':
df_processed[col] = df_processed[col].astype(str).str.extract(r'(\d+\.?\d*)')[0]
df_processed[col] = pd.to_numeric(df_processed[col], errors='coerce')
# 5. 欠損値処理 (単純な削除)
df_cleaned = df_processed.dropna(subset=FEATURE_COLUMNS + [TARGET_COLUMN])
print(f"前処理後のデータ行数: {len(df_cleaned)}")
if len(df_cleaned) == 0:
raise ValueError("エラー: 前処理とNaN除去の結果、使用できるデータが残りませんでした。")
return df_cleaned
和暦変換の部分は、jeraconvライブラリが期待する形式(例:「平成1年」)と実際のデータ形式(例:「平成元年」や「平成1」)の差異を吸収しようと試みています。また、特徴量と目的変数に使用するカラムを数値型に変換し、変換できないものは NaN とした後、dropna()で欠損値を含む行を削除しています。
3. モデル学習、評価、保存関数
この関数 train_evaluate_and_save_model が今回のメイン処理です。
def train_evaluate_and_save_model(
df: pd.DataFrame,
feature_cols: list,
target_col: str,
lgbm_params: Dict[str, Any],
test_size: float,
random_state_split: int
) -> None:
"""
データを使用してLightGBMモデルを学習、評価し、モデルとスケーラーを保存します。
"""
X = df[feature_cols]
y = df[target_col]
# データ分割
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=test_size, random_state=random_state_split
)
print(f"学習データ数: {len(X_train)}, テストデータ数: {len(X_test)}")
# データ標準化
scaler = StandardScaler()
X_train_std = scaler.fit_transform(X_train) # 学習データでfit & transform
X_test_std = scaler.transform(X_test) # テストデータはtransformのみ
# モデル学習
print("LightGBMモデルの学習を開始します...")
model = LGBMRegressor(**lgbm_params)
model.fit(X_train_std, y_train) # 標準化された学習データで学習
print("モデル学習完了。")
# 評価
y_pred_test = model.predict(X_test_std) # 標準化されたテストデータで予測
y_pred_train = model.predict(X_train_std) # 標準化された学習データで予測
r2_test = r2_score(y_test, y_pred_test)
rmse_test = np.sqrt(mean_squared_error(y_test, y_pred_test))
r2_train = r2_score(y_train, y_pred_train)
rmse_train = np.sqrt(mean_squared_error(y_train, y_pred_train))
print("\n--- モデル評価結果 ---")
print(f"学習データ: R2スコア = {r2_train:.4f}, RMSE = {rmse_train:.2f}")
print(f"テストデータ: R2スコア = {r2_test:.4f}, RMSE = {rmse_test:.2f}")
# モデルとスケーラーの保存
model_filename_joblib = 'lgbm_real_estate_model.joblib'
scaler_filename_joblib = 'lgbm_real_estate_scaler.joblib'
model_filename_pickle = 'lgbm_real_estate_model.pkl'
scaler_filename_pickle = 'lgbm_real_estate_scaler.pkl'
print(f"\nモデルを '{model_filename_joblib}' (joblib) と '{model_filename_pickle}' (pickle) に保存中...")
joblib.dump(model, model_filename_joblib, compress=3) # joblibで保存
with open(model_filename_pickle, 'wb') as f:
pickle.dump(model, f) # pickleで保存
print(f"スケーラーを '{scaler_filename_joblib}' (joblib) と '{scaler_filename_pickle}' (pickle) に保存中...")
joblib.dump(scaler, scaler_filename_joblib, compress=3) # joblibでスケーラー保存
with open(scaler_filename_pickle, 'wb') as f:
pickle.dump(scaler, f) # pickleでスケーラー保存
print("モデルとスケーラーの保存が完了しました。")
ポイントは以下の通りです。
• 標準化: StandardScaler を使用し、学習データでスケーラーを学習 (fit_transform) し、そのスケーラーを使ってテストデータを変換 (transform) しています。
• モデル学習: LGBMRegressor に先ほど定義した LGBM_PARAMS を渡してインスタンス化し、標準化された学習データ (X_train_std, y_train) で学習 (fit) します。
• 評価: 学習済みモデルを使って、学習データとテストデータの両方で予測を行い、R2スコアとRMSEを計算・表示します。これにより、モデルが学習データに過剰適合していないか(過学習)の目安も得られます。
• モデルとスケーラーの保存:
◦ joblib.dump() と pickle.dump() の2種類の方法で、学習済みモデル (model) と標準化に使ったスケーラー (scaler) の両方を保存しています。joblib は特にNumPy配列を効率的に扱うため、Scikit-learnのオブジェクト保存に適していると言われています。
4. メイン実行部分
最後に、これまで定義した関数を呼び出して処理全体を実行する main 関数です。
def main():
"""全体の処理を実行するメイン関数。"""
try:
# 1. データ読み込みと前処理
df_processed = load_and_preprocess_data(FILE_PATH)
# 2. モデルの学習、評価、保存
train_evaluate_and_save_model(
df_processed,
FEATURE_COLUMNS,
TARGET_COLUMN,
LGBM_PARAMS,
TEST_SIZE,
RANDOM_STATE_SPLIT
)
except FileNotFoundError as e:
print(e)
except ValueError as e:
print(e)
except Exception as e:
print(f"予期せぬエラーが発生しました: {e}")
if __name__ == "__main__":
main()
このスクリプトを実行すると、コンソールに学習の進捗、評価結果、そして保存されたファイル名が表示されます。
実行結果の例
私の環境で、前の記事で紹介したようなデータに対して上記のコードを実行した場合、以下のような出力が得られます。
'############.csv' からデータを読み込んでいます...
学習データ数: 302, テストデータ数: 130
LightGBMモデルの学習を開始します...
[LightGBM] [Warning] bagging_fraction is set=0.6, subsample=0.9 will be ignored. Current value: bagging_fraction=0.6
[LightGBM] [Warning] bagging_fraction is set=0.6, subsample=0.9 will be ignored. Current value: bagging_fraction=0.6
・・・
・・・
・・・
[LightGBM] [Info] Start training from score XXXXX.XXXX
・・・
・・・
モデル学習完了。
[LightGBM] [Warning] bagging_fraction is set=0.6, subsample=0.9 will be ignored. Current value: bagging_fraction=0.6
[LightGBM] [Warning] bagging_fraction is set=0.6, subsample=0.9 will be ignored. Current value: bagging_fraction=0.6
--- モデル評価結果 ---
学習データ: R2スコア = 0.8623, RMSE = 3716750.91
テストデータ: R2スコア = 0.8203, RMSE = 4175033.27
モデルを 'lgbm_real_estate_model.joblib' (joblib) と 'lgbm_real_estate_model.pkl' (pickle) に保存中...
スケーラーを 'lgbm_real_estate_scaler.joblib' (joblib) と 'lgbm_real_estate_scaler.pkl' (pickle) に保存中...
モデルとスケーラーの保存が完了しました。
R2スコアやRMSEの値は、使用するデータセットによって変わりますが、今回は前回の記事と同じデータセットを用いており、比較するとLightGBMで実行することで若干テストデータでの評価値が向上しているようです。
まとめと今後のステップ
今回は、LightGBMモデルを選択し、その学習、評価、そして最も重要なモデルとスケーラーの保存までの一連のフローを詳細なコードと共に解説しました。
モデルを保存することで、以下のようなメリットがあります。
• 再学習の回避: 一度学習したモデルを再利用できるため、時間と計算資源を節約できます。
• 予測への活用: 新しいデータに対して、保存したモデルとスケーラーを読み込んで予測を行うことができます。
良いモデルが得られてきそうな結果が出てきましたのでさて、次はモデルの性能向上に取り組むフェーズです。前回の記事の最後にも触れましたが、以下のような改善策が考えられます。
1. 特徴量エンジニアリングの深化:
◦ 「市区町村」「最寄駅」などのカテゴリカル変数をOne-Hot EncodingやTarget Encodingなどで数値化してモデル入力に追加する。
◦ 土地の形状、用途地域、前面道路の種類など、より詳細な情報を特徴量として加える。
2. ハイパーパラメータチューニング: LGBM_PARAMS で指定したようなパラメータを、Grid Search、Randomized Search、Optunaなどの手法を用いて最適化する。これがモデル性能に大きく寄与することがあります。
3. クロスバリデーションの再導入: 今回はモデル保存に焦点を当てたため省略しましたが、より汎化性能の高いモデルを評価・選択するためには、前の記事で行ったようなクロスバリデーションが有効です。
保存したモデルをベースとして、これらの改善策を試し、より精度の高い不動産価格予測モデルを目指していきましょう。身近なデータとして皆さんの地域データで試してみると、さらに興味深い結果が得られるかもしれません。今回の記事が、その一助となれば幸いです!
コードの全体の記載
import pandas as pd
import numpy as np
import random
import joblib
import pickle
import os
from jeraconv import jeraconv
from IPython.display import display
from lightgbm import LGBMRegressor
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_squared_error, r2_score
from typing import Tuple, Dict, Any
# --- 設定 ---
FILE_PATH = "#############.csv"
TARGET_COLUMN = '取引価格(総額)'
FEATURE_COLUMNS = [
'最寄駅:距離(分)', '面積(㎡)', '間口',
'延床面積(㎡)', '前面道路:幅員(m)', '築年数'
]
WAREKI_COLUMN = '建築年'
TRADE_TIMING_COLUMN = '取引時点'
NEW_SEIREKI_COLUMN = '建築年_西暦'
NEW_TRADE_YEAR_COLUMN = '取引年'
NEW_AGE_COLUMN = '築年数' # 特徴量として使用するため、FEATURE_COLUMNSにも含める
TEST_SIZE = 0.3
RANDOM_STATE_SPLIT = 34
# LightGBMのハイパーパラメータ (標準範囲で適宜指定)
LGBM_PARAMS = {
'boosting_type': 'gbdt',
'bagging_fraction': 0.6,
'reg_alpha': 0.01,
'reg_lambda': 0.002,
'num_leaves': 48,
'colsample_bytree': 0.6,
'subsample': 0.9,
'subsample_freq': 2,
'min_child_samples': 20,
'random_state': 34 # モデルの再現性のために追加
}
# --- 関数定義 ---
def load_and_preprocess_data(filepath: str) -> pd.DataFrame:
"""
CSVファイルを読み込み、基本的な前処理を行います。
具体的には、和暦の建築年を西暦に変換し、取引年から築年数を計算します。
"""
print(f"'{filepath}' からデータを読み込んでいます...")
if not os.path.exists(filepath):
raise FileNotFoundError(f"エラー: ファイルが見つかりません: {filepath}")
try:
df = pd.read_csv(filepath, encoding='utf-8')
except UnicodeDecodeError:
print("UTF-8での読み込みに失敗。'cp932'で再試行します...")
df = pd.read_csv(filepath, encoding='cp932')
df_processed = df.copy()
j2w = jeraconv.J2W()
# 1. 和暦の建築年を西暦に変換
def convert_wareki_to_seireki_val(wareki_val):
if pd.isna(wareki_val) or not isinstance(wareki_val, str):
return np.nan
try:
# "元年"を"1年"に置換するなど、jeraconvが受け付ける形式に近づける
if "元年" in wareki_val:
wareki_val = wareki_val.replace("元年", "1年")
# jeraconvは"平成1年"のような形式を期待するが、データが"平成1"のような場合もあるため、
# 数字で終わる場合は"年"を補うことを試みる (より堅牢なエラー処理が必要な場合もある)
if wareki_val[-1].isdigit() and not wareki_val.endswith("年"):
# 元号部分と年部分を分離しようと試みる
era_part = ""
year_part_str = ""
for char_idx, char_val in enumerate(wareki_val):
if char_val.isdigit():
era_part = wareki_val[:char_idx]
year_part_str = wareki_val[char_idx:]
break
if era_part and year_part_str:
wareki_val = era_part + year_part_str + "年"
return j2w.convert(wareki_val)
except Exception: # jeraconvが変換できない場合
return np.nan
if WAREKI_COLUMN in df_processed.columns:
df_processed[NEW_SEIREKI_COLUMN] = df_processed[WAREKI_COLUMN].apply(convert_wareki_to_seireki_val)
else:
print(f"警告: '{WAREKI_COLUMN}' カラムが見つかりません。")
df_processed[NEW_SEIREKI_COLUMN] = np.nan
# 2. 取引時点から取引年を抽出
if TRADE_TIMING_COLUMN in df_processed.columns:
df_processed[NEW_TRADE_YEAR_COLUMN] = df_processed[TRADE_TIMING_COLUMN].astype(str).str.extract(r'(\d{4})').astype(float)
else:
print(f"警告: '{TRADE_TIMING_COLUMN}' カラムが見つかりません。")
df_processed[NEW_TRADE_YEAR_COLUMN] = np.nan
# 3. 築年数を計算
valid_years_mask = df_processed[NEW_SEIREKI_COLUMN].notna() & df_processed[NEW_TRADE_YEAR_COLUMN].notna()
df_processed[NEW_AGE_COLUMN] = np.nan
df_processed.loc[valid_years_mask, NEW_AGE_COLUMN] = (
df_processed.loc[valid_years_mask, NEW_TRADE_YEAR_COLUMN] -
df_processed.loc[valid_years_mask, NEW_SEIREKI_COLUMN] + 1
)
# 築年数が負または0になる異常ケースを補正(例として0歳未満は0歳とする)
df_processed.loc[df_processed[NEW_AGE_COLUMN] < 0, NEW_AGE_COLUMN] = 0
# 4. 特徴量と目的変数に必要なカラムを数値型に変換し、変換不能な値はNaNにする
cols_to_convert = FEATURE_COLUMNS + [TARGET_COLUMN]
for col in cols_to_convert:
if col in df_processed.columns:
if df_processed[col].dtype == 'object': # 文字列型の場合
# '10分' や '300㎡' のような文字列から数値部分を抽出
df_processed[col] = df_processed[col].astype(str).str.extract(r'(\d+\.?\d*)')[0]
df_processed[col] = pd.to_numeric(df_processed[col], errors='coerce')
# 5. 欠損値処理 (この記事では単純に削除)
df_cleaned = df_processed.dropna(subset=FEATURE_COLUMNS + [TARGET_COLUMN])
print(f"前処理後のデータ行数: {len(df_cleaned)}")
if len(df_cleaned) == 0:
raise ValueError("エラー: 前処理とNaN除去の結果、使用できるデータが残りませんでした。")
return df_cleaned
def train_evaluate_and_save_model(
df: pd.DataFrame,
feature_cols: list,
target_col: str,
lgbm_params: Dict[str, Any],
test_size: float,
random_state_split: int
) -> None:
"""
データを使用してLightGBMモデルを学習、評価し、モデルとスケーラーを保存します。
"""
X = df[feature_cols]
y = df[target_col]
# データ分割
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=test_size, random_state=random_state_split
)
print(f"学習データ数: {len(X_train)}, テストデータ数: {len(X_test)}")
# データ標準化
scaler = StandardScaler()
X_train_std = scaler.fit_transform(X_train)
X_test_std = scaler.transform(X_test)
# モデル学習
print("LightGBMモデルの学習を開始します...")
model = LGBMRegressor(**lgbm_params)
model.fit(X_train_std, y_train)
print("モデル学習完了。")
# 評価
y_pred_test = model.predict(X_test_std)
y_pred_train = model.predict(X_train_std)
r2_test = r2_score(y_test, y_pred_test)
rmse_test = np.sqrt(mean_squared_error(y_test, y_pred_test))
r2_train = r2_score(y_train, y_pred_train)
rmse_train = np.sqrt(mean_squared_error(y_train, y_pred_train))
print("\n--- モデル評価結果 ---")
print(f"学習データ: R2スコア = {r2_train:.4f}, RMSE = {rmse_train:.2f}")
print(f"テストデータ: R2スコア = {r2_test:.4f}, RMSE = {rmse_test:.2f}")
# モデルとスケーラーの保存
model_filename_joblib = 'lgbm_real_estate_model.joblib'
scaler_filename_joblib = 'lgbm_real_estate_scaler.joblib'
model_filename_pickle = 'lgbm_real_estate_model.pkl'
scaler_filename_pickle = 'lgbm_real_estate_scaler.pkl'
print(f"\nモデルを '{model_filename_joblib}' (joblib) と '{model_filename_pickle}' (pickle) に保存中...")
joblib.dump(model, model_filename_joblib, compress=3)
with open(model_filename_pickle, 'wb') as f:
pickle.dump(model, f)
print(f"スケーラーを '{scaler_filename_joblib}' (joblib) と '{scaler_filename_pickle}' (pickle) に保存中...")
joblib.dump(scaler, scaler_filename_joblib, compress=3)
with open(scaler_filename_pickle, 'wb') as f:
pickle.dump(scaler, f)
print("モデルとスケーラーの保存が完了しました。")
# --- メイン実行部分 ---
def main():
"""全体の処理を実行するメイン関数。"""
try:
# 1. データ読み込みと前処理
df_processed = load_and_preprocess_data(FILE_PATH)
# 2. モデルの学習、評価、保存
train_evaluate_and_save_model(
df_processed,
FEATURE_COLUMNS,
TARGET_COLUMN,
LGBM_PARAMS,
TEST_SIZE,
RANDOM_STATE_SPLIT
)
except FileNotFoundError as e:
print(e)
except ValueError as e:
print(e)
except Exception as e:
print(f"予期せぬエラーが発生しました: {e}")
if __name__ == "__main__":
main()
Discussion