sentiment-phrases / app_backup.py
tassid's picture
Create app_backup.py
8ed8ed6 verified
"""
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()