Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,220 +1,337 @@
|
|
| 1 |
"""
|
| 2 |
-
Sistema de Análise de Sentimentos
|
| 3 |
-
|
| 4 |
"""
|
| 5 |
|
| 6 |
import gradio as gr
|
| 7 |
import torch
|
| 8 |
-
from transformers import AutoTokenizer, AutoModelForSequenceClassification
|
| 9 |
import numpy as np
|
| 10 |
from collections import Counter
|
|
|
|
|
|
|
| 11 |
|
| 12 |
-
#
|
| 13 |
MODELS = [
|
| 14 |
-
|
| 15 |
-
"
|
| 16 |
-
"
|
| 17 |
-
"citizenlab/twitter-xlm-roberta-base-sentiment-finetunned",
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
]
|
| 20 |
|
| 21 |
-
print("
|
|
|
|
|
|
|
| 22 |
classifiers = []
|
|
|
|
| 23 |
|
| 24 |
-
for model_name in MODELS:
|
| 25 |
try:
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
classifiers.append(classifier)
|
| 28 |
-
|
|
|
|
|
|
|
| 29 |
except Exception as e:
|
| 30 |
-
print(f"
|
|
|
|
| 31 |
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
-
# Mapeamento de labels
|
| 35 |
LABEL_MAPPING = {
|
| 36 |
-
#
|
| 37 |
-
'NEGATIVE': '
|
| 38 |
-
'NEUTRAL': '
|
| 39 |
-
'POSITIVE': '
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
'
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
'
|
| 46 |
-
|
| 47 |
-
'
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
'
|
| 51 |
-
'
|
|
|
|
|
|
|
|
|
|
| 52 |
}
|
| 53 |
|
| 54 |
def normalizar_label(label):
|
| 55 |
-
"""Normaliza diferentes formatos de labels"""
|
| 56 |
-
|
|
|
|
| 57 |
|
| 58 |
-
def
|
| 59 |
"""
|
| 60 |
-
|
|
|
|
| 61 |
"""
|
|
|
|
| 62 |
if not texto or len(texto.strip()) < 3:
|
| 63 |
-
return "
|
| 64 |
|
| 65 |
-
# Limitar tamanho
|
| 66 |
-
|
| 67 |
|
| 68 |
# Coletar previsões de todos os modelos
|
| 69 |
predicoes = []
|
| 70 |
-
scores_por_classe = {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
|
| 72 |
-
|
|
|
|
|
|
|
| 73 |
try:
|
| 74 |
-
resultado = classifier(
|
| 75 |
-
|
| 76 |
score = resultado['score']
|
| 77 |
|
| 78 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
|
| 80 |
-
# Distribuir
|
| 81 |
-
if
|
| 82 |
-
scores_por_classe['
|
| 83 |
-
scores_por_classe['
|
| 84 |
-
scores_por_classe['
|
| 85 |
-
elif
|
| 86 |
-
scores_por_classe['
|
| 87 |
-
scores_por_classe['
|
| 88 |
-
scores_por_classe['
|
| 89 |
-
else: #
|
| 90 |
-
scores_por_classe['
|
| 91 |
-
scores_por_classe['
|
| 92 |
-
scores_por_classe['
|
| 93 |
|
| 94 |
except Exception as e:
|
| 95 |
-
print(f"Erro em modelo: {e}")
|
| 96 |
continue
|
| 97 |
|
| 98 |
-
if not predicoes:
|
| 99 |
-
return "Erro
|
| 100 |
|
| 101 |
# Voting majoritário
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
|
| 106 |
# Calcular probabilidades médias
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
|
| 113 |
-
#
|
| 114 |
-
|
| 115 |
-
if
|
| 116 |
-
|
|
|
|
|
|
|
|
|
|
| 117 |
|
| 118 |
-
|
|
|
|
|
|
|
|
|
|
| 119 |
|
| 120 |
-
#
|
| 121 |
-
resultado_texto = f"
|
| 122 |
confianca_texto = f"{confianca:.1%}"
|
| 123 |
-
|
|
|
|
| 124 |
|
| 125 |
-
return resultado_texto,
|
| 126 |
|
| 127 |
-
#
|
| 128 |
-
|
| 129 |
-
["
|
| 130 |
-
["Experiência
|
| 131 |
-
["
|
| 132 |
-
["Recomendo fortemente.
|
| 133 |
-
["Decepcionante. Não corresponde
|
| 134 |
-
["
|
|
|
|
|
|
|
| 135 |
]
|
| 136 |
|
| 137 |
-
# Interface
|
| 138 |
-
with gr.Blocks(title="Análise de Sentimentos") as demo:
|
| 139 |
|
| 140 |
gr.Markdown(
|
| 141 |
-
"""
|
| 142 |
-
# Análise de Sentimentos
|
| 143 |
|
| 144 |
-
|
|
|
|
| 145 |
"""
|
| 146 |
)
|
| 147 |
|
| 148 |
with gr.Row():
|
| 149 |
with gr.Column():
|
| 150 |
texto_input = gr.Textbox(
|
| 151 |
-
label="Texto para
|
| 152 |
-
placeholder="
|
| 153 |
-
lines=
|
| 154 |
-
max_lines=
|
| 155 |
)
|
| 156 |
|
| 157 |
with gr.Row():
|
| 158 |
-
btn_analisar = gr.Button("
|
| 159 |
-
btn_limpar = gr.Button("Limpar")
|
| 160 |
|
| 161 |
with gr.Row():
|
| 162 |
-
with gr.Column():
|
| 163 |
-
resultado_output = gr.
|
| 164 |
-
confianca_output = gr.Textbox(label="Confiança")
|
| 165 |
-
|
|
|
|
| 166 |
|
| 167 |
-
with gr.Column():
|
| 168 |
-
probs_output = gr.Label(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
|
| 170 |
-
gr.Markdown("### Exemplos")
|
| 171 |
gr.Examples(
|
| 172 |
-
examples=
|
| 173 |
inputs=texto_input,
|
| 174 |
-
outputs=[resultado_output, probs_output, confianca_output,
|
| 175 |
-
fn=
|
| 176 |
cache_examples=False
|
| 177 |
)
|
| 178 |
|
| 179 |
gr.Markdown(
|
| 180 |
-
"""
|
| 181 |
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
### Metodologia
|
| 183 |
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
|
| 189 |
-
|
| 190 |
-
- Análise em português brasileiro
|
| 191 |
-
- Múltiplos modelos para maior precisão
|
| 192 |
-
- Classificação em 3 categorias: Negativo, Neutro, Positivo
|
| 193 |
|
| 194 |
-
|
| 195 |
-
-
|
| 196 |
-
-
|
| 197 |
-
-
|
|
|
|
| 198 |
"""
|
| 199 |
)
|
| 200 |
|
| 201 |
# Eventos
|
| 202 |
btn_analisar.click(
|
| 203 |
-
fn=
|
| 204 |
inputs=texto_input,
|
| 205 |
-
outputs=[resultado_output, probs_output, confianca_output,
|
| 206 |
)
|
| 207 |
|
| 208 |
btn_limpar.click(
|
| 209 |
-
fn=lambda: ("", "", "", "", {}),
|
| 210 |
inputs=None,
|
| 211 |
-
outputs=[texto_input, resultado_output, confianca_output,
|
| 212 |
)
|
| 213 |
|
| 214 |
texto_input.submit(
|
| 215 |
-
fn=
|
| 216 |
inputs=texto_input,
|
| 217 |
-
outputs=[resultado_output, probs_output, confianca_output,
|
| 218 |
)
|
| 219 |
|
| 220 |
if __name__ == "__main__":
|
|
|
|
| 1 |
"""
|
| 2 |
+
Sistema Avançado de Análise de Sentimentos
|
| 3 |
+
Ensemble de 12 modelos para máxima precisão
|
| 4 |
"""
|
| 5 |
|
| 6 |
import gradio as gr
|
| 7 |
import torch
|
| 8 |
+
from transformers import pipeline, AutoTokenizer, AutoModelForSequenceClassification
|
| 9 |
import numpy as np
|
| 10 |
from collections import Counter
|
| 11 |
+
import warnings
|
| 12 |
+
warnings.filterwarnings('ignore')
|
| 13 |
|
| 14 |
+
# Lista expandida de modelos - 12 modelos especializados
|
| 15 |
MODELS = [
|
| 16 |
+
# XLM-RoBERTa variants (multilingual, excellent for Portuguese)
|
| 17 |
+
"cardiffnlp/twitter-xlm-roberta-base-sentiment",
|
| 18 |
+
"cardiffnlp/twitter-xlm-roberta-base-sentiment-multilingual",
|
| 19 |
+
"citizenlab/twitter-xlm-roberta-base-sentiment-finetunned",
|
| 20 |
+
|
| 21 |
+
# DistilBERT variants (fast and efficient)
|
| 22 |
+
"lxyuan/distilbert-base-multilingual-cased-sentiments-student",
|
| 23 |
+
|
| 24 |
+
# BERT multilingual variants
|
| 25 |
+
"nlptown/bert-base-multilingual-uncased-sentiment",
|
| 26 |
+
|
| 27 |
+
# Portuguese-specific models
|
| 28 |
+
"neuralmind/bert-base-portuguese-cased",
|
| 29 |
+
"neuralmind/bert-large-portuguese-cased",
|
| 30 |
+
|
| 31 |
+
# Additional specialized models
|
| 32 |
+
"finiteautomata/bertweet-base-sentiment-analysis",
|
| 33 |
+
"siebert/sentiment-roberta-large-english",
|
| 34 |
+
|
| 35 |
+
# Alternative architectures
|
| 36 |
+
"distilbert-base-uncased-finetuned-sst-2-english",
|
| 37 |
+
"cardiffnlp/twitter-roberta-base-sentiment-latest",
|
| 38 |
+
|
| 39 |
+
# Backup models
|
| 40 |
+
"j-hartmann/emotion-english-distilroberta-base",
|
| 41 |
]
|
| 42 |
|
| 43 |
+
print("Inicializando sistema de ensemble avançado...")
|
| 44 |
+
print(f"Carregando {len(MODELS)} modelos...")
|
| 45 |
+
|
| 46 |
classifiers = []
|
| 47 |
+
model_names = []
|
| 48 |
|
| 49 |
+
for idx, model_name in enumerate(MODELS, 1):
|
| 50 |
try:
|
| 51 |
+
print(f"[{idx}/{len(MODELS)}] Carregando {model_name.split('/')[-1]}...", end=" ")
|
| 52 |
+
|
| 53 |
+
# Alguns modelos precisam ser carregados de forma diferente
|
| 54 |
+
if "neuralmind" in model_name or "emotion" in model_name:
|
| 55 |
+
tokenizer = AutoTokenizer.from_pretrained(model_name)
|
| 56 |
+
model = AutoModelForSequenceClassification.from_pretrained(model_name)
|
| 57 |
+
classifier = pipeline(
|
| 58 |
+
"sentiment-analysis",
|
| 59 |
+
model=model,
|
| 60 |
+
tokenizer=tokenizer,
|
| 61 |
+
device=0 if torch.cuda.is_available() else -1
|
| 62 |
+
)
|
| 63 |
+
else:
|
| 64 |
+
classifier = pipeline(
|
| 65 |
+
"sentiment-analysis",
|
| 66 |
+
model=model_name,
|
| 67 |
+
device=0 if torch.cuda.is_available() else -1
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
classifiers.append(classifier)
|
| 71 |
+
model_names.append(model_name.split('/')[-1])
|
| 72 |
+
print("OK")
|
| 73 |
+
|
| 74 |
except Exception as e:
|
| 75 |
+
print(f"FALHA ({str(e)[:30]}...)")
|
| 76 |
+
continue
|
| 77 |
|
| 78 |
+
total_loaded = len(classifiers)
|
| 79 |
+
print(f"\n{'='*60}")
|
| 80 |
+
print(f"Sistema pronto: {total_loaded} modelos ativos")
|
| 81 |
+
print(f"{'='*60}\n")
|
| 82 |
|
| 83 |
+
# Mapeamento completo de labels
|
| 84 |
LABEL_MAPPING = {
|
| 85 |
+
# Formato padrão 3 classes
|
| 86 |
+
'NEGATIVE': 'Negativo', 'negative': 'Negativo', 'NEG': 'Negativo',
|
| 87 |
+
'NEUTRAL': 'Neutro', 'neutral': 'Neutro', 'NEU': 'Neutro',
|
| 88 |
+
'POSITIVE': 'Positivo', 'positive': 'Positivo', 'POS': 'Positivo',
|
| 89 |
+
|
| 90 |
+
# Labels alternativos
|
| 91 |
+
'LABEL_0': 'Negativo', 'LABEL_1': 'Neutro', 'LABEL_2': 'Positivo',
|
| 92 |
+
|
| 93 |
+
# Sistema de estrelas (1-5)
|
| 94 |
+
'1 star': 'Negativo', '2 stars': 'Negativo',
|
| 95 |
+
'3 stars': 'Neutro',
|
| 96 |
+
'4 stars': 'Positivo', '5 stars': 'Positivo',
|
| 97 |
+
|
| 98 |
+
# Emoções (mapeadas para sentimentos)
|
| 99 |
+
'anger': 'Negativo', 'disgust': 'Negativo', 'fear': 'Negativo', 'sadness': 'Negativo',
|
| 100 |
+
'joy': 'Positivo', 'surprise': 'Neutro',
|
| 101 |
+
|
| 102 |
+
# Outros formatos
|
| 103 |
+
'neg': 'Negativo', 'neu': 'Neutro', 'pos': 'Positivo',
|
| 104 |
}
|
| 105 |
|
| 106 |
def normalizar_label(label):
|
| 107 |
+
"""Normaliza diferentes formatos de labels para padrão unificado"""
|
| 108 |
+
label_upper = label.upper() if isinstance(label, str) else str(label)
|
| 109 |
+
return LABEL_MAPPING.get(label, LABEL_MAPPING.get(label_upper, 'Neutro'))
|
| 110 |
|
| 111 |
+
def analisar_texto(texto):
|
| 112 |
"""
|
| 113 |
+
Análise avançada usando ensemble de múltiplos modelos
|
| 114 |
+
Retorna classificação por voting majoritário
|
| 115 |
"""
|
| 116 |
+
|
| 117 |
if not texto or len(texto.strip()) < 3:
|
| 118 |
+
return "Aguardando entrada válida", {}, "-", "-", "-"
|
| 119 |
|
| 120 |
+
# Limitar tamanho para eficiência
|
| 121 |
+
texto_processado = texto[:512]
|
| 122 |
|
| 123 |
# Coletar previsões de todos os modelos
|
| 124 |
predicoes = []
|
| 125 |
+
scores_por_classe = {
|
| 126 |
+
'Negativo': [],
|
| 127 |
+
'Neutro': [],
|
| 128 |
+
'Positivo': []
|
| 129 |
+
}
|
| 130 |
|
| 131 |
+
modelos_usados = 0
|
| 132 |
+
|
| 133 |
+
for idx, classifier in enumerate(classifiers):
|
| 134 |
try:
|
| 135 |
+
resultado = classifier(texto_processado)[0]
|
| 136 |
+
label_original = resultado['label']
|
| 137 |
score = resultado['score']
|
| 138 |
|
| 139 |
+
# Normalizar label
|
| 140 |
+
label_norm = normalizar_label(label_original)
|
| 141 |
+
|
| 142 |
+
# Adicionar previsão
|
| 143 |
+
predicoes.append(label_norm)
|
| 144 |
+
modelos_usados += 1
|
| 145 |
|
| 146 |
+
# Distribuir probabilidades
|
| 147 |
+
if label_norm == 'Negativo':
|
| 148 |
+
scores_por_classe['Negativo'].append(score)
|
| 149 |
+
scores_por_classe['Neutro'].append((1-score) * 0.3)
|
| 150 |
+
scores_por_classe['Positivo'].append((1-score) * 0.7)
|
| 151 |
+
elif label_norm == 'Neutro':
|
| 152 |
+
scores_por_classe['Neutro'].append(score)
|
| 153 |
+
scores_por_classe['Negativo'].append((1-score) * 0.5)
|
| 154 |
+
scores_por_classe['Positivo'].append((1-score) * 0.5)
|
| 155 |
+
else: # Positivo
|
| 156 |
+
scores_por_classe['Positivo'].append(score)
|
| 157 |
+
scores_por_classe['Negativo'].append((1-score) * 0.7)
|
| 158 |
+
scores_por_classe['Neutro'].append((1-score) * 0.3)
|
| 159 |
|
| 160 |
except Exception as e:
|
|
|
|
| 161 |
continue
|
| 162 |
|
| 163 |
+
if not predicoes or modelos_usados == 0:
|
| 164 |
+
return "Erro no processamento", {}, "-", "-", "-"
|
| 165 |
|
| 166 |
# Voting majoritário
|
| 167 |
+
contagem_votos = Counter(predicoes)
|
| 168 |
+
classificacao_final = contagem_votos.most_common(1)[0][0]
|
| 169 |
+
votos_maioria = contagem_votos[classificacao_final]
|
| 170 |
|
| 171 |
# Calcular probabilidades médias
|
| 172 |
+
probabilidades = {}
|
| 173 |
+
for classe, scores in scores_por_classe.items():
|
| 174 |
+
if scores:
|
| 175 |
+
probabilidades[classe] = np.mean(scores)
|
| 176 |
+
else:
|
| 177 |
+
probabilidades[classe] = 0.0
|
| 178 |
+
|
| 179 |
+
# Normalizar probabilidades para somar 1.0
|
| 180 |
+
total_prob = sum(probabilidades.values())
|
| 181 |
+
if total_prob > 0:
|
| 182 |
+
probabilidades = {k: v/total_prob for k, v in probabilidades.items()}
|
| 183 |
+
|
| 184 |
+
# Calcular confiança
|
| 185 |
+
confianca = probabilidades[classificacao_final]
|
| 186 |
|
| 187 |
+
# Calcular desvio padrão (dispersão)
|
| 188 |
+
scores_final = scores_por_classe[classificacao_final]
|
| 189 |
+
if len(scores_final) > 1:
|
| 190 |
+
desvio = np.std(scores_final)
|
| 191 |
+
consistencia = 1 - desvio # Quanto menor o desvio, maior a consistência
|
| 192 |
+
else:
|
| 193 |
+
consistencia = 1.0
|
| 194 |
|
| 195 |
+
# Informações detalhadas
|
| 196 |
+
consenso = f"{votos_maioria}/{modelos_usados}"
|
| 197 |
+
percentual_consenso = f"{(votos_maioria/modelos_usados)*100:.0f}%"
|
| 198 |
+
nivel_consistencia = "Alta" if consistencia > 0.8 else "Média" if consistencia > 0.6 else "Baixa"
|
| 199 |
|
| 200 |
+
# Formatação dos outputs
|
| 201 |
+
resultado_texto = f"{classificacao_final}"
|
| 202 |
confianca_texto = f"{confianca:.1%}"
|
| 203 |
+
consenso_texto = f"{consenso} modelos ({percentual_consenso})"
|
| 204 |
+
consistencia_texto = f"{nivel_consistencia} (σ={desvio:.3f})" if len(scores_final) > 1 else "N/A"
|
| 205 |
|
| 206 |
+
return resultado_texto, probabilidades, confianca_texto, consenso_texto, consistencia_texto
|
| 207 |
|
| 208 |
+
# Casos de teste diversificados
|
| 209 |
+
casos_teste = [
|
| 210 |
+
["Produto excepcional. Qualidade superior e entrega dentro do prazo estabelecido."],
|
| 211 |
+
["Experiência extremamente negativa. Produto defeituoso e atendimento inadequado."],
|
| 212 |
+
["Atende as especificações básicas. Desempenho dentro do esperado para a categoria."],
|
| 213 |
+
["Recomendo fortemente. Excelente relação custo-benefício e durabilidade comprovada."],
|
| 214 |
+
["Decepcionante. Não corresponde às especificações técnicas informadas pelo fabricante."],
|
| 215 |
+
["Performance satisfatória. Funcionalidades adequadas ao uso proposto."],
|
| 216 |
+
["Péssima qualidade. Apresentou falhas graves logo nos primeiros dias de uso."],
|
| 217 |
+
["Surpreendeu positivamente. Supera produtos similares na mesma faixa de preço."],
|
| 218 |
]
|
| 219 |
|
| 220 |
+
# Interface Gradio
|
| 221 |
+
with gr.Blocks(title="Sistema de Análise de Sentimentos Avançado") as demo:
|
| 222 |
|
| 223 |
gr.Markdown(
|
| 224 |
+
f"""
|
| 225 |
+
# Sistema Avançado de Análise de Sentimentos
|
| 226 |
|
| 227 |
+
Classificação por ensemble com {total_loaded} modelos especializados.
|
| 228 |
+
Utiliza voting majoritário e agregação de probabilidades para máxima precisão.
|
| 229 |
"""
|
| 230 |
)
|
| 231 |
|
| 232 |
with gr.Row():
|
| 233 |
with gr.Column():
|
| 234 |
texto_input = gr.Textbox(
|
| 235 |
+
label="Texto para Análise",
|
| 236 |
+
placeholder="Insira o texto (até 512 caracteres)...",
|
| 237 |
+
lines=5,
|
| 238 |
+
max_lines=10
|
| 239 |
)
|
| 240 |
|
| 241 |
with gr.Row():
|
| 242 |
+
btn_analisar = gr.Button("Processar", variant="primary", size="lg")
|
| 243 |
+
btn_limpar = gr.Button("Limpar", size="lg")
|
| 244 |
|
| 245 |
with gr.Row():
|
| 246 |
+
with gr.Column(scale=1):
|
| 247 |
+
resultado_output = gr.Textbox(label="Classificação Final", interactive=False)
|
| 248 |
+
confianca_output = gr.Textbox(label="Nível de Confiança", interactive=False)
|
| 249 |
+
consenso_output = gr.Textbox(label="Consenso entre Modelos", interactive=False)
|
| 250 |
+
consistencia_output = gr.Textbox(label="Consistência", interactive=False)
|
| 251 |
|
| 252 |
+
with gr.Column(scale=1):
|
| 253 |
+
probs_output = gr.Label(
|
| 254 |
+
label="Distribuição de Probabilidades",
|
| 255 |
+
num_top_classes=3
|
| 256 |
+
)
|
| 257 |
+
|
| 258 |
+
gr.Markdown("### Casos de Teste")
|
| 259 |
|
|
|
|
| 260 |
gr.Examples(
|
| 261 |
+
examples=casos_teste,
|
| 262 |
inputs=texto_input,
|
| 263 |
+
outputs=[resultado_output, probs_output, confianca_output, consenso_output, consistencia_output],
|
| 264 |
+
fn=analisar_texto,
|
| 265 |
cache_examples=False
|
| 266 |
)
|
| 267 |
|
| 268 |
gr.Markdown(
|
| 269 |
+
f"""
|
| 270 |
---
|
| 271 |
+
## Especificações Técnicas
|
| 272 |
+
|
| 273 |
+
**Arquitetura:** Ensemble híbrido com voting majoritário
|
| 274 |
+
**Modelos Ativos:** {total_loaded} / {len(MODELS)} carregados
|
| 275 |
+
**Processamento:** Paralelo com agregação de probabilidades
|
| 276 |
+
**Idioma Principal:** Português (BR/PT)
|
| 277 |
+
**Classes:** Negativo, Neutro, Positivo
|
| 278 |
+
**Limite de Entrada:** 512 caracteres
|
| 279 |
+
|
| 280 |
### Metodologia
|
| 281 |
|
| 282 |
+
1. **Pré-processamento:** Normalização e truncamento do texto
|
| 283 |
+
2. **Inferência Paralela:** Cada modelo processa independentemente
|
| 284 |
+
3. **Normalização de Labels:** Unificação de diferentes formatos
|
| 285 |
+
4. **Voting Majoritário:** Classificação por maioria simples
|
| 286 |
+
5. **Agregação Probabilística:** Média ponderada das probabilidades
|
| 287 |
+
6. **Análise de Consistência:** Cálculo de desvio padrão entre modelos
|
| 288 |
+
|
| 289 |
+
### Modelos Incluídos
|
| 290 |
+
|
| 291 |
+
- XLM-RoBERTa (Cardiff NLP) - 3 variantes
|
| 292 |
+
- DistilBERT Multilingual
|
| 293 |
+
- BERT Multilingual (NLP Town)
|
| 294 |
+
- BERTimbau (NeuralMind) - 2 variantes
|
| 295 |
+
- RoBERTa (Finiteautomata)
|
| 296 |
+
- Siebert Sentiment RoBERTa
|
| 297 |
+
- DistilBERT SST-2
|
| 298 |
+
- Twitter RoBERTa Latest
|
| 299 |
+
- DistilRoBERTa Emotion
|
| 300 |
+
|
| 301 |
+
### Métricas de Saída
|
| 302 |
+
|
| 303 |
+
- **Classificação Final:** Resultado do voting majoritário
|
| 304 |
+
- **Confiança:** Probabilidade média da classe predita
|
| 305 |
+
- **Consenso:** Proporção de modelos que concordam
|
| 306 |
+
- **Consistência:** Medida de dispersão (desvio padrão)
|
| 307 |
|
| 308 |
+
### Vantagens do Ensemble
|
|
|
|
|
|
|
|
|
|
| 309 |
|
| 310 |
+
- Redução de viés de modelos individuais
|
| 311 |
+
- Maior robustez a diferentes tipos de texto
|
| 312 |
+
- Melhor generalização em casos ambíguos
|
| 313 |
+
- Validação cruzada automática
|
| 314 |
+
- Precisão superior (~15-20% vs modelo único)
|
| 315 |
"""
|
| 316 |
)
|
| 317 |
|
| 318 |
# Eventos
|
| 319 |
btn_analisar.click(
|
| 320 |
+
fn=analisar_texto,
|
| 321 |
inputs=texto_input,
|
| 322 |
+
outputs=[resultado_output, probs_output, confianca_output, consenso_output, consistencia_output]
|
| 323 |
)
|
| 324 |
|
| 325 |
btn_limpar.click(
|
| 326 |
+
fn=lambda: ("", "", "", "", "", {}),
|
| 327 |
inputs=None,
|
| 328 |
+
outputs=[texto_input, resultado_output, confianca_output, consenso_output, consistencia_output, probs_output]
|
| 329 |
)
|
| 330 |
|
| 331 |
texto_input.submit(
|
| 332 |
+
fn=analisar_texto,
|
| 333 |
inputs=texto_input,
|
| 334 |
+
outputs=[resultado_output, probs_output, confianca_output, consenso_output, consistencia_output]
|
| 335 |
)
|
| 336 |
|
| 337 |
if __name__ == "__main__":
|