eaglelandsonce commited on
Commit
bf76313
·
verified ·
1 Parent(s): 04fce5f

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +228 -0
app.py ADDED
@@ -0,0 +1,228 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Interactive Decision Tree Classifier for the Iris dataset using scikit-learn + Gradio.
4
+
5
+ Features
6
+ - Visualizes the trained decision tree (matplotlib.plot_tree).
7
+ - Lets users tweak hyperparameters (criterion, max_depth, min_samples_split).
8
+ - Shows accuracy and a full classification report.
9
+ - Modular code organization for clarity and reuse.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import io
15
+ from dataclasses import dataclass
16
+ from typing import Optional, Tuple
17
+
18
+ import numpy as np
19
+ import pandas as pd
20
+ import matplotlib.pyplot as plt
21
+
22
+ from sklearn import datasets
23
+ from sklearn.model_selection import train_test_split
24
+ from sklearn.tree import DecisionTreeClassifier, plot_tree
25
+ from sklearn.metrics import accuracy_score, classification_report
26
+
27
+
28
+ # -----------------------------
29
+ # Data & Config
30
+ # -----------------------------
31
+
32
+ @dataclass
33
+ class TrainConfig:
34
+ criterion: str = "gini" # "gini", "entropy", or "log_loss"
35
+ max_depth: Optional[int] = None # None means unlimited depth
36
+ min_samples_split: int = 2 # integer >= 2
37
+ test_size: float = 0.25 # test split fraction
38
+ random_state: int = 42 # reproducibility
39
+
40
+
41
+ def load_iris() -> Tuple[pd.DataFrame, pd.Series, list[str]]:
42
+ """Load the Iris dataset and return X (DataFrame), y (Series), and class names."""
43
+ iris = datasets.load_iris()
44
+ X = pd.DataFrame(iris.data, columns=iris.feature_names)
45
+ y = pd.Series(iris.target, name="target")
46
+ class_names = iris.target_names.tolist()
47
+ return X, y, class_names
48
+
49
+
50
+ # -----------------------------
51
+ # Model Pipeline
52
+ # -----------------------------
53
+
54
+ def build_model(cfg: TrainConfig) -> DecisionTreeClassifier:
55
+ """Instantiate a DecisionTreeClassifier from a config."""
56
+ model = DecisionTreeClassifier(
57
+ criterion=cfg.criterion,
58
+ max_depth=cfg.max_depth,
59
+ min_samples_split=cfg.min_samples_split,
60
+ random_state=cfg.random_state,
61
+ )
62
+ return model
63
+
64
+
65
+ def train_and_evaluate(
66
+ cfg: TrainConfig,
67
+ ) -> Tuple[DecisionTreeClassifier, float, str, pd.DataFrame, pd.Series, list[str]]:
68
+ """
69
+ Train a decision tree and compute accuracy + classification report.
70
+ Returns:
71
+ model, accuracy, report (str), X_test (DataFrame), y_test (Series), class_names (list)
72
+ """
73
+ X, y, class_names = load_iris()
74
+ X_train, X_test, y_train, y_test = train_test_split(
75
+ X, y, test_size=cfg.test_size, random_state=cfg.random_state, stratify=y
76
+ )
77
+ model = build_model(cfg)
78
+ model.fit(X_train, y_train)
79
+
80
+ y_pred = model.predict(X_test)
81
+ acc = accuracy_score(y_test, y_pred)
82
+ report = classification_report(y_test, y_pred, target_names=class_names)
83
+ return model, acc, report, X_test, y_test, class_names
84
+
85
+
86
+ # -----------------------------
87
+ # Visualization
88
+ # -----------------------------
89
+
90
+ def render_tree_image(
91
+ model: DecisionTreeClassifier,
92
+ feature_names: list[str],
93
+ class_names: list[str],
94
+ dpi: int = 120,
95
+ ) -> np.ndarray:
96
+ """
97
+ Render a matplotlib plot_tree to a PNG image array suitable for display in Gradio.
98
+ """
99
+ fig, ax = plt.subplots(figsize=(12, 8), dpi=dpi)
100
+ plot_tree(
101
+ model,
102
+ feature_names=feature_names,
103
+ class_names=class_names,
104
+ filled=True,
105
+ rounded=True,
106
+ impurity=True,
107
+ proportion=True,
108
+ fontsize=8,
109
+ ax=ax,
110
+ )
111
+ buf = io.BytesIO()
112
+ fig.tight_layout()
113
+ fig.savefig(buf, format="png")
114
+ plt.close(fig)
115
+ buf.seek(0)
116
+
117
+ # Convert buffer to numpy array for gr.Image
118
+ import PIL.Image as Image
119
+ img = Image.open(buf)
120
+ return np.array(img)
121
+
122
+
123
+ # -----------------------------
124
+ # Gradio App
125
+ # -----------------------------
126
+
127
+ def inference(
128
+ criterion: str,
129
+ max_depth_enabled: bool,
130
+ max_depth_val: int,
131
+ min_samples_split: int,
132
+ test_size: float,
133
+ random_state: int,
134
+ ) -> tuple[np.ndarray, str, str]:
135
+ """
136
+ Gradio handler: trains, evaluates, and returns (tree_image, accuracy_text, classification_report).
137
+ """
138
+ cfg = TrainConfig(
139
+ criterion=criterion,
140
+ max_depth=(max_depth_val if max_depth_enabled else None),
141
+ min_samples_split=int(min_samples_split),
142
+ test_size=float(test_size),
143
+ random_state=int(random_state),
144
+ )
145
+ model, acc, report, X_test, y_test, class_names = train_and_evaluate(cfg)
146
+ tree_img = render_tree_image(model, feature_names=X_test.columns.tolist(), class_names=class_names)
147
+ acc_text = f"Accuracy: {acc:.4f}\n\nTest Size: {cfg.test_size} | Random State: {cfg.random_state}\nParams -> criterion={cfg.criterion}, max_depth={cfg.max_depth}, min_samples_split={cfg.min_samples_split}"
148
+ return tree_img, acc_text, report
149
+
150
+
151
+ def build_interface():
152
+ import gradio as gr
153
+
154
+ with gr.Blocks(title="Iris Decision Tree (scikit-learn)", theme=gr.themes.Soft()) as demo:
155
+ gr.Markdown(
156
+ """
157
+ # 🌸 Iris Decision Tree Classifier
158
+ Tweak hyperparameters and see how the decision tree changes. View accuracy and a full classification report.
159
+ """
160
+ )
161
+
162
+ with gr.Row():
163
+ with gr.Column(scale=1):
164
+ criterion = gr.Dropdown(
165
+ label="Criterion",
166
+ choices=["gini", "entropy", "log_loss"],
167
+ value="gini",
168
+ info="Split quality metric"
169
+ )
170
+ max_depth_enabled = gr.Checkbox(
171
+ label="Limit max_depth?",
172
+ value=False
173
+ )
174
+ max_depth_val = gr.Slider(
175
+ label="max_depth (if enabled)",
176
+ minimum=1,
177
+ maximum=10,
178
+ step=1,
179
+ value=3
180
+ )
181
+ min_samples_split = gr.Slider(
182
+ label="min_samples_split",
183
+ minimum=2,
184
+ maximum=20,
185
+ step=1,
186
+ value=2
187
+ )
188
+ test_size = gr.Slider(
189
+ label="test_size (fraction for test)",
190
+ minimum=0.1,
191
+ maximum=0.5,
192
+ step=0.05,
193
+ value=0.25
194
+ )
195
+ random_state = gr.Slider(
196
+ label="random_state",
197
+ minimum=0,
198
+ maximum=9999,
199
+ step=1,
200
+ value=42
201
+ )
202
+ run_btn = gr.Button("Train & Evaluate", variant="primary")
203
+
204
+ with gr.Column(scale=2):
205
+ tree_img = gr.Image(label="Decision Tree", interactive=False)
206
+ accuracy_box = gr.Textbox(label="Accuracy & Parameters", interactive=False, lines=4)
207
+ report_box = gr.Textbox(label="Classification Report", interactive=False, lines=12)
208
+
209
+ # Wire events
210
+ inputs = [criterion, max_depth_enabled, max_depth_val, min_samples_split, test_size, random_state]
211
+ outputs = [tree_img, accuracy_box, report_box]
212
+
213
+ # Run once on load
214
+ demo.load(fn=inference, inputs=inputs, outputs=outputs)
215
+
216
+ # Run on button click
217
+ run_btn.click(fn=inference, inputs=inputs, outputs=outputs)
218
+
219
+ gr.Markdown(
220
+ "Tip: Uncheck **Limit max_depth?** to let the tree grow fully (may overfit)."
221
+ )
222
+
223
+ return demo
224
+
225
+
226
+ if __name__ == "__main__":
227
+ demo = build_interface()
228
+ demo.launch(server_name="0.0.0.0", server_port=7860, ssr_mode=False)