BiGRU_T_version / src /bigru_t /training /rlhf_mapping.py
PowerMachine's picture
V6: Kohonen 4D SOM + EWC per-neuron + MTP+entropy + Xeon V6
79e8e52 verified
Raw History Blame Contribute Delete
6.27 kB
"""rlhf_mapping.py — V6: learnable possibility mapping + RLHF.
Implementa (item do PowerMachine/gru-ring-v13-9-2):
- **Learnable possibility mapping**: mapeia logits do modelo para uma
distribuição de "possibilidades" aprendível. Substitui o softmax fixo
por uma transformação aprendível que pode capturar distribuições não
uniformes (heavy-tailed, multimodais).
- **RLHF integration**: integra feedback humano (preference pairs) via
DPO-style loss. Diferente do DPO clássico, mantém a possibility mapping
aprendível durante o alignment.
Matemática
──────────
Possibility Mapping:
p_poss(y|x) = softmax(T_poss * logits + b_poss)
onde T_poss (temperatura aprendível) e b_poss (bias aprendível) são
parâmetros treináveis. T_poss permite que o modelo aprenda a "calibrar"
sua própria confiança.
RLHF (DPO-style):
L_DPO = -log σ(β * (log p_θ(y_w|x) / p_ref(y_w|x)
- log p_θ(y_l|x) / p_ref(y_l|x)))
onde y_w = winner, y_l = loser, β = temperatura DPO.
"""
from __future__ import annotations
import math
from typing import Optional, Tuple, Dict, List
from dataclasses import dataclass
import torch
import torch.nn as nn
import torch.nn.functional as F
@dataclass
class RLHFConfig:
"""Configuração RLHF V6."""
# Learnable possibility mapping
init_temp: float = 1.0
init_bias: float = 0.0
temp_min: float = 0.1
temp_max: float = 10.0
# DPO
dpo_beta: float = 0.1
dpo_loss_weight: float = 0.5
# PPO-like penalty
kl_penalty_weight: float = 0.01
class LearnablePossibilityMapping(nn.Module):
"""V6: mapeamento aprendível de logits para possibilidades.
p_poss(y|x) = softmax(T_poss * logits + b_poss)
T_poss é aprendível mas clipado em [temp_min, temp_max] via sigmoid.
"""
def __init__(
self,
init_temp: float = 1.0,
init_bias: float = 0.0,
temp_min: float = 0.1,
temp_max: float = 10.0,
):
super().__init__()
self.temp_min = temp_min
self.temp_max = temp_max
# Parametrização: T = temp_min + (temp_max - temp_min) * sigmoid(raw_T)
# Inicializa raw_T tal que T(0) = init_temp
init_raw = math.log(
(init_temp - temp_min) / max(temp_max - init_temp, 1e-8)
) if temp_min < init_temp < temp_max else 0.0
self.raw_temp = nn.Parameter(torch.tensor(float(init_raw)))
self.bias = nn.Parameter(torch.tensor(float(init_bias)))
@property
def temperature(self) -> torch.Tensor:
return self.temp_min + (self.temp_max - self.temp_min) * torch.sigmoid(self.raw_temp)
def forward(self, logits: torch.Tensor) -> torch.Tensor:
"""logits: (..., V) → possibilities: (..., V) — distribuição suavizada."""
T = self.temperature
return F.softmax(T * logits + self.bias, dim=-1)
class RLHFMapping(nn.Module):
"""V6: RLHF com learnable possibility mapping.
Forward (preference):
logits_w: (B, V) — logits do winner
logits_l: (B, V) — logits do loser
→ dpo_loss: scalar, metrics: dict
Forward (standard):
logits: (B, V) → possibilities: (B, V)
"""
def __init__(self, config: Optional[RLHFConfig] = None):
super().__init__()
self.cfg = config or RLHFConfig()
self.possibility = LearnablePossibilityMapping(
init_temp=self.cfg.init_temp,
init_bias=self.cfg.init_bias,
temp_min=self.cfg.temp_min,
temp_max=self.cfg.temp_max,
)
def forward(self, logits: torch.Tensor) -> torch.Tensor:
"""Standard forward: logits → possibilities."""
return self.possibility(logits)
def dpo_loss(
self,
logits_w: torch.Tensor,
logits_l: torch.Tensor,
targets_w: torch.Tensor,
targets_l: torch.Tensor,
ref_logits_w: Optional[torch.Tensor] = None,
ref_logits_l: Optional[torch.Tensor] = None,
) -> Tuple[torch.Tensor, Dict[str, float]]:
"""Computa DPO loss.
Se ref_logits não fornecido, usa logits_w/l como referência (toy mode).
"""
# Log probs do winner e loser
log_p_w = F.log_softmax(logits_w, dim=-1)
log_p_l = F.log_softmax(logits_l, dim=-1)
log_p_w_target = log_p_w.gather(-1, targets_w.unsqueeze(-1)).squeeze(-1).sum(-1)
log_p_l_target = log_p_l.gather(-1, targets_l.unsqueeze(-1)).squeeze(-1).sum(-1)
if ref_logits_w is None or ref_logits_l is None:
# Toy mode: assume ref = uniform
ref_log_p_w = torch.full_like(log_p_w_target, -math.log(logits_w.size(-1)))
ref_log_p_l = torch.full_like(log_p_l_target, -math.log(logits_l.size(-1)))
else:
ref_log_p_w = F.log_softmax(ref_logits_w, dim=-1).gather(
-1, targets_w.unsqueeze(-1)
).squeeze(-1).sum(-1)
ref_log_p_l = F.log_softmax(ref_logits_l, dim=-1).gather(
-1, targets_l.unsqueeze(-1)
).squeeze(-1).sum(-1)
# DPO: -log σ(β * (log p_w/p_ref_w - log p_l/p_ref_l))
delta = self.cfg.dpo_beta * (
(log_p_w_target - ref_log_p_w) - (log_p_l_target - ref_log_p_l)
)
loss = -F.logsigmoid(delta).mean()
metrics = {
"dpo_loss": float(loss.item()),
"dpo_delta": float(delta.mean().item()),
"temperature": float(self.possibility.temperature.item()),
"bias": float(self.possibility.bias.item()),
"accuracy": float((delta > 0).float().mean().item()),
}
return self.cfg.dpo_loss_weight * loss, metrics
def kl_penalty(
self,
current_logits: torch.Tensor,
ref_logits: torch.Tensor,
) -> torch.Tensor:
"""KL(p_current || p_ref) — penalty PPO-style."""
p_cur = F.softmax(current_logits, dim=-1)
log_p_cur = F.log_softmax(current_logits, dim=-1)
log_p_ref = F.log_softmax(ref_logits, dim=-1)
kl = (p_cur * (log_p_cur - log_p_ref)).sum(dim=-1).mean()
return self.cfg.kl_penalty_weight * kl
__all__ = ["RLHFMapping", "LearnablePossibilityMapping", "RLHFConfig"]