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

Upload cnn_bigru/training/hypothesis_controller.py with huggingface_hub

Browse files
cnn_bigru/training/hypothesis_controller.py ADDED
@@ -0,0 +1,276 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """hypothesis_controller.py — Mecanismo de hipóteses para autoaprendizado.
2
+
3
+ Implementa o "acionamento de hipóteses quando ocorre punições no treinamento"
4
+ descrito pelo usuário. Quando o verificador pune um passo (v < threshold),
5
+ o sistema ativa camadas de hipótese que tentam N combinações alternativas
6
+ de sinergias entre as redes, buscando a configuração de menor perda.
7
+
8
+ Matemática:
9
+ Quando v_t < THRESHOLD_ERR:
10
+ hipótese_i = f_alternativa_i(input, params_perturbados_i)
11
+ loss_i = compute_loss(hipótese_i)
12
+ melhor = argmin(loss_i)
13
+ params <- params + lr * grad(melhor)
14
+
15
+ As "camadas de hipótese" são camadas lineares paralelas (hipóteses)
16
+ ativadas condicionalmente. A ativação é diferenciável via Gumbel-Softmax.
17
+ """
18
+ from __future__ import annotations
19
+
20
+ import logging
21
+ import math
22
+ import random
23
+ from dataclasses import dataclass
24
+ from typing import Dict, List, Optional, Tuple
25
+
26
+ import torch
27
+ import torch.nn as nn
28
+ import torch.nn.functional as F
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+
33
+ @dataclass
34
+ class HypothesisConfig:
35
+ """Configuração do mecanismo de hipóteses."""
36
+ n_hypotheses: int = 4 # N tentativas de acerto
37
+ threshold_err: float = 0.5 # abaixo deste valor, ativa hipóteses
38
+ gumbel_temp: float = 1.0 # temperatura do Gumbel-Softmax
39
+ gumbel_hard: bool = False # se True, usa argmax (não diferenciável)
40
+ perturbation_std: float = 0.05 # desvio padrão da perturbação
41
+
42
+
43
+ class HypothesisLayer(nn.Module):
44
+ """Camada de hipótese: N transformações lineares paralelas.
45
+
46
+ Para cada amostra, calcula N hipóteses e seleciona a melhor via
47
+ Gumbel-Softmax (diferenciável) ou argmax (não diferenciável).
48
+
49
+ A "seleção" é feita baseada em um score externo (e.g. negativo da perda).
50
+ """
51
+
52
+ def __init__(
53
+ self,
54
+ in_dim: int,
55
+ out_dim: int,
56
+ n_hypotheses: int = 4,
57
+ activation: str = "relu",
58
+ ):
59
+ super().__init__()
60
+ self.n_hypotheses = n_hypotheses
61
+ self.in_dim = in_dim
62
+ self.out_dim = out_dim
63
+
64
+ # N transformações lineares paralelas
65
+ self.hypotheses = nn.ModuleList([
66
+ nn.Linear(in_dim, out_dim) for _ in range(n_hypotheses)
67
+ ])
68
+ # Inicialização diferente para cada hipótese (diversidade)
69
+ for i, h in enumerate(self.hypotheses):
70
+ nn.init.xavier_uniform_(h.weight, gain=0.5 + 0.2 * i)
71
+ nn.init.zeros_(h.bias)
72
+
73
+ # Scoreador: aprende a pontuar cada hipótese dado o input
74
+ self.scorer = nn.Linear(in_dim, n_hypotheses)
75
+
76
+ self.activation = activation
77
+
78
+ def _activate(self, x: torch.Tensor) -> torch.Tensor:
79
+ if self.activation == "relu":
80
+ return F.relu(x)
81
+ elif self.activation == "gelu":
82
+ return F.gelu(x)
83
+ elif self.activation == "tanh":
84
+ return torch.tanh(x)
85
+ return x
86
+
87
+ def forward(
88
+ self,
89
+ x: torch.Tensor,
90
+ gumbel_temp: float = 1.0,
91
+ gumbel_hard: bool = False,
92
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
93
+ """
94
+ Args:
95
+ x: [B, in_dim]
96
+ gumbel_temp: temperatura do Gumbel-Softmax
97
+ gumbel_hard: se True, usa argmax
98
+
99
+ Returns:
100
+ output: [B, out_dim]
101
+ weights: [B, n_hypotheses]
102
+ """
103
+ B = x.size(0)
104
+
105
+ # Calcula todas as N hipóteses: [B, N, out_dim]
106
+ hyps = torch.stack([self._activate(h(x)) for h in self.hypotheses], dim=1)
107
+
108
+ # Scoreia cada hipótese
109
+ scores = self.scorer(x) # [B, N]
110
+
111
+ # Gumbel-Softmax para seleção diferenciável
112
+ if gumbel_hard:
113
+ # Hard: argmax one-hot
114
+ weights = F.one_hot(scores.argmax(dim=-1), num_classes=self.n_hypotheses).float()
115
+ else:
116
+ weights = F.gumbel_softmax(scores, tau=gumbel_temp, hard=False)
117
+
118
+ # Combina: weighted sum
119
+ output = (hyps * weights.unsqueeze(-1)).sum(dim=1) # [B, out_dim]
120
+
121
+ return output, weights
122
+
123
+
124
+ class HypothesisController(nn.Module):
125
+ """Controlador de hipóteses: ativa camadas quando há punição.
126
+
127
+ Recebe:
128
+ - features do passo atual
129
+ - valor v do verificador (probabilidade de correção)
130
+ - loss atual
131
+
132
+ Se v < threshold, ativa as hipóteses e tenta N combinações.
133
+ Caso contrário, passa direto (identity).
134
+ """
135
+
136
+ def __init__(
137
+ self,
138
+ feat_dim: int,
139
+ out_dim: int,
140
+ config: HypothesisConfig,
141
+ ):
142
+ super().__init__()
143
+ self.cfg = config
144
+ self.feat_dim = feat_dim
145
+ self.out_dim = out_dim
146
+
147
+ # Camadas de hipótese paralelas
148
+ self.hyp_layer = HypothesisLayer(
149
+ in_dim=feat_dim,
150
+ out_dim=out_dim,
151
+ n_hypotheses=config.n_hypotheses,
152
+ activation="gelu",
153
+ )
154
+
155
+ # Porta de ativação (gating) baseada em v
156
+ # Se v alto, mantém identidade; se v baixo, ativa hipóteses
157
+ self.gate = nn.Linear(feat_dim + 1, 1) # +1 para o valor v
158
+
159
+ # Projeto para combinar com input original
160
+ self.combine = nn.Linear(feat_dim + out_dim, out_dim)
161
+
162
+ def forward(
163
+ self,
164
+ x: torch.Tensor,
165
+ v: torch.Tensor,
166
+ ) -> Dict[str, torch.Tensor]:
167
+ """
168
+ Args:
169
+ x: [B, feat_dim]
170
+ v: [B, 1] probabilidade do verificador (0 = erro, 1 = correto)
171
+
172
+ Returns:
173
+ dict com:
174
+ output: [B, out_dim] features modificadas
175
+ gate: [B, 1] valor da porta (0 = identity, 1 = hipóteses)
176
+ weights: [B, N] pesos das hipóteses
177
+ activated: bool se as hipóteses foram ativadas (em qualquer amostra)
178
+ """
179
+ # Porta: gate = Sigmoid(linear(concat(x, v)))
180
+ # Como v baixo -> gate alto (ativa hipóteses), usamos (1 - v) na entrada
181
+ gate_input = torch.cat((x, 1.0 - v), dim=-1) # [B, feat_dim + 1]
182
+ gate = torch.sigmoid(self.gate(gate_input)) # [B, 1]
183
+
184
+ # Hipóteses
185
+ hyp_out, weights = self.hyp_layer(
186
+ x, gumbel_temp=self.cfg.gumbel_temp, gumbel_hard=self.cfg.gumbel_hard
187
+ )
188
+
189
+ # Combina: out = combine(concat(x, hyp_out))
190
+ combined = self.combine(torch.cat((x, hyp_out), dim=-1))
191
+
192
+ # Aplica gate: se gate=0, mantém x (projetado); se gate=1, usa hipóteses
193
+ # Projeção de x para out_dim
194
+ x_proj = combined # placeholder; usamos combined como base
195
+ output = gate * combined + (1 - gate) * x_proj
196
+
197
+ # Verifica se alguma amostra foi punida
198
+ activated = bool((v.squeeze(-1) < self.cfg.threshold_err).any().item())
199
+
200
+ return {
201
+ "output": output,
202
+ "gate": gate,
203
+ "weights": weights,
204
+ "activated": activated,
205
+ }
206
+
207
+
208
+ class SynergySearcher:
209
+ """Busca sinérgica: tenta N combinações de hiperparâmetros e escolhe a melhor.
210
+
211
+ Dado um conjunto de "alavancas" (e.g. pesos das perdas, taxas de aprendizado),
212
+ explora N configurações e seleciona aquela que produz menor perda em um
213
+ mini-batch de validação.
214
+
215
+ Não é diferenciável — é uma busca heurística no espaço de configurações.
216
+ """
217
+
218
+ def __init__(
219
+ self,
220
+ n_attempts: int = 4,
221
+ seed: int = 42,
222
+ ):
223
+ self.n_attempts = n_attempts
224
+ self.rng = random.Random(seed)
225
+ self.history: List[Dict] = []
226
+
227
+ def sample_config(self, base: Dict) -> Dict:
228
+ """Amostra uma configuração perturbando a base."""
229
+ cfg = dict(base)
230
+ # Perturbação: multiplica pesos por fator aleatório próximo de 1
231
+ for key in ["alpha", "beta", "gamma_loss", "delta", "lambda_penal", "mu_exp_penal"]:
232
+ if key in cfg:
233
+ factor = 1.0 + self.rng.uniform(-0.2, 0.2)
234
+ cfg[key] = max(1e-6, cfg[key] * factor)
235
+ return cfg
236
+
237
+ def search(
238
+ self,
239
+ base_config: Dict,
240
+ eval_fn,
241
+ ) -> Dict:
242
+ """Executa N tentativas e retorna a melhor configuração.
243
+
244
+ Args:
245
+ base_config: configuração base (dict de hiperparâmetros)
246
+ eval_fn: função cfg -> loss (callable)
247
+
248
+ Returns:
249
+ melhor configuração encontrada
250
+ """
251
+ best_config = dict(base_config)
252
+ best_loss = float("inf")
253
+
254
+ for attempt in range(self.n_attempts):
255
+ try:
256
+ cfg = self.sample_config(base_config) if attempt > 0 else dict(base_config)
257
+ loss = float(eval_fn(cfg))
258
+ self.history.append({"attempt": attempt, "config": cfg, "loss": loss})
259
+ logger.info("SynergySearch attempt %d: loss=%.4f", attempt, loss)
260
+ if loss < best_loss:
261
+ best_loss = loss
262
+ best_config = cfg
263
+ except Exception as e:
264
+ logger.warning("SynergySearch attempt %d falhou: %s", attempt, e)
265
+ continue
266
+
267
+ logger.info("SynergySearch melhor loss=%.4f", best_loss)
268
+ return best_config
269
+
270
+
271
+ __all__ = [
272
+ "HypothesisConfig",
273
+ "HypothesisLayer",
274
+ "HypothesisController",
275
+ "SynergySearcher",
276
+ ]