Imputación probabilística zero-shot de series temporales univariantes. Un encoder bidireccional de 10.2M de parámetros (estilo ModernBERT, un punto por posición) que rellena huecos en cualquier serie numérica sin entrenamiento por dataset, devolviendo 9 cuantiles por posición imputada.
Es un experimento sobre una pregunta concreta: ¿qué da de sí un imputador neuronal de propósito general, pequeño, entrenado con un curriculum de enmascarado sobre un corpus grande de series? La respuesta corta: mejora de forma consistente a la interpolación clásica en huecos de bloque y patrones mixtos, con intervalos bien calibrados, y aporta poco sobre la interpolación en huecos puntuales de alta frecuencia.
inference.py solo necesita torch, numpy y safetensors.
import numpy as np
from inference import Imputer
imp = Imputer.from_safetensors("goia-impute-base.safetensors")
s = np.asarray(mi_serie, dtype=np.float32) # los NaN marcan los huecos
res = imp.impute(s)
res.filled # serie con los NaN sustituidos por la mediana
res.median # mediana en las posiciones imputadas
res.q(0.1) # cualquier cuantil de (0.1 ... 0.9)
res.missing_idx # posiciones imputadasCon pandas: imp.impute_df(serie) devuelve un DataFrame con filled, q10, q50 y q90. Las series más largas que el contexto del modelo (2048) se procesan con ventanas deslizantes solapadas. También se puede pasar extra_mask para ocultar posiciones observadas (evaluación o análisis what-if).
Los pesos están en Hugging Face: gredio/goia-impute.
Encoder bidireccional sin tokenizador ni patching: cada instante es una posición de la secuencia, con dos canales de entrada (valor normalizado, indicador de visibilidad) y un embedding [MASK] aprendido que se suma en las posiciones ocultas — nunca un valor centinela. Cuerpo estilo ModernBERT: pre-norm, RoPE, GeGLU, sin biases, atención alternada (1 capa global cada 3, el resto local con ventana de 128, todas por la ruta de Flash Attention). Normalización RevIN calculada exclusivamente sobre posiciones observadas y visibles, de modo que las estadísticas nunca ven los valores a predecir. Cabeza de 9 cuantiles entrenada con pinball loss solo sobre posiciones ocultas y originalmente observadas.
12 capas, d=256, 8 cabezas, GeGLU 768, contexto máximo 2048. Total: 10.23M de parámetros.
Estilo MLM con un curriculum de enmascarado: un régimen por ventana (puntos dispersos 35%, bloques contiguos 35%, mixto 15%, cola 10%, cabeza 5%), con reparto 80/10/10 tipo BERT sobre las posiciones ocultas. Los NaN nativos del corpus se tratan como missingness real, sin interpolación previa.
Datos: ~3.87M de series — 90% GiftEvalPretrain procesado, 7% TempoPFN, 3% KernelSynth (el mismo mix que goia-forecast; solo la parte de train de cada serie). Dos fases: 250k pasos con ventana 1024 (batch 128, lr 3e-4 con coseno) y 25k pasos de extensión de contexto a 2048 (batch 64, lr constante 3e-5). Una RTX 4090, bf16, Flash Attention.
Notebook: 01_train_impute.ipynb (reanudable, con checkpoints rotados).
Protocolo reproducible sobre las 97 configuraciones de GIFT-Eval: máscaras deterministas generadas con seed publicada (EVAL_SEED = 2026), ciclando los cinco regímenes de missingness, puntuando solo posiciones ocultas y originalmente observadas. Todos los ratios se normalizan por serie frente a interpolación lineal (media geométrica; < 1 = gana el modelo). Notebook: 02_impute_bench_practical.ipynb.
| método | MAE rel. (geo) | CRPS rel. (geo) | cobertura q10–q90 | latencia med. |
|---|---|---|---|---|
| goia-impute-base | 0.744 (IC 0.67–0.83) | 0.603 | 0.798 | 3.9 ms |
| pchip | 0.998 | 0.998 | — | 0.19 ms |
| interp. lineal | 1.000 | 1.000 | — | 0.02 ms |
| Kalman (local level) | 1.119 | 1.119 | — | 1.6 ms |
| LOCF | 1.185 | 1.185 | — | 0.01 ms |
| interp. estacional | 1.191 | 1.191 | — | 3.7 ms |
Frente a los baselines clásicos evaluados, el modelo obtiene el menor MAE en el 77% de las configuraciones. La cobertura empírica del intervalo q10–q90 (0.798 frente al nominal 0.80) se obtiene sin calibración posterior. Los comparadores son referencias clásicas, baratas y aplicables sin entrenamiento específico; esta evaluación no pretende cubrir los métodos neuronales supervisados de imputación. El intervalo bootstrap se calculó para la métrica principal (MAE relativo); el del CRPS se obtiene igual con bootstrap_geomean_ci(df, metric="crps_rel_geo").
Por régimen de missingness (MAE relativo):
| points | blocks | mixed | tail | head |
|---|---|---|---|---|
| 0.82 | 0.69 | 0.68 | 0.73 | 0.80 |
La lectura es clara: donde los vecinos inmediatos son muy informativos (huecos puntuales, sobre todo en frecuencias de minutos), la interpolación ya constituye un baseline muy fuerte y el margen del modelo se estrecha. Donde el hueco es largo o el patrón complejo, el modelo aprovecha la estructura global de la serie.
La imputación de cola es forecasting, así que el modelo también se evaluó en el GIFT-Eval oficial de forecasting (CRPS normalizado por seasonal naive, geomean sobre 97 configs): 0.576, al nivel de chronos_bolt_small (0.577) y por detrás de modelos especializados de tamaño comparable como FlowState-9.1M (0.502). En horizonte corto (0.557) supera incluso a su hermano goia-forecast (0.574); en medio y largo la extensión autoregresiva pasa factura. Es una capacidad secundaria: si lo que necesitas es forecasting, hay modelos mejores del mismo tamaño.
Las máscaras de evaluación se generan con un RNG determinista a partir de EVAL_SEED = 2026, ciclando los cinco regímenes. El estado del generador avanza serie a serie, así que la reproducción exacta requiere el código publicado (src/masking.py), la seed y la misma lista de series en el mismo orden; conviene además fijar la versión de numpy, porque los algoritmos de muestreo de Generator pueden cambiar entre versiones. Con eso, cualquier otro modelo puede evaluarse en igualdad de condiciones. Los CSVs de resultados están en results/.
Univariante: no explota correlaciones entre canales. En huecos puntuales sobre datos de alta frecuencia el beneficio sobre pchip/lineal es pequeño. El forecasting a horizonte medio/largo queda por detrás de los modelos especializados. Series con menos de ~8 puntos visibles por ventana no se pueden normalizar de forma fiable y el modelo se abstiene de puntuarlas. Como todo modelo entrenado sobre un corpus, dominios muy alejados del mix de entrenamiento pueden comportarse peor de lo que esta evaluación sugiere.
src/ modelo, datos, masking, trainer, evaluación, benchmark
inference.py inferencia standalone (torch + numpy + safetensors)
01_train_impute.ipynb entrenamiento y evaluación interna
02_impute_bench_practical.ipynb benchmark de imputación sobre GIFT-Eval
03_release_hf.ipynb release en Hugging Face y verificación del artefacto
results/ CSVs y figuras
MIT.