kronector / ml /predict.py
Prathamesh Bhamare
Fix MLflow deprecation error and fallback model loading
a13ee72
Raw
History Blame Contribute Delete
3.07 kB
"""
KRONECTOR - Prediction helpers.
Inference must reuse the LabelEncoders fitted during training. Do not call
prepare_model_data without the loaded encoders in production prediction paths.
"""
from __future__ import annotations
import argparse
import numpy as np
import pandas as pd
from ml.feature_engineering import load_encoders, prepare_model_data
def load_model_and_encoders(run_id: str):
"""Load a logged MLflow model and its fitted categorical encoders."""
import mlflow.lightgbm
from mlflow.artifacts import download_artifacts
import logging
logger = logging.getLogger(__name__)
try:
model = mlflow.lightgbm.load_model(f"runs:/{run_id}/model")
except Exception as e:
logger.warning(f"Could not load from runs:/ URI: {e}. Trying registered model...")
try:
model = mlflow.lightgbm.load_model("models:/kronector-f1-lgbm/latest")
except Exception as e2:
logger.error(f"Failed to load registered model: {e2}")
raise
encoder_path = download_artifacts(
run_id=run_id, artifact_path="encoders/label_encoders.pkl"
)
encoders = load_encoders(encoder_path)
return model, encoders
def _positive_class_shap_values(shap_values):
"""Normalize SHAP binary-class outputs to one 2D array."""
if isinstance(shap_values, list):
return shap_values[1] if len(shap_values) > 1 else shap_values[0]
if isinstance(shap_values, np.ndarray) and shap_values.ndim == 3:
return shap_values[:, :, 1]
return shap_values
def _explain_predictions(model, X: pd.DataFrame) -> list[dict[str, float]]:
import shap
explainer = shap.TreeExplainer(model)
shap_values = _positive_class_shap_values(explainer.shap_values(X))
return [
{
feature_name: float(shap_value)
for feature_name, shap_value in zip(X.columns, row_values)
}
for row_values in shap_values
]
def predict_dataframe(
df: pd.DataFrame, model, encoders: dict, explain: bool = True
) -> pd.DataFrame:
"""Return win probabilities and optional SHAP dictionaries."""
bundle, _ = prepare_model_data(df, encoders=encoders)
if hasattr(model, "predict_proba"):
probabilities = model.predict_proba(bundle.X)[:, 1]
else:
probabilities = model.predict(bundle.X)
result = bundle.metadata.copy()
result["win_probability"] = probabilities
if explain:
result["shap_values"] = _explain_predictions(model, bundle.X)
return result
def main() -> None:
parser = argparse.ArgumentParser(description="Run KRONECTOR model inference")
parser.add_argument("--run-id", required=True)
parser.add_argument("--data-path", default="data_output/fastf1_races.parquet")
args = parser.parse_args()
model, encoders = load_model_and_encoders(args.run_id)
df = pd.read_parquet(args.data_path)
predictions = predict_dataframe(df, model, encoders)
print(predictions.head().to_string(index=False))
if __name__ == "__main__":
main()