import torch import numpy as np import gradio as gr from transformers import AutoTokenizer, AutoModelForSequenceClassification, pipeline from deep_translator import GoogleTranslator import lime from lime.lime_text import LimeTextExplainer import shap import matplotlib.pyplot as plt REPO_ID = "engsoftexperimental/modelos" MODEL_NAME = "modelo_final_distilbert2" tokenizer = AutoTokenizer.from_pretrained(REPO_ID, subfolder=MODEL_NAME) model = AutoModelForSequenceClassification.from_pretrained(REPO_ID, subfolder=MODEL_NAME) labels = ["hate_speech", "offensive", "neither", "spam"] model.config.id2label = {0: labels[0], 1: labels[1], 2: labels[2], 3: labels[3]} model.config.label2id = {labels[0]: 0, labels[1]: 1, labels[2]: 2, labels[3]: 3} lime_explainer = LimeTextExplainer(class_names=labels) pipe = pipeline("text-classification", model=model, tokenizer=tokenizer, top_k=None) shap_explainer = shap.Explainer(pipe) def predictor_for_lime(texts): inputs = tokenizer(texts, return_tensors="pt", truncation=True, padding=True, max_length=128).to(model.device) with torch.no_grad(): logits = model(**inputs).logits probs = torch.softmax(logits, dim=1).cpu().numpy() return probs def translate_text(text, source_lang_name, languages_dict): if not text: return "" if source_lang_name == "Detectar Automaticamente": src = 'auto' else: src = languages_dict.get(source_lang_name, 'auto') try: translator = GoogleTranslator(source=src, target='en') return translator.translate(text) except Exception as e: return f"Erro: {str(e)}" def generate_lime_plot(text_en, pred_idx): try: exp = lime_explainer.explain_instance( text_en, predictor_for_lime, num_features=6, num_samples=100, labels=[pred_idx] ) fig = exp.as_pyplot_figure(label=pred_idx) plt.tight_layout() return fig except Exception as e: print(f"Erro LIME: {e}") return None def generate_shap_plot(text_en, pred_idx): try: shap_values = shap_explainer([text_en]) vals = shap_values.values if len(vals.shape) == 3: class_values = vals[0, :, pred_idx] elif len(vals.shape) == 2: class_values = vals[0, :] else: raise ValueError(f"Shape inesperado do SHAP: {vals.shape}") raw_data = shap_values.data[0] if hasattr(raw_data, 'tolist'): tokens = raw_data.tolist() else: tokens = list(raw_data) tokens = [str(t).strip() for t in tokens] if len(tokens) != len(class_values): min_len = min(len(tokens), len(class_values)) tokens = tokens[:min_len] class_values = class_values[:min_len] explanation = shap.Explanation( values=class_values, feature_names=tokens ) plt.close('all') plt.figure() shap.plots.bar(explanation, max_display=10, show=False) plt.tight_layout() return plt.gcf() except Exception as e: print(f"Erro CRÍTICO no SHAP: {e}") try: print(f"Debug Shape: {shap_values.values.shape}") print(f"Debug Tokens: {shap_values.data[0]}") except: pass plt.close('all') fig, ax = plt.subplots(figsize=(6, 2)) ax.text(0.5, 0.5, "Não foi possível gerar a explicação SHAP\npara este texto específico.", ha='center', va='center', fontsize=10, color='red') ax.axis('off') return fig def process_pipeline(source_lang, text): try: LANGUAGES_DICT = GoogleTranslator().get_supported_languages(as_dict=True) except: LANGUAGES_DICT = {'portuguese': 'pt', 'english': 'en'} text_en = translate_text(text, source_lang, LANGUAGES_DICT) probs = predictor_for_lime([text_en])[0] pred_idx = int(np.argmax(probs)) pred_label = labels[pred_idx] lime_fig = generate_lime_plot(text_en, pred_idx) shap_fig = generate_shap_plot(text_en, pred_idx) return ( text_en, pred_label, f"{probs[0]:.4f}", f"{probs[1]:.4f}", f"{probs[3]:.4f}", f"{probs[2]:.4f}", lime_fig, shap_fig ) try: LANGUAGES_DICT = GoogleTranslator().get_supported_languages(as_dict=True) except: LANGUAGES_DICT = {'portuguese': 'pt', 'english': 'en'} language_options = ["Detectar Automaticamente"] + list(LANGUAGES_DICT.keys()) with gr.Blocks(title="Moderador AI + XAI") as demo: gr.Markdown("# Moderador de Conteúdo com IA Explicável (Modelo DistilBERT com Hate Speech Dataset + LIME/SHAP)") gr.Markdown("Ferramenta experimental utilizada na pesquisa para classificação de conteúdo em quatro classes: Hate Speech, Offensive, Spam e Neither.
O modelo foi treinado com dados no idioma inglês. Por isso, textos em outros idiomas serão traduzidos antes da classificação para garantir a precisão da IA.") gr.Markdown("Digite um texto. O sistema realiza a tradução automática, classifica e gera gráficos de explicabilidade.") with gr.Row(): with gr.Column(scale=1): dd_lang = gr.Dropdown(choices=language_options, value="Detectar Automaticamente", label="Idioma de Origem") txt_input = gr.Textbox(lines=5, label="Texto Original", placeholder="Digite aqui...") btn_submit = gr.Button("Analisar e Explicar", variant="primary") gr.Markdown("### Tradução Utilizada") txt_translated = gr.Textbox(lines=3, label="Texto em Inglês (Base da Análise)", interactive=False) with gr.Column(scale=1): lbl_class = gr.Label(label="Classificação Final") with gr.Group(): with gr.Row(): txt_p_hate = gr.Textbox(label="Prob. Hate Speech") txt_p_offensive = gr.Textbox(label="Prob. Offensive") with gr.Row(): txt_p_spam = gr.Textbox(label="Prob. Spam") txt_p_neither = gr.Textbox(label="Prob. Neither") gr.Markdown("---") gr.Markdown("## Explicabilidade (XAI)") with gr.Tabs(): with gr.TabItem("LIME Analysis"): gr.Markdown(""" Mostra o peso das palavras para a classe vencedora. O LIME (Local Interpretable Model-agnostic Explanations) tenta explicar a decisão simulando pequenas variações no texto. Ele adota uma abordagem mais próxima da leitura humana, separando os termos por espaços em branco. Por isso, ele geralmente analisa palavras completas. O gráfico mostra as 6 palavras mais impactantes na decisão. """) gr.Markdown(""" * VERDE: Palavras que aumentaram a probabilidade da classe escolhida. * VERMELHO: Palavras que diminuíram a confiança na escolha ou que apontaram para outra classe. """) plot_lime = gr.Plot(label="Gráfico LIME") with gr.TabItem("SHAP Analysis"): gr.Markdown(""" Mostra a magnitude do impacto (Positivo/Negativo) de cada token para a classe prevista. O SHAP (SHapley Additive exPlanations) baseia-se na Teoria dos Jogos para calcular a contribuição matemática exata de cada fragmento. Ele utiliza o vocabulário fixo do modelo (DistilBERT). Isso significa que ele identifica o texto exatamente como a rede neural: palavras desconhecidas podem ser divididas em pedaços (ex: "Jackman" vira "Jack" + "man") e sinais de pontuação (aspas, vírgulas) são avaliados isoladamente. """) gr.Markdown(""" * ROSA (Positivo): Tokens que empurraram a probabilidade da classe vencedora para cima. São as evidências fortes que confirmam a decisão final. * AZUL (Negativo): Tokens que empurraram a probabilidade para baixo. São elementos que fizeram o modelo duvidar ou considerar as outras categorias. """) plot_shap = gr.Plot(label="Gráfico SHAP") btn_submit.click( fn=process_pipeline, inputs=[dd_lang, txt_input], outputs=[txt_translated, lbl_class, txt_p_hate, txt_p_offensive, txt_p_spam, txt_p_neither, plot_lime, plot_shap] ) demo.launch()