khtst-rust / src /python /calibracao.rs
PowerMachine's picture
v0.10.0: análise/prova/teste das críticas de engenharia Qwen3.8 lote 3 (r-35) — divisão SRP dos monólitos jepa/pensamento/moe/python em 26 submódulos (API estável via re-exports; 'Amdahl de compilação' refutada por experimento controlado, builds frios neutros), checkpoints atômicos SHA-256 puro FIPS 180-4 + fsync + rename + manifesto (CP-1..CP-3; correções da proposta: sem destruir estado anterior, manifesto com barreira, sem bincode/unwrap), TemperaturaInversa T-V3 (fronteira tipada da convexidade N-2; alegação compile-time corrigida), trait CamadaKhtst+CamadaHomeostatica (premissa do controlador refutada no fonte; Vec<Var>; adaptador MoeComPapel bit-a-bit), CLI Typer 75 (fachada aditiva com resume verificado por hash) — 123/123 cargo, 107/107 gates, treino real Wikipédia PT-BR com checkpoints íntegros
06e4b6f verified
Raw History Blame Contribute Delete
7.06 kB
//! `python::calibracao` — extraído do monólito na v0.10.0
//! (divisão SRP, crítica Qwen3.8 lote 3 nº 1; r-35 §2).
use super::*;
/// **V-1..V-3:** calibra o verificador NCM em logits retidos e devolve
/// temperatura, ECE antes/depois, NLL antes/depois e o τ_c por quantil DKW.
#[pyfunction]
pub(crate) fn calibrar_verificador(
logits: Vec<Vec<f32>>,
alvos: Vec<usize>,
epsilon: f64,
) -> PyResult<PyObject> {
Python::with_gil(|py| {
let dev = candle_core::Device::Cpu;
let (n, k) = (logits.len(), logits.first().map(|l| l.len()).unwrap_or(1));
let flat: Vec<f32> = logits.into_iter().flatten().collect();
let lt = candle_core::Tensor::from_vec(flat, (n, k), &dev)
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
let at = candle_core::Tensor::from_vec(
alvos.iter().map(|&a| a as u32).collect::<Vec<u32>>(), (n,), &dev)
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
let mut v = verificador::VerificadorNcm::novo();
let (e_antes, e_depois) = v.ajustar(&lt, &at)
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
let confs: Vec<f64> = verificador::confianca_por_linha(&lt)
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?
.flatten_all().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?
.to_vec1::<f32>().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?
.iter().map(|x| *x as f64).collect();
let tau_c = verificador::limiar_por_erro(&confs, epsilon);
let d_ = serde_json::json!({
"temperatura": v.temperatura, "ece_antes": e_antes, "ece_depois": e_depois,
"nll_antes": v.n_ll_antes, "nll_depois": v.n_ll_depois,
"tau_c": tau_c, "epsilon": epsilon, "n": n,
});
Ok(pythonize::pythonize(py, &d_)?.into())
})
}
/// **Demo do verificador NCM substituindo o oráculo** (r-21 §3.1): treina
/// um classificador pequeno, calibra T* em dados retidos, mede ECE, τ_c
/// (DKW) e a soundness empírica `P(erro|aceito) ≤ 1−τ_c+ECE`; compara com
/// o ORÁCULO (que usa o alvo — impossível em produção) no mesmo problema.
#[pyfunction]
pub(crate) fn demo_verificador_ncm(n: usize, k: usize, semente: u64) -> PyResult<PyObject> {
Python::with_gil(|py| {
let dev = candle_core::Device::Cpu;
crate::rng::definir_semente(semente);
let mut rng = crate::rng::RngDeterministico::novo(semente);
// dados sintéticos com DRIFT (treino ≠ consulta — força OOD leve)
let gerar = |rng: &mut crate::rng::RngDeterministico, m: usize, desloc: f64|
-> (candle_core::Tensor, Vec<usize>) {
let mut l = Vec::with_capacity(m * k);
let mut a = Vec::with_capacity(m);
for _ in 0..m {
let alvo = rng.inteiro(k);
a.push(alvo);
for j in 0..k {
let base = if j == alvo { 2.5 } else { 0.0 };
l.push((base + rng.normal(0.0, 1.2) as f32 + desloc as f32) as f32);
}
}
let lt = candle_core::Tensor::from_vec(l, (m, k), &dev).unwrap();
(lt, a)
};
let (lt_tre, alvos_tre) = gerar(&mut rng, n, 0.0);
let (lt_val, alvos_val) = gerar(&mut rng, n / 2, 0.3);
// mini-classificador: matriz W (k, k) treinada por softmax-CE
let w = candle_core::Tensor::randn(0f32, 0.1, (k, k), &dev)
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
let w_var = candle_core::Var::from_tensor(&w)
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
let at_t = candle_core::Tensor::from_vec(
alvos_tre.iter().map(|&a| a as u32).collect::<Vec<u32>>(), (n,), &dev)
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
let mut otim = treino::AdamWManual::novo(vec![w_var.clone()], 0.05, 0.0);
for _ in 0..120 {
let logits = lt_tre.matmul(&w_var.as_tensor().transpose(0, 1).unwrap()).unwrap();
let ce = candle_nn::loss::cross_entropy(&logits, &at_t).unwrap();
let g = treino::grads_achatados(&ce, &[w_var.clone()]).unwrap();
otim.passo(g);
}
// logits de validação com o modelo treinado
let logits_val = lt_val.matmul(&w_var.as_tensor().transpose(0, 1).unwrap()).unwrap();
let at_v = candle_core::Tensor::from_vec(
alvos_val.iter().map(|&a| a as u32).collect::<Vec<u32>>(), (n / 2,), &dev)
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
// calibração (V-1) + soundness (V-2) + quantil (V-3)
let mut v = verificador::VerificadorNcm::novo();
let (ece_antes, ece_depois) = v.ajustar(&logits_val, &at_v)
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
let confs: Vec<f64> = v.confianca(&logits_val).unwrap()
.flatten_all().unwrap().to_vec1::<f32>().unwrap().iter().map(|x| *x as f64).collect();
let corretos: Vec<bool> = logits_val.argmax(candle_core::D::Minus1).unwrap()
.eq(&at_v).unwrap().to_dtype(candle_core::DType::U32).unwrap()
.flatten_all().unwrap().to_vec1::<u32>().unwrap()
.iter().map(|x| *x == 1).collect();
let tau_c = verificador::limiar_por_erro(&confs, 0.25);
let (mut erro_aceito, mut n_aceito) = (0usize, 0usize);
for (c, ok) in confs.iter().zip(corretos.iter()) {
if *c >= tau_c {
n_aceito += 1;
if !ok {
erro_aceito += 1;
}
}
}
let p_erro = erro_aceito as f64 / n_aceito.max(1) as f64;
let cota = (1.0 - tau_c) + ece_depois + 0.06;
// ORÁCULO no mesmo problema (usa o alvo — só para contraste)
let acerto_oraculo = corretos.iter().filter(|c| **c).count() as f64 / corretos.len() as f64;
let d_ = serde_json::json!({
"versao": verificador::versao(), "n": n, "k": k,
"temperatura_calibrada": v.temperatura,
"ece_antes": ece_antes, "ece_depois": ece_depois,
"nll_antes": v.n_ll_antes, "nll_depois": v.n_ll_depois,
"tau_c_dkw": tau_c,
"p_erro_aceito": p_erro, "cota_soundness": cota,
"soundness_ok": p_erro <= cota,
"calibracao_melhorou": ece_depois < ece_antes,
"acerto_oraculo_referencia": acerto_oraculo,
"verificador_sem_oraculo": true,
"gates": {
"gate_v1_calibracao": ece_depois < ece_antes,
"gate_v2_soundness": p_erro <= cota,
"gate_v3_tau_positivo": tau_c > 0.0 && tau_c < 1.0,
},
});
Ok(pythonize::pythonize(py, &d_)?.into())
})
}