CoolFace
Apppublic

kikikara/TUFA-Explainable_AI

sourceHugging Faceapache-2.0updated 1y agoView on Hugging Face
1likes
app.py344 linesDownload Raw Back to root
1import gradio as gr2import os3import joblib4import torch5import numpy as np6import html # 여전히 highlighted_text_data 생성 시 html.escape를 사용할 수 있으므로 유지7from transformers import AutoTokenizer, AutoModel, logging as hf_logging8import pandas as pd9import matplotlib10matplotlib.use('Agg')11import matplotlib.pyplot as plt12from sklearn.decomposition import PCA13import plotly.graph_objects as go14 15# --- Global Settings and Model Loading ---16hf_logging.set_verbosity_error()17 18MODEL_NAME = "bert-base-uncased"19DEVICE     = "cpu"20SAVE_DIR   = "저장저장1"21LAYER_ID   = 422SEED       = 023CLF_NAME   = "linear"24 25CLASS_LABEL_MAP = {26    0: "World",27    1: "Sports",28    2: "Business",29    3: "Sci/Tech"30}31 32TOKENIZER_GLOBAL, MODEL_GLOBAL = None, None33W_GLOBAL, MU_GLOBAL, W_P_GLOBAL, B_P_GLOBAL = None, None, None, None34MODELS_LOADED_SUCCESSFULLY = False35MODEL_LOADING_ERROR_MESSAGE = ""36 37try:38    print("Gradio App: Initializing model loading...")39    lda_file_path = os.path.join(SAVE_DIR, f"lda_layer{LAYER_ID}_seed{SEED}.pkl")40    clf_file_path = os.path.join(SAVE_DIR, f"{CLF_NAME}_layer{LAYER_ID}_projlda_seed{SEED}.pkl")41 42    if not os.path.isdir(SAVE_DIR):43        raise FileNotFoundError(f"Error: Model storage directory '{SAVE_DIR}' not found.")44    if not os.path.exists(lda_file_path):45        raise FileNotFoundError(f"Error: LDA model file '{lda_file_path}' not found.")46    if not os.path.exists(clf_file_path):47        raise FileNotFoundError(f"Error: Classifier model file '{clf_file_path}' not found.")48 49    lda = joblib.load(lda_file_path)50    clf = joblib.load(clf_file_path)51 52    if hasattr(clf, "base_estimator"): clf = clf.base_estimator53 54    W_GLOBAL   = torch.tensor(lda.scalings_,  dtype=torch.float32, device=DEVICE)55    MU_GLOBAL  = torch.tensor(lda.xbar_,     dtype=torch.float32, device=DEVICE)56    W_P_GLOBAL = torch.tensor(clf.coef_,     dtype=torch.float32, device=DEVICE)57    B_P_GLOBAL = torch.tensor(clf.intercept_, dtype=torch.float32, device=DEVICE)58 59    TOKENIZER_GLOBAL = AutoTokenizer.from_pretrained(MODEL_NAME, use_fast=True)60    MODEL_GLOBAL     = AutoModel.from_pretrained(61        MODEL_NAME, output_hidden_states=True, output_attentions=False62    ).to(DEVICE).eval()63    64    MODELS_LOADED_SUCCESSFULLY = True65    print("Gradio App: All models and data loaded successfully!")66 67except Exception as e:68    MODELS_LOADED_SUCCESSFULLY = False69    MODEL_LOADING_ERROR_MESSAGE = f"Critical error during model loading: {str(e)}\nPlease ensure the '{SAVE_DIR}' folder and its contents are correct."70    print(MODEL_LOADING_ERROR_MESSAGE)71 72# Helper function: 3D PCA Visualization using Plotly73def plot_token_pca_3d_plotly(token_embeddings_3d, tokens, scores, title="Token Embeddings 3D PCA (Colored by Importance)"):74    num_annotations = min(len(tokens), 20)75    scores_array = np.array(scores).flatten()76    text_annotations = [''] * len(tokens)77    if len(scores_array) > 0 and len(tokens) > 0:78        indices_to_annotate = np.argsort(scores_array)[-num_annotations:]79        for i in indices_to_annotate:80            if i < len(tokens):81                 text_annotations[i] = tokens[i]82    83    fig = go.Figure(data=[go.Scatter3d(84        x=token_embeddings_3d[:, 0],85        y=token_embeddings_3d[:, 1],86        z=token_embeddings_3d[:, 2],87        mode='markers+text',88        text=text_annotations,89        textfont=dict(size=9, color='#333333'),90        textposition='top center',91        marker=dict(92            size=6,93            color=scores_array,94            colorscale='RdBu', 95            reversescale=True,96            opacity=0.8,97            colorbar=dict(title='Importance', tickfont=dict(size=9), len=0.75, yanchor='middle')98        ),99        hoverinfo='text',100        hovertext=[f"Token: {t}<br>Score: {s:.3f}" for t, s in zip(tokens, scores_array)]101    )])102    103    fig.update_layout(104        title=dict(text=title, x=0.5, font=dict(size=16)),105        scene=dict(106            xaxis=dict(title=dict(text='PCA Comp 1', font=dict(size=10)), tickfont=dict(size=9), backgroundcolor="rgba(230, 230, 230, 0.8)"),107            yaxis=dict(title=dict(text='PCA Comp 2', font=dict(size=10)), tickfont=dict(size=9), backgroundcolor="rgba(230, 230, 230, 0.8)"),108            zaxis=dict(title=dict(text='PCA Comp 3', font=dict(size=10)), tickfont=dict(size=9), backgroundcolor="rgba(230, 230, 230, 0.8)"),109            bgcolor="rgba(255, 255, 255, 0.95)",110            camera_eye=dict(x=1.5, y=1.5, z=0.5)111        ),112        margin=dict(l=5, r=5, b=5, t=45),113        paper_bgcolor='rgba(0,0,0,0)'114    )115    return fig116 117# Helper function: Create an empty Plotly figure for placeholders118def create_empty_plotly_figure(message="N/A"):119    fig = go.Figure()120    fig.add_annotation(text=message, xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False, font=dict(size=12, color="grey"))121    fig.update_layout(122        xaxis={'visible': False}, 123        yaxis={'visible': False}, 124        height=300,125        paper_bgcolor='rgba(0,0,0,0)',126        plot_bgcolor='rgba(0,0,0,0)'127        )128    return fig129 130# --- Core Analysis Function (returns 6 items for Gradio UI) ---131def analyze_sentence_for_gradio(sentence_text, top_k_value):132    if not MODELS_LOADED_SUCCESSFULLY:133        # HTML output removed, adjust error return134        empty_df = pd.DataFrame(columns=['token', 'score'])135        empty_fig = create_empty_plotly_figure("Model Loading Failed")136        error_label_output = {"Status": "Error", "Message": "Model Loading Failed. Check logs."}137        return [], "Model Loading Failed", error_label_output, [], empty_df, empty_fig # 6 items138 139    try:140        tokenizer, model = TOKENIZER_GLOBAL, MODEL_GLOBAL141        W, mu, w_p, b_p = W_GLOBAL, MU_GLOBAL, W_P_GLOBAL, B_P_GLOBAL142        143        enc = tokenizer(sentence_text, return_tensors="pt", truncation=True, max_length=510, padding=True)144        input_ids, attn_mask  = enc["input_ids"].to(DEVICE), enc["attention_mask"].to(DEVICE)145 146        if input_ids.shape[1] == 0:147            empty_df = pd.DataFrame(columns=['token', 'score'])148            empty_fig = create_empty_plotly_figure("Invalid Input")149            error_label_output = {"Status": "Error", "Message": "Invalid input, no valid tokens."}150            return [], "Input Error", error_label_output, [], empty_df, empty_fig # 6 items151 152        input_embeds_detached = model.embeddings.word_embeddings(input_ids).clone().detach()153        input_embeds_for_grad = input_embeds_detached.clone().requires_grad_(True)154        155        outputs = model(inputs_embeds=input_embeds_for_grad, attention_mask=attn_mask, 156                        output_hidden_states=True, output_attentions=False)157        cls_vec = outputs.hidden_states[LAYER_ID][:, 0, :]158        159        z_projected = (cls_vec - mu) @ W160        logit_output = z_projected @ w_p.T + b_p161        probs = torch.softmax(logit_output, dim=1)162        pred_idx, pred_prob_val = torch.argmax(probs, dim=1).item(), probs[0, torch.argmax(probs, dim=1).item()].item()163 164        if input_embeds_for_grad.grad is not None: input_embeds_for_grad.grad.zero_()165        logit_output[0, pred_idx].backward()166        if input_embeds_for_grad.grad is None:167            empty_df = pd.DataFrame(columns=['token', 'score'])168            empty_fig = create_empty_plotly_figure("Gradient Error")169            error_label_output = {"Status": "Error", "Message": "Gradient calculation failed."}170            return [],"Analysis Error", error_label_output, [], empty_df, empty_fig # 6 items171        172        grads = input_embeds_for_grad.grad.clone().detach()173        scores = (grads * input_embeds_detached).norm(dim=2).squeeze(0)174        scores_np = scores.cpu().numpy()175        valid_scores_for_norm = scores_np[np.isfinite(scores_np)]176        scores_np = scores_np / (valid_scores_for_norm.max() + 1e-9) if len(valid_scores_for_norm) > 0 and valid_scores_for_norm.max() > 0 else np.zeros_like(scores_np)177 178        tokens_raw = tokenizer.convert_ids_to_tokens(input_ids[0], skip_special_tokens=False)179        actual_tokens = [tok for i, tok in enumerate(tokens_raw) if input_ids[0,i] != tokenizer.pad_token_id]180        actual_scores_np = scores_np[:len(actual_tokens)]181        actual_input_embeds = input_embeds_detached[0, :len(actual_tokens), :].cpu().numpy()182 183        # HTML generation logic removed184        highlighted_text_data = []185        cls_token_id, sep_token_id = tokenizer.cls_token_id, tokenizer.sep_token_id186 187        for i, tok_str in enumerate(actual_tokens):188            clean_tok_str = tok_str.replace("##", "") if "##" not in tok_str else tok_str[2:]189            current_score = actual_scores_np[i]190            current_score_clipped = max(0, min(1, current_score))191            current_token_id = input_ids[0, i].item()192 193            if current_token_id == cls_token_id or current_token_id == sep_token_id:194                highlighted_text_data.append((clean_tok_str + " ", None))195            else:196                highlighted_text_data.append((clean_tok_str + " ", round(current_score_clipped, 3)))197 198        top_tokens_for_df, top_tokens_for_barplot_list = [], []199        valid_indices = [idx for idx, token_id in enumerate(input_ids[0,:len(actual_tokens)].tolist())200                         if token_id not in [cls_token_id, sep_token_id]] 201        sorted_valid_indices = sorted(valid_indices, key=lambda idx: -actual_scores_np[idx])202        for token_idx in sorted_valid_indices[:top_k_value]:203            token_str = actual_tokens[token_idx]204            score_val_str = f"{actual_scores_np[token_idx]:.3f}"205            top_tokens_for_df.append([token_str, score_val_str])206            top_tokens_for_barplot_list.append({"token": token_str, "score": actual_scores_np[token_idx]})207        208        barplot_df = pd.DataFrame(top_tokens_for_barplot_list) if top_tokens_for_barplot_list else pd.DataFrame(columns=['token', 'score'])209            210        predicted_class_label_str = CLASS_LABEL_MAP.get(pred_idx, f"Unknown Index ({pred_idx})")211        212        prediction_summary_text = f"Predicted Class: {predicted_class_label_str}\nProbability: {pred_prob_val:.3f}"213        prediction_details_for_label = {predicted_class_label_str: float(f"{pred_prob_val:.3f}")}214 215        pca_fig = create_empty_plotly_figure("PCA Plot N/A\n(Not enough non-special tokens for 3D)")216        non_special_token_indices = [idx for idx, token_id in enumerate(input_ids[0,:len(actual_tokens)].tolist())217                                     if token_id not in [cls_token_id, sep_token_id]]218        219        if len(non_special_token_indices) >= 3 : 220            pca_tokens = [actual_tokens[i] for i in non_special_token_indices]221            if len(pca_tokens) > 0:222                pca_embeddings = actual_input_embeds[non_special_token_indices, :]223                pca_scores_for_plot = actual_scores_np[non_special_token_indices]224            225                pca = PCA(n_components=3, random_state=SEED)226                token_embeddings_3d = pca.fit_transform(pca_embeddings)227                pca_fig = plot_token_pca_3d_plotly(token_embeddings_3d, pca_tokens, pca_scores_for_plot)228        229        return (highlighted_text_data, # HTML output removed230                prediction_summary_text, prediction_details_for_label, 231                top_tokens_for_df, barplot_df, 232                pca_fig) # 6 items233 234    except Exception as e:235        import traceback236        tb_str = traceback.format_exc()237        # HTML output removed238        print(f"analyze_sentence_for_gradio error: {e}\n{tb_str}")239        empty_df = pd.DataFrame(columns=['token', 'score'])240        empty_fig = create_empty_plotly_figure("Analysis Error")241        error_label_output = {"Status": "Error", "Message": f"Analysis failed: {str(e)}"}242        return [], "Analysis Failed", error_label_output, [], empty_df, empty_fig # 6 items243 244# --- Gradio UI Definition (HTML Highlight Tab removed) ---245theme = gr.themes.Monochrome(246    primary_hue=gr.themes.colors.blue, 247    secondary_hue=gr.themes.colors.sky, 248    neutral_hue=gr.themes.colors.slate249).set(250    body_background_fill="#f0f2f6",251    block_shadow="*shadow_drop_lg",252    button_primary_background_fill="*primary_500",253    button_primary_text_color="white",254)255 256with gr.Blocks(title="AI Sentence Analyzer XAI 🚀", theme=theme, css=".gradio-container {max-width: 98% !important;}") as demo:257    gr.Markdown("# 🚀 AI Sentence Analyzer XAI: Exploring Model Explanations")258    gr.Markdown("Analyze English sentences to understand BERT model predictions through various XAI visualization techniques. "259                "Explore token importance and their distribution in the embedding space.")260 261    with gr.Row(equal_height=False):262        with gr.Column(scale=1, min_width=350):263            with gr.Group():264                gr.Markdown("### ✏️ Input Sentence & Settings")265                input_sentence = gr.Textbox(lines=5, label="English Sentence to Analyze", placeholder="Enter the English sentence you want to analyze here...")266                input_top_k = gr.Slider(minimum=1, maximum=10, value=5, step=1, label="Number of Top-K Tokens")267                submit_button = gr.Button("Analyze Sentence 💫", variant="primary")268        269        with gr.Column(scale=2):270            with gr.Accordion("🎯 Prediction Outcome", open=True):271                output_prediction_summary = gr.Textbox(label="Prediction Summary", lines=2, interactive=False)272                output_prediction_details = gr.Label(label="Prediction Details & Confidence")273            with gr.Accordion("⭐ Top-K Important Tokens (Table)", open=True):274                output_top_tokens_df = gr.DataFrame(headers=["Token", "Score"], label="Most Important Tokens", 275                                                    row_count=(1,"dynamic"), col_count=(2,"fixed"), interactive=False, wrap=True)276    gr.Markdown("---") 277 278    gr.Markdown("## 📊 Detailed Visualizations")279    280    # HTML Highlight (Custom) section removed281 282    with gr.Group(): # HighlightedText283        gr.Markdown("### 🖍️ Highlighted Text (Gradio)")284        output_highlighted_text = gr.HighlightedText(285            label="Token Importance (Score: 0-1)",286            show_legend=True,287            combine_adjacent=False 288        )289    290    with gr.Row(): # BarPlot and PCA Plot Side-by-Side291        with gr.Column(scale=1, min_width=400):292            with gr.Group():293                gr.Markdown("### 📊 Top-K Bar Plot")294                output_top_tokens_barplot = gr.BarPlot(295                    label="Top-K Token Importance Scores", 296                    x="token", 297                    y="score", 298                    tooltip=['token', 'score'],299                    min_width=300300                )301        with gr.Column(scale=1, min_width=400):302            with gr.Group():303                gr.Markdown("### 🌐 Token Embeddings 3D PCA (Interactive)")304                output_pca_plot = gr.Plot(label="3D PCA of Token Embeddings (Colored by Importance Score)")305            306    gr.Markdown("---")307    308    gr.Examples(309        examples=[310            ["This movie is an absolute masterpiece, captivating from start to finish.", 5],311            ["Despite some flaws, the film offers a compelling narrative.", 3],312            ["I was thoroughly disappointed with the lackluster performance and predictable plot.", 4]313        ],314        inputs=[input_sentence, input_top_k],315        outputs=[ # output_html_visualization removed316            output_highlighted_text,317            output_prediction_summary, output_prediction_details,318            output_top_tokens_df, output_top_tokens_barplot,319            output_pca_plot320        ],321        fn=analyze_sentence_for_gradio,322        cache_examples=False323    )324    gr.HTML("<p style='text-align: center; color: #4a5568;'>Explainable AI Demo powered by Gradio & Hugging Face Transformers</p>")325    326    submit_button.click(327        fn=analyze_sentence_for_gradio,328        inputs=[input_sentence, input_top_k],329        outputs=[ # output_html_visualization removed330            output_highlighted_text,331            output_prediction_summary, output_prediction_details, 332            output_top_tokens_df, output_top_tokens_barplot,333            output_pca_plot334        ],335        api_name="explain_sentence_xai"336    )337 338if __name__ == "__main__":339    if not MODELS_LOADED_SUCCESSFULLY:340        print("*"*80)341        print(f"WARNING: Models failed to load! {MODEL_LOADING_ERROR_MESSAGE}")342        print("The Gradio UI will be displayed, but analysis will fail.")343        print("*"*80)344    demo.launch()