tassid commited on
Commit
724b0cd
·
verified ·
1 Parent(s): 60894bd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +148 -83
app.py CHANGED
@@ -1,6 +1,6 @@
1
  """
2
- Sistema de Análise de Sentimentos com Alerta de Conteúdo
3
- Analisa SEMPRE o sentimento, depois avisa se tiver linguagem imprópria
4
  """
5
 
6
  import gradio as gr
@@ -11,9 +11,12 @@ from collections import Counter
11
  import warnings
12
  warnings.filterwarnings('ignore')
13
 
14
- # Modelos de moderação (usados DEPOIS da análise)
15
  MODERATION_MODELS = [
16
  "citizenlab/distilbert-base-multilingual-cased-toxicity",
 
 
 
17
  "Hate-speech-CNERG/dehatebert-mono-portuguese",
18
  ]
19
 
@@ -22,9 +25,9 @@ moderators = []
22
 
23
  for model_name in MODERATION_MODELS:
24
  try:
25
- print(f"Carregando: {model_name.split('/')[-1]}...", end=" ")
26
 
27
- if "dehatebert" in model_name:
28
  tokenizer = AutoTokenizer.from_pretrained(model_name)
29
  model = AutoModelForSequenceClassification.from_pretrained(model_name)
30
  moderator = pipeline(
@@ -49,20 +52,39 @@ for model_name in MODERATION_MODELS:
49
 
50
  print(f"Moderadores ativos: {len(moderators)}")
51
 
52
- # Modelos de análise de sentimentos (12 modelos)
53
  SENTIMENT_MODELS = [
 
54
  "neuralmind/bert-base-portuguese-cased",
55
  "neuralmind/bert-large-portuguese-cased",
 
 
 
56
  "cardiffnlp/twitter-xlm-roberta-base-sentiment",
57
  "cardiffnlp/twitter-xlm-roberta-base-sentiment-multilingual",
58
  "citizenlab/twitter-xlm-roberta-base-sentiment-finetunned",
 
 
59
  "lxyuan/distilbert-base-multilingual-cased-sentiments-student",
60
  "nlptown/bert-base-multilingual-uncased-sentiment",
 
 
61
  "finiteautomata/bertweet-base-sentiment-analysis",
62
  "siebert/sentiment-roberta-large-english",
63
- "distilbert-base-uncased-finetuned-sst-2-english",
64
  "cardiffnlp/twitter-roberta-base-sentiment-latest",
 
 
 
 
 
 
 
65
  "j-hartmann/emotion-english-distilroberta-base",
 
 
 
 
 
66
  ]
67
 
68
  print("\nCarregando modelos de análise de sentimentos...")
@@ -72,11 +94,11 @@ for idx, model_name in enumerate(SENTIMENT_MODELS, 1):
72
  try:
73
  print(f"[{idx}/{len(SENTIMENT_MODELS)}] {model_name.split('/')[-1]}...", end=" ")
74
 
75
- if "neuralmind" in model_name or "emotion" in model_name:
76
  tokenizer = AutoTokenizer.from_pretrained(model_name)
77
  model = AutoModelForSequenceClassification.from_pretrained(model_name)
78
  classifier = pipeline(
79
- "sentiment-analysis",
80
  model=model,
81
  tokenizer=tokenizer,
82
  device=0 if torch.cuda.is_available() else -1
@@ -91,7 +113,7 @@ for idx, model_name in enumerate(SENTIMENT_MODELS, 1):
91
  classifiers.append(classifier)
92
  print("OK")
93
 
94
- except:
95
  print("FALHA")
96
  continue
97
 
@@ -101,25 +123,34 @@ print(f"- Analisadores: {len(classifiers)}")
101
  print(f"- Moderadores: {len(moderators)}")
102
  print(f"{'='*60}\n")
103
 
104
- # Limiar para alerta
105
- TOXICITY_THRESHOLD = 0.70
106
 
107
- # Mapeamento de labels
108
  LABEL_MAPPING = {
 
109
  'NEGATIVE': 'Negativo', 'negative': 'Negativo', 'NEG': 'Negativo',
110
  'NEUTRAL': 'Neutro', 'neutral': 'Neutro', 'NEU': 'Neutro',
111
  'POSITIVE': 'Positivo', 'positive': 'Positivo', 'POS': 'Positivo',
112
  'LABEL_0': 'Negativo', 'LABEL_1': 'Neutro', 'LABEL_2': 'Positivo',
113
- '1 star': 'Negativo', '2 stars': 'Negativo', '3 stars': 'Neutro',
 
 
 
114
  '4 stars': 'Positivo', '5 stars': 'Positivo',
 
 
115
  'anger': 'Negativo', 'disgust': 'Negativo', 'fear': 'Negativo',
116
- 'sadness': 'Negativo', 'joy': 'Positivo', 'surprise': 'Neutro',
 
 
 
 
117
  }
118
 
119
  def verificar_linguagem(texto):
120
  """
121
- Verifica se há linguagem imprópria (DEPOIS da análise)
122
- Retorna: (has_improper, score)
123
  """
124
  if not moderators or len(texto.strip()) < 3:
125
  return False, 0.0
@@ -132,12 +163,13 @@ def verificar_linguagem(texto):
132
  label = resultado['label'].lower()
133
  score = resultado['score']
134
 
135
- # Interpretar resultado
136
- is_toxic_label = any(word in label for word in ['toxic', 'hate', 'offensive'])
137
 
138
  if is_toxic_label:
139
  toxicity = score
140
  else:
 
141
  toxicity = 1 - score
142
 
143
  scores_toxicos.append(toxicity)
@@ -150,6 +182,8 @@ def verificar_linguagem(texto):
150
 
151
  # Média dos scores
152
  toxicity_score = np.mean(scores_toxicos)
 
 
153
  has_improper = toxicity_score > TOXICITY_THRESHOLD
154
 
155
  return has_improper, toxicity_score
@@ -161,18 +195,17 @@ def normalizar_label(label):
161
 
162
  def analisar_texto(texto):
163
  """
164
- NOVA LÓGICA:
165
- 1. SEMPRE analisa o sentimento primeiro
166
- 2. DEPOIS verifica se tem linguagem imprópria
167
- 3. Mostra sentimento + aviso (se necessário)
168
  """
169
 
170
  if not texto or len(texto.strip()) < 3:
171
  return "Aguardando texto para análise", {}, "-", "-", "-"
172
 
173
- # PASSO 1: ANÁLISE DE SENTIMENTO (SEMPRE!)
174
  texto_processado = texto[:512]
175
  predicoes = []
 
 
176
  scores_por_classe = {
177
  'Negativo': [],
178
  'Neutro': [],
@@ -188,20 +221,25 @@ def analisar_texto(texto):
188
  score = resultado['score']
189
 
190
  predicoes.append(label_norm)
 
191
  modelos_usados += 1
192
 
 
193
  if label_norm == 'Negativo':
194
  scores_por_classe['Negativo'].append(score)
195
- scores_por_classe['Neutro'].append((1-score) * 0.3)
196
- scores_por_classe['Positivo'].append((1-score) * 0.7)
 
197
  elif label_norm == 'Neutro':
198
  scores_por_classe['Neutro'].append(score)
199
- scores_por_classe['Negativo'].append((1-score) * 0.5)
200
- scores_por_classe['Positivo'].append((1-score) * 0.5)
201
- else:
 
202
  scores_por_classe['Positivo'].append(score)
203
- scores_por_classe['Negativo'].append((1-score) * 0.7)
204
- scores_por_classe['Neutro'].append((1-score) * 0.3)
 
205
 
206
  except:
207
  continue
@@ -209,18 +247,32 @@ def analisar_texto(texto):
209
  if not predicoes or modelos_usados == 0:
210
  return "Erro no processamento", {}, "-", "-", "-"
211
 
212
- # Voting
213
  contagem = Counter(predicoes)
214
  classificacao = contagem.most_common(1)[0][0]
215
  votos = contagem[classificacao]
216
 
217
- # Probabilidades
218
- probs = {k: np.mean(v) if v else 0.0 for k, v in scores_por_classe.items()}
 
 
 
 
 
 
 
 
 
219
  total = sum(probs.values())
220
  if total > 0:
221
  probs = {k: v/total for k, v in probs.items()}
222
 
223
- confianca = probs[classificacao]
 
 
 
 
 
224
 
225
  # Consistência
226
  scores_final = scores_por_classe[classificacao]
@@ -231,7 +283,7 @@ def analisar_texto(texto):
231
  desvio = 0
232
  nivel = "N/A"
233
 
234
- # PASSO 2: VERIFICAR LINGUAGEM IMPRÓPRIA (DEPOIS!)
235
  has_improper, improper_score = verificar_linguagem(texto)
236
 
237
  # Formatar resultado
@@ -240,46 +292,45 @@ def analisar_texto(texto):
240
 
241
  ⚠️ **Alerta de Conteúdo**
242
 
243
- Este texto contém linguagem imprópria ou ofensiva (nível: {improper_score:.1%}).
244
 
245
- Evite usar:
246
  • Discurso de ódio
247
- • Termos racistas ou homofóbicos
248
- • Linguagem discriminatória
249
- • Xingamentos graves
250
 
251
- O sentimento foi analisado, mas recomendamos reformular o texto de forma respeitosa."""
252
  else:
253
  resultado_texto = f"**{classificacao}**"
254
 
255
- confianca_texto = f"{confianca:.1%}"
256
  consenso_texto = f"{votos}/{modelos_usados} modelos ({(votos/modelos_usados)*100:.0f}%)"
257
  consistencia_texto = f"{nivel} (σ={desvio:.3f})" if desvio > 0 else "N/A"
258
 
259
  return resultado_texto, probs, confianca_texto, consenso_texto, consistencia_texto
260
 
261
- # Casos de teste
262
  casos_teste = [
263
- ["Este produto superou minhas expectativas. Qualidade excelente e entrega rápida."],
264
- ["Experiência muito negativa. O produto apresentou defeitos e o atendimento foi inadequado."],
265
- ["Produto atende o esperado. Funcionalidades básicas dentro do padrão da categoria."],
266
- ["Recomendo fortemente. Excelente custo-benefício e durabilidade comprovada."],
267
- ["Satisfatório. Cumpre o prometido sem grandes destaques."],
268
- ["Produto horrível, péssima qualidade, muito ruim."],
 
 
269
  ]
270
 
271
  # Interface
272
- with gr.Blocks(title="Análise de Sentimentos") as demo:
273
 
274
  gr.Markdown(
275
  f"""
276
- # Sistema de Análise de Sentimentos
277
 
278
- Análise por ensemble de {len(classifiers)} modelos com verificação de linguagem imprópria.
279
 
280
- **Como funciona:**
281
- 1. Analisa o sentimento do texto (Negativo, Neutro ou Positivo)
282
- 2. Verifica se há linguagem imprópria e alerta (mas ainda mostra o sentimento)
283
  """
284
  )
285
 
@@ -298,7 +349,7 @@ with gr.Blocks(title="Análise de Sentimentos") as demo:
298
 
299
  with gr.Row():
300
  with gr.Column(scale=1):
301
- resultado_output = gr.Markdown(label="Resultado")
302
  confianca_output = gr.Textbox(label="Confiança", interactive=False)
303
  consenso_output = gr.Textbox(label="Consenso", interactive=False)
304
  consistencia_output = gr.Textbox(label="Consistência", interactive=False)
@@ -322,43 +373,57 @@ with gr.Blocks(title="Análise de Sentimentos") as demo:
322
  gr.Markdown(
323
  f"""
324
  ---
325
- ## Sobre o Sistema
326
 
327
- ### Fluxo de Análise
328
 
329
- 1. **Análise de Sentimento** (sempre executada)
330
- - {len(classifiers)} modelos analisam o texto
331
- - Voting majoritário determina: Negativo, Neutro ou Positivo
332
- - Cálculo de confiança e consenso
333
 
334
- 2. **Verificação de Linguagem** (após a análise)
335
- - {len(moderators)} modelos verificam conteúdo impróprio
336
- - Se detectado (>{TOXICITY_THRESHOLD*100:.0f}%), adiciona alerta
337
- - **Não bloqueia a análise**, apenas informa
338
-
339
- ### Modelos de Análise
340
-
341
- - BERTimbau (2 variantes) - Português BR
342
- - XLM-RoBERTa (3 variantes) - Multilíngue
343
- - BERT e DistilBERT Multilingual
344
  - RoBERTa especializados
345
- - Outros modelos complementares
 
 
 
 
 
346
 
347
  ### Verificação de Linguagem
348
 
349
- - DistilBERT Toxicity Multilingual
 
 
 
 
 
 
 
 
350
  - DeHateBERT Portuguese
351
 
352
- **Importante:** O sistema SEMPRE analisa o sentimento. Se houver linguagem imprópria,
353
- você verá o resultado da análise + um aviso recomendando reformular o texto.
 
 
 
 
 
 
 
 
 
354
 
355
- ### Por Que Este Design?
356
 
357
- Este approach permite:
358
- - **Análise técnica completa** mesmo de textos com problemas
359
- - **Feedback educativo** sobre linguagem inadequada
360
- - **Transparência** nos resultados
361
- - **Não censura** a análise, apenas orienta
 
362
  """
363
  )
364
 
 
1
  """
2
+ Sistema Avançado de Análise de Sentimentos
3
+ Versão melhorada com mais modelos e melhor cálculo de confiança
4
  """
5
 
6
  import gradio as gr
 
11
  import warnings
12
  warnings.filterwarnings('ignore')
13
 
14
+ # Modelos de moderação - MAIS MODELOS
15
  MODERATION_MODELS = [
16
  "citizenlab/distilbert-base-multilingual-cased-toxicity",
17
+ "unitary/toxic-bert",
18
+ "martin-ha/toxic-comment-model",
19
+ "facebook/roberta-hate-speech-dynabench-r4-target",
20
  "Hate-speech-CNERG/dehatebert-mono-portuguese",
21
  ]
22
 
 
25
 
26
  for model_name in MODERATION_MODELS:
27
  try:
28
+ print(f"Moderador: {model_name.split('/')[-1]}...", end=" ")
29
 
30
+ if "dehatebert" in model_name or "roberta-hate" in model_name:
31
  tokenizer = AutoTokenizer.from_pretrained(model_name)
32
  model = AutoModelForSequenceClassification.from_pretrained(model_name)
33
  moderator = pipeline(
 
52
 
53
  print(f"Moderadores ativos: {len(moderators)}")
54
 
55
+ # MAIS MODELOS DE SENTIMENTO - Expandido de 12 para 18
56
  SENTIMENT_MODELS = [
57
+ # Português específico (prioritários)
58
  "neuralmind/bert-base-portuguese-cased",
59
  "neuralmind/bert-large-portuguese-cased",
60
+ "rufimelo/bert-large-portuguese-cased-finetuned-with-yelp-reviews",
61
+
62
+ # XLM-RoBERTa (excelentes para multilíngue)
63
  "cardiffnlp/twitter-xlm-roberta-base-sentiment",
64
  "cardiffnlp/twitter-xlm-roberta-base-sentiment-multilingual",
65
  "citizenlab/twitter-xlm-roberta-base-sentiment-finetunned",
66
+
67
+ # BERT Multilíngue
68
  "lxyuan/distilbert-base-multilingual-cased-sentiments-student",
69
  "nlptown/bert-base-multilingual-uncased-sentiment",
70
+
71
+ # RoBERTa variants
72
  "finiteautomata/bertweet-base-sentiment-analysis",
73
  "siebert/sentiment-roberta-large-english",
 
74
  "cardiffnlp/twitter-roberta-base-sentiment-latest",
75
+ "cardiffnlp/twitter-roberta-base-sentiment",
76
+
77
+ # DistilBERT variants
78
+ "distilbert-base-uncased-finetuned-sst-2-english",
79
+ "bhadresh-savani/distilbert-base-uncased-emotion",
80
+
81
+ # Emotion models (mapeados para sentimento)
82
  "j-hartmann/emotion-english-distilroberta-base",
83
+ "arpanghoshal/EmoRoBERTa",
84
+
85
+ # Modelos adicionais especializados
86
+ "michellejieli/emotion_text_classifier",
87
+ "mrm8488/distilroberta-finetuned-financial-news-sentiment-analysis",
88
  ]
89
 
90
  print("\nCarregando modelos de análise de sentimentos...")
 
94
  try:
95
  print(f"[{idx}/{len(SENTIMENT_MODELS)}] {model_name.split('/')[-1]}...", end=" ")
96
 
97
+ if "neuralmind" in model_name or "emotion" in model_name or "Emo" in model_name:
98
  tokenizer = AutoTokenizer.from_pretrained(model_name)
99
  model = AutoModelForSequenceClassification.from_pretrained(model_name)
100
  classifier = pipeline(
101
+ "sentiment-analysis" if "sentiment" in model_name else "text-classification",
102
  model=model,
103
  tokenizer=tokenizer,
104
  device=0 if torch.cuda.is_available() else -1
 
113
  classifiers.append(classifier)
114
  print("OK")
115
 
116
+ except Exception as e:
117
  print("FALHA")
118
  continue
119
 
 
123
  print(f"- Moderadores: {len(moderators)}")
124
  print(f"{'='*60}\n")
125
 
126
+ # Limiar AUMENTADO para evitar falsos positivos
127
+ TOXICITY_THRESHOLD = 0.75 # Aumentado de 0.70 para 0.75
128
 
129
+ # Mapeamento expandido de labels
130
  LABEL_MAPPING = {
131
+ # Sentimento padrão
132
  'NEGATIVE': 'Negativo', 'negative': 'Negativo', 'NEG': 'Negativo',
133
  'NEUTRAL': 'Neutro', 'neutral': 'Neutro', 'NEU': 'Neutro',
134
  'POSITIVE': 'Positivo', 'positive': 'Positivo', 'POS': 'Positivo',
135
  'LABEL_0': 'Negativo', 'LABEL_1': 'Neutro', 'LABEL_2': 'Positivo',
136
+
137
+ # Estrelas
138
+ '1 star': 'Negativo', '2 stars': 'Negativo',
139
+ '3 stars': 'Neutro',
140
  '4 stars': 'Positivo', '5 stars': 'Positivo',
141
+
142
+ # Emoções -> Sentimentos
143
  'anger': 'Negativo', 'disgust': 'Negativo', 'fear': 'Negativo',
144
+ 'sadness': 'Negativo', 'surprise': 'Neutro',
145
+ 'joy': 'Positivo', 'love': 'Positivo', 'admiration': 'Positivo',
146
+
147
+ # Outros formatos
148
+ 'neg': 'Negativo', 'neu': 'Neutro', 'pos': 'Positivo',
149
  }
150
 
151
  def verificar_linguagem(texto):
152
  """
153
+ Verifica linguagem imprópria com MAIS modelos e threshold MAIOR
 
154
  """
155
  if not moderators or len(texto.strip()) < 3:
156
  return False, 0.0
 
163
  label = resultado['label'].lower()
164
  score = resultado['score']
165
 
166
+ # Interpretar com mais cuidado
167
+ is_toxic_label = any(word in label for word in ['toxic', 'hate', 'offensive', 'hateful'])
168
 
169
  if is_toxic_label:
170
  toxicity = score
171
  else:
172
+ # Se label é "normal" ou "not toxic", inverter
173
  toxicity = 1 - score
174
 
175
  scores_toxicos.append(toxicity)
 
182
 
183
  # Média dos scores
184
  toxicity_score = np.mean(scores_toxicos)
185
+
186
+ # Threshold MAIOR para reduzir falsos positivos
187
  has_improper = toxicity_score > TOXICITY_THRESHOLD
188
 
189
  return has_improper, toxicity_score
 
195
 
196
  def analisar_texto(texto):
197
  """
198
+ Análise com MELHOR cálculo de confiança
 
 
 
199
  """
200
 
201
  if not texto or len(texto.strip()) < 3:
202
  return "Aguardando texto para análise", {}, "-", "-", "-"
203
 
204
+ # ANÁLISE DE SENTIMENTO
205
  texto_processado = texto[:512]
206
  predicoes = []
207
+ scores_brutos = [] # Para melhor cálculo
208
+
209
  scores_por_classe = {
210
  'Negativo': [],
211
  'Neutro': [],
 
221
  score = resultado['score']
222
 
223
  predicoes.append(label_norm)
224
+ scores_brutos.append(score)
225
  modelos_usados += 1
226
 
227
+ # Distribuição mais conservadora
228
  if label_norm == 'Negativo':
229
  scores_por_classe['Negativo'].append(score)
230
+ remaining = 1 - score
231
+ scores_por_classe['Neutro'].append(remaining * 0.4)
232
+ scores_por_classe['Positivo'].append(remaining * 0.6)
233
  elif label_norm == 'Neutro':
234
  scores_por_classe['Neutro'].append(score)
235
+ remaining = 1 - score
236
+ scores_por_classe['Negativo'].append(remaining * 0.5)
237
+ scores_por_classe['Positivo'].append(remaining * 0.5)
238
+ else: # Positivo
239
  scores_por_classe['Positivo'].append(score)
240
+ remaining = 1 - score
241
+ scores_por_classe['Negativo'].append(remaining * 0.6)
242
+ scores_por_classe['Neutro'].append(remaining * 0.4)
243
 
244
  except:
245
  continue
 
247
  if not predicoes or modelos_usados == 0:
248
  return "Erro no processamento", {}, "-", "-", "-"
249
 
250
+ # Voting majoritário
251
  contagem = Counter(predicoes)
252
  classificacao = contagem.most_common(1)[0][0]
253
  votos = contagem[classificacao]
254
 
255
+ # MELHOR cálculo de probabilidades
256
+ probs = {}
257
+ for classe in ['Negativo', 'Neutro', 'Positivo']:
258
+ scores = scores_por_classe[classe]
259
+ if scores:
260
+ # Usar mediana ao invés de média para reduzir outliers
261
+ probs[classe] = float(np.median(scores))
262
+ else:
263
+ probs[classe] = 0.0
264
+
265
+ # Normalizar
266
  total = sum(probs.values())
267
  if total > 0:
268
  probs = {k: v/total for k, v in probs.items()}
269
 
270
+ # Confiança baseada em voting + score
271
+ confianca_voting = votos / modelos_usados
272
+ confianca_score = probs[classificacao]
273
+
274
+ # Confiança final = média ponderada (60% voting, 40% score)
275
+ confianca_final = (confianca_voting * 0.6) + (confianca_score * 0.4)
276
 
277
  # Consistência
278
  scores_final = scores_por_classe[classificacao]
 
283
  desvio = 0
284
  nivel = "N/A"
285
 
286
+ # VERIFICAR LINGUAGEM (com threshold mais alto)
287
  has_improper, improper_score = verificar_linguagem(texto)
288
 
289
  # Formatar resultado
 
292
 
293
  ⚠️ **Alerta de Conteúdo**
294
 
295
+ Detectada possível linguagem imprópria (confiança: {improper_score:.1%}).
296
 
297
+ Recomendamos evitar:
298
  • Discurso de ódio
299
+ • Termos discriminatórios
300
+ • Linguagem ofensiva
 
301
 
302
+ O sentimento foi analisado normalmente."""
303
  else:
304
  resultado_texto = f"**{classificacao}**"
305
 
306
+ confianca_texto = f"{confianca_final:.1%}"
307
  consenso_texto = f"{votos}/{modelos_usados} modelos ({(votos/modelos_usados)*100:.0f}%)"
308
  consistencia_texto = f"{nivel} (σ={desvio:.3f})" if desvio > 0 else "N/A"
309
 
310
  return resultado_texto, probs, confianca_texto, consenso_texto, consistencia_texto
311
 
312
+ # Casos de teste variados
313
  casos_teste = [
314
+ ["Este produto superou todas as minhas expectativas. Qualidade excepcional!"],
315
+ ["Experiência extremamente negativa. Produto defeituoso e atendimento péssimo."],
316
+ ["Produto normal. Atende o básico sem grandes destaques ou problemas."],
317
+ ["Recomendo! Excelente custo-benefício e entrega rápida."],
318
+ ["Satisfatório. Funciona conforme descrito, nada além disso."],
319
+ ["Produto horrível, péssima qualidade, muito ruim, não recomendo."],
320
+ ["Maravilhoso! Adorei cada detalhe, perfeito em todos os aspectos!"],
321
+ ["Decepcionante. Não corresponde à descrição e apresenta defeitos graves."],
322
  ]
323
 
324
  # Interface
325
+ with gr.Blocks(title="Análise de Sentimentos Avançada") as demo:
326
 
327
  gr.Markdown(
328
  f"""
329
+ # Sistema Avançado de Análise de Sentimentos
330
 
331
+ Análise por ensemble de **{len(classifiers)} modelos** especializados.
332
 
333
+ **Sistema de verificação:** {len(moderators)} moderadores detectam linguagem imprópria.
 
 
334
  """
335
  )
336
 
 
349
 
350
  with gr.Row():
351
  with gr.Column(scale=1):
352
+ resultado_output = gr.Markdown(label="Classificação")
353
  confianca_output = gr.Textbox(label="Confiança", interactive=False)
354
  consenso_output = gr.Textbox(label="Consenso", interactive=False)
355
  consistencia_output = gr.Textbox(label="Consistência", interactive=False)
 
373
  gr.Markdown(
374
  f"""
375
  ---
376
+ ## Especificações do Sistema
377
 
378
+ ### Análise de Sentimento
379
 
380
+ **Modelos Ativos:** {len(classifiers)} / {len(SENTIMENT_MODELS)}
 
 
 
381
 
382
+ **Arquitetura:**
383
+ - BERTimbau (português específico)
384
+ - XLM-RoBERTa (multilíngue)
385
+ - BERT e DistilBERT
 
 
 
 
 
 
386
  - RoBERTa especializados
387
+ - Modelos de emoção
388
+
389
+ **M��todo:**
390
+ - Voting majoritário
391
+ - Agregação por mediana (reduz outliers)
392
+ - Confiança combinada (voting + score)
393
 
394
  ### Verificação de Linguagem
395
 
396
+ **Moderadores Ativos:** {len(moderators)} / {len(MODERATION_MODELS)}
397
+
398
+ **Threshold:** {TOXICITY_THRESHOLD*100:.0f}% (balanceado para evitar falsos positivos)
399
+
400
+ **Modelos:**
401
+ - DistilBERT Toxicity
402
+ - Toxic-BERT (Unitary)
403
+ - Toxic Comment Model
404
+ - RoBERTa Hate Speech
405
  - DeHateBERT Portuguese
406
 
407
+ ### Melhorias Implementadas
408
+
409
+ ✅ **Mais modelos** ({len(classifiers)} analisadores, {len(moderators)} moderadores)
410
+
411
+ ✅ **Melhor confiança** (combina voting + probabilidades)
412
+
413
+ ✅ **Menos falsos positivos** (threshold aumentado de 70% → 75%)
414
+
415
+ ✅ **Agregação robusta** (mediana ao invés de média)
416
+
417
+ ✅ **Distribuição conservadora** (scores mais equilibrados)
418
 
419
+ ### Fluxo de Processamento
420
 
421
+ 1. **Análise paralela** por todos os modelos
422
+ 2. **Voting majoritário** determina classificação
423
+ 3. **Agregação por mediana** calcula probabilidades
424
+ 4. **Confiança combinada** (60% voting + 40% score)
425
+ 5. **Verificação de linguagem** com threshold elevado
426
+ 6. **Resultado final** com métricas de qualidade
427
  """
428
  )
429