Download app.py from engsoftexperimental/moderador-conteudo: direct link, hf CLI and curl.
- Browser
- Download file 8.66 kB
-
https://huggingface.co/spaces/engsoftexperimental/moderador-conteudo/resolve/main/app.py
- Command line
-
hf download hf://spaces/engsoftexperimental/moderador-conteudo/app.py
-
curl -L -o app.py https://huggingface.co/spaces/engsoftexperimental/moderador-conteudo/resolve/main/app.py
8.66 kB
| 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.<br>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(""" | |
| * <span style='color: green'>VERDE:</span> Palavras que aumentaram a probabilidade da classe escolhida. | |
| * <span style='color: red'>VERMELHO:</span> 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(""" | |
| * <span style='color: #ff0051'>ROSA (Positivo):</span> Tokens que empurraram a probabilidade da classe vencedora para cima. São as evidências fortes que confirmam a decisão final. | |
| * <span style='color: #008bfb'>AZUL (Negativo):</span> 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() |