File size: 5,281 Bytes
97df2ae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e26f4cc
97df2ae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
//! Memória: SOM de conceitos + JEPA latente + RM-lite por nível — port
//! minimalista dos contratos do KHTST (docs 01/13/17/28; Teos 17.4, 28.5–28.8,
//! D.3, D.5). Tudo com stop-grad no caminho pós-geração (V14-análogo).

use candle_core::{Device, Result, Tensor, Var};
use crate::camadas::Linear;

/// SOM 2D online sobre o espaço dos códigos de conceito (contrato doc 01 +
/// Teo 17.4: protótipos atualizados com stop-grad; EQ médio monotônico
/// esperado). Grid lado×lado em d_lat.
pub struct SomConceitos {
    pub prototipos: Tensor, // (lado*lado, d_lat) — buffer, SEM gradiente
    pub lado: usize,
    pub lr: f64,
    pub eq_medio: f64,
}

impl SomConceitos {
    pub fn novo(lado: usize, d_lat: usize, dev: &Device) -> Result<Self> {
        Ok(SomConceitos {
            prototipos: Tensor::randn(0f32, 0.1, (lado * lado, d_lat), dev)?,
            lado,
            lr: 0.05,
            eq_medio: 0.0,
        })
    }

    /// Insere vetores (n, d_lat) com atualização online (sem gradiente — V14).
    pub fn inserir(&mut self, vetores: &Tensor) -> Result<f64> {
        let p = self.prototipos.flatten_all()?.to_vec1::<f32>()?;
        let v = vetores.flatten_all()?.to_vec1::<f32>()?;
        let (n, d) = (vetores.dim(0)?, vetores.dim(1)?);
        let total = self.lado * self.lado;
        let mut eq_soma = 0f64;
        let mut protos = p;
        for i in 0..n {
            // BMU
            let (mut melhor, mut dist_melhor) = (0usize, f32::INFINITY);
            for c in 0..total {
                let mut dist = 0f32;
                for t in 0..d {
                    let dif = v[i * d + t] - protos[c * d + t];
                    dist += dif * dif;
                }
                if dist < dist_melhor {
                    dist_melhor = dist;
                    melhor = c;
                }
            }
            eq_soma += dist_melhor as f64;
            // vizinhança gaussiana no grid
            let (bx, by) = (melhor % self.lado, melhor / self.lado);
            for c in 0..total {
                let (cx, cy) = (c % self.lado, c / self.lado);
                let dist2 = ((cx as i32 - bx as i32).pow(2) + (cy as i32 - by as i32).pow(2)) as f32;
                let w = (-dist2 / 2.0).exp();
                for t in 0..d {
                    protos[c * d + t] += (self.lr as f32 * w) * (v[i * d + t] - protos[c * d + t]);
                }
            }
        }
        self.eq_medio = eq_soma / n.max(1) as f64;
        let dev = self.prototipos.device();
        self.prototipos = Tensor::from_vec(protos, (total, d), dev)?;
        Ok(self.eq_medio)
    }
}

/// JEPA latente: preditor do resíduo do PRÓXIMO nível no espaço latente
/// (contrato doc 30 — jepa_familia; alvo com stop-grad; perda de cosseno).
pub struct JepaLatente {
    pub preditor: Linear, // d_lat → d_lat
}

impl JepaLatente {
    pub fn novo(d_lat: usize, dev: &Device) -> Result<Self> {
        Ok(JepaLatente { preditor: Linear::sem_vies(d_lat, d_lat, 0.02, dev)? })
    }

    /// Perda JEPA entre reconstruções cumulativas de níveis adjacentes:
    /// L = 1 − cos(pred(ẑ_{ℓ−1} interp), sg[ẑ_ℓ − r_ℓ]) — prevê o resíduo.
    pub fn perda(&self, zhat_ant: &Tensor, alvo: &Tensor) -> Result<Tensor> {
        let pred = self.preditor.fwd(zhat_ant)?;
        let alvo_sg = alvo.detach();
        // cosseno por linha
        let dot = (&pred * &alvo_sg)?.sum(candle_core::D::Minus1)?;
        let np = pred.sqr()?.sum(candle_core::D::Minus1)?.sqrt()?;
        let na = alvo_sg.sqr()?.sum(candle_core::D::Minus1)?.sqrt()?;
        let cos = dot.broadcast_div(&(&np * &na)?)?;
        (&cos * -1.0)?.mean_all()?.affine(1.0, 1.0)
    }

    pub fn vars(&self) -> Vec<Var> {
        self.preditor.vars()
    }
}

/// RM-lite por nível (Teo D.3 — condições de Borkar): calibração de confiança
/// por nível com atualização em duas escalas (τ rápidos, sinal lento).
pub struct RmNivel {
    pub medias_confianca: Vec<f64>, // por nível (escala lenta)
    pub tau_por_nivel: Vec<f64>,    // por nível (escala rápida — bissersão D.3)
    pub contagem: usize,
}

impl RmNivel {
    pub fn novo(l: usize) -> Self {
        RmNivel { medias_confianca: vec![0.5; l], tau_por_nivel: vec![1.0; l], contagem: 0 }
    }

    /// Atualização duas-escalas (Borkar Thm 6.2): sinal lento = média de
    /// confiança; rápidos = τ por bisseção contra entropia alvo por nível.
    pub fn passo(&mut self, confiancas_por_nivel: &[f64], entropias_por_nivel: &[f64], alvo: f64) {
        self.contagem += 1;
        let a_lento = 1.0 / (self.contagem as f64).sqrt().max(1.0); // Σa_t=∞, Σa_t²<∞
        for (i, c) in confiancas_por_nivel.iter().enumerate() {
            if i < self.medias_confianca.len() {
                self.medias_confianca[i] += a_lento * (c - self.medias_confianca[i]);
            }
        }
        for (i, h) in entropias_por_nivel.iter().enumerate() {
            if i < self.tau_por_nivel.len() {
                // ajuste proporcional rápido (proxy da bisseção — D.3(ii))
                let delta = h - alvo;
                self.tau_por_nivel[i] = (self.tau_por_nivel[i] * (1.0 - 0.1 * delta)).clamp(0.5, 1.5);
            }
        }
    }
}