DecisionTree / app.py
eaglelandsonce's picture
Create app.py
7ac4226 verified
Raw
History Blame Contribute Delete
9.87 kB
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import (
accuracy_score,
precision_score,
recall_score,
f1_score,
confusion_matrix,
classification_report
)
import gradio as gr
# -------- Data --------
iris = load_iris()
X_full = pd.DataFrame(iris.data, columns=iris.feature_names)
y_full = pd.Series(iris.target, name="target")
class_names = iris.target_names
FEATURE_CHOICES = list(X_full.columns)
# -------- Plot helpers --------
def plot_confusion_matrix(cm, class_names):
fig, ax = plt.subplots(figsize=(5, 4), dpi=120)
im = ax.imshow(cm, interpolation="nearest")
ax.figure.colorbar(im, ax=ax)
ax.set(
xticks=np.arange(cm.shape[1]),
yticks=np.arange(cm.shape[0]),
xticklabels=class_names,
yticklabels=class_names,
ylabel="True label",
xlabel="Predicted label",
title="Confusion Matrix",
)
# Show counts on cells
thresh = cm.max() / 2.0
for i in range(cm.shape[0]):
for j in range(cm.shape[1]):
ax.text(
j, i, format(cm[i, j], "d"),
ha="center", va="center",
color="white" if cm[i, j] > thresh else "black"
)
fig.tight_layout()
return fig
def plot_feature_importances(model, feature_names):
importances = model.feature_importances_
order = np.argsort(importances)[::-1]
fig, ax = plt.subplots(figsize=(6, 4), dpi=120)
ax.bar(range(len(importances)), importances[order])
ax.set_xticks(range(len(importances)))
ax.set_xticklabels([feature_names[i] for i in order], rotation=30, ha="right")
ax.set_ylabel("Importance")
ax.set_title("Feature Importances")
fig.tight_layout()
return fig
def plot_decision_regions_2d(model, X, y, feature_x_name, feature_y_name, class_names):
# X: dataframe with exactly two columns (selected features)
# Create a mesh
x_min, x_max = X.iloc[:, 0].min() - 0.5, X.iloc[:, 0].max() + 0.5
y_min, y_max = X.iloc[:, 1].min() - 0.5, X.iloc[:, 1].max() + 0.5
xx, yy = np.meshgrid(
np.linspace(x_min, x_max, 300),
np.linspace(y_min, y_max, 300)
)
grid = np.c_[xx.ravel(), yy.ravel()]
Z = model.predict(grid).reshape(xx.shape)
fig, ax = plt.subplots(figsize=(6, 5), dpi=120)
ax.contourf(xx, yy, Z, alpha=0.2)
# Scatter original points
for idx, cname in enumerate(class_names):
mask = (y == idx)
ax.scatter(
X.loc[mask, feature_x_name],
X.loc[mask, feature_y_name],
label=cname, s=24
)
ax.set_xlabel(feature_x_name)
ax.set_ylabel(feature_y_name)
ax.set_title("Decision Regions (2 features)")
ax.legend(loc="upper right", fontsize=8)
fig.tight_layout()
return fig
# -------- Core training + evaluation --------
def run_decision_tree(
test_size,
random_state,
criterion,
splitter,
unlimited_depth,
max_depth,
min_samples_split,
min_samples_leaf,
max_features,
class_weight_mode,
feature_x_name,
feature_y_name
):
# Map UI values to sklearn-friendly options
md = None if unlimited_depth else int(max_depth)
if max_features == "None":
mf = None
elif max_features == "auto/sqrt":
mf = "sqrt"
elif max_features == "log2":
mf = "log2"
else:
mf = None
cw = None if class_weight_mode == "None" else "balanced"
# Train / Test split
X_train, X_test, y_train, y_test = train_test_split(
X_full, y_full,
test_size=float(test_size),
random_state=int(random_state),
stratify=y_full
)
# Model
clf = DecisionTreeClassifier(
criterion=criterion,
splitter=splitter,
max_depth=md,
min_samples_split=int(min_samples_split),
min_samples_leaf=int(min_samples_leaf),
max_features=mf,
class_weight=cw,
random_state=int(random_state)
)
clf.fit(X_train, y_train)
# Predictions & metrics
y_pred = clf.predict(X_test)
acc = accuracy_score(y_test, y_pred)
prec = precision_score(y_test, y_pred, average="macro", zero_division=0)
rec = recall_score(y_test, y_pred, average="macro", zero_division=0)
f1 = f1_score(y_test, y_pred, average="macro", zero_division=0)
report = classification_report(y_test, y_pred, target_names=class_names, zero_division=0)
# Confusion matrix
cm = confusion_matrix(y_test, y_pred)
cm_fig = plot_confusion_matrix(cm, class_names)
# Feature importances
fi_fig = plot_feature_importances(clf, X_full.columns)
# Decision boundary for 2 chosen features
# Refit a new tree **on those two features only** to plot clear regions (same hyperparams)
if feature_x_name == feature_y_name:
# If same feature accidentally chosen, pick a safe default distinct pair
feature_x_name, feature_y_name = FEATURE_CHOICES[0], FEATURE_CHOICES[1]
two_feat_cols = [feature_x_name, feature_y_name]
X2_train = X_train[two_feat_cols]
X2_test = X_test[two_feat_cols]
clf2 = DecisionTreeClassifier(
criterion=criterion,
splitter=splitter,
max_depth=md,
min_samples_split=int(min_samples_split),
min_samples_leaf=int(min_samples_leaf),
max_features=None, # force 2D features for plotting
class_weight=cw,
random_state=int(random_state)
)
clf2.fit(X2_train, y_train)
boundary_fig = plot_decision_regions_2d(
clf2,
pd.concat([X2_train, X2_test], axis=0),
pd.concat([y_train, y_test], axis=0).values,
feature_x_name, feature_y_name, class_names
)
# Make a small metrics dataframe for display
metrics_df = pd.DataFrame(
[{"accuracy": acc, "precision_macro": prec, "recall_macro": rec, "f1_macro": f1}]
)
# Classification report as preformatted text
report_text = f"```\n{report}\n```"
return (
metrics_df,
report_text,
cm_fig,
fi_fig,
boundary_fig
)
# -------- Gradio UI --------
with gr.Blocks(title="Iris Decision Tree Explorer") as demo:
gr.Markdown(
"""
# 🌸 Iris Decision Tree Explorer
Train a Decision Tree on the classic Iris dataset. Tweak hyperparameters on the left, then inspect metrics and plots on the right.
"""
)
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### Hyperparameters")
criterion = gr.Dropdown(
label="criterion",
choices=["gini", "entropy", "log_loss"],
value="gini"
)
splitter = gr.Dropdown(
label="splitter",
choices=["best", "random"],
value="best"
)
unlimited_depth = gr.Checkbox(
label="Unlimited depth (ignore max_depth)",
value=True
)
max_depth = gr.Slider(
label="max_depth (used only if Unlimited depth = False)",
minimum=1, maximum=30, step=1, value=5
)
min_samples_split = gr.Slider(
label="min_samples_split",
minimum=2, maximum=20, step=1, value=2
)
min_samples_leaf = gr.Slider(
label="min_samples_leaf",
minimum=1, maximum=20, step=1, value=1
)
max_features = gr.Dropdown(
label="max_features",
choices=["None", "auto/sqrt", "log2"],
value="None"
)
class_weight_mode = gr.Dropdown(
label="class_weight",
choices=["None", "balanced"],
value="None"
)
gr.Markdown("### Data & Reproducibility")
test_size = gr.Slider(
label="test_size",
minimum=0.1, maximum=0.5, step=0.05, value=0.2
)
random_state = gr.Number(
label="random_state",
value=42, precision=0
)
gr.Markdown("### Decision Region (2D) Features")
feature_x_name = gr.Dropdown(
label="X-axis feature",
choices=FEATURE_CHOICES,
value=FEATURE_CHOICES[0]
)
feature_y_name = gr.Dropdown(
label="Y-axis feature",
choices=FEATURE_CHOICES,
value=FEATURE_CHOICES[1]
)
run_btn = gr.Button("Train & Evaluate", variant="primary")
with gr.Column(scale=2):
gr.Markdown("### Results")
metrics_df = gr.Dataframe(
label="Metrics (test set)",
interactive=False,
headers=["accuracy", "precision_macro", "recall_macro", "f1_macro"]
)
report_md = gr.Markdown(label="Classification Report")
with gr.Row():
cm_plot = gr.Plot(label="Confusion Matrix")
fi_plot = gr.Plot(label="Feature Importances")
boundary_plot = gr.Plot(label="Decision Regions (2 features)")
run_btn.click(
fn=run_decision_tree,
inputs=[
test_size, random_state,
criterion, splitter, unlimited_depth, max_depth,
min_samples_split, min_samples_leaf, max_features, class_weight_mode,
feature_x_name, feature_y_name
],
outputs=[metrics_df, report_md, cm_plot, fi_plot, boundary_plot]
)
if __name__ == "__main__":
# You can set share=True if you want a public link when running locally
demo.launch()