PowerMachine commited on
Commit
9328407
·
verified ·
1 Parent(s): c82b8d4

Delete cnn_bigru/training/trainer.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. cnn_bigru/training/trainer.py +0 -495
cnn_bigru/training/trainer.py DELETED
@@ -1,495 +0,0 @@
1
- """trainer.py — Loop de treinamento cooperativo com auto-aprendizado.
2
-
3
- Implementa a seção 11 de dados.txt:
4
-
5
- PARA época = 1 ATÉ NUM_EPOCAS:
6
- PARA batch EM DataLoader:
7
- // Forward Gerador
8
- logits_prova, enc_out, _ = gerador.forward(axiomas, conjectura, prova_alvo)
9
-
10
- // Calcular perdas e avaliações do verificador
11
- L_total, L_G, L_V, L_AH, lista_v = calcular_perdas(...)
12
-
13
- // Backward
14
- zerar_gradientes
15
- L_total.backward()
16
-
17
- // Controle de gradiente (clip + spectral norm)
18
- aplicar_controle_gradiente(gerador)
19
- aplicar_controle_gradiente(verificador)
20
-
21
- // Ajuste dinâmico de LR baseado em normas de gradiente e v_media
22
- eta_G, eta_V = atualizar_taxas(...)
23
-
24
- // Passo de otimização
25
- otimizador_G.step()
26
- otimizador_V.step()
27
-
28
- // Normalização espectral após step
29
- aplicar_espectral_norm(gerador)
30
- aplicar_espectral_norm(verificador)
31
-
32
- Adicionalmente:
33
- - Mecanismo de hipóteses ativado quando v < threshold
34
- - N tentativas de sinergia (SynergySearcher) no início de cada época
35
- - Meta-controlador que ajusta pesos das perdas baseado na variância do gradiente
36
- """
37
- from __future__ import annotations
38
-
39
- import logging
40
- import math
41
- import os
42
- import time
43
- from dataclasses import dataclass, field
44
- from typing import Any, Dict, List, Optional, Tuple
45
-
46
- import torch
47
- import torch.nn as nn
48
- import torch.nn.functional as F
49
- from torch.utils.data import DataLoader
50
-
51
- from ..losses.losses import LossConfig, MultiLoss
52
- from ..models.generator_verifier import (
53
- GeneratorCNNBiGRU,
54
- VerifierCNNBiGRU,
55
- AntiHallucinationLayer,
56
- )
57
- from ..models.multimodal_model import MultimodalCNNBiGRU
58
- from ..utils.memory_optimizer import MemoryOptimizer
59
- from .auto_learner import AutoLearnConfig, AutoLearner, estimate_curvature, orthogonal_init_model
60
- from .hypothesis_controller import HypothesisConfig, HypothesisController, SynergySearcher
61
-
62
- logger = logging.getLogger(__name__)
63
-
64
-
65
- @dataclass
66
- class TrainerConfig:
67
- """Configuração do treinador."""
68
- # Épocas e batches
69
- num_epochs: int = 2
70
- max_batches_per_epoch: int = 10 # limita batches para teste de 50 amostras
71
- batch_size: int = 8
72
-
73
- # Synergy search
74
- n_synergy_attempts: int = 3 # N tentativas de acerto do treinamento
75
- use_synergy_search: bool = True
76
-
77
- # Hypothesis
78
- use_hypotheses: bool = True
79
- n_hypotheses: int = 4
80
-
81
- # Loss config
82
- loss_config: LossConfig = field(default_factory=LossConfig)
83
-
84
- # Auto-learn config
85
- auto_config: AutoLearnConfig = field(default_factory=AutoLearnConfig)
86
-
87
- # Device
88
- device: str = "cpu"
89
-
90
- # Logging
91
- log_every: int = 1
92
-
93
-
94
- class CooperativeTrainer:
95
- """Treina o modelo multimodal CNN-BiGRU cooperativo.
96
-
97
- Args:
98
- model: MultimodalCNNBiGRU
99
- generator: GeneratorCNNBiGRU (opcional, para modo gerar+verificar)
100
- verifier: VerifierCNNBiGRU (opcional)
101
- tokenizer: BBPETokenizer
102
- config: TrainerConfig
103
- """
104
-
105
- def __init__(
106
- self,
107
- model: MultimodalCNNBiGRU,
108
- tokenizer,
109
- config: TrainerConfig,
110
- generator: Optional[GeneratorCNNBiGRU] = None,
111
- verifier: Optional[VerifierCNNBiGRU] = None,
112
- anti_hallucination: Optional[AntiHallucinationLayer] = None,
113
- ):
114
- self.model = model.to(config.device)
115
- self.tokenizer = tokenizer
116
- self.cfg = config
117
- self.device = config.device
118
-
119
- # Componentes opcionais
120
- self.generator = generator.to(config.device) if generator else None
121
- self.verifier = verifier.to(config.device) if verifier else None
122
- self.anti_hall = (
123
- anti_hallucination.to(config.device) if anti_hallucination else None
124
- )
125
-
126
- # Loss
127
- self.loss_fn = MultiLoss(config.loss_config, pad_idx=tokenizer.pad_id)
128
-
129
- # Auto-learner
130
- self.auto = AutoLearner(config.auto_config)
131
-
132
- # Synergy searcher
133
- self.synergy = SynergySearcher(
134
- n_attempts=config.n_synergy_attempts,
135
- seed=42,
136
- )
137
-
138
- # Otimizadores
139
- self.opt_model = torch.optim.AdamW(
140
- self.model.parameters(), lr=config.auto_config.initial_lr_G,
141
- weight_decay=config.auto_config.l2_reg if hasattr(config.auto_config, 'l2_reg') else 1e-5
142
- )
143
- self.opt_gen = (
144
- torch.optim.AdamW(self.generator.parameters(), lr=config.auto_config.initial_lr_G)
145
- if self.generator else None
146
- )
147
- self.opt_ver = (
148
- torch.optim.AdamW(self.verifier.parameters(), lr=config.auto_config.initial_lr_V)
149
- if self.verifier else None
150
- )
151
-
152
- # Memory optimizer
153
- self.mem = MemoryOptimizer(enable_amp=(self.device != "cpu"))
154
- self.mem.configure()
155
-
156
- # Hypothesis controller (aplicado sobre features do modelo)
157
- self.hypothesis = HypothesisController(
158
- feat_dim=model.fusion.out_dim,
159
- out_dim=model.fusion.out_dim,
160
- config=HypothesisConfig(
161
- n_hypotheses=config.n_hypotheses,
162
- threshold_err=config.loss_config.threshold_err,
163
- ),
164
- ).to(self.device)
165
-
166
- # Histórico de treinamento
167
- self.history: List[Dict[str, Any]] = []
168
- self.metrics: Dict[str, List[float]] = {"loss": [], "ppl": [], "v_mean": [], "lr_G": [], "lr_V": []}
169
-
170
- def _compute_v_and_tau(
171
- self,
172
- fused: torch.Tensor,
173
- targets: torch.Tensor,
174
- ) -> Tuple[List[torch.Tensor], List[torch.Tensor]]:
175
- """Computa valores do verificador (v) e anti-alucinação (τ).
176
-
177
- Simplificação: usa o fused como proxy para o passo gerado.
178
- Em uma versão completa, isto integraria com o generator e verifier.
179
- """
180
- v_list = []
181
- tau_list = []
182
-
183
- # Verifier: para cada passo temporal, calcula v
184
- # Aqui simplificamos: v é calculado uma vez por batch
185
- if self.verifier is not None:
186
- # Passo hipotético: usa o fused repetido como "passo"
187
- B = fused.size(0)
188
- passo = fused[:, :128] # dimensão compatível com o encoder
189
- # Concatena com zeros para matching dimensional (simplificação)
190
- passo_full = torch.cat([passo, passo], dim=-1) # [B, 256]
191
-
192
- # Não temos tokens aqui; usamos features direto para uma v proxy
193
- # v = sigmoid(linear(fused))
194
- v_proxy = torch.sigmoid(fused.mean(dim=-1, keepdim=True)) # [B, 1]
195
- v_list.append(v_proxy)
196
- else:
197
- # Sem verifier: v = 1 (sem punição)
198
- v_list.append(torch.ones(fused.size(0), 1, device=fused.device))
199
-
200
- # Anti-hallucination: τ sobre os logits
201
- if self.anti_hall is not None:
202
- # Logits proxy: projeta fused para vocab_size via lm_head
203
- logits_proxy = self.model.lm_head(fused) # [B, V]
204
- tau = self.anti_hall(logits_proxy) # [B, 1]
205
- tau_list.append(tau)
206
- else:
207
- tau_list.append(torch.ones(fused.size(0), 1, device=fused.device))
208
-
209
- return v_list, tau_list
210
-
211
- def _train_step(
212
- self,
213
- batch: Dict[str, torch.Tensor],
214
- ) -> Dict[str, float]:
215
- """Executa um passo de treinamento. Retorna métricas."""
216
- # Move para device
217
- input_ids_a = batch["input_ids_a"].to(self.device, non_blocking=True)
218
- input_ids_b = batch["input_ids_b"].to(self.device, non_blocking=True)
219
- attn_mask_a = batch["attn_mask_a"].to(self.device, non_blocking=True)
220
- attn_mask_b = batch["attn_mask_b"].to(self.device, non_blocking=True)
221
- images = batch["images"].to(self.device, non_blocking=True)
222
- audios = batch["audios"].to(self.device, non_blocking=True)
223
- labels = batch["labels"].to(self.device, non_blocking=True)
224
-
225
- # Forward
226
- with self.mem.amp_context(self.device):
227
- out = self.model(
228
- input_ids_a, input_ids_b,
229
- images=images, audios=audios,
230
- attn_mask_a=attn_mask_a, attn_mask_b=attn_mask_b,
231
- mode="classify",
232
- )
233
- logits = out["logits"] # [B, num_classes]
234
- fused = out["fused"] # [B, fusion_dim]
235
-
236
- # Targets: usar labels como targets de classificação
237
- # Para LM-style loss, convertemos para one-hot vs logits
238
- # Aqui usamos CrossEntropy direto sobre os labels de classificação
239
- ce_loss = F.cross_entropy(logits, labels)
240
-
241
- # Verifier + anti-hallucination (proxy)
242
- v_list, tau_list = self._compute_v_and_tau(fused, labels)
243
-
244
- # Targets para o gerador: usar input_ids_b como "prova alvo"
245
- # (apenas se generator estiver presente)
246
- gen_logits = None
247
- if self.generator is not None:
248
- try:
249
- gen_out = self.generator(
250
- input_ids_a, input_ids_b,
251
- target_proof=input_ids_b, # teacher forcing
252
- bos_id=self.tokenizer.bos_id,
253
- eos_id=self.tokenizer.eos_id,
254
- )
255
- gen_logits = gen_out["logits"]
256
- except Exception as e:
257
- logger.debug("Generator forward falhou: %s", e)
258
- gen_logits = None
259
-
260
- # Constrói logits para a MultiLoss:
261
- # Se temos generator, usa ele; senão, projeta fused para [B, T, V]
262
- if gen_logits is not None:
263
- loss_logits = gen_logits
264
- loss_targets = input_ids_b
265
- else:
266
- # Proxy: expande logits de classificação para [B, T, V]
267
- # Não é ideal mas mantém o pipeline funcional
268
- B = fused.size(0)
269
- T = input_ids_b.size(1)
270
- V = self.model.vocab_size
271
- loss_logits = self.model.lm_head(fused).unsqueeze(1).expand(-1, T, -1)
272
- loss_targets = input_ids_b
273
-
274
- # Hypothesis controller (ativa hipóteses se v < threshold)
275
- v_mean_tensor = torch.stack([v.squeeze(-1) for v in v_list]).mean(dim=0).unsqueeze(-1)
276
- hyp_out = self.hypothesis(fused, v_mean_tensor)
277
-
278
- # Curvatura
279
- try:
280
- curv = estimate_curvature(
281
- self.model,
282
- lambda: self.loss_fn(
283
- loss_logits, loss_targets, v_list, tau_list,
284
- models_for_reg=[self.model],
285
- ),
286
- eps=self.cfg.loss_config.curvature_eps,
287
- n_samples=2,
288
- )
289
- self.loss_fn.set_curvature_estimate(curv)
290
- except Exception as e:
291
- logger.debug("Curvatura falhou: %s", e)
292
-
293
- # Compute losses
294
- losses = self.loss_fn(
295
- loss_logits, loss_targets, v_list, tau_list,
296
- models_for_reg=[m for m in [self.model, self.generator, self.verifier] if m is not None],
297
- )
298
-
299
- # Adiciona loss de classificação
300
- total_loss = losses["total"] + ce_loss
301
-
302
- # Backward
303
- self.opt_model.zero_grad(set_to_none=True)
304
- if self.opt_gen: self.opt_gen.zero_grad(set_to_none=True)
305
- if self.opt_ver: self.opt_ver.zero_grad(set_to_none=True)
306
-
307
- total_loss.backward()
308
-
309
- # Clipping
310
- self.auto.apply_clipping(self.model)
311
- if self.generator: self.auto.apply_clipping(self.generator)
312
- if self.verifier: self.auto.apply_clipping(self.verifier)
313
-
314
- # Normas de gradiente
315
- grad_G = self.auto.compute_total_grad_norm(self.model)
316
- grad_V = self.auto.compute_total_grad_norm(self.verifier) if self.verifier else 0.0
317
-
318
- # v_mean e L_V_mean
319
- v_mean = float(torch.stack([v.squeeze(-1) for v in v_list]).mean().item())
320
- L_V_mean = float(losses["l_v"].item()) if isinstance(losses["l_v"], torch.Tensor) else 0.0
321
-
322
- # Ajuste dinâmico de LR
323
- novo_lr_G, novo_lr_V = self.auto.update_learning_rates(
324
- grad_G, grad_V, v_mean, L_V_mean
325
- )
326
- self.auto.set_optimizer_lr(self.opt_model, novo_lr_G)
327
- if self.opt_gen: self.auto.set_optimizer_lr(self.opt_gen, novo_lr_G)
328
- if self.opt_ver: self.auto.set_optimizer_lr(self.opt_ver, novo_lr_V)
329
-
330
- # Step
331
- self.opt_model.step()
332
- if self.opt_gen: self.opt_gen.step()
333
- if self.opt_ver: self.opt_ver.step()
334
-
335
- # Spectral norm após step
336
- if self.cfg.auto_config.apply_after_step:
337
- self.auto.apply_spectral_norm(self.model)
338
- if self.generator: self.auto.apply_spectral_norm(self.generator)
339
- if self.verifier: self.auto.apply_spectral_norm(self.verifier)
340
-
341
- # Métricas
342
- loss_val = float(total_loss.item())
343
- try:
344
- ppl = math.exp(min(loss_val, 20)) # evita overflow
345
- except OverflowError:
346
- ppl = float("inf")
347
-
348
- return {
349
- "loss": loss_val,
350
- "ppl": ppl,
351
- "v_mean": v_mean,
352
- "lr_G": novo_lr_G,
353
- "lr_V": novo_lr_V,
354
- "l_g": float(losses["l_g"].item()) if isinstance(losses["l_g"], torch.Tensor) else 0.0,
355
- "l_v": L_V_mean,
356
- "l_ah": float(losses["l_ah"].item()) if isinstance(losses["l_ah"], torch.Tensor) else 0.0,
357
- "hypothesis_activated": hyp_out["activated"],
358
- }
359
-
360
- def _eval_config(self, cfg: Dict, sample_batch: Dict[str, torch.Tensor]) -> float:
361
- """Avalia uma configuração de perda em um mini-batch (para synergy search)."""
362
- # Salva config atual
363
- original = {k: getattr(self.loss_fn.cfg, k) for k in cfg}
364
- # Aplica nova config
365
- for k, v in cfg.items():
366
- if hasattr(self.loss_fn.cfg, k):
367
- setattr(self.loss_fn.cfg, k, v)
368
- try:
369
- metrics = self._train_step(sample_batch)
370
- return metrics["loss"]
371
- finally:
372
- # Restaura
373
- for k, v in original.items():
374
- setattr(self.loss_fn.cfg, k, v)
375
-
376
- def train(
377
- self,
378
- dataloader: DataLoader,
379
- ) -> Dict[str, Any]:
380
- """Executa o treinamento completo."""
381
- logger.info("Iniciando treinamento cooperativo CNN-BiGRU")
382
- logger.info(" epochs=%d, batch_size=%d, n_synergy=%d",
383
- self.cfg.num_epochs, self.cfg.batch_size, self.cfg.n_synergy_attempts)
384
-
385
- start_time = time.time()
386
-
387
- for epoch in range(self.cfg.num_epochs):
388
- epoch_loss = 0.0
389
- epoch_ppl = 0.0
390
- n_batches = 0
391
- hypothesis_activations = 0
392
-
393
- # Synergy search no início da época (apenas época > 0 para economizar tempo)
394
- if self.cfg.use_synergy_search and epoch == 0:
395
- try:
396
- # Pega um batch de amostra para avaliação
397
- sample_batch = next(iter(dataloader))
398
- sample_batch = {k: (v.to(self.device) if isinstance(v, torch.Tensor) else v)
399
- for k, v in sample_batch.items()}
400
-
401
- base_cfg = {
402
- "alpha": self.loss_fn.cfg.alpha,
403
- "beta": self.loss_fn.cfg.beta,
404
- "gamma_loss": self.loss_fn.cfg.gamma_loss,
405
- "delta": self.loss_fn.cfg.delta,
406
- "lambda_penal": self.loss_fn.cfg.lambda_penal,
407
- "mu_exp_penal": self.loss_fn.cfg.mu_exp_penal,
408
- }
409
-
410
- logger.info("Synergy search: tentando %d configurações...", self.cfg.n_synergy_attempts)
411
- best_cfg = self.synergy.search(
412
- base_config=base_cfg,
413
- eval_fn=lambda c: self._eval_config(c, sample_batch),
414
- )
415
-
416
- # Aplica a melhor configuração
417
- for k, v in best_cfg.items():
418
- if hasattr(self.loss_fn.cfg, k):
419
- setattr(self.loss_fn.cfg, k, v)
420
- logger.info("Synergy search concluído. Melhor config aplicada.")
421
- except Exception as e:
422
- logger.warning("Synergy search falhou: %s", e)
423
-
424
- # Loop de batches
425
- self.model.train()
426
- if self.generator: self.generator.train()
427
- if self.verifier: self.verifier.train()
428
- if self.anti_hall: self.anti_hall.train()
429
-
430
- for batch_idx, batch in enumerate(dataloader):
431
- if batch_idx >= self.cfg.max_batches_per_epoch:
432
- break
433
-
434
- try:
435
- metrics = self._train_step(batch)
436
- epoch_loss += metrics["loss"]
437
- epoch_ppl += metrics["ppl"]
438
- n_batches += 1
439
- if metrics["hypothesis_activated"]:
440
- hypothesis_activations += 1
441
-
442
- # Armazena métricas
443
- for k in ["loss", "ppl", "v_mean", "lr_G", "lr_V"]:
444
- self.metrics[k].append(metrics[k])
445
-
446
- if batch_idx % self.cfg.log_every == 0:
447
- logger.info(
448
- "Época %d | Batch %d | loss=%.4f | ppl=%.2f | v_mean=%.3f | lr_G=%.2e | hyp_act=%s",
449
- epoch + 1, batch_idx, metrics["loss"], metrics["ppl"],
450
- metrics["v_mean"], metrics["lr_G"],
451
- "Y" if metrics["hypothesis_activated"] else "N",
452
- )
453
- except Exception as e:
454
- logger.error("Erro no batch %d: %s", batch_idx, e, exc_info=True)
455
- continue
456
-
457
- # Cleanup de memória ao final da época
458
- self.mem.cleanup()
459
-
460
- avg_loss = epoch_loss / max(n_batches, 1)
461
- avg_ppl = epoch_ppl / max(n_batches, 1)
462
- logger.info(
463
- "Época %d concluída | avg_loss=%.4f | avg_ppl=%.2f | hyp_activations=%d | batches_ok=%d/%d",
464
- epoch + 1, avg_loss, avg_ppl, hypothesis_activations,
465
- n_batches, self.cfg.max_batches_per_epoch,
466
- )
467
-
468
- # Detecta falha sistêmica: se nenhum batch teve sucesso, marca erro
469
- if n_batches == 0:
470
- logger.error("Época %d: NENHUM batch teve sucesso — possível bug no pipeline", epoch + 1)
471
-
472
- self.history.append({
473
- "epoch": epoch + 1,
474
- "avg_loss": avg_loss,
475
- "avg_ppl": avg_ppl,
476
- "n_batches": n_batches,
477
- "hypothesis_activations": hypothesis_activations,
478
- "epoch_loss_total": epoch_loss,
479
- })
480
-
481
- elapsed = time.time() - start_time
482
- logger.info("Treinamento concluído em %.1fs", elapsed)
483
-
484
- return {
485
- "history": self.history,
486
- "metrics": self.metrics,
487
- "final_loss": self.history[-1]["avg_loss"] if self.history else 0.0,
488
- "final_ppl": self.history[-1]["avg_ppl"] if self.history else 0.0,
489
- "elapsed_s": elapsed,
490
- "synergy_history": self.synergy.history,
491
- "auto_history": self.auto.history,
492
- }
493
-
494
-
495
- __all__ = ["TrainerConfig", "CooperativeTrainer"]