Predicción de pCR en BreastDCEDL
Pesos del modelo final de ML_PrediccionpCR. A partir de una resonancia de mama con contraste (DCE-MRI) tomada antes de la quimioterapia, el modelo estima la probabilidad de pCR, es decir, de que tras el tratamiento no quede tumor invasivo.
Uso educativo, sin validez clínica.
Qué es
- CNN propia, entrenada desde cero, sin nada preentrenado: 3 bloques convolucionales de 8, 16 y 32 filtros. Trabaja a 128 px dentro de la red y tiene 18.329 parámetros.
- Conjunto de 5 redes, una por fold de validación cruzada. La probabilidad de un corte es la media de las 5 sigmoides.
- Umbral 0,3635, fijado en validación (pCR si la probabilidad es > 0,3635).
- Entrada: un corte de 3 fases del contraste (PRE, EARLY, LATE, que no son RGB), cada una
un PNG en gris de 256×256 dividido entre 255. La forma es
(N, 3, 256, 256),float32, en [0, 1]. El resto del preprocesado (la reducción a 128 px) va dentro de la red. - Salida de cada red: un logit; la sigmoide da p(pCR).
Uso previsto y no previsto
- Previsto: docencia e investigación. Sirve para estudiar el problema, reproducir los resultados y probar la web.
- No previsto: cualquier uso clínico. No sirve para diagnosticar ni para decidir tratamientos, y no es un dispositivo médico. Un buen resultado retrospectivo no demostraría seguridad ni utilidad clínica, y este, además, no supera al azar en test.
Datos y partición
- Dataset: BreastDCEDL (Fridman et al., 2025, arXiv:2506.12190). Está construido a partir de las colecciones públicas I-SPY1, I-SPY2 y Duke Breast Cancer MRI y tiene licencia CC BY-NC 4.0. Estos pesos derivan de él y se publican con la misma licencia, para uso no comercial y conservando la atribución.
- Tamaño: 1.273 pacientes y 12.703 cortes; el 70,6 % son pCR=0.
- Train: 1.097 pacientes (10.945 cortes).
- Test: 176 pacientes (1.758 cortes).
- Partición por paciente: ningún corte de una paciente aparece a la vez en entrenamiento y validación (5 folds) ni en train y test.
- Test usado una sola vez, al final, con un plan fijado antes (pesos y su SHA-256, umbral y agregación). Nada se ajustó con él.
Resultados
- Validación (5 folds): AUC medio de todas las épocas, 0,551 ± 0,011.
- Test (evaluado una sola vez, por paciente, con la probabilidad media de sus cortes):
| Métrica | Valor |
|---|---|
| AUC | 0,544 [IC 95 %: 0,457-0,629] |
| Sensibilidad | 0,038 |
| Especificidad | 0,935 |
Matriz de confusión (176 pacientes):
| Predicho pCR | Predicho no pCR | |
|---|---|---|
| pCR real | VP 2 | FN 51 |
| No pCR real | FP 8 | VN 115 |
Los intervalos son bootstrap de pacientes (2.000 remuestreos). Fridman et al. 2025 obtienen 0,72 en el mismo test con un modelo preentrenado, así que no es comparable con una CNN desde cero. El detalle está en docs/decisiones.md.
Limitaciones
- No se distingue del azar en test: el IC 95 % del AUC incluye 0,5. Con el umbral elegido, detecta 2 de las 53 pCR.
- Un corte frente a un umbral por paciente: el umbral se eligió con la media de los cortes de cada paciente (unos 10). Aplicado a un solo corte, como en la web, la decisión es menos fiable.
- EARLY saturado: en la zona que más realza, más de la mitad de los píxeles están a 255 en EARLY en el 63 % de los cortes de train. La magnitud del realce no está en la imagen. Es una limitación de los PNG de 8 bits del dataset, no de este modelo.
- Diferencias entre cohortes: las tres cohortes se procesaron de forma distinta. En test, el AUC por cohorte va de 0,351 (I-SPY1, 35 pacientes) a 0,650 (Duke, 42).
- TF32 frente a FP32: las cifras oficiales se calcularon en GPU con TF32, el valor por
defecto de PyTorch. En FP32, como calculan la web y
src.inferencia, las probabilidades cambian hasta ~7e-4.
Cómo cargarlos
La arquitectura está en el repositorio de GitHub (src/models/cnn.py). Con el repositorio
clonado, desde su raíz:
import torch
from huggingface_hub import hf_hub_download
from src.models.cnn import cargar
REPO_HF = "Valdi183/ML_PrediccionpCR"
NOMBRES = ["it6_3bloques_f0", "it6_3bloques_f1", "it8_3bloques_f2",
"it8_3bloques_f3", "it8_3bloques_f4"]
UMBRAL = 0.363489137
# cargar() usa torch.load(..., weights_only=True) y devuelve la red en modo evaluación
redes = [cargar(hf_hub_download(REPO_HF, f"{n}.pt"))[0] for n in NOMBRES]
x = torch.rand(1, 3, 256, 256) # PRE, EARLY, LATE en [0, 1]
with torch.no_grad():
prob = torch.stack([torch.sigmoid(r(x)) for r in redes]).mean().item()
print(prob, "pCR" if prob > UMBRAL else "no pCR")
Desde PNG reales: python -m src.inferencia --pre X_PRE.png --early X_EARLY.png --late X_LATE.png
valida la entrada y comprueba el SHA-256 de cada peso.
- Web, predicción en el navegador con el ONNX: https://valdi183.github.io/ML_PrediccionpCR/
- Código, entrenamiento y resultados: https://github.com/Valdi183/ML_PrediccionpCR
Ficheros
| Fichero | Contenido | SHA-256 |
|---|---|---|
it6_3bloques_f0.pt |
red del fold 0 | f1eb794abdee19ae3c8308727741a1d3db2b53c27e8112b1d241d6728e671862 |
it6_3bloques_f1.pt |
red del fold 1 | 0c62d2e98b46ae493758c1670dbd56fcfd683d7e6f75074de00e02ff52e3a563 |
it8_3bloques_f2.pt |
red del fold 2 | b58239fd676a4d9050524933589dd80fb3f56d178e5f6dec5e3b56df4f5e29c8 |
it8_3bloques_f3.pt |
red del fold 3 | 3c3826e3f1d07d3254b7ddf6a068c94e1ee24e791f04d981d38c9242afd47145 |
it8_3bloques_f4.pt |
red del fold 4 | e2d184a2ea3275a958630c1481be64dd3c9d751ed8c20e8e0e1d97d4db7ff1f3 |
conjunto.onnx |
las 5 redes en un solo grafo (opset 17); devuelve prob (media) y por_modelo |
b6779db9769e1c62f61032dfec4c67215e862a0a4696fcd266f141382853413d |
La fuente de verdad de los hashes de los .pt es
results/test/plan.json;
la del ONNX, web/modelo.json.