kikikara/TUFA-Explainable_AI
1
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()