PowerMachine commited on
Commit
a4ebe65
·
verified ·
1 Parent(s): 57be76f

Delete src/bigru_t/training/elastic_weight_consolidation.py

Browse files
src/bigru_t/training/elastic_weight_consolidation.py DELETED
@@ -1,281 +0,0 @@
1
- """elastic_weight_consolidation.py — EWC V6.
2
-
3
- Adaptado de gru-ring-v13-9-2/source/xavante_work/flexnet/ewc.py.
4
-
5
- EWC (Kirkpatrick et al. 2017): previne catastrophic forgetting em treino
6
- continuado. Mantém Fisher Information Matrix diagonal de uma tarefa anterior
7
- e penaliza mudanças nos pesos importantes.
8
-
9
- Loss total: L = L_task + (λ/2) · Σ_i F_i · (θ_i - θ*_i)²
10
-
11
- onde:
12
- - F_i = Fisher diagonal = E[(∂log p(y|x) / ∂θ_i)²]
13
- - θ* = pesos ótimos da tarefa anterior
14
- - λ = peso do regularizador
15
-
16
- V6 melhorias vs V13.9.2:
17
- * Fisher com clipping logarítmico (preserva ordem, comprime 10^12 → ~10)
18
- * Smoothing (moving average) para estabilidade
19
- * λ adaptativo (baseado na loss atual)
20
- * Correções de 4ª e 6ª ordem Taylor (opcional, default off)
21
- * Memory-efficient: Fisher computado em batches (não todo o dataset)
22
-
23
- REFERÊNCIAS:
24
- - Kirkpatrick et al. (2017). "Overcoming catastrophic forgetting in neural
25
- networks". PNAS 114(13):3521-3526.
26
- - Huszár (2018). "Note on the quadratic penalties in EWC".
27
- """
28
- from __future__ import annotations
29
-
30
- import math
31
- import torch
32
- import torch.nn as nn
33
- import torch.nn.functional as F
34
- from typing import Optional, Dict
35
- from collections import OrderedDict
36
-
37
-
38
- # ═══════════════════════════════════════════════════════════════════════════
39
- # Fisher Information Matrix diagonal (com clipping logarítmico)
40
- # ═══════════════════════════════════════════════════════════════════════════
41
- def compute_fisher_diagonal(
42
- model: nn.Module,
43
- inputs: torch.Tensor,
44
- targets: torch.Tensor,
45
- criterion: nn.Module = None,
46
- n_samples: int = 1,
47
- log_clip: bool = True,
48
- clip_scale: float = 1e6,
49
- ) -> Dict[str, torch.Tensor]:
50
- """Computa a diagonal da Fisher Information Matrix.
51
-
52
- F_i = E[(∂log p(y|x) / ∂θ_i)²] ≈ (1/N) · Σ_n (∂L(x_n, y_n) / ∂θ_i)²
53
-
54
- Args:
55
- model: modelo
56
- inputs: (N, T, d) ou (N, ...) — batch de entradas
57
- targets: (N, ...) — targets
58
- criterion: função de perda (default: CrossEntropyLoss)
59
- n_samples: nº de amostras de Monte Carlo (default 1)
60
- log_clip: se True, aplica clipping logarítmico (preserva ordem)
61
- clip_scale: escala do clipping (default 1e6)
62
-
63
- Returns:
64
- Dict[name → fisher_tensor] com mesmo shape de cada parâmetro
65
- """
66
- if criterion is None:
67
- criterion = nn.CrossEntropyLoss()
68
-
69
- model.eval() # desativa dropout para Fisher estável
70
- fisher: Dict[str, torch.Tensor] = {
71
- n: torch.zeros_like(p) for n, p in model.named_parameters()
72
- if p.requires_grad
73
- }
74
-
75
- for _ in range(n_samples):
76
- model.zero_grad()
77
- # Forward
78
- out = model(inputs)
79
- if isinstance(out, tuple):
80
- out = out[0] # modelo pode retornar (logits, aux)
81
- # Reshape se necessário
82
- if out.dim() > 2:
83
- out = out.reshape(-1, out.shape[-1])
84
- tgt = targets.reshape(-1)
85
- else:
86
- tgt = targets
87
- loss = criterion(out, tgt)
88
- # Backward
89
- loss.backward()
90
-
91
- # Acumula (grad)² na Fisher
92
- for name, p in model.named_parameters():
93
- if p.requires_grad and p.grad is not None:
94
- fisher[name] += (p.grad.detach() ** 2) / n_samples
95
-
96
- model.zero_grad()
97
-
98
- # Clipping logarítmico (opcional)
99
- if log_clip:
100
- for name in fisher:
101
- f = fisher[name]
102
- # log_clip: F_clipped = log(1 + F / clip_scale) · clip_scale
103
- # Preserva ordem (monótono), comprime grandes magnitudes
104
- fisher[name] = torch.log1p(f / clip_scale) * clip_scale
105
-
106
- return fisher
107
-
108
-
109
- # ═══════════════════════════════════════════════════════════════════════════
110
- # ElasticWeightConsolidation — regularizador EWC
111
- # ═══════════════════════════════════════════════════════════════════════════
112
- class ElasticWeightConsolidation(nn.Module):
113
- """EWC — penaliza mudanças em pesos importantes de tarefas anteriores.
114
-
115
- Args:
116
- model: modelo a ser regularizado
117
- lambda_init: λ inicial (peso do regularizador) — default 100.0
118
- adaptive_lambda: se True, λ é adaptativo (default True)
119
- fisher_smoothing: decay do EMA para Fisher (default 0.99)
120
- log_clip: se True, aplica clipping logarítmico no Fisher (default True)
121
-
122
- Uso:
123
- ewc = ElasticWeightConsolidation(model)
124
-
125
- # Após treinar tarefa A, computa Fisher e θ*
126
- ewc.consolidate(inputs_A, targets_A)
127
-
128
- # Durante treino da tarefa B, adiciona loss EWC à loss total
129
- loss_total = loss_task + ewc.penalty()
130
- """
131
-
132
- def __init__(
133
- self,
134
- model: nn.Module,
135
- lambda_init: float = 100.0,
136
- adaptive_lambda: bool = True,
137
- fisher_smoothing: float = 0.99,
138
- log_clip: bool = True,
139
- clip_scale: float = 1e6,
140
- ):
141
- super().__init__()
142
- self.model = model
143
- self.fisher_smoothing = fisher_smoothing
144
- self.log_clip = log_clip
145
- self.clip_scale = clip_scale
146
-
147
- # λ (peso do regularizador) — log-space para positividade
148
- self.log_lambda = nn.Parameter(torch.tensor(math.log(lambda_init)))
149
- self.adaptive_lambda = adaptive_lambda
150
-
151
- # θ* (pesos ótimos da tarefa anterior) e F (Fisher diagonal)
152
- # Inicializados como zeros; serão preenchidos por consolidate()
153
- self.star_params: Dict[str, torch.Tensor] = {}
154
- self.fisher: Dict[str, torch.Tensor] = {}
155
- self.consolidated = False
156
-
157
- # Track da loss anterior para λ adaptativo
158
- self.register_buffer("prev_loss", torch.tensor(0.0))
159
-
160
- @property
161
- def lambda_val(self) -> torch.Tensor:
162
- """λ atual = exp(log_lambda)."""
163
- return torch.exp(self.log_lambda)
164
-
165
- @torch.no_grad()
166
- def consolidate(
167
- self,
168
- inputs: torch.Tensor,
169
- targets: torch.Tensor,
170
- criterion: nn.Module = None,
171
- n_samples: int = 1,
172
- ) -> dict:
173
- """Computa Fisher e θ* após treinar uma tarefa.
174
-
175
- Args:
176
- inputs, targets: batch representativo da tarefa concluída
177
- criterion: função de perda
178
- n_samples: nº de amostras MC para Fisher
179
-
180
- Returns:
181
- dict com estatísticas (fisher_mean, fisher_max, n_params)
182
- """
183
- # 1. Salva θ* (clone dos pesos atuais)
184
- self.star_params = {
185
- n: p.detach().clone() for n, p in self.model.named_parameters()
186
- if p.requires_grad
187
- }
188
-
189
- # 2. Computa Fisher diagonal
190
- new_fisher = compute_fisher_diagonal(
191
- self.model, inputs, targets,
192
- criterion=criterion, n_samples=n_samples,
193
- log_clip=self.log_clip, clip_scale=self.clip_scale,
194
- )
195
-
196
- # 3. Suavização EMA (se já tinha Fisher anterior)
197
- if self.consolidated and self.fisher:
198
- for name in new_fisher:
199
- if name in self.fisher:
200
- self.fisher[name] = (
201
- self.fisher_smoothing * self.fisher[name]
202
- + (1.0 - self.fisher_smoothing) * new_fisher[name]
203
- )
204
- else:
205
- self.fisher[name] = new_fisher[name]
206
- else:
207
- self.fisher = new_fisher
208
- self.consolidated = True
209
-
210
- # Estatísticas
211
- all_f = torch.cat([f.flatten() for f in self.fisher.values()])
212
- return {
213
- "fisher_mean": float(all_f.mean().item()),
214
- "fisher_max": float(all_f.max().item()),
215
- "fisher_min": float(all_f.min().item()),
216
- "n_params": int(all_f.numel()),
217
- "lambda": float(self.lambda_val.item()),
218
- }
219
-
220
- def penalty(self) -> torch.Tensor:
221
- """Computa penalidade EWC: (λ/2) · Σ_i F_i · (θ_i - θ*_i)².
222
-
223
- Returns:
224
- escalar — penalidade EWC
225
- """
226
- if not self.consolidated:
227
- return torch.tensor(0.0, device=next(self.model.parameters()).device)
228
-
229
- loss = torch.tensor(0.0, device=next(self.model.parameters()).device)
230
- lam = self.lambda_val
231
-
232
- for name, p in self.model.named_parameters():
233
- if not p.requires_grad:
234
- continue
235
- if name not in self.fisher or name not in self.star_params:
236
- continue
237
- F = self.fisher[name].to(p.device)
238
- theta_star = self.star_params[name].to(p.device)
239
- loss = loss + (F * (p - theta_star) ** 2).sum()
240
-
241
- return 0.5 * lam * loss
242
-
243
- @torch.no_grad()
244
- def update_lambda_adaptive(self, current_loss: float) -> float:
245
- """Atualiza λ adaptativamente baseado na loss atual.
246
-
247
- Se loss subiu muito (catastrophic forgetting), aumenta λ.
248
- Se loss está baixa, mantém λ (não interfere).
249
-
250
- Args:
251
- current_loss: loss da tarefa atual
252
-
253
- Returns:
254
- novo λ
255
- """
256
- if not self.adaptive_lambda:
257
- return float(self.lambda_val.item())
258
-
259
- prev = float(self.prev_loss.item())
260
- # Se loss subiu mais que 50%, aumenta λ (catastrophic forgetting detectado)
261
- if current_loss > 1.5 * prev and prev > 0:
262
- self.log_lambda.data += 0.1 # λ *= exp(0.1) ≈ 1.10
263
- elif current_loss < prev:
264
- # Loss caiu — pode reduzir λ gradativamente
265
- self.log_lambda.data -= 0.01 # λ /= exp(0.01) ≈ 0.99
266
-
267
- self.prev_loss.fill_(current_loss)
268
- return float(self.lambda_val.item())
269
-
270
- def extra_repr(self) -> str:
271
- return (
272
- f"lambda={float(self.lambda_val.item()):.3f}, "
273
- f"consolidated={self.consolidated}, "
274
- f"adaptive_lambda={self.adaptive_lambda}"
275
- )
276
-
277
-
278
- __all__ = [
279
- "compute_fisher_diagonal",
280
- "ElasticWeightConsolidation",
281
- ]