import gradio as gr import giskard import pandas as pd import tempfile import traceback import os import joblib import zipfile import onnxruntime as ort import torch import numpy as np import matplotlib.pyplot as plt import seaborn as sns from reportlab.lib.pagesizes import letter from reportlab.pdfgen import canvas from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, mean_squared_error, r2_score from nltk.translate.bleu_score import sentence_bleu try: from transformers import pipeline, AutoModelForCausalLM, AutoTokenizer except ImportError: pipeline = None try: import tensorflow as tf except ImportError: tf = None try: import xgboost as xgb except ImportError: xgb = None try: import lightgbm as lgb except ImportError: lgb = None def detect_model_type(model): try: # Sklearn classification if hasattr(model, "predict_proba") and hasattr(model, "classes_"): return "classification" # Sklearn regression if hasattr(model, "predict") and hasattr(model, "n_features_in_") and not hasattr(model, "classes_"): return "regression" # Text generation (Hugging Face pipelines or models) if hasattr(model, "generate") or "transformers.pipelines" in str(type(model)): return "text_generation" # TensorFlow model if tf and isinstance(model, tf.keras.Model): return "tensorflow" # XGBoost if xgb and isinstance(model, xgb.Booster): # Try to infer from attributes if possible (e.g., check for 'multi:softprob' param) params = model.attributes() if hasattr(model, "attributes") else {} booster_type = params.get("objective", "") if "binary" in booster_type or "softprob" in booster_type: return "classification" else: return "regression" # LightGBM if lgb and isinstance(model, lgb.Booster): objective = model.params.get("objective", "") if "binary" in objective or "multiclass" in objective: return "classification" else: return "regression" # ONNX if isinstance(model, str) and model.endswith(".onnx"): return "onnx" except Exception as e: print(f"[detect_model_type] Warning: {str(e)}") return "unknown" def create_giskard_model(model, model_type, name="", description=""): feature_names = [] if hasattr(model, "feature_names_in_"): feature_names = list(model.feature_names_in_) elif hasattr(model, "n_features_in_"): feature_names = [f"feature{i+1}" for i in range(model.n_features_in_)] else: feature_names = ["feature1", "feature2"] if model_type == "text_generation": return giskard.Model( model=model, model_type="text_generation", name=name or "Text Generator", description=description or "Text generation model", feature_names=["text"] ) elif model_type == "classification": labels = list(getattr(model, "classes_", [])) if not labels: raise ValueError("Cannot infer classification labels; model must have `classes_` attribute.") def predict_proba_safe(df): predictions = model.predict_proba(df.values) return predictions.astype(np.float32) return giskard.Model( model=predict_proba_safe, model_type="classification", feature_names=feature_names, classification_labels=labels ) elif model_type == "regression": def predict_safe(df): predictions = model.predict(df.values) return predictions.astype(np.float32) return giskard.Model( model=predict_safe, model_type="regression", feature_names=feature_names ) elif model_type == "tensorflow": return giskard.Model( model=model.predict, model_type="regression", name=name, description=description ) elif model_type == "onnx": def onnx_predict(input_data): ort_session = ort.InferenceSession(model) inputs = {ort_session.get_inputs()[0].name: input_data.to_numpy().astype('float32')} result = ort_session.run(None, inputs)[0] return result.astype(np.float32) return giskard.Model( model=onnx_predict, model_type="regression", feature_names=feature_names ) else: raise ValueError("Unsupported model type") def extract_if_zip(path): if zipfile.is_zipfile(path): with zipfile.ZipFile(path, 'r') as zip_ref: extracted_path = tempfile.mkdtemp() zip_ref.extractall(extracted_path) for file in os.listdir(extracted_path): full_path = os.path.join(extracted_path, file) if os.path.isfile(full_path): return full_path return path def save_issues_pdf(issues_df, performance_metrics, deviation_info, output_path): c = canvas.Canvas(output_path, pagesize=letter) width, height = letter text = c.beginText(40, height - 40) text.setFont("Helvetica", 10) text.textLine("Performance Metrics:") for metric, value in performance_metrics.items(): text.textLine(f"{metric}: {value:.4f}" if isinstance(value, (int, float)) else f"{metric}: {value}") text.textLine("\nDeviation from Expected Outcomes:") for dev in deviation_info: text.textLine(dev) if 'bias' in issues_df.columns: text.textLine("\nBias/Fairness Issues:") bias_issues = issues_df[issues_df['bias'] == True] if not bias_issues.empty: for issue in bias_issues['issue_description']: text.textLine(issue) else: text.textLine("No bias or fairness issues found.") else: text.textLine("No bias or fairness issues found.") text.textLine("\nIssues Detected:") if issues_df.empty: text.textLine("No issues detected.") else: lines = issues_df.to_string().split('\n') for line in lines: text.textLine(line) c.drawText(text) c.save() def assess_uploaded_model(model_file, name, description, manual_type): try: model = None model_type = "unknown" with tempfile.NamedTemporaryFile(delete=False) as tmp_file: with open(model_file.name, "rb") as f: tmp_file.write(f.read()) tmp_file_path = tmp_file.name model_path = extract_if_zip(tmp_file_path) try: model = joblib.load(model_path) model_type = detect_model_type(model) except Exception: pass if model is None and tf is not None: try: model = tf.keras.models.load_model(model_path) model_type = detect_model_type(model) except Exception: pass if model is None and xgb is not None: try: model = xgb.Booster() model.load_model(model_path) model_type = detect_model_type(model) except Exception: pass if model is None and lgb is not None: try: model = lgb.Booster(model_file=model_path) model_type = detect_model_type(model) except Exception: pass if model is None and model_path.endswith(".onnx"): model = model_path model_type = detect_model_type(model) elif model is None and name.lower().startswith("gpt") and pipeline is not None: tokenizer = AutoTokenizer.from_pretrained(name) model = AutoModelForCausalLM.from_pretrained(name) model = pipeline("text-generation", model=model, tokenizer=tokenizer) model_type = detect_model_type(model) if model is None: return gr.update(value="❌ Error: Could not load model."), gr.update(value=""), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False) giskard_model = create_giskard_model(model, model_type, name, description) issues_df = pd.DataFrame() if hasattr(model, "n_features_in_"): n_features = model.n_features_in_ feature_names = [f"feature{i+1}" for i in range(n_features)] elif hasattr(model, "feature_names_in_"): feature_names = list(model.feature_names_in_) else: n_features = 3 # fallback default feature_names = [f"feature{i+1}" for i in range(n_features)] row_count = 100 rng = np.random.default_rng(seed=42) df_base = pd.DataFrame(rng.normal(loc=0, scale=5, size=(row_count, len(feature_names))), columns=feature_names) df_base.iloc[0, 0] = np.nan df_base.iloc[1, 1] = 9999 df_base.iloc[2, 2] = -9999 df_scan = df_base.fillna(-999) target = rng.integers(0, 2, size=row_count) if model_type == "classification" else rng.normal(size=row_count) df_scan["__target__"] = target column_types = {col: "numeric" for col in feature_names} dataset = giskard.Dataset(df=df_scan, target="__target__", column_types=column_types) try: scan_results = giskard.scan(giskard_model, dataset) issues_df = scan_results.to_dataframe() except Exception as scan_error: return gr.update(value=f"❌ Error: {str(scan_error)}"), gr.update(value=traceback.format_exc()), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False) performance_metrics = {} try: if model_type in ["classification", "regression"]: preds = model.predict(df_scan[feature_names]) if model_type == "classification": if hasattr(model, "predict_proba"): preds = np.argmax(model.predict_proba(df_scan[feature_names]), axis=1) performance_metrics = { "Accuracy": accuracy_score(target, preds), "Precision": precision_score(target, preds, average="binary", zero_division=0), "Recall": recall_score(target, preds, average="binary", zero_division=0), "F1 Score": f1_score(target, preds, average="binary", zero_division=0) } elif model_type == "regression": performance_metrics = { "MSE": mean_squared_error(target, preds), "R²": r2_score(target, preds) } elif model_type == "tensorflow": preds = model.predict(df_scan[feature_names]) performance_metrics = { "MSE": mean_squared_error(target, preds), "R²": r2_score(target, preds) } elif model_type == "onnx": ort_session = ort.InferenceSession(model_path) input_name = ort_session.get_inputs()[0].name onnx_input = df_scan[feature_names].to_numpy().astype("float32") preds = ort_session.run(None, {input_name: onnx_input})[0] performance_metrics = { "MSE": mean_squared_error(target, preds), "R²": r2_score(target, preds) } elif model_type == "text_generation": reference = "This is a reference sentence." input_text = "This is" result = model(input_text)[0]['generated_text'] bleu_score = sentence_bleu([reference.split()], result.split()) performance_metrics = { "BLEU Score": bleu_score } else: performance_metrics = { "Warning": "Unsupported model type for auto-metrics" } except Exception as e: performance_metrics = { "Error": f"Could not calculate metrics: {str(e)}" } deviation_info = ["Feature `feature1` has fail rate due to perturbation: 5%"] pdf_path = os.path.join(tempfile.gettempdir(), "issues_report.pdf") save_issues_pdf(issues_df, performance_metrics, deviation_info, pdf_path) status_msg = f"✅ Scan complete.\nModel type: {model_type}.\n[Download PDF]({pdf_path})" return ( gr.update(value=status_msg), gr.update(value="Check PDF report"), gr.update(value=pdf_path, visible=True), gr.update(value=None, visible=False), gr.update(value=None, visible=False) ) except Exception as e: return ( gr.update(value=f"❌ Error: {str(e)}"), gr.update(value=traceback.format_exc()), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False) ) def main(): with gr.Blocks() as demo: gr.Markdown("## 🧠 Universal ML Model Assessment (with Giskard)") gr.Markdown("Upload a `.pkl`, `.joblib`, `.onnx`, `.zip`, `.h5`, `.pb` or HuggingFace Transformers model (like `gpt2`).") model_file = gr.File(label="Upload Model File", file_types=[".pkl", ".joblib", ".h5", ".pb", ".onnx", ".zip"]) name = gr.Textbox(label="Model Name (e.g. gpt2)") description = gr.Textbox(label="Model Description") manual_type = gr.Dropdown(label="Optional Model Type", choices=["", "classification", "regression", "text_generation", "tensorflow", "onnx"], value="") assess_btn = gr.Button("🩺 Assess Model") status = gr.Textbox(label="Status") issues = gr.Textbox(label="Assessment Results", lines=10) with gr.Row(): download_pdf = gr.File(label="📄 Download PDF Report", visible=False) download_csv = gr.File(label="📊 Download CSV Report", visible=False) download_plot = gr.File(label="🖼️ View Plot", visible=False) assess_btn.click( assess_uploaded_model, inputs=[model_file, name, description, manual_type], outputs=[status, issues, download_pdf, download_csv, download_plot] ) demo.launch() if __name__ == "__main__": main()