Files
lemdb/tests/test_training_models.py
T

99 lines
3.0 KiB
Python

import numpy as np
import pytest
from sklearn.ensemble import RandomForestClassifier
from sklearn.gaussian_process import GaussianProcessRegressor
from lmpm.training.dataset import (
CATEGORICAL_FEATURE_FIELDS,
NUMERIC_FEATURE_FIELDS,
TrainingDataset,
TrainingRecord,
)
from lmpm.training.models import (
build_estimator,
load_training_bundle,
save_training_bundle,
train_models,
)
from lmpm.training.routing import default_model_routes
def make_record(number: int, material_name: str) -> TrainingRecord:
features = {
field: float(number + index + 1)
for index, field in enumerate(NUMERIC_FEATURE_FIELDS)
}
features.update(
{
CATEGORICAL_FEATURE_FIELDS[0]: "bidirectional",
CATEGORICAL_FEATURE_FIELDS[1]: "20x20",
}
)
return TrainingRecord(
metadata={
"experiment_id": f"LAS-2026-{number:04d}",
"material_name": material_name,
},
features=features,
targets={
"is_cut_through": number % 2 == 0,
"carbonized_edge_width": number / 10,
"etching_depth": number * 10.0,
"is_fire_smolder": False,
"pattern_clarity_score": float(number),
"presentation_balance_score": float(10 - number),
},
)
def training_dataset() -> TrainingDataset:
return TrainingDataset(
records=(
make_record(1, "Acrylic"),
make_record(2, "Acrylic"),
make_record(3, "Basswood"),
make_record(4, "Basswood"),
)
)
def test_model_factory_builds_the_routed_estimator_types():
routes = {route.target_name: route for route in default_model_routes()}
assert isinstance(
build_estimator(routes["etching_depth"]), GaussianProcessRegressor
)
assert isinstance(build_estimator(routes["is_cut_through"]), RandomForestClassifier)
def test_training_runs_leakage_safe_lomo_and_persists_bundle(tmp_path):
bundle = train_models(training_dataset())
evaluation = bundle.evaluations["etching_depth"]
assert [fold.held_out_material for fold in evaluation.folds] == [
"Acrylic",
"Basswood",
]
assert evaluation.mean_metrics["mae"] is not None
assert bundle.models["etching_depth"].route.model_family == (
"gaussian_process_regressor"
)
features = bundle.models["etching_depth"].preprocessor.transform(training_dataset())
predictions = bundle.models["etching_depth"].estimator.predict(features.values)
assert isinstance(predictions, np.ndarray)
assert predictions.shape == (4,)
artifact_path = save_training_bundle(bundle, tmp_path / "models.joblib")
restored = load_training_bundle(artifact_path)
assert restored.report() == bundle.report()
def test_training_requires_two_distinct_materials():
dataset = TrainingDataset(
records=(make_record(1, "Acrylic"), make_record(2, "Acrylic"))
)
with pytest.raises(ValueError, match="at least two materials"):
train_models(dataset)