initial iris classification space
Browse files- README.md +21 -9
- app.py +67 -0
- metrics.json +8 -0
- model.joblib +3 -0
- requirements.txt +4 -0
- train.py +70 -0
README.md
CHANGED
|
@@ -1,14 +1,26 @@
|
|
| 1 |
---
|
| 2 |
-
title: Iris Classification
|
| 3 |
-
emoji: 🏃
|
| 4 |
-
colorFrom: green
|
| 5 |
-
colorTo: red
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version: 6.15.2
|
| 8 |
-
python_version: '3.13'
|
| 9 |
app_file: app.py
|
| 10 |
-
pinned: false
|
| 11 |
-
short_description: A Space that predicts Iris species using KNN classifier.
|
| 12 |
---
|
| 13 |
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Iris Classification Demo
|
|
|
|
|
|
|
|
|
|
| 3 |
sdk: gradio
|
|
|
|
|
|
|
| 4 |
app_file: app.py
|
|
|
|
|
|
|
| 5 |
---
|
| 6 |
|
| 7 |
+
# Iris Classification Demo
|
| 8 |
+
|
| 9 |
+
A simple Hugging Face Space that predicts Iris species using a KNN classifier.
|
| 10 |
+
|
| 11 |
+
## What’s inside
|
| 12 |
+
|
| 13 |
+
- `train.py`: trains a `StandardScaler + KNN` model with `GridSearchCV` and saves `model.joblib`.
|
| 14 |
+
- `app.py`: Gradio UI for interactive predictions.
|
| 15 |
+
|
| 16 |
+
## Running locally
|
| 17 |
+
|
| 18 |
+
```bash
|
| 19 |
+
pip install -r requirements.txt
|
| 20 |
+
python train.py
|
| 21 |
+
python app.py
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
## Notes
|
| 25 |
+
|
| 26 |
+
- The app will auto-train the model if `model.joblib` is missing.
|
app.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
|
| 5 |
+
import gradio as gr
|
| 6 |
+
import joblib
|
| 7 |
+
import pandas as pd
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
ARTIFACT_PATH = Path(__file__).with_name("model.joblib")
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def _load_bundle() -> dict:
|
| 14 |
+
if not ARTIFACT_PATH.exists():
|
| 15 |
+
from train import train_and_save
|
| 16 |
+
|
| 17 |
+
train_and_save(ARTIFACT_PATH)
|
| 18 |
+
|
| 19 |
+
return joblib.load(ARTIFACT_PATH)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
BUNDLE = _load_bundle()
|
| 23 |
+
MODEL = BUNDLE["model"]
|
| 24 |
+
TARGET_NAMES = BUNDLE["target_names"]
|
| 25 |
+
FEATURE_NAMES = BUNDLE["feature_names"]
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def predict(sepal_length: float, sepal_width: float, petal_length: float, petal_width: float):
|
| 29 |
+
x = pd.DataFrame(
|
| 30 |
+
[[sepal_length, sepal_width, petal_length, petal_width]],
|
| 31 |
+
columns=FEATURE_NAMES,
|
| 32 |
+
)
|
| 33 |
+
|
| 34 |
+
pred_idx = int(MODEL.predict(x)[0])
|
| 35 |
+
pred_label = TARGET_NAMES[pred_idx]
|
| 36 |
+
|
| 37 |
+
if hasattr(MODEL, "predict_proba"):
|
| 38 |
+
proba = MODEL.predict_proba(x)[0]
|
| 39 |
+
proba_dict = {str(TARGET_NAMES[i]): float(proba[i]) for i in range(len(TARGET_NAMES))}
|
| 40 |
+
else:
|
| 41 |
+
proba_dict = {TARGET_NAMES[pred_idx]: 1.0}
|
| 42 |
+
|
| 43 |
+
return pred_label, proba_dict
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
demo = gr.Interface(
|
| 47 |
+
fn=predict,
|
| 48 |
+
inputs=[
|
| 49 |
+
gr.Number(label=FEATURE_NAMES[0], value=5.8),
|
| 50 |
+
gr.Number(label=FEATURE_NAMES[1], value=3.0),
|
| 51 |
+
gr.Number(label=FEATURE_NAMES[2], value=4.0),
|
| 52 |
+
gr.Number(label=FEATURE_NAMES[3], value=1.2),
|
| 53 |
+
],
|
| 54 |
+
outputs=[
|
| 55 |
+
gr.Textbox(label="Predicted class"),
|
| 56 |
+
gr.Label(label="Class probabilities"),
|
| 57 |
+
],
|
| 58 |
+
title="Iris Classification (KNN)",
|
| 59 |
+
description=(
|
| 60 |
+
"KNN classifier trained on the classic Iris dataset. "
|
| 61 |
+
"Enter measurements and get a predicted species + probabilities."
|
| 62 |
+
),
|
| 63 |
+
)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
if __name__ == "__main__":
|
| 67 |
+
demo.launch()
|
metrics.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cv_best_accuracy": 0.9666666666666668,
|
| 3 |
+
"best_params": {
|
| 4 |
+
"knn__n_neighbors": 6,
|
| 5 |
+
"knn__p": 2,
|
| 6 |
+
"knn__weights": "uniform"
|
| 7 |
+
}
|
| 8 |
+
}
|
model.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:796d9efca47a8b3495bba825f57b2a65be7b9d9ce5abf1c81bd17e761b47dc98
|
| 3 |
+
size 15144
|
requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio
|
| 2 |
+
scikit-learn
|
| 3 |
+
joblib
|
| 4 |
+
pandas
|
train.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import json
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
import joblib
|
| 7 |
+
from sklearn.datasets import load_iris
|
| 8 |
+
from sklearn.model_selection import GridSearchCV
|
| 9 |
+
from sklearn.neighbors import KNeighborsClassifier
|
| 10 |
+
from sklearn.pipeline import Pipeline
|
| 11 |
+
from sklearn.preprocessing import StandardScaler
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
ARTIFACT_PATH = Path(__file__).with_name("model.joblib")
|
| 15 |
+
METRICS_PATH = Path(__file__).with_name("metrics.json")
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def train_and_save(artifact_path: Path = ARTIFACT_PATH) -> dict:
|
| 19 |
+
iris = load_iris(as_frame=True)
|
| 20 |
+
X = iris.data
|
| 21 |
+
y = iris.target
|
| 22 |
+
|
| 23 |
+
target_names = [str(name) for name in iris.target_names]
|
| 24 |
+
feature_names = [str(name) for name in iris.feature_names]
|
| 25 |
+
|
| 26 |
+
pipeline = Pipeline(
|
| 27 |
+
steps=[
|
| 28 |
+
("scaler", StandardScaler()),
|
| 29 |
+
("knn", KNeighborsClassifier()),
|
| 30 |
+
]
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
param_grid = {
|
| 34 |
+
"knn__n_neighbors": list(range(1, 21)),
|
| 35 |
+
"knn__weights": ["uniform", "distance"],
|
| 36 |
+
"knn__p": [1, 2],
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
search = GridSearchCV(
|
| 40 |
+
estimator=pipeline,
|
| 41 |
+
param_grid=param_grid,
|
| 42 |
+
cv=5,
|
| 43 |
+
scoring="accuracy",
|
| 44 |
+
n_jobs=-1,
|
| 45 |
+
refit=True,
|
| 46 |
+
)
|
| 47 |
+
search.fit(X, y)
|
| 48 |
+
|
| 49 |
+
best_model = search.best_estimator_
|
| 50 |
+
joblib.dump(
|
| 51 |
+
{
|
| 52 |
+
"model": best_model,
|
| 53 |
+
"target_names": target_names,
|
| 54 |
+
"feature_names": feature_names,
|
| 55 |
+
},
|
| 56 |
+
artifact_path,
|
| 57 |
+
)
|
| 58 |
+
|
| 59 |
+
metrics = {
|
| 60 |
+
"cv_best_accuracy": float(search.best_score_),
|
| 61 |
+
"best_params": search.best_params_,
|
| 62 |
+
}
|
| 63 |
+
METRICS_PATH.write_text(json.dumps(metrics, indent=2), encoding="utf-8")
|
| 64 |
+
return metrics
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
if __name__ == "__main__":
|
| 68 |
+
metrics = train_and_save()
|
| 69 |
+
print(f"Saved model to: {ARTIFACT_PATH}")
|
| 70 |
+
print(json.dumps(metrics, indent=2))
|