tassid commited on
Commit
8ed8ed6
·
verified ·
1 Parent(s): 2129645

Create app_backup.py

Browse files
Files changed (1) hide show
  1. app_backup.py +471 -0
app_backup.py ADDED
@@ -0,0 +1,471 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
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
+ # 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
+
23
+ print("Carregando sistema de moderação...")
24
+ moderators = []
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(
34
+ "text-classification",
35
+ model=model,
36
+ tokenizer=tokenizer,
37
+ device=0 if torch.cuda.is_available() else -1
38
+ )
39
+ else:
40
+ moderator = pipeline(
41
+ "text-classification",
42
+ model=model_name,
43
+ device=0 if torch.cuda.is_available() else -1
44
+ )
45
+
46
+ moderators.append(moderator)
47
+ print("OK")
48
+
49
+ except Exception as e:
50
+ print(f"FALHA")
51
+ continue
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...")
91
+ classifiers = []
92
+
93
+ for idx, model_name in enumerate(SENTIMENT_MODELS, 1):
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
105
+ )
106
+ else:
107
+ classifier = pipeline(
108
+ "sentiment-analysis",
109
+ model=model_name,
110
+ device=0 if torch.cuda.is_available() else -1
111
+ )
112
+
113
+ classifiers.append(classifier)
114
+ print("OK")
115
+
116
+ except Exception as e:
117
+ print("FALHA")
118
+ continue
119
+
120
+ print(f"\n{'='*60}")
121
+ print(f"Sistema completo:")
122
+ print(f"- Analisadores: {len(classifiers)}")
123
+ print(f"- Moderadores: {len(moderators)}")
124
+ print(f"{'='*60}\n")
125
+
126
+ # Limiar AUMENTADO para evitar falsos positivos
127
+ TOXICITY_THRESHOLD = 0.80 # Aumentado para reduzir falsos positivos
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
+ Com interpretação melhorada de labels
155
+ """
156
+ if not moderators or len(texto.strip()) < 3:
157
+ return False, 0.0
158
+
159
+ scores_toxicos = []
160
+
161
+ for moderator in moderators:
162
+ try:
163
+ resultado = moderator(texto[:512])[0]
164
+ label = resultado['label'].lower()
165
+ score = resultado['score']
166
+
167
+ # Interpretar labels com MAIS cuidado
168
+ # Labels que indicam TOXICIDADE
169
+ toxic_keywords = ['toxic', 'hate', 'offensive', 'hateful', 'obscene', 'threat', 'insult']
170
+ # Labels que indicam NORMALIDADE
171
+ normal_keywords = ['not', 'normal', 'neutral', 'clean']
172
+
173
+ is_toxic_label = any(word in label for word in toxic_keywords)
174
+ is_normal_label = any(word in label for word in normal_keywords)
175
+
176
+ # Calcular toxicity score com lógica melhorada
177
+ if is_toxic_label and not is_normal_label:
178
+ # Label diz que é tóxico
179
+ toxicity = score
180
+ elif is_normal_label or 'not' in label:
181
+ # Label diz que NÃO é tóxico
182
+ toxicity = 1 - score
183
+ else:
184
+ # Label ambíguo, assumir score direto se alto
185
+ toxicity = score if score > 0.5 else 1 - score
186
+
187
+ scores_toxicos.append(toxicity)
188
+
189
+ except:
190
+ continue
191
+
192
+ if not scores_toxicos:
193
+ return False, 0.0
194
+
195
+ # Média dos scores
196
+ toxicity_score = np.mean(scores_toxicos)
197
+
198
+ # Threshold MAIOR para reduzir falsos positivos
199
+ has_improper = toxicity_score > TOXICITY_THRESHOLD
200
+
201
+ return has_improper, toxicity_score
202
+
203
+ def normalizar_label(label):
204
+ """Normaliza labels"""
205
+ label_upper = label.upper() if isinstance(label, str) else str(label)
206
+ return LABEL_MAPPING.get(label, LABEL_MAPPING.get(label_upper, 'Neutro'))
207
+
208
+ def analisar_texto(texto):
209
+ """
210
+ Análise com MELHOR cálculo de confiança
211
+ """
212
+
213
+ if not texto or len(texto.strip()) < 3:
214
+ return "Aguardando texto para análise", {}, "-", "-", "-"
215
+
216
+ # ANÁLISE DE SENTIMENTO
217
+ texto_processado = texto[:512]
218
+ predicoes = []
219
+ scores_brutos = [] # Para melhor cálculo
220
+
221
+ scores_por_classe = {
222
+ 'Negativo': [],
223
+ 'Neutro': [],
224
+ 'Positivo': []
225
+ }
226
+
227
+ modelos_usados = 0
228
+
229
+ for classifier in classifiers:
230
+ try:
231
+ resultado = classifier(texto_processado)[0]
232
+ label_norm = normalizar_label(resultado['label'])
233
+ score = resultado['score']
234
+
235
+ predicoes.append(label_norm)
236
+ scores_brutos.append(score)
237
+ modelos_usados += 1
238
+
239
+ # Distribuição mais conservadora
240
+ if label_norm == 'Negativo':
241
+ scores_por_classe['Negativo'].append(score)
242
+ remaining = 1 - score
243
+ scores_por_classe['Neutro'].append(remaining * 0.4)
244
+ scores_por_classe['Positivo'].append(remaining * 0.6)
245
+ elif label_norm == 'Neutro':
246
+ scores_por_classe['Neutro'].append(score)
247
+ remaining = 1 - score
248
+ scores_por_classe['Negativo'].append(remaining * 0.5)
249
+ scores_por_classe['Positivo'].append(remaining * 0.5)
250
+ else: # Positivo
251
+ scores_por_classe['Positivo'].append(score)
252
+ remaining = 1 - score
253
+ scores_por_classe['Negativo'].append(remaining * 0.6)
254
+ scores_por_classe['Neutro'].append(remaining * 0.4)
255
+
256
+ except:
257
+ continue
258
+
259
+ if not predicoes or modelos_usados == 0:
260
+ return "Erro no processamento", {}, "-", "-", "-"
261
+
262
+ # Voting majoritário
263
+ contagem = Counter(predicoes)
264
+ classificacao = contagem.most_common(1)[0][0]
265
+ votos = contagem[classificacao]
266
+
267
+ # MELHOR cálculo de probabilidades
268
+ probs = {}
269
+ for classe in ['Negativo', 'Neutro', 'Positivo']:
270
+ scores = scores_por_classe[classe]
271
+ if scores:
272
+ # Usar mediana ao invés de média para reduzir outliers
273
+ probs[classe] = float(np.median(scores))
274
+ else:
275
+ probs[classe] = 0.0
276
+
277
+ # Normalizar
278
+ total = sum(probs.values())
279
+ if total > 0:
280
+ probs = {k: v/total for k, v in probs.items()}
281
+
282
+ # Confiança baseada em voting + score
283
+ confianca_voting = votos / modelos_usados
284
+ confianca_score = probs[classificacao]
285
+
286
+ # Confiança final = média ponderada (60% voting, 40% score)
287
+ confianca_final = (confianca_voting * 0.6) + (confianca_score * 0.4)
288
+
289
+ # Consistência
290
+ scores_final = scores_por_classe[classificacao]
291
+ if len(scores_final) > 1:
292
+ desvio = np.std(scores_final)
293
+ nivel = "Alta" if desvio < 0.1 else "Média" if desvio < 0.2 else "Baixa"
294
+ else:
295
+ desvio = 0
296
+ nivel = "N/A"
297
+
298
+ # VERIFICAR LINGUAGEM (com threshold mais alto)
299
+ has_improper, improper_score = verificar_linguagem(texto)
300
+
301
+ # LÓGICA INTELIGENTE: Se Positivo com boa confiança, provavelmente não é ofensivo
302
+ if classificacao == 'Positivo' and confianca_final > 0.70:
303
+ has_improper = False # Ignora alerta para textos claramente positivos
304
+
305
+ # Se Neutro ou Negativo, ainda verifica normalmente
306
+
307
+ # Formatar resultado
308
+ if has_improper:
309
+ resultado_texto = f"""**{classificacao}**
310
+
311
+ ⚠️ **Alerta de Conteúdo**
312
+
313
+ Detectada possível linguagem imprópria (confiança: {improper_score:.1%}).
314
+
315
+ Recomendamos evitar:
316
+ • Discurso de ódio
317
+ • Termos discriminatórios
318
+ • Linguagem ofensiva
319
+
320
+ O sentimento foi analisado normalmente."""
321
+ else:
322
+ resultado_texto = f"**{classificacao}**"
323
+
324
+ confianca_texto = f"{confianca_final:.1%}"
325
+ consenso_texto = f"{votos}/{modelos_usados} modelos ({(votos/modelos_usados)*100:.0f}%)"
326
+ consistencia_texto = f"{nivel} (σ={desvio:.3f})" if desvio > 0 else "N/A"
327
+
328
+ return resultado_texto, probs, confianca_texto, consenso_texto, consistencia_texto
329
+
330
+ # Casos de teste variados
331
+ casos_teste = [
332
+ ["Este produto superou todas as minhas expectativas. Qualidade excepcional!"],
333
+ ["Experiência extremamente negativa. Produto defeituoso e atendimento péssimo."],
334
+ ["Produto normal. Atende o básico sem grandes destaques ou problemas."],
335
+ ["Recomendo! Excelente custo-benefício e entrega rápida."],
336
+ ["Satisfatório. Funciona conforme descrito, nada além disso."],
337
+ ["Produto horrível, péssima qualidade, muito ruim, não recomendo."],
338
+ ["Maravilhoso! Adorei cada detalhe, perfeito em todos os aspectos!"],
339
+ ["Decepcionante. Não corresponde à descrição e apresenta defeitos graves."],
340
+ ]
341
+
342
+ # Interface
343
+ with gr.Blocks(title="Análise de Sentimentos Avançada") as demo:
344
+
345
+ gr.Markdown(
346
+ f"""
347
+ # Sistema Avançado de Análise de Sentimentos
348
+
349
+ Análise por ensemble de **{len(classifiers)} modelos** especializados.
350
+
351
+ **Sistema de verificação:** {len(moderators)} moderadores detectam linguagem imprópria.
352
+ """
353
+ )
354
+
355
+ with gr.Row():
356
+ with gr.Column():
357
+ texto_input = gr.Textbox(
358
+ label="Texto para Análise",
359
+ placeholder="Digite ou cole o texto aqui (até 512 caracteres)...",
360
+ lines=5,
361
+ max_lines=10
362
+ )
363
+
364
+ with gr.Row():
365
+ btn_analisar = gr.Button("Analisar", variant="primary", size="lg")
366
+ btn_limpar = gr.Button("Limpar", size="lg")
367
+
368
+ with gr.Row():
369
+ with gr.Column(scale=1):
370
+ resultado_output = gr.Markdown(label="Classificação")
371
+ confianca_output = gr.Textbox(label="Confiança", interactive=False)
372
+ consenso_output = gr.Textbox(label="Consenso", interactive=False)
373
+ consistencia_output = gr.Textbox(label="Consistência", interactive=False)
374
+
375
+ with gr.Column(scale=1):
376
+ probs_output = gr.Label(
377
+ label="Distribuição de Probabilidades",
378
+ num_top_classes=3
379
+ )
380
+
381
+ gr.Markdown("### Casos de Teste")
382
+
383
+ gr.Examples(
384
+ examples=casos_teste,
385
+ inputs=texto_input,
386
+ outputs=[resultado_output, probs_output, confianca_output, consenso_output, consistencia_output],
387
+ fn=analisar_texto,
388
+ cache_examples=False
389
+ )
390
+
391
+ gr.Markdown(
392
+ f"""
393
+ ---
394
+ ## Especificações do Sistema
395
+
396
+ ### Análise de Sentimento
397
+
398
+ **Modelos Ativos:** {len(classifiers)} / {len(SENTIMENT_MODELS)}
399
+
400
+ **Arquitetura:**
401
+ - BERTimbau (português específico)
402
+ - XLM-RoBERTa (multilíngue)
403
+ - BERT e DistilBERT
404
+ - RoBERTa especializados
405
+ - Modelos de emoção
406
+
407
+ **Método:**
408
+ - Voting majoritário
409
+ - Agregação por mediana (reduz outliers)
410
+ - Confiança combinada (voting + score)
411
+
412
+ ### Verificação de Linguagem
413
+
414
+ **Moderadores Ativos:** {len(moderators)} / {len(MODERATION_MODELS)}
415
+
416
+ **Threshold:** {TOXICITY_THRESHOLD*100:.0f}% (mais alto para evitar falsos positivos)
417
+
418
+ **Lógica Inteligente:**
419
+ - Textos claramente positivos (>70% confiança) não geram alertas
420
+ - Foco em detectar problemas reais
421
+
422
+ **Modelos:**
423
+ - DistilBERT Toxicity
424
+ - Toxic-BERT (Unitary)
425
+ - Toxic Comment Model
426
+ - RoBERTa Hate Speech
427
+ - DeHateBERT Portuguese
428
+
429
+ ### Melhorias Implementadas
430
+
431
+ ✅ **Mais modelos** ({len(classifiers)} analisadores, {len(moderators)} moderadores)
432
+
433
+ ✅ **Melhor confiança** (combina voting + probabilidades)
434
+
435
+ ✅ **Menos falsos positivos** (threshold aumentado de 70% → 75%)
436
+
437
+ ✅ **Agregação robusta** (mediana ao invés de média)
438
+
439
+ ✅ **Distribuição conservadora** (scores mais equilibrados)
440
+
441
+ ### Fluxo de Processamento
442
+
443
+ 1. **Análise paralela** por todos os modelos
444
+ 2. **Voting majoritário** determina classificação
445
+ 3. **Agregação por mediana** calcula probabilidades
446
+ 4. **Confiança combinada** (60% voting + 40% score)
447
+ 5. **Verificação de linguagem** com threshold elevado
448
+ 6. **Resultado final** com métricas de qualidade
449
+ """
450
+ )
451
+
452
+ btn_analisar.click(
453
+ fn=analisar_texto,
454
+ inputs=texto_input,
455
+ outputs=[resultado_output, probs_output, confianca_output, consenso_output, consistencia_output]
456
+ )
457
+
458
+ btn_limpar.click(
459
+ fn=lambda: ("", "", "", "", "", {}),
460
+ inputs=None,
461
+ outputs=[texto_input, resultado_output, confianca_output, consenso_output, consistencia_output, probs_output]
462
+ )
463
+
464
+ texto_input.submit(
465
+ fn=analisar_texto,
466
+ inputs=texto_input,
467
+ outputs=[resultado_output, probs_output, confianca_output, consenso_output, consistencia_output]
468
+ )
469
+
470
+ if __name__ == "__main__":
471
+ demo.launch()