new file: checkpoints_tree/task1_tr_tree/model_selection.csv new file: evaluation_outputs/task1_tr_tree/evaluation_train_all_samples.csv new file: evaluation_outputs/task1_tr_tree/evaluation_train_curve.png new file: evaluation_outputs/task1_tr_tree/evaluation_val_all_samples.csv new file: evaluation_outputs/task1_tr_tree/evaluation_val_curve.png new file: evaluation_outputs/task1_tr_tree/harmonic_5mm_1.55Hz_prediction.png new file: scripts_tree/__pycache__/config.cpython-310.pyc new file: scripts_tree/config.py new file: scripts_tree/evaluate.py new file: scripts_tree/predict_single.py new file: scripts_tree/train.py
161 lines
5.6 KiB
Python
161 lines
5.6 KiB
Python
from __future__ import annotations
|
|
|
|
import pickle
|
|
from dataclasses import asdict
|
|
from pathlib import Path
|
|
from typing import Any
|
|
import sys
|
|
|
|
import pandas as pd
|
|
from sklearn.ensemble import ExtraTreesRegressor, GradientBoostingRegressor, RandomForestRegressor
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
|
if str(PROJECT_ROOT) not in sys.path:
|
|
sys.path.insert(0, str(PROJECT_ROOT))
|
|
|
|
from scripts.dataset import build_datasets, report_to_text
|
|
|
|
try:
|
|
from .config import FEATURE_NAMES, ExperimentConfig, checkpoint_dir, make_experiment_config
|
|
except ImportError:
|
|
from config import FEATURE_NAMES, ExperimentConfig, checkpoint_dir, make_experiment_config
|
|
|
|
|
|
def build_regressor(model_name: str, random_state: int):
|
|
if model_name == "extra_trees":
|
|
return ExtraTreesRegressor(
|
|
n_estimators=600,
|
|
max_depth=None,
|
|
min_samples_leaf=1,
|
|
min_samples_split=2,
|
|
random_state=random_state,
|
|
)
|
|
if model_name == "random_forest":
|
|
return RandomForestRegressor(
|
|
n_estimators=500,
|
|
max_depth=None,
|
|
min_samples_leaf=1,
|
|
min_samples_split=2,
|
|
random_state=random_state,
|
|
)
|
|
if model_name == "gradient_boosting":
|
|
return GradientBoostingRegressor(
|
|
n_estimators=300,
|
|
learning_rate=0.03,
|
|
max_depth=3,
|
|
random_state=random_state,
|
|
loss="huber",
|
|
)
|
|
raise ValueError(f"Unsupported model: {model_name}")
|
|
|
|
|
|
def records_to_frame(records: list[Any]) -> pd.DataFrame:
|
|
rows = []
|
|
for record in records:
|
|
row = {
|
|
"file_name": record.file_path.name,
|
|
"frequency_hz": float(record.frequency_hz),
|
|
"x_rms": float(record.x_rms.item()),
|
|
"y_rms": float(record.y_rms.item()),
|
|
"target_tr": float(record.target_y_rms.item()),
|
|
}
|
|
for name, value in zip(FEATURE_NAMES, record.features.tolist()):
|
|
row[name] = float(value)
|
|
rows.append(row)
|
|
return pd.DataFrame(rows)
|
|
|
|
|
|
def evaluate_frame(model, frame: pd.DataFrame) -> dict[str, float]:
|
|
feature_matrix = frame.loc[:, FEATURE_NAMES].to_numpy()
|
|
pred_tr = model.predict(feature_matrix)
|
|
pred_tr = pred_tr.clip(min=1e-6)
|
|
pred_rms = pred_tr * frame["x_rms"].to_numpy()
|
|
true_rms = frame["y_rms"].to_numpy()
|
|
relative_error = abs(pred_rms - true_rms) / true_rms.clip(min=1e-6)
|
|
return {
|
|
"mean_rms_error": float(relative_error.mean()),
|
|
"median_rms_error": float(pd.Series(relative_error).median()),
|
|
"max_rms_error": float(relative_error.max()),
|
|
}
|
|
|
|
|
|
def serialize_for_checkpoint(value: Any) -> Any:
|
|
if isinstance(value, Path):
|
|
return str(value)
|
|
if isinstance(value, dict):
|
|
return {key: serialize_for_checkpoint(sub_value) for key, sub_value in value.items()}
|
|
if isinstance(value, tuple):
|
|
return [serialize_for_checkpoint(item) for item in value]
|
|
if isinstance(value, list):
|
|
return [serialize_for_checkpoint(item) for item in value]
|
|
return value
|
|
|
|
|
|
def train() -> None:
|
|
config = make_experiment_config()
|
|
datasets, raw_records, reports = build_datasets(config.data)
|
|
del datasets
|
|
print(report_to_text(reports))
|
|
|
|
train_df = records_to_frame(raw_records["train"])
|
|
val_df = records_to_frame(raw_records["val"])
|
|
|
|
feature_matrix = train_df.loc[:, FEATURE_NAMES].to_numpy()
|
|
target = train_df["target_tr"].to_numpy()
|
|
|
|
results: list[dict[str, Any]] = []
|
|
best_bundle: dict[str, Any] | None = None
|
|
best_val_error = float("inf")
|
|
|
|
for model_name in config.model.candidate_models:
|
|
model = build_regressor(model_name, config.model.random_state)
|
|
model.fit(feature_matrix, target)
|
|
|
|
train_metrics = evaluate_frame(model, train_df)
|
|
val_metrics = evaluate_frame(model, val_df)
|
|
row = {
|
|
"model_name": model_name,
|
|
"train_mean_rms_error": train_metrics["mean_rms_error"],
|
|
"train_median_rms_error": train_metrics["median_rms_error"],
|
|
"train_max_rms_error": train_metrics["max_rms_error"],
|
|
"val_mean_rms_error": val_metrics["mean_rms_error"],
|
|
"val_median_rms_error": val_metrics["median_rms_error"],
|
|
"val_max_rms_error": val_metrics["max_rms_error"],
|
|
}
|
|
results.append(row)
|
|
print(
|
|
f"{model_name}: train_mean={train_metrics['mean_rms_error']:.6f} | "
|
|
f"val_mean={val_metrics['mean_rms_error']:.6f}"
|
|
)
|
|
|
|
if val_metrics["mean_rms_error"] < best_val_error:
|
|
best_val_error = val_metrics["mean_rms_error"]
|
|
best_bundle = {
|
|
"model_name": model_name,
|
|
"model": model,
|
|
"config": serialize_for_checkpoint(asdict(config)),
|
|
"feature_names": list(FEATURE_NAMES),
|
|
"val_metrics": val_metrics,
|
|
"train_metrics": train_metrics,
|
|
}
|
|
|
|
summary_df = pd.DataFrame(results).sort_values("val_mean_rms_error").reset_index(drop=True)
|
|
ckpt_dir = checkpoint_dir(config)
|
|
summary_path = ckpt_dir / config.train.summary_name
|
|
summary_df.to_csv(summary_path, index=False)
|
|
print(f"Model selection saved to: {summary_path}")
|
|
print(summary_df.to_string(index=False))
|
|
|
|
if best_bundle is None:
|
|
raise RuntimeError("No valid model trained.")
|
|
|
|
best_path = ckpt_dir / config.train.best_model_name
|
|
with best_path.open("wb") as handle:
|
|
pickle.dump(best_bundle, handle)
|
|
print(f"Best tree model saved to: {best_path}")
|
|
print(f"Best model: {best_bundle['model_name']} | val_mean_rms_error={best_val_error:.6f}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
train()
|