""" Sistema Avançado de Análise de Sentimentos Versão melhorada com mais modelos e melhor cálculo de confiança """ import gradio as gr import torch from transformers import pipeline, AutoTokenizer, AutoModelForSequenceClassification import numpy as np from collections import Counter import warnings warnings.filterwarnings('ignore') # Modelos de moderação - MAIS MODELOS MODERATION_MODELS = [ "citizenlab/distilbert-base-multilingual-cased-toxicity", "unitary/toxic-bert", "martin-ha/toxic-comment-model", "facebook/roberta-hate-speech-dynabench-r4-target", "Hate-speech-CNERG/dehatebert-mono-portuguese", ] print("Carregando sistema de moderação...") moderators = [] for model_name in MODERATION_MODELS: try: print(f"Moderador: {model_name.split('/')[-1]}...", end=" ") if "dehatebert" in model_name or "roberta-hate" in model_name: tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForSequenceClassification.from_pretrained(model_name) moderator = pipeline( "text-classification", model=model, tokenizer=tokenizer, device=0 if torch.cuda.is_available() else -1 ) else: moderator = pipeline( "text-classification", model=model_name, device=0 if torch.cuda.is_available() else -1 ) moderators.append(moderator) print("OK") except Exception as e: print(f"FALHA") continue print(f"Moderadores ativos: {len(moderators)}") # MAIS MODELOS DE SENTIMENTO - Expandido de 12 para 18 SENTIMENT_MODELS = [ # Português específico (prioritários) "neuralmind/bert-base-portuguese-cased", "neuralmind/bert-large-portuguese-cased", "rufimelo/bert-large-portuguese-cased-finetuned-with-yelp-reviews", # XLM-RoBERTa (excelentes para multilíngue) "cardiffnlp/twitter-xlm-roberta-base-sentiment", "cardiffnlp/twitter-xlm-roberta-base-sentiment-multilingual", "citizenlab/twitter-xlm-roberta-base-sentiment-finetunned", # BERT Multilíngue "lxyuan/distilbert-base-multilingual-cased-sentiments-student", "nlptown/bert-base-multilingual-uncased-sentiment", # RoBERTa variants "finiteautomata/bertweet-base-sentiment-analysis", "siebert/sentiment-roberta-large-english", "cardiffnlp/twitter-roberta-base-sentiment-latest", "cardiffnlp/twitter-roberta-base-sentiment", # DistilBERT variants "distilbert-base-uncased-finetuned-sst-2-english", "bhadresh-savani/distilbert-base-uncased-emotion", # Emotion models (mapeados para sentimento) "j-hartmann/emotion-english-distilroberta-base", "arpanghoshal/EmoRoBERTa", # Modelos adicionais especializados "michellejieli/emotion_text_classifier", "mrm8488/distilroberta-finetuned-financial-news-sentiment-analysis", ] print("\nCarregando modelos de análise de sentimentos...") classifiers = [] for idx, model_name in enumerate(SENTIMENT_MODELS, 1): try: print(f"[{idx}/{len(SENTIMENT_MODELS)}] {model_name.split('/')[-1]}...", end=" ") if "neuralmind" in model_name or "emotion" in model_name or "Emo" in model_name: tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForSequenceClassification.from_pretrained(model_name) classifier = pipeline( "sentiment-analysis" if "sentiment" in model_name else "text-classification", model=model, tokenizer=tokenizer, device=0 if torch.cuda.is_available() else -1 ) else: classifier = pipeline( "sentiment-analysis", model=model_name, device=0 if torch.cuda.is_available() else -1 ) classifiers.append(classifier) print("OK") except Exception as e: print("FALHA") continue print(f"\n{'='*60}") print(f"Sistema completo:") print(f"- Analisadores: {len(classifiers)}") print(f"- Moderadores: {len(moderators)}") print(f"{'='*60}\n") # Limiar AUMENTADO para evitar falsos positivos TOXICITY_THRESHOLD = 0.80 # Aumentado para reduzir falsos positivos # Mapeamento expandido de labels LABEL_MAPPING = { # Sentimento padrão 'NEGATIVE': 'Negativo', 'negative': 'Negativo', 'NEG': 'Negativo', 'NEUTRAL': 'Neutro', 'neutral': 'Neutro', 'NEU': 'Neutro', 'POSITIVE': 'Positivo', 'positive': 'Positivo', 'POS': 'Positivo', 'LABEL_0': 'Negativo', 'LABEL_1': 'Neutro', 'LABEL_2': 'Positivo', # Estrelas '1 star': 'Negativo', '2 stars': 'Negativo', '3 stars': 'Neutro', '4 stars': 'Positivo', '5 stars': 'Positivo', # Emoções -> Sentimentos 'anger': 'Negativo', 'disgust': 'Negativo', 'fear': 'Negativo', 'sadness': 'Negativo', 'surprise': 'Neutro', 'joy': 'Positivo', 'love': 'Positivo', 'admiration': 'Positivo', # Outros formatos 'neg': 'Negativo', 'neu': 'Neutro', 'pos': 'Positivo', } def verificar_linguagem(texto): """ Verifica linguagem imprópria com MAIS modelos e threshold MAIOR Com interpretação melhorada de labels """ if not moderators or len(texto.strip()) < 3: return False, 0.0 scores_toxicos = [] for moderator in moderators: try: resultado = moderator(texto[:512])[0] label = resultado['label'].lower() score = resultado['score'] # Interpretar labels com MAIS cuidado # Labels que indicam TOXICIDADE toxic_keywords = ['toxic', 'hate', 'offensive', 'hateful', 'obscene', 'threat', 'insult'] # Labels que indicam NORMALIDADE normal_keywords = ['not', 'normal', 'neutral', 'clean'] is_toxic_label = any(word in label for word in toxic_keywords) is_normal_label = any(word in label for word in normal_keywords) # Calcular toxicity score com lógica melhorada if is_toxic_label and not is_normal_label: # Label diz que é tóxico toxicity = score elif is_normal_label or 'not' in label: # Label diz que NÃO é tóxico toxicity = 1 - score else: # Label ambíguo, assumir score direto se alto toxicity = score if score > 0.5 else 1 - score scores_toxicos.append(toxicity) except: continue if not scores_toxicos: return False, 0.0 # Média dos scores toxicity_score = np.mean(scores_toxicos) # Threshold MAIOR para reduzir falsos positivos has_improper = toxicity_score > TOXICITY_THRESHOLD return has_improper, toxicity_score def normalizar_label(label): """Normaliza labels""" label_upper = label.upper() if isinstance(label, str) else str(label) return LABEL_MAPPING.get(label, LABEL_MAPPING.get(label_upper, 'Neutro')) def analisar_texto(texto): """ Análise com MELHOR cálculo de confiança """ if not texto or len(texto.strip()) < 3: return "Aguardando texto para análise", {}, "-", "-", "-" # ANÁLISE DE SENTIMENTO texto_processado = texto[:512] predicoes = [] scores_brutos = [] # Para melhor cálculo scores_por_classe = { 'Negativo': [], 'Neutro': [], 'Positivo': [] } modelos_usados = 0 for classifier in classifiers: try: resultado = classifier(texto_processado)[0] label_norm = normalizar_label(resultado['label']) score = resultado['score'] predicoes.append(label_norm) scores_brutos.append(score) modelos_usados += 1 # Distribuição mais conservadora if label_norm == 'Negativo': scores_por_classe['Negativo'].append(score) remaining = 1 - score scores_por_classe['Neutro'].append(remaining * 0.4) scores_por_classe['Positivo'].append(remaining * 0.6) elif label_norm == 'Neutro': scores_por_classe['Neutro'].append(score) remaining = 1 - score scores_por_classe['Negativo'].append(remaining * 0.5) scores_por_classe['Positivo'].append(remaining * 0.5) else: # Positivo scores_por_classe['Positivo'].append(score) remaining = 1 - score scores_por_classe['Negativo'].append(remaining * 0.6) scores_por_classe['Neutro'].append(remaining * 0.4) except: continue if not predicoes or modelos_usados == 0: return "Erro no processamento", {}, "-", "-", "-" # Voting majoritário contagem = Counter(predicoes) classificacao = contagem.most_common(1)[0][0] votos = contagem[classificacao] # MELHOR cálculo de probabilidades probs = {} for classe in ['Negativo', 'Neutro', 'Positivo']: scores = scores_por_classe[classe] if scores: # Usar mediana ao invés de média para reduzir outliers probs[classe] = float(np.median(scores)) else: probs[classe] = 0.0 # Normalizar total = sum(probs.values()) if total > 0: probs = {k: v/total for k, v in probs.items()} # Confiança baseada em voting + score confianca_voting = votos / modelos_usados confianca_score = probs[classificacao] # Confiança final = média ponderada (60% voting, 40% score) confianca_final = (confianca_voting * 0.6) + (confianca_score * 0.4) # Consistência scores_final = scores_por_classe[classificacao] if len(scores_final) > 1: desvio = np.std(scores_final) nivel = "Alta" if desvio < 0.1 else "Média" if desvio < 0.2 else "Baixa" else: desvio = 0 nivel = "N/A" # VERIFICAR LINGUAGEM (com threshold mais alto) has_improper, improper_score = verificar_linguagem(texto) # LÓGICA INTELIGENTE: Se Positivo com boa confiança, provavelmente não é ofensivo if classificacao == 'Positivo' and confianca_final > 0.70: has_improper = False # Ignora alerta para textos claramente positivos # Se Neutro ou Negativo, ainda verifica normalmente # Formatar resultado if has_improper: resultado_texto = f"""**{classificacao}** ⚠️ **Alerta de Conteúdo** Detectada possível linguagem imprópria (confiança: {improper_score:.1%}). Recomendamos evitar: • Discurso de ódio • Termos discriminatórios • Linguagem ofensiva O sentimento foi analisado normalmente.""" else: resultado_texto = f"**{classificacao}**" confianca_texto = f"{confianca_final:.1%}" consenso_texto = f"{votos}/{modelos_usados} modelos ({(votos/modelos_usados)*100:.0f}%)" consistencia_texto = f"{nivel} (σ={desvio:.3f})" if desvio > 0 else "N/A" return resultado_texto, probs, confianca_texto, consenso_texto, consistencia_texto # Casos de teste variados casos_teste = [ ["Este produto superou todas as minhas expectativas. Qualidade excepcional!"], ["Experiência extremamente negativa. Produto defeituoso e atendimento péssimo."], ["Produto normal. Atende o básico sem grandes destaques ou problemas."], ["Recomendo! Excelente custo-benefício e entrega rápida."], ["Satisfatório. Funciona conforme descrito, nada além disso."], ["Produto horrível, péssima qualidade, muito ruim, não recomendo."], ["Maravilhoso! Adorei cada detalhe, perfeito em todos os aspectos!"], ["Decepcionante. Não corresponde à descrição e apresenta defeitos graves."], ] # Interface with gr.Blocks(title="Análise de Sentimentos Avançada") as demo: gr.Markdown( f""" # Sistema Avançado de Análise de Sentimentos Análise por ensemble de **{len(classifiers)} modelos** especializados. **Sistema de verificação:** {len(moderators)} moderadores detectam linguagem imprópria. """ ) with gr.Row(): with gr.Column(): texto_input = gr.Textbox( label="Texto para Análise", placeholder="Digite ou cole o texto aqui (até 512 caracteres)...", lines=5, max_lines=10 ) with gr.Row(): btn_analisar = gr.Button("Analisar", variant="primary", size="lg") btn_limpar = gr.Button("Limpar", size="lg") with gr.Row(): with gr.Column(scale=1): resultado_output = gr.Markdown(label="Classificação") confianca_output = gr.Textbox(label="Confiança", interactive=False) consenso_output = gr.Textbox(label="Consenso", interactive=False) consistencia_output = gr.Textbox(label="Consistência", interactive=False) with gr.Column(scale=1): probs_output = gr.Label( label="Distribuição de Probabilidades", num_top_classes=3 ) gr.Markdown("### Casos de Teste") gr.Examples( examples=casos_teste, inputs=texto_input, outputs=[resultado_output, probs_output, confianca_output, consenso_output, consistencia_output], fn=analisar_texto, cache_examples=False ) gr.Markdown( f""" --- ## Especificações do Sistema ### Análise de Sentimento **Modelos Ativos:** {len(classifiers)} / {len(SENTIMENT_MODELS)} **Arquitetura:** - BERTimbau (português específico) - XLM-RoBERTa (multilíngue) - BERT e DistilBERT - RoBERTa especializados - Modelos de emoção **Método:** - Voting majoritário - Agregação por mediana (reduz outliers) - Confiança combinada (voting + score) ### Verificação de Linguagem **Moderadores Ativos:** {len(moderators)} / {len(MODERATION_MODELS)} **Threshold:** {TOXICITY_THRESHOLD*100:.0f}% (mais alto para evitar falsos positivos) **Lógica Inteligente:** - Textos claramente positivos (>70% confiança) não geram alertas - Foco em detectar problemas reais **Modelos:** - DistilBERT Toxicity - Toxic-BERT (Unitary) - Toxic Comment Model - RoBERTa Hate Speech - DeHateBERT Portuguese ### Melhorias Implementadas ✅ **Mais modelos** ({len(classifiers)} analisadores, {len(moderators)} moderadores) ✅ **Melhor confiança** (combina voting + probabilidades) ✅ **Menos falsos positivos** (threshold aumentado de 70% → 75%) ✅ **Agregação robusta** (mediana ao invés de média) ✅ **Distribuição conservadora** (scores mais equilibrados) ### Fluxo de Processamento 1. **Análise paralela** por todos os modelos 2. **Voting majoritário** determina classificação 3. **Agregação por mediana** calcula probabilidades 4. **Confiança combinada** (60% voting + 40% score) 5. **Verificação de linguagem** com threshold elevado 6. **Resultado final** com métricas de qualidade """ ) btn_analisar.click( fn=analisar_texto, inputs=texto_input, outputs=[resultado_output, probs_output, confianca_output, consenso_output, consistencia_output] ) btn_limpar.click( fn=lambda: ("", "", "", "", "", {}), inputs=None, outputs=[texto_input, resultado_output, confianca_output, consenso_output, consistencia_output, probs_output] ) texto_input.submit( fn=analisar_texto, inputs=texto_input, outputs=[resultado_output, probs_output, confianca_output, consenso_output, consistencia_output] ) if __name__ == "__main__": demo.launch()