|
| 1 | +# Pipeline de filogenia real: Cytochrome C + comparación de baselines |
| 2 | + |
| 3 | +## Contexto |
| 4 | + |
| 5 | +`docs/plans/EXTERNAL-VALIDATION-ROADMAP.md` deja documentado un problema de |
| 6 | +fondo: todas las métricas de "jerarquía" reportadas hasta ahora (Spearman |
| 7 | +0.8335, ARI=1.0) miden si las losses hacen lo que fueron escritas para hacer |
| 8 | +— `v_3(índice)` se calcula del índice y se inyecta directamente como target, |
| 9 | +así que el resultado es casi tautológico. El roadmap identifica dos cosas que |
| 10 | +faltan para que esto sea una demostración real en vez de un ejercicio |
| 11 | +autoconsistente: |
| 12 | + |
| 13 | +1. Mover a un dominio real cuya jerarquía sea conocida **de forma |
| 14 | + independiente**, no inyectada en la loss. |
| 15 | +2. Comparar contra baselines (Euclidiano, hiperbólico genérico) en la misma |
| 16 | + tarea — comparación que hoy no existe en ningún lado del repo. |
| 17 | + |
| 18 | +Elegimos citocromo C como dominio porque: es el dataset clásico de reloj |
| 19 | +molecular (Fitch & Margoliash), es corto (~104-110 aa, pocas ventanas por |
| 20 | +especie), tiene cobertura real desde bacterias hasta humanos en |
| 21 | +UniProt/NCBI, y reutiliza el encoding de hidropatía que ya existe en |
| 22 | +`scripts/data/peptide_encoding.py` (ya usado por 4 scripts). El diseño de |
| 23 | +experimento acordado son **3 condiciones** (Euclidiano / Hiperbólico |
| 24 | +genérico / P-ádico actual) evaluadas post-hoc contra distancia taxonómica |
| 25 | +real — sin tocar `src/losses/` para esta primera fase (la variante D, |
| 26 | +"reapuntar" `target_radius` a la taxonomía real vía el `valuation_type` |
| 27 | +plegable que ya existe en `src/losses/combined.py:120`, queda deliberadamente |
| 28 | +fuera de alcance hasta tener resultados de las 3 primeras). |
| 29 | + |
| 30 | +**Objetivo final medible:** ¿la distancia hiperbólica/p-ádica entre especies |
| 31 | +correlaciona con su distancia taxonómica real mejor de lo que lo hace un VAE |
| 32 | +euclidiano plano, en secuencias nunca usadas para fijar el target de ninguna |
| 33 | +loss? |
| 34 | + |
| 35 | +--- |
| 36 | + |
| 37 | +## Fase 1 — Datos reales con procedencia auditable |
| 38 | + |
| 39 | +**Problema a evitar:** el roadmap ya documenta que `validate_humsavar_tp53.py` |
| 40 | +usa una secuencia "tipeada a mano en un comentario". Esta vez todo dato debe |
| 41 | +venir de una fuente descargable y verificable. |
| 42 | + |
| 43 | +- **Nuevo:** `scripts/data/fetch_cytochrome_c.py` — descarga secuencias reales |
| 44 | + de citocromo C vía UniProt REST API para una lista curada de ~30-40 |
| 45 | + especies que cubran un rango taxonómico amplio (bacterias, hongos, |
| 46 | + plantas, invertebrados, vertebrados incluyendo humano). Guarda FASTA crudo |
| 47 | + + manifest committeable (especie, accession, fecha de descarga, URL) en |
| 48 | + `data/cytochrome_c/manifest.csv` — el manifest se commitea aunque |
| 49 | + `data/` esté gitignoreado para las secuencias, así queda auditable sin |
| 50 | + necesidad de volver a bajar nada para revisar procedencia. |
| 51 | +- **Nuevo:** `scripts/data/fetch_taxonomy.py` — para la misma lista de |
| 52 | + especies, obtiene el linaje taxonómico completo (dominio→especie) vía la |
| 53 | + API de NCBI Taxonomy. Calcula una matriz de distancia par-a-par por |
| 54 | + "profundidad del último ancestro común en rangos Linneanos" (ej. mismo |
| 55 | + género = distancia 1, mismo dominio nada más = distancia 7) — es una |
| 56 | + ultramétrica genuina, calculable con comparación de strings de linaje, sin |
| 57 | + dependencias nuevas. Guarda `data/cytochrome_c/taxonomic_distance.npy` + |
| 58 | + `taxonomy_lineage.json`. |
| 59 | + |
| 60 | +**Riesgo flageado:** ambos scripts requieren acceso a red en tiempo de |
| 61 | +ejecución (UniProt/NCBI). No se puede validar esto en modo plan; se |
| 62 | +verificará al ejecutar. |
| 63 | + |
| 64 | +--- |
| 65 | + |
| 66 | +## Fase 2 — Alineamiento y encoding a ventanas de 9 residuos |
| 67 | + |
| 68 | +Las 96+ ventanas de 9 residuos por especie que salen de un tiling naive no |
| 69 | +son comparables entre especies (posición i en la especie X no es |
| 70 | +homóloga a posición i en la especie Y sin alinear). Se necesita alinear |
| 71 | +antes de definir las ventanas. |
| 72 | + |
| 73 | +- **Nuevo:** `scripts/data/align_cytochrome_c.py` — alinea cada secuencia |
| 74 | + contra una referencia (humano) con `Bio.Align.PairwiseAligner` |
| 75 | + (Biopython — confirmar que está en requirements, si no, agregarlo). |
| 76 | + Define los límites de ventana sobre las coordenadas de la referencia |
| 77 | + alineada, así "ventana k" es la misma posición estructural para todas las |
| 78 | + especies. |
| 79 | +- **Reutiliza sin cambios:** `scripts/data/peptide_encoding.py` |
| 80 | + (`encode_peptide_window`, `AA_MAP`) para mapear cada ventana alineada a un |
| 81 | + vector de 9 dígitos ternarios {-1,0,1} — el mismo encoding que ya usan |
| 82 | + `scan_human_proteome.py`, `qspr_bioactivity_scoring.py`, etc. |
| 83 | +- **Nuevo:** `scripts/data/prepare_cytochrome_c_dataset.py` — junta todo: |
| 84 | + por especie, produce ~10-12 ventanas alineadas, las convierte a índices |
| 85 | + ternarios (0-19682) vía `TERNARY.from_ternary` (mismo patrón que |
| 86 | + `seq_to_ternary_index` en `prepare_codon_data.py`), y guarda |
| 87 | + `data/cytochrome_c/indices.pt` + un mapa `window_id → (species, window_idx)` |
| 88 | + para poder reagregar embeddings por especie después del entrenamiento. |
| 89 | + El formato de salida (`torch.save(tensor(indices), ...)`) sigue el mismo |
| 90 | + patrón que `prep_human_tp53.py`. |
| 91 | + |
| 92 | +--- |
| 93 | + |
| 94 | +## Fase 3 — Tres condiciones de entrenamiento |
| 95 | + |
| 96 | +Las 3 condiciones entrenan sobre el mismo `data/cytochrome_c/indices.pt` |
| 97 | +(vía `data.indices_path`, mecanismo que **ya existe y ya soporta datasets de |
| 98 | +tamaño arbitrario** — confirmado en `src/training/bootstrap.py:92-99`, |
| 99 | +`DataAuditor.prepare_data` no asume `N=19683` en ningún punto). |
| 100 | + |
| 101 | +### Condición A — Euclidiano plano (código nuevo, aislado) |
| 102 | +El motor de entrenamiento actual (`src/train.py`) trae consigo maquinaria |
| 103 | +específica de la curricula p-ádica (StateNet, Lagrangian dual ascent, LR |
| 104 | +controller, grokking detector) que no aplica a un VAE plano — forzarla |
| 105 | +sería más trabajo que escribir el loop directo. |
| 106 | + |
| 107 | +- **Nuevo:** `src/models/vae_baseline.py` — `TernaryVAEEuclideanBaseline`: |
| 108 | + reutiliza `EncoderHead`/decoder de `src/models/vae.py` tal cual, pero |
| 109 | + se salta `DualHyperbolicProjection` por completo — reparametrización |
| 110 | + N(0,I) estándar, decodifica directo del espacio tangente. |
| 111 | +- **Nuevo:** `scripts/applications/train_euclidean_baseline.py` — loop de |
| 112 | + entrenamiento simple (reconstrucción cross-entropy + KL gaussiano |
| 113 | + estándar, Adam, sin curricula) sobre `data/cytochrome_c/indices.pt`. |
| 114 | + |
| 115 | +### Condición B — Hiperbólico genérico (sin código nuevo) |
| 116 | +Arquitectura actual (`TernaryVAEV6Controllable`) + preset YAML que apaga |
| 117 | +**todas** las losses p-ádicas (`radial`, `monotonic`, `rich_hierarchy. |
| 118 | +hierarchy_weight=0`, `angular_coherence.enabled=false`, |
| 119 | +`algebraic_*.enabled=false`, `valuation_prior.enabled=false`, |
| 120 | +`geodesic`/`rank` desactivadas) dejando solo `rich_hierarchy.coverage_weight` |
| 121 | +(reconstrucción) + `hyperbolic_kl` activos. Esto es 100% config-driven — |
| 122 | +el patrón de habilitar/deshabilitar por loss ya existe en cada bloque YAML |
| 123 | +(ver `surrogate_property.enabled: false` en `v24.0_tangent_fix.yaml`). |
| 124 | +- **Nuevo:** `src/presets/cytochrome_c_B_hyperbolic_generic.yaml` |
| 125 | + |
| 126 | +### Condición C — P-ádico actual, sin cambios de arquitectura |
| 127 | +Mismo `train.py` + mismo `TernaryVAEV6Controllable`, config casi idéntica a |
| 128 | +`v24.0_tangent_fix.yaml` pero apuntando `data.indices_path` al dataset de |
| 129 | +citocromo C. Esta es la prueba de "transferencia pura": el índice ternario |
| 130 | +de cada ventana de citocromo C no tiene relación causal con la especie de |
| 131 | +origen, así que si esta condición correlaciona con taxonomía real sería |
| 132 | +sorprendente (y hay que decirlo así en el resultado, sin sobre-vender). |
| 133 | +- **Nuevo:** `src/presets/cytochrome_c_C_padic.yaml` |
| 134 | + |
| 135 | +**Nota de tamaño de dataset:** ~30-40 especies × ~10-12 ventanas ≈ 300-480 |
| 136 | +muestras. Verificar en la ejecución si el batch size / hiperparámetros por |
| 137 | +defecto de `v24.0_tangent_fix.yaml` siguen siendo razonables a esta escala |
| 138 | +(probablemente sí, pero con menos pasos por época). |
| 139 | + |
| 140 | +--- |
| 141 | + |
| 142 | +## Fase 4 — Evaluación: ¿recuperan filogenia real? |
| 143 | + |
| 144 | +- **Nuevo:** `scripts/analysis/evaluate_phylogeny_recovery.py`: |
| 145 | + 1. Para cada checkpoint (A/B/C), calcular embeddings de todas las |
| 146 | + ventanas (`model.get_mu_representations` / `get_hyperbolic_representations`, |
| 147 | + igual que `probe_foreign_genome.py`). |
| 148 | + 2. Agregar las ~10-12 ventanas por especie a **un punto por especie** |
| 149 | + (media en espacio tangente vía `logmap0`/`expmap0`, ya usados en todo |
| 150 | + el codebase, para B/C; media aritmética simple para A). |
| 151 | + 3. Calcular matriz de distancia par-a-par entre especies (Poincaré para |
| 152 | + B/C, Euclidiana para A) y correlacionarla (Spearman) contra |
| 153 | + `taxonomic_distance.npy` de la Fase 1. |
| 154 | + 4. **Test de Mantel** (permutación, no Spearman naive) para el p-valor — |
| 155 | + los pares de una matriz de distancia no son independientes, así que |
| 156 | + un Spearman ingenuo infla la significancia. Reutilizar el patrón |
| 157 | + auto-escéptico de `scripts/validation/check_zero_count_semantics.py` |
| 158 | + (hipótesis nula explícita + baseline de permutación aleatoria). |
| 159 | + 5. Bootstrap sobre especies para intervalo de confianza de la correlación |
| 160 | + de cada condición, y comparar A vs B vs C. |
| 161 | + 6. Hold-out: separar ~20% de especies que nunca entren en el |
| 162 | + entrenamiento, reportar la correlación también solo sobre esas. |
| 163 | + Donde aplique, reutilizar `representation_probe_suite`/ |
| 164 | + `retrieval_ablation_suite` de `scripts/analysis/project_audit.py` |
| 165 | + (`project_audit.py:341`, `:534`) en vez de reimplementar estadística — |
| 166 | + el roadmap ya señala esa máquina como la más reutilizable para esto. |
| 167 | + |
| 168 | +--- |
| 169 | + |
| 170 | +## Fase 5 — Reporte honesto |
| 171 | + |
| 172 | +Actualizar `docs/plans/EXTERNAL-VALIDATION-ROADMAP.md` con el resultado |
| 173 | +(cualquiera sea) siguiendo el mismo formato que ya usa el documento para |
| 174 | +`check_zero_count_semantics.py`: hipótesis, método, resultado numérico, |
| 175 | +veredicto explícito. Si el resultado es negativo (ninguna condición |
| 176 | +correlaciona con taxonomía real), documentarlo igual — es la clase de |
| 177 | +resultado que el roadmap pide, no uno a evitar. |
| 178 | + |
| 179 | +--- |
| 180 | + |
| 181 | +## Archivos nuevos (resumen) |
| 182 | + |
| 183 | +| Archivo | Rol | |
| 184 | +|---|---| |
| 185 | +| `scripts/data/fetch_cytochrome_c.py` | Descarga secuencias reales + manifest | |
| 186 | +| `scripts/data/fetch_taxonomy.py` | Linaje NCBI + matriz de distancia taxonómica | |
| 187 | +| `scripts/data/align_cytochrome_c.py` | Alineamiento contra referencia humana | |
| 188 | +| `scripts/data/prepare_cytochrome_c_dataset.py` | Ventanas alineadas → `indices.pt` | |
| 189 | +| `src/models/vae_baseline.py` | `TernaryVAEEuclideanBaseline` (condición A) | |
| 190 | +| `scripts/applications/train_euclidean_baseline.py` | Loop de entrenamiento condición A | |
| 191 | +| `src/presets/cytochrome_c_B_hyperbolic_generic.yaml` | Config condición B | |
| 192 | +| `src/presets/cytochrome_c_C_padic.yaml` | Config condición C | |
| 193 | +| `scripts/analysis/evaluate_phylogeny_recovery.py` | Evaluación A/B/C + Mantel + bootstrap | |
| 194 | + |
| 195 | +Reutilizados sin cambios: `scripts/data/peptide_encoding.py`, |
| 196 | +`src/core/ternary.py` (`TERNARY.from_ternary`), `src/train.py`, |
| 197 | +`src/models/vae.py` (`TernaryVAEV6Controllable`), patrones de |
| 198 | +`scripts/analysis/probe_foreign_genome.py` y `project_audit.py`. |
| 199 | + |
| 200 | +--- |
| 201 | + |
| 202 | +## Verificación |
| 203 | + |
| 204 | +1. `fetch_cytochrome_c.py` / `fetch_taxonomy.py`: correr y confirmar que el |
| 205 | + manifest tiene N especies reales con accessions válidos (no placeholders). |
| 206 | +2. `prepare_cytochrome_c_dataset.py`: confirmar shape de `indices.pt` y que |
| 207 | + el mapa especie↔ventana cuadra. |
| 208 | +3. Entrenar las 3 condiciones (pocas épocas primero, smoke test, luego full |
| 209 | + run) y confirmar convergencia básica (reconstrucción no se estanca). |
| 210 | +4. Correr `evaluate_phylogeny_recovery.py` y revisar que el test de Mantel |
| 211 | + produzca un p-valor y no solo una correlación cruda. |
| 212 | +5. Confirmar que el resultado (positivo o negativo) queda escrito en el |
| 213 | + roadmap con el mismo nivel de rigor que las entradas existentes. |
0 commit comments