""" Tabular Flower Classifier - Gradio App Homework 3 - GUI Module Author: Anyu Huang Model Source: its-zion-18/flowers-tabular-autolguon-predictor This app loads an AutoGluon TabularPredictor from a ZIP file and exposes a simple Gradio interface to make predictions and show class probabilities. """ # ============================================================================ # IMPORTS # ============================================================================ import os import shutil import zipfile import pathlib import pandas as pd import gradio as gr import numpy as np from autogluon.tabular import TabularPredictor # ============================================================================ # CONFIGURATION # ============================================================================ ZIP_FILENAME = "autogluon_predictor_dir.zip" EXTRACT_DIR = pathlib.Path("predictor_native") # ============================================================================ # MODEL LOADING # ============================================================================ def load_predictor(): """ Extract and load an AutoGluon TabularPredictor from a ZIP file. Workflow: 1) Check if ZIP exists in the repository root 2) Extract into EXTRACT_DIR (clean if exists) 3) Find the predictor root (folder that contains 'models') and load Returns: TabularPredictor: Loaded predictor ready for inference. Raises: FileNotFoundError: If ZIP cannot be found. """ # Check if ZIP exists in repo if not os.path.exists(ZIP_FILENAME): raise FileNotFoundError(f"ZIP file not found: {ZIP_FILENAME}") print(f"Found ZIP file: {ZIP_FILENAME}") # Clean & re-create extraction directory if EXTRACT_DIR.exists(): shutil.rmtree(EXTRACT_DIR) EXTRACT_DIR.mkdir(parents=True, exist_ok=True) # Extract the predictor directory print("Extracting predictor...") with zipfile.ZipFile(ZIP_FILENAME, 'r') as zip_ref: zip_ref.extractall(str(EXTRACT_DIR)) # Find the predictor root (heuristic: folder containing 'models') for root, dirs, files in os.walk(str(EXTRACT_DIR)): if 'models' in dirs: print(f"Loading predictor from: {root}") return TabularPredictor.load(root, require_py_version_match=False) # Fallback: try the top-level extract dir print(f"Loading predictor from: {EXTRACT_DIR}") return TabularPredictor.load(str(EXTRACT_DIR), require_py_version_match=False) # Initialize predictor once at startup print("Loading AutoGluon TabularPredictor...") PREDICTOR = load_predictor() print("Predictor loaded successfully!") # Metadata helpers (feature names & label) FEATURE_COLS = ( PREDICTOR.feature_metadata.get_features() if hasattr(PREDICTOR, 'feature_metadata') else [] ) TARGET_COL = PREDICTOR.label if hasattr(PREDICTOR, 'label') else "target" print(f"Features: {FEATURE_COLS}") print(f"Target: {TARGET_COL}") # ============================================================================ # PREDICTION FUNCTION # ============================================================================ def predict(*feature_values): """ Build a single-row DataFrame from UI inputs and get prediction + probabilities. Args: *feature_values: Sequence of values corresponding to FEATURE_COLS order. Returns: (proba_dict, message) proba_dict: dict(label -> probability), sorted desc, top-N shown by gr.Label message: Markdown summary with predicted label + confidence """ try: # Map UI inputs to a dict matching the model's feature columns input_data = {} for col, val in zip(FEATURE_COLS, feature_values[:len(FEATURE_COLS)]): try: # Try numeric first (keeps sliders/numbers numeric) input_data[col] = float(val) if val != "" else 0.0 except: # Otherwise leave as string (for categorical columns) input_data[col] = val print(f"Input data: {input_data}") # Build a DataFrame row for inference X = pd.DataFrame([input_data]) print(f"DataFrame shape: {X.shape}") print(f"DataFrame columns: {X.columns.tolist()}") # Predicted label (or regression value) pred = PREDICTOR.predict(X) pred_value = pred.iloc[0] print(f"Prediction: {pred_value}") # Class probabilities (if classifier). If regression, synthesize 100% on prediction. try: proba_df = PREDICTOR.predict_proba(X) if isinstance(proba_df, pd.Series): # Normalize to DataFrame shape if AG returns a Series proba_df = proba_df.to_frame().T proba_dict = {} for col in proba_df.columns: proba_dict[str(col)] = float(proba_df[col].iloc[0]) # Sort highest to lowest proba_dict = dict(sorted(proba_dict.items(), key=lambda x: x[1], reverse=True)) except Exception as e: print(f"Error getting probabilities: {e}") # Regression or unsupported proba: show pseudo-confidence proba_dict = {str(pred_value): 1.0} # Human-readable summary (confidence = max probability * 100) confidence = max(proba_dict.values()) * 100 if proba_dict else 100 message = f"**Prediction:** {pred_value}\n**Confidence:** {confidence:.2f}%" return proba_dict, message except Exception as e: error_msg = f"**Error:** {str(e)}\n\nPlease check the logs for details." print(f"Prediction error: {e}") import traceback traceback.print_exc() return {}, error_msg # ============================================================================ # EXAMPLES (quick-start presets for the first 4 features) # ============================================================================ EXAMPLES = [ [5.1, 3.5, 1.4, 0.2], [7.0, 3.2, 4.7, 1.4], [6.3, 3.3, 6.0, 2.5], ] if len(FEATURE_COLS) > 4: EXAMPLES = [ex + [0.0] * (len(FEATURE_COLS) - 4) for ex in EXAMPLES] # ============================================================================ # GRADIO UI # ============================================================================ with gr.Blocks(title="Tabular Flower Classifier", theme=gr.themes.Soft()) as demo: # Title & instructions gr.Markdown(""" # Tabular Flower Classifier This app uses an **AutoGluon TabularPredictor** to classify flowers based on their features. Adjust the feature values below and click **Predict** to see the classification results. """) with gr.Row(): # LEFT: Inputs with gr.Column(scale=1): gr.Markdown("### Input Features") feature_inputs = [] # For the first 4 features, use sliders (0-10) to make the demo interactive. # Remaining features (up to 10 shown) use numeric inputs for compactness. for i, feature in enumerate(FEATURE_COLS[:10]): if i < 4: input_widget = gr.Slider(0, 10, 5.0, label=feature) else: input_widget = gr.Number(value=0.0, label=feature) feature_inputs.append(input_widget) predict_btn = gr.Button("Predict", variant="primary", size="lg") # RIGHT: Outputs with gr.Column(scale=1): gr.Markdown("### Prediction Results") prediction_output = gr.Markdown(value="*Adjust features and click Predict*") proba_display = gr.Label(num_top_classes=5, label="Top 5 Class Probabilities") # Button click handler predict_btn.click( fn=predict, inputs=feature_inputs, outputs=[proba_display, prediction_output] ) gr.Markdown("### Example flower measurements") # Example presets gr.Examples( examples=EXAMPLES, inputs=feature_inputs, outputs=[proba_display, prediction_output], fn=predict, cache_examples=False ) gr.Markdown(""" --- ### About - **Model**: AutoGluon TabularPredictor - **Task**: Flower classification based on measurements - **Features**: Adjust the sliders/inputs above to test different flower measurements """) # ============================================================================ # ENTRY POINT # ============================================================================ if __name__ == "__main__": demo.launch()