forest-cover-classifier / inference.py
Tanuj124's picture
Add reproducible Forest Cover classifier package
c12b04c verified
Raw History Blame Contribute Delete
2.54 kB
"""Inference helper for the UCI Covertype decision-tree classifier."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any, Mapping, Sequence
import joblib
import pandas as pd
ROOT = Path(__file__).resolve().parent
FEATURE_ORDER = [
"Elevation", "Aspect", "Slope", "Horizontal_Distance_To_Hydrology",
"Vertical_Distance_To_Hydrology", "Horizontal_Distance_To_Roadways",
"Hillshade_9am", "Hillshade_Noon", "Hillshade_3pm",
"Horizontal_Distance_To_Fire_Points",
*[f"Wilderness_Area{i}" for i in range(1, 5)],
*[f"Soil_Type{i}" for i in range(1, 41)],
]
LABELS = {
1: "Spruce/Fir",
2: "Lodgepole Pine",
3: "Ponderosa Pine",
4: "Cottonwood/Willow",
5: "Aspen",
6: "Douglas-fir",
7: "Krummholz",
}
def _as_frame(record: Mapping[str, Any] | Sequence[Any] | pd.DataFrame) -> pd.DataFrame:
if isinstance(record, pd.DataFrame):
missing = [name for name in FEATURE_ORDER if name not in record.columns]
if missing:
raise ValueError(f"Missing required columns: {missing}")
return record[FEATURE_ORDER]
if isinstance(record, Mapping):
missing = [name for name in FEATURE_ORDER if name not in record]
if missing:
raise ValueError(f"Missing required features: {missing}")
return pd.DataFrame([[record[name] for name in FEATURE_ORDER]], columns=FEATURE_ORDER)
values = list(record)
if len(values) != len(FEATURE_ORDER):
raise ValueError(f"Expected {len(FEATURE_ORDER)} values, received {len(values)}")
return pd.DataFrame([values], columns=FEATURE_ORDER)
def predict(record: Mapping[str, Any] | Sequence[Any] | pd.DataFrame) -> dict[str, Any]:
"""Predict one Covertype class from the original 54-feature order."""
model = joblib.load(ROOT / "model.joblib")
features = _as_frame(record)
class_id = int(model.predict(features)[0])
probabilities = model.predict_proba(features)[0]
return {
"label": LABELS[class_id],
"class_id": class_id,
"probabilities": {
str(int(label)): float(probability)
for label, probability in zip(model.classes_, probabilities)
},
}
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--json", required=True, help="JSON object with all 54 named features")
args = parser.parse_args()
print(json.dumps(predict(json.loads(args.json)), indent=2))
if __name__ == "__main__":
main()