PowerMachine commited on
Commit
488a878
·
verified ·
1 Parent(s): 1202546

Upload cnn_bigru/training/trainer.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. cnn_bigru/training/trainer.py +495 -0
cnn_bigru/training/trainer.py ADDED
@@ -0,0 +1,495 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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"]