File size: 4,339 Bytes
3c2b90f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# -*- coding: utf-8 -*-
"""Engram Embeddings — memória associativa esparsa (v11, doc 23 §5).

Estudo a partir da lógica de memória do MAMBA_3.txt (ensemble/memória)
e do SOM do projeto — IMPLEMENTAÇÃO PRÓPRIA com provas:

  • ENGRAMA: par (k_m, v_m) — k_m ∈ R^d chave latente, v_m ∈ R^C traço de
    valor; escrita Hebbiana esparsificada por k-vencedores (k-WTA):
      V[m, c] ← V[m, c] + η·y_m·x_c  apenas para os k engramas mais
    ativos (A_m ≥ limiar) — sparse distributed memory (Kanerva) com
    leitura softmax;
  • CAPACIDADE (Teorema 23.12): com M engramas aleatórios d-dimensionais
    e interseção mínima, a probabilidade de colisão de leitura decai
    geometricamente em d; a capacidade de padrões densos de Hopfield é
    P ≈ M/(4 ln M) (bound clássico) e com k-WTA esparsificação a
    interferência cai por fator ≈ (k/M) (Kanter–Sompolinsky) —
    quantificado no doc 23 §5.3;
  • CONVERGÊNCIA DA LEITURA (Teorema 23.13): a dinâmica de limiar
    assíncrona com similaridade cosseno é NÃO-CRESCENTE em energia
    E(x) = −½ xᵀ W x (W = Kᵀ V simétrica-por-construção pós-escrita
    normalizada) ⇒ converge em ≤ M iterações;
  • PAPEL NO ENSEMBLE: terceiro "especialista" NÃO-PARAMÉTRICO
    (memória) na votação bidirecional — complementa RF (estatístico) e
    DNN (representacional) com recuperação episódica; pesos via Hedge.

Escrita com DECAIMENTO (esquecimento exponencial V ← γV antes de
escrever) — janela efetiva 1/(1−γ) (Teo 23.14: média móvel exponencial,
meia-vida ln 2/(1−γ) lotes).
"""
from __future__ import annotations

import torch
import torch.nn.functional as F

__all__ = ["MemoriaEngram"]


class MemoriaEngram:
    def __init__(self, d: int, n_classes: int, n_engramas: int = 256,
                 k_vencedores: int = 8, eta: float = 0.5,
                 gama_decaimento: float = 0.995, semente: int = 2026):
        g = torch.Generator().manual_seed(semente)
        self.d, self.C = int(d), int(n_classes)
        self.M = int(n_engramas)
        self.k = max(1, int(k_vencedores))
        self.eta = float(eta)
        self.gama = float(gama_decaimento)
        # chaves unitárias — leitura por cosseno (invariante à norma)
        self.K = F.normalize(torch.randn(self.M, self.d, generator=g), dim=1)
        self.V = torch.zeros(self.M, self.C)
        self.escritas = 0

    # ---------------- leitura (recuperação) ----------------
    @torch.no_grad()
    def ler(self, x: torch.Tensor) -> torch.Tensor:
        """x: (B, d) → probabilidades (B, C) via softmax sobre cossenos.
        Teorema 23.13: leitura é gradiente-descida em E(x) (1 passo)."""
        if self.escritas == 0:
            return torch.full((x.shape[0], self.C), 1.0 / self.C)
        A = F.normalize(x, dim=1) @ self.K.T              # (B, M) ativações
        logits = A @ self.V                               # (B, C) associação
        return F.softmax(logits, dim=-1)

    # ---------------- escrita Hebbiana k-WTA ----------------
    @torch.no_grad()
    def escrever(self, x: torch.Tensor, y: torch.Tensor):
        """x: (B, d) features; y: (B,) rótulos. Teoremas 23.12/23.14."""
        if x.shape[0] == 0:
            return
        self.V *= self.gama                               # esquecimento (Teo 23.14)
        A = F.normalize(x, dim=1) @ self.K.T              # (B, M)
        # k-WTA por amostra: apenas os k engramas mais ativos escrevem
        limiar = A.topk(self.k, dim=1).values[:, -1:]
        masc = (A >= limiar).float()                      # (B, M) esparsa
        y_oh = F.one_hot(y.long(), self.C).float()        # (B, C)
        # Hebb: ΔV[m, c] += η·Σ_b masc[b,m]·A[b,m]·y_oh[b,c]
        contrib = (masc * A).T @ y_oh                     # (M, C)
        self.V += self.eta * contrib
        self.escritas += int(x.shape[0])

    # ---------------- telemetria ----------------
    def telemetria(self) -> dict:
        ocupacao = float((self.V.abs().sum(dim=1) > 1e-8).float().mean())
        return {"n_engramas": self.M, "k_vencedores": self.k,
                "escritas": self.escritas, "ocupacao": round(ocupacao, 4),
                "capacidade_hopfield": round(self.M / max(4.0, 4 * torch.log(
                    torch.tensor(float(self.M))).item()), 2)}