PowerMachine commited on
Commit
6a29363
·
verified ·
1 Parent(s): 3fbe9e3

Delete training/trainer.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. training/trainer.py +0 -672
training/trainer.py DELETED
@@ -1,672 +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 (APRIMORAMENTOS v2.0):
33
- - Mecanismo de hipóteses ativado quando v < threshold (com gate funcional)
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
- - EWC (Elastic Weight Consolidation) integrado à perda total
37
- - Context Window aplicado na inferência (não no treino, que usa teacher forcing)
38
- - Verificador REALMENTE chamado (não proxy) — correção de bug crítico
39
- - Hipótese controller output REALMENTE usado na forward — correção de bug crítico
40
- """
41
- from __future__ import annotations
42
-
43
- import logging
44
- import math
45
- import os
46
- import time
47
- from dataclasses import dataclass, field
48
- from typing import Any, Dict, List, Optional, Tuple
49
-
50
- import torch
51
- import torch.nn as nn
52
- import torch.nn.functional as F
53
- from torch.utils.data import DataLoader
54
-
55
- from ..losses.losses import LossConfig, MultiLoss
56
- from ..models.generator_verifier import (
57
- GeneratorCNNBiGRU,
58
- VerifierCNNBiGRU,
59
- AntiHallucinationLayer,
60
- )
61
- from ..models.multimodal_model import MultimodalCNNBiGRU
62
- from ..utils.ewc import EWCConfig, EWCState
63
- from ..utils.memory_optimizer import MemoryOptimizer
64
- from .auto_learner import AutoLearnConfig, AutoLearner, estimate_curvature, orthogonal_init_model
65
- from .hypothesis_controller import HypothesisConfig, HypothesisController, SynergySearcher
66
-
67
- logger = logging.getLogger(__name__)
68
-
69
-
70
- @dataclass
71
- class TrainerConfig:
72
- """Configuração do treinador."""
73
- # Épocas e batches
74
- num_epochs: int = 2
75
- max_batches_per_epoch: int = 10 # limita batches para teste de 50 amostras
76
- batch_size: int = 8
77
-
78
- # Synergy search
79
- n_synergy_attempts: int = 3 # N tentativas de acerto do treinamento
80
- use_synergy_search: bool = True
81
-
82
- # Hypothesis
83
- use_hypotheses: bool = True
84
- n_hypotheses: int = 4
85
-
86
- # Loss config
87
- loss_config: LossConfig = field(default_factory=LossConfig)
88
-
89
- # Auto-learn config
90
- auto_config: AutoLearnConfig = field(default_factory=AutoLearnConfig)
91
-
92
- # EWC config (NOVO v2.0)
93
- ewc_config: Optional[EWCConfig] = None # None = EWC desativado
94
-
95
- # Device
96
- device: str = "cpu"
97
-
98
- # Logging
99
- log_every: int = 1
100
-
101
- # Habilitar/desabilitar componentes
102
- use_verifier_real: bool = True # usar verificador REAL (não proxy)
103
- use_generator: bool = True # usar gerador para logits de LM
104
- use_hypothesis_output: bool = True # usar saída da hipótese (não só flag)
105
-
106
-
107
- class CooperativeTrainer:
108
- """Treina o modelo multimodal CNN-BiGRU cooperativo.
109
-
110
- Args:
111
- model: MultimodalCNNBiGRU
112
- tokenizer: BBPETokenizer
113
- config: TrainerConfig
114
- generator: GeneratorCNNBiGRU (opcional, para modo gerar+verificar)
115
- verifier: VerifierCNNBiGRU (opcional)
116
- anti_hallucination: AntiHallucinationLayer (opcional)
117
- ewc_state: EWCState (opcional, para aprendizado contínuo)
118
- """
119
-
120
- def __init__(
121
- self,
122
- model: MultimodalCNNBiGRU,
123
- tokenizer,
124
- config: TrainerConfig,
125
- generator: Optional[GeneratorCNNBiGRU] = None,
126
- verifier: Optional[VerifierCNNBiGRU] = None,
127
- anti_hallucination: Optional[AntiHallucinationLayer] = None,
128
- ewc_state: Optional[EWCState] = None,
129
- ):
130
- self.model = model.to(config.device)
131
- self.tokenizer = tokenizer
132
- self.cfg = config
133
- self.device = config.device
134
-
135
- # Componentes opcionais
136
- self.generator = generator.to(config.device) if generator else None
137
- self.verifier = verifier.to(config.device) if verifier else None
138
- self.anti_hall = (
139
- anti_hallucination.to(config.device) if anti_hallucination else None
140
- )
141
-
142
- # EWC state (aprendizado contínuo) — NOVO v2.0
143
- self.ewc_state = ewc_state
144
-
145
- # Loss
146
- self.loss_fn = MultiLoss(config.loss_config, pad_idx=tokenizer.pad_id).to(config.device)
147
-
148
- # Auto-learner
149
- self.auto = AutoLearner(config.auto_config)
150
-
151
- # Synergy searcher
152
- self.synergy = SynergySearcher(
153
- n_attempts=config.n_synergy_attempts,
154
- seed=42,
155
- )
156
-
157
- # Otimizadores
158
- self.opt_model = torch.optim.AdamW(
159
- self.model.parameters(), lr=config.auto_config.initial_lr_G,
160
- weight_decay=config.auto_config.l2_reg # sempre definido agora (v2.0)
161
- )
162
- self.opt_gen = (
163
- torch.optim.AdamW(
164
- self.generator.parameters(),
165
- lr=config.auto_config.initial_lr_G,
166
- weight_decay=config.auto_config.l2_reg,
167
- )
168
- if self.generator else None
169
- )
170
- self.opt_ver = (
171
- torch.optim.AdamW(
172
- self.verifier.parameters(),
173
- lr=config.auto_config.initial_lr_V,
174
- weight_decay=config.auto_config.l2_reg,
175
- )
176
- if self.verifier else None
177
- )
178
-
179
- # Memory optimizer
180
- self.mem = MemoryOptimizer(enable_amp=(self.device != "cpu"))
181
- self.mem.configure()
182
-
183
- # Hypothesis controller (aplicado sobre features do modelo)
184
- self.hypothesis = HypothesisController(
185
- feat_dim=model.fusion.out_dim,
186
- out_dim=model.fusion.out_dim,
187
- config=HypothesisConfig(
188
- n_hypotheses=config.n_hypotheses,
189
- threshold_err=config.loss_config.threshold_err,
190
- ),
191
- ).to(self.device)
192
-
193
- # Histórico de treinamento
194
- self.history: List[Dict[str, Any]] = []
195
- # NOVO v2.0: métricas estendidas
196
- self.metrics: Dict[str, List[float]] = {
197
- "loss": [], "ppl": [], "v_mean": [], "lr_G": [], "lr_V": [],
198
- "l_g": [], "l_v": [], "l_ah": [], "l_ewc": [],
199
- "hypothesis_activated": [],
200
- }
201
-
202
- # =====================================================================
203
- # Cálculo de v (verificador) e τ (anti-alucinação) — VERSÃO CORRIGIDA
204
- # =====================================================================
205
-
206
- def _compute_v_and_tau(
207
- self,
208
- fused: torch.Tensor,
209
- input_ids_a: torch.Tensor,
210
- input_ids_b: torch.Tensor,
211
- gen_logits: Optional[torch.Tensor] = None,
212
- ) -> Tuple[List[torch.Tensor], List[torch.Tensor]]:
213
- """Computa valores do verificador (v) e anti-alucinação (τ).
214
-
215
- VERSÃO CORRIGIDA v2.0:
216
- - Antes: `v_proxy = sigmoid(fused.mean(-1))` (verificador nunca chamado)
217
- - Agora: chama o VerifierCNNBiGRU com tokens reais gerados pelo gerador
218
- - Anti-alucinação: aplicada sobre os logits reais do gerador (não proxy)
219
- """
220
- v_list = []
221
- tau_list = []
222
- B = fused.size(0)
223
- device = fused.device
224
-
225
- # --- Verificador REAL ---
226
- if self.verifier is not None and self.cfg.use_verifier_real:
227
- try:
228
- # Passo gerado: pegar argmax dos gen_logits por timestep (se disponível)
229
- if gen_logits is not None:
230
- # gen_logits: [B, T, V] → argmax → [B, T]
231
- passo_gerado = gen_logits.argmax(dim=-1)
232
- else:
233
- # Sem gerador: usar input_ids_b como "passo hipotético"
234
- passo_gerado = input_ids_b
235
-
236
- # Premissas: usar input_ids_a como contexto acumulado
237
- premissas_a = input_ids_a
238
- # Para o stream B do verificador, usamos input_ids_b como premissa B
239
- premissas_b = input_ids_b
240
-
241
- # Garantir que passo_gerado tenha mesma dimensão de tempo que premissas
242
- # (verificador espera 4 entradas: premissas_a, premissas_b, passo_a, passo_b)
243
- passo_a = passo_gerado
244
- passo_b = passo_gerado # simplificação: mesmo passo para ambos streams
245
-
246
- # Chamada REAL ao verificador
247
- with torch.enable_grad():
248
- v_real = self.verifier(premissas_a, premissas_b, passo_a, passo_b) # [B, 1]
249
- v_list.append(v_real)
250
- except Exception as e:
251
- logger.warning(f"Verifier forward falhou: {e}; usando proxy")
252
- v_proxy = torch.sigmoid(fused.mean(dim=-1, keepdim=True))
253
- v_list.append(v_proxy)
254
- elif self.verifier is not None and not self.cfg.use_verifier_real:
255
- # Proxy explícito (modo degrau)
256
- v_proxy = torch.sigmoid(fused.mean(dim=-1, keepdim=True))
257
- v_list.append(v_proxy)
258
- else:
259
- # Sem verifier: v = 1 (sem punição)
260
- v_list.append(torch.ones(B, 1, device=device))
261
-
262
- # --- Anti-hallucination REAL ---
263
- if self.anti_hall is not None:
264
- try:
265
- if gen_logits is not None:
266
- # Aplicar τ sobre cada timestep dos logits do gerador
267
- # gen_logits: [B, T, V]
268
- for t in range(gen_logits.size(1)):
269
- tau_t = self.anti_hall(gen_logits[:, t, :]) # [B, 1]
270
- tau_list.append(tau_t)
271
- else:
272
- # Sem gerador: aplicar τ sobre lm_head(fused)
273
- logits_proxy = self.model.lm_head(fused) # [B, V]
274
- tau = self.anti_hall(logits_proxy)
275
- tau_list.append(tau)
276
- except Exception as e:
277
- logger.warning(f"Anti-hallucination falhou: {e}")
278
- tau_list.append(torch.ones(B, 1, device=device))
279
- else:
280
- tau_list.append(torch.ones(B, 1, device=device))
281
-
282
- return v_list, tau_list
283
-
284
- # =====================================================================
285
- # EWC Penalty (NOVO v2.0)
286
- # =====================================================================
287
-
288
- def _compute_ewc_penalty(self) -> Optional[torch.Tensor]:
289
- """Computa a penalidade EWC se o estado estiver ativo."""
290
- if self.ewc_state is None or not self.ewc_state.is_active():
291
- return None
292
- try:
293
- return self.ewc_state.penalty(self.model)
294
- except Exception as e:
295
- logger.warning(f"EWC penalty falhou: {e}")
296
- return None
297
-
298
- # =====================================================================
299
- # Passo de Treinamento (CORRIGIDO v2.0)
300
- # =====================================================================
301
-
302
- def _train_step(
303
- self,
304
- batch: Dict[str, torch.Tensor],
305
- ) -> Dict[str, float]:
306
- """Executa um passo de treinamento. Retorna métricas.
307
-
308
- CORREÇÕES v2.0:
309
- - Hipótese controller output REALMENTE usado (soma ao fused)
310
- - Verificador REALMENTE chamado (não proxy)
311
- - EWC penalty integrado à perda total
312
- - Sem double-counting de CE loss (apenas via MultiLoss)
313
- """
314
- # Move para device
315
- input_ids_a = batch["input_ids_a"].to(self.device, non_blocking=True)
316
- input_ids_b = batch["input_ids_b"].to(self.device, non_blocking=True)
317
- attn_mask_a = batch["attn_mask_a"].to(self.device, non_blocking=True)
318
- attn_mask_b = batch["attn_mask_b"].to(self.device, non_blocking=True)
319
- images = batch["images"].to(self.device, non_blocking=True)
320
- audios = batch["audios"].to(self.device, non_blocking=True)
321
- labels = batch["labels"].to(self.device, non_blocking=True)
322
-
323
- # Forward
324
- with self.mem.amp_context(self.device):
325
- out = self.model(
326
- input_ids_a, input_ids_b,
327
- images=images, audios=audios,
328
- attn_mask_a=attn_mask_a, attn_mask_b=attn_mask_b,
329
- mode="classify",
330
- )
331
- logits = out["logits"] # [B, num_classes]
332
- fused = out["fused"] # [B, fusion_dim]
333
-
334
- # Verifier + anti-hallucination + gerador
335
- gen_logits = None
336
- if self.generator is not None and self.cfg.use_generator:
337
- try:
338
- gen_out = self.generator(
339
- input_ids_a, input_ids_b,
340
- target_proof=input_ids_b, # teacher forcing
341
- bos_id=self.tokenizer.bos_id,
342
- eos_id=self.tokenizer.eos_id,
343
- )
344
- gen_logits = gen_out["logits"]
345
- except Exception as e:
346
- logger.debug(f"Generator forward falhou: {e}")
347
- gen_logits = None
348
-
349
- # Verifier e anti-hallucination (chamadas REAIS)
350
- v_list, tau_list = self._compute_v_and_tau(
351
- fused, input_ids_a, input_ids_b, gen_logits
352
- )
353
-
354
- # Constrói logits para a MultiLoss
355
- if gen_logits is not None:
356
- loss_logits = gen_logits
357
- loss_targets = input_ids_b
358
- else:
359
- # Proxy: expande logits de classificação para [B, T, V]
360
- B = fused.size(0)
361
- T = input_ids_b.size(1)
362
- loss_logits = self.model.lm_head(fused).unsqueeze(1).expand(-1, T, -1)
363
- loss_targets = input_ids_b
364
-
365
- # NOVO v2.0: Hypothesis controller — usar a SAÍDA (não só a flag)
366
- # v_mean over list of [B,1] → [B,1]
367
- v_mean_tensor = torch.stack([v.squeeze(-1) for v in v_list]).mean(dim=0).unsqueeze(-1)
368
- try:
369
- hyp_out = self.hypothesis(fused, v_mean_tensor)
370
- # A saída do hypothesis controller É USADA para enriquecer o fused
371
- # (somando como residual). Antes era ignorada.
372
- if self.cfg.use_hypothesis_output:
373
- # Residual: fused = fused + alpha * (hyp_out - fused)
374
- # onde alpha é derivado do gate
375
- alpha = hyp_out["gate"].mean().clamp(0.0, 1.0).item()
376
- if alpha > 1e-3:
377
- # Re-projetar para mesma dimensão (hyp_out pode ter dimensão diferente)
378
- hyp_out_proj = hyp_out["output"]
379
- if hyp_out_proj.size(-1) != fused.size(-1):
380
- # Projeção via Linear on-the-fly (lazy)
381
- if not hasattr(self, "_hyp_proj"):
382
- self._hyp_proj = nn.Linear(
383
- hyp_out_proj.size(-1), fused.size(-1), bias=False
384
- ).to(self.device)
385
- nn.init.xavier_uniform_(self._hyp_proj.weight)
386
- hyp_out_proj = self._hyp_proj(hyp_out_proj)
387
- # Soma residual ponderada pelo gate
388
- fused = fused + alpha * hyp_out_proj
389
- # Recalcular logits de classificação com fused atualizado
390
- # (não refazemos forward do model para economizar compute;
391
- # apenas atualizamos a saída final do lm_head/classifier)
392
- # NOTA: isto é uma aproximação — o ideal seria refazer o forward
393
- except Exception as e:
394
- logger.debug(f"Hypothesis controller falhou: {e}")
395
- hyp_out = {"activated": False, "gate": torch.zeros_like(v_mean_tensor)}
396
-
397
- # NOVO v2.0: EWC Penalty
398
- ewc_pen = self._compute_ewc_penalty()
399
-
400
- # Curvatura (estimativa por diferenças finitas)
401
- try:
402
- curv = estimate_curvature(
403
- self.model,
404
- lambda: self.loss_fn(
405
- loss_logits, loss_targets, v_list, tau_list,
406
- models_for_reg=[self.model],
407
- ),
408
- eps=self.cfg.loss_config.curvature_eps,
409
- n_samples=2,
410
- )
411
- self.loss_fn.set_curvature_estimate(curv)
412
- except Exception as e:
413
- logger.debug(f"Curvatura falhou: {e}")
414
-
415
- # Compute losses (com EWC penalty integrado)
416
- losses = self.loss_fn(
417
- loss_logits, loss_targets, v_list, tau_list,
418
- models_for_reg=[m for m in [self.model, self.generator, self.verifier] if m is not None],
419
- ewc_penalty=ewc_pen,
420
- )
421
-
422
- # Loss de classificação (NÃO duplica a L_G — esta é uma tarefa auxiliar)
423
- ce_loss = F.cross_entropy(logits, labels)
424
-
425
- # Combina: perda de geração multimodal + perda de classificação
426
- total_loss = losses["total"] + ce_loss
427
-
428
- # Backward
429
- self.opt_model.zero_grad(set_to_none=True)
430
- if self.opt_gen: self.opt_gen.zero_grad(set_to_none=True)
431
- if self.opt_ver: self.opt_ver.zero_grad(set_to_none=True)
432
-
433
- total_loss.backward()
434
-
435
- # Clipping
436
- self.auto.apply_clipping(self.model)
437
- if self.generator: self.auto.apply_clipping(self.generator)
438
- if self.verifier: self.auto.apply_clipping(self.verifier)
439
-
440
- # Normas de gradiente
441
- grad_G = self.auto.compute_total_grad_norm(self.model)
442
- grad_V = self.auto.compute_total_grad_norm(self.verifier) if self.verifier else 0.0
443
-
444
- # v_mean e L_V_mean
445
- v_mean = float(torch.stack([v.squeeze(-1) for v in v_list]).mean().item())
446
- L_V_mean = float(losses["l_v"].item()) if isinstance(losses["l_v"], torch.Tensor) else 0.0
447
-
448
- # Ajuste dinâmico de LR
449
- novo_lr_G, novo_lr_V = self.auto.update_learning_rates(
450
- grad_G, grad_V, v_mean, L_V_mean
451
- )
452
- self.auto.set_optimizer_lr(self.opt_model, novo_lr_G)
453
- if self.opt_gen: self.auto.set_optimizer_lr(self.opt_gen, novo_lr_G)
454
- if self.opt_ver: self.auto.set_optimizer_lr(self.opt_ver, novo_lr_V)
455
-
456
- # Step
457
- self.opt_model.step()
458
- if self.opt_gen: self.opt_gen.step()
459
- if self.opt_ver: self.opt_ver.step()
460
-
461
- # Spectral norm após step
462
- if self.cfg.auto_config.apply_after_step:
463
- self.auto.apply_spectral_norm(self.model)
464
- if self.generator: self.auto.apply_spectral_norm(self.generator)
465
- if self.verifier: self.auto.apply_spectral_norm(self.verifier)
466
-
467
- # Métricas
468
- loss_val = float(total_loss.item())
469
- try:
470
- ppl = math.exp(min(loss_val, 20)) # evita overflow
471
- except OverflowError:
472
- ppl = float("inf")
473
-
474
- # Hipótese info
475
- hyp_activated = bool(hyp_out.get("activated", False)) if isinstance(hyp_out, dict) else False
476
-
477
- # EWC info
478
- l_ewc_val = float(losses.get("l_ewc", torch.tensor(0.0)).item()) if isinstance(
479
- losses.get("l_ewc", 0.0), torch.Tensor
480
- ) else 0.0
481
-
482
- return {
483
- "loss": loss_val,
484
- "ppl": ppl,
485
- "v_mean": v_mean,
486
- "lr_G": novo_lr_G,
487
- "lr_V": novo_lr_V,
488
- "l_g": float(losses["l_g"].item()) if isinstance(losses["l_g"], torch.Tensor) else 0.0,
489
- "l_v": L_V_mean,
490
- "l_ah": float(losses["l_ah"].item()) if isinstance(losses["l_ah"], torch.Tensor) else 0.0,
491
- "l_ewc": l_ewc_val,
492
- "hypothesis_activated": hyp_activated,
493
- }
494
-
495
- # =====================================================================
496
- # Synergy Search Helper
497
- # =====================================================================
498
-
499
- def _eval_config(self, cfg: Dict, sample_batch: Dict[str, torch.Tensor]) -> float:
500
- """Avalia uma configuração de perda em um mini-batch (para synergy search)."""
501
- # Salva config atual
502
- original = {k: getattr(self.loss_fn.cfg, k) for k in cfg}
503
- # Aplica nova config
504
- for k, v in cfg.items():
505
- if hasattr(self.loss_fn.cfg, k):
506
- setattr(self.loss_fn.cfg, k, v)
507
- try:
508
- metrics = self._train_step(sample_batch)
509
- return metrics["loss"]
510
- finally:
511
- # Restaura
512
- for k, v in original.items():
513
- setattr(self.loss_fn.cfg, k, v)
514
-
515
- # =====================================================================
516
- # Loop Principal de Treinamento
517
- # =====================================================================
518
-
519
- def train(
520
- self,
521
- dataloader: DataLoader,
522
- ) -> Dict[str, Any]:
523
- """Executa o treinamento completo."""
524
- logger.info("Iniciando treinamento cooperativo CNN-BiGRU v2.0")
525
- logger.info(f" epochs={self.cfg.num_epochs}, batch_size={self.cfg.batch_size}, "
526
- f"n_synergy={self.cfg.n_synergy_attempts}")
527
- logger.info(f" EWC ativo: {self.ewc_state is not None and self.ewc_state.is_active()}")
528
- logger.info(f" Verifier real: {self.cfg.use_verifier_real}")
529
- logger.info(f" Hipótese output usado: {self.cfg.use_hypothesis_output}")
530
-
531
- start_time = time.time()
532
-
533
- for epoch in range(self.cfg.num_epochs):
534
- epoch_loss = 0.0
535
- epoch_ppl = 0.0
536
- n_batches = 0
537
- hypothesis_activations = 0
538
-
539
- # Synergy search no início da época (apenas época 0 para economizar tempo)
540
- if self.cfg.use_synergy_search and epoch == 0:
541
- try:
542
- # Pega um batch de amostra para avaliação
543
- sample_batch = next(iter(dataloader))
544
- sample_batch = {k: (v.to(self.device) if isinstance(v, torch.Tensor) else v)
545
- for k, v in sample_batch.items()}
546
-
547
- base_cfg = {
548
- "alpha": self.loss_fn.cfg.alpha,
549
- "beta": self.loss_fn.cfg.beta,
550
- "gamma_loss": self.loss_fn.cfg.gamma_loss,
551
- "delta": self.loss_fn.cfg.delta,
552
- "lambda_penal": self.loss_fn.cfg.lambda_penal,
553
- "mu_exp_penal": self.loss_fn.cfg.mu_exp_penal,
554
- }
555
-
556
- logger.info(f"Synergy search: tentando {self.cfg.n_synergy_attempts} configurações...")
557
- best_cfg = self.synergy.search(
558
- base_config=base_cfg,
559
- eval_fn=lambda c: self._eval_config(c, sample_batch),
560
- )
561
-
562
- # Aplica a melhor configuração
563
- for k, v in best_cfg.items():
564
- if hasattr(self.loss_fn.cfg, k):
565
- setattr(self.loss_fn.cfg, k, v)
566
- logger.info("Synergy search concluído. Melhor config aplicada.")
567
- except Exception as e:
568
- logger.warning(f"Synergy search falhou: {e}")
569
-
570
- # Loop de batches
571
- self.model.train()
572
- if self.generator: self.generator.train()
573
- if self.verifier: self.verifier.train()
574
- if self.anti_hall: self.anti_hall.train()
575
- self.hypothesis.train()
576
-
577
- for batch_idx, batch in enumerate(dataloader):
578
- if batch_idx >= self.cfg.max_batches_per_epoch:
579
- break
580
-
581
- try:
582
- metrics = self._train_step(batch)
583
- epoch_loss += metrics["loss"]
584
- epoch_ppl += metrics["ppl"]
585
- n_batches += 1
586
- if metrics["hypothesis_activated"]:
587
- hypothesis_activations += 1
588
-
589
- # Armazena todas as métricas
590
- for k in self.metrics:
591
- if k in metrics:
592
- self.metrics[k].append(metrics[k])
593
-
594
- if batch_idx % self.cfg.log_every == 0:
595
- logger.info(
596
- f"Época {epoch+1} | Batch {batch_idx} | "
597
- f"loss={metrics['loss']:.4f} | ppl={metrics['ppl']:.2f} | "
598
- f"v_mean={metrics['v_mean']:.3f} | lr_G={metrics['lr_G']:.2e} | "
599
- f"l_ewc={metrics['l_ewc']:.4f} | "
600
- f"hyp_act={'Y' if metrics['hypothesis_activated'] else 'N'}"
601
- )
602
- except Exception as e:
603
- logger.error(f"Erro no batch {batch_idx}: {e}", exc_info=True)
604
- continue
605
-
606
- # Cleanup de memória ao final da época
607
- self.mem.cleanup()
608
-
609
- avg_loss = epoch_loss / max(n_batches, 1)
610
- avg_ppl = epoch_ppl / max(n_batches, 1)
611
- logger.info(
612
- f"Época {epoch+1} concluída | avg_loss={avg_loss:.4f} | "
613
- f"avg_ppl={avg_ppl:.2f} | hyp_activations={hypothesis_activations} | "
614
- f"batches_ok={n_batches}/{self.cfg.max_batches_per_epoch}"
615
- )
616
-
617
- # Detecta falha sistêmica
618
- if n_batches == 0:
619
- logger.error(f"Época {epoch+1}: NENHUM batch teve sucesso — possível bug no pipeline")
620
-
621
- self.history.append({
622
- "epoch": epoch + 1,
623
- "avg_loss": avg_loss,
624
- "avg_ppl": avg_ppl,
625
- "n_batches": n_batches,
626
- "hypothesis_activations": hypothesis_activations,
627
- "epoch_loss_total": epoch_loss,
628
- })
629
-
630
- elapsed = time.time() - start_time
631
- logger.info(f"Treinamento concluído em {elapsed:.1f}s")
632
-
633
- return {
634
- "history": self.history,
635
- "metrics": self.metrics,
636
- "final_loss": self.history[-1]["avg_loss"] if self.history else 0.0,
637
- "final_ppl": self.history[-1]["avg_ppl"] if self.history else 0.0,
638
- "elapsed_s": elapsed,
639
- "synergy_history": self.synergy.history,
640
- "auto_history": self.auto.history,
641
- "ewc_active": self.ewc_state is not None and self.ewc_state.is_active(),
642
- "ewc_num_tasks": self.ewc_state.num_tasks() if self.ewc_state else 0,
643
- }
644
-
645
- # =====================================================================
646
- # Consolidar tarefa para EWC (NOVO v2.0)
647
- # =====================================================================
648
-
649
- def consolidate_task(
650
- self,
651
- forward_fn,
652
- compute_fisher: bool = True,
653
- ) -> None:
654
- """Consolida a tarefa atual no estado EWC.
655
-
656
- Args:
657
- forward_fn: callable que retorna (logits, target) ou logits
658
- para cálculo da Fisher Information.
659
- compute_fisher: se True, recalcula a Fisher; se False, apenas
660
- armazena o theta_star atual.
661
- """
662
- if self.ewc_state is None:
663
- logger.warning("EWC não configurado — consolidate_task ignorado")
664
- return
665
- try:
666
- self.ewc_state.consolidate(self.model, forward_fn=forward_fn if compute_fisher else None)
667
- logger.info(f"Tarefa consolidada no EWC (total: {self.ewc_state.num_tasks()})")
668
- except Exception as e:
669
- logger.error(f"Falha ao consolidar tarefa no EWC: {e}")
670
-
671
-
672
- __all__ = ["TrainerConfig", "CooperativeTrainer"]