Genera mascotas virtuales que se parecen a sus dueños humanos, extrayendo rasgos faciales y mapeándolos a características de mascota mediante un modelo generativo.
Cuatro aproximaciones complementarias extraen atributos faciales a partir de fotos de personas:
- CNN entrenada desde cero (
face_extractor/cnn_from_scratch/) — backbone convolucional propio con 17 cabezas, entrenado sobre CelebA y fine-tuneado con FairFace, más dos cabezas extra de visión clásica para color de ojos y tono de piel. Sirve como baseline ligero (~1.6M parámetros) frente a los enfoques preentrenados. - Transfer learning con ResNet-18 (
face_extractor/resnet/) — produce un embedding de 256 dimensiones y predice 15 atributos binarios. - Vision Transformer multi-head (
face_extractor/vit/) — ViT-B/16 con 17 cabezas softmax, entrenado en SageMaker. - Pipeline geométrico con MediaPipe Face Mesh (
face_extractor/clip_facemesh/) — MediaPipe (478 landmarks) + DeepFace, extrae rasgos interpretables y los traduce a prompt tokens para Stable Diffusion.
| CNN from scratch | ResNet-18 | ViT Multi-Head | |
|---|---|---|---|
| Backbone | Custom (5 conv blocks) | ResNet-18 (ImageNet-1k) | ViT-B/16 (ImageNet-21k) |
| Tipo de entrenamiento | Desde cero | Transfer learning | Transfer learning |
| Datasets | CelebA + FairFace + pseudo-labels | CelebA | CelebA |
| Salida | 19 atributos multi-clase | 15 atributos binarios (sigmoide) | 17 categorías multi-clase (softmax) |
| Parámetros | 1.6M | 11.3M | 86.0M |
| Embedding / descriptor | 512-dim | 256-dim | 256-dim |
| Mejor epoch | 8 | 19 | 9 |
| Métrica de validación | acc media 0.872 | mAP 0.7530 | acc media 0.883 |
Los tres modelos usan esquemas de etiquetas y métricas distintos, así que las cifras no son directamente comparables. La CNN desde cero queda como baseline más ligero y autocontenido; el ViT alcanza la mayor accuracy media pero es ~4x más lento que el ResNet; el ResNet queda como la opción equilibrada cuando la velocidad importa.
El proyecto combina tres familias de técnicas: entrenamiento desde cero sobre una CNN propia; transfer learning sobre backbones preentrenados con fine-tuning; y modelos preentrenados usados sin entrenar en inferencia directa. A continuación el marco teórico de cada bloque.
Backbone convolucional propio de cinco bloques (Conv 3x3 + BatchNorm + ReLU + MaxPool) que recorre la secuencia 3→32→64→128→256→512 canales mientras la resolución espacial desciende de 224 a 7 píxeles. Un adaptive average pooling final produce un descriptor compacto de 512 dimensiones que alimenta 19 cabezas lineales independientes para atributos como color y textura de pelo, forma de ojos, tono de piel y rango de edad. La pérdida es multi-tarea (suma ponderada de cross-entropies) con ignore_index=-1 para muestras sin etiqueta en una cabeza concreta, lo que permite aprovechar CelebA aunque la señal por atributo sea heterogénea. Tras el entrenamiento principal sobre CelebA (20 epochs), se hace fine-tuning de las cabezas de tono de piel y rango de edad sobre FairFace, y se añaden dos cabezas adicionales (color de ojos y tono de piel LAB) entrenadas sobre pseudo-etiquetas generadas con visión clásica.
El transfer learning reutiliza una red ya entrenada sobre un dataset enorme y la adapta a la tarea actual, en lugar de entrenar desde cero. Las primeras capas aprenden features visuales genéricas (bordes, texturas, formas) que sirven para casi cualquier problema de imágenes, así que solo es necesario re-entrenar las capas finales y ajustar suavemente el resto. Esto permite entrenar con relativamente pocos datos y en poco tiempo.
ResNet-18 (CNN residual). Red convolucional de 18 capas preentrenada en ImageNet-1k. Su aporte teórico son las conexiones residuales (skip connections): cada bloque aprende un residuo que se suma a su entrada, lo que evita el problema del gradiente que se desvanece y permite entrenar redes profundas de forma estable. Las convoluciones aplican filtros locales que detectan patrones espaciales con invariancia de traslación. Se utiliza como extractor liviano: 11.3M de parámetros, 256-dim de embedding, salida con 15 atributos binarios y activación sigmoide (cada atributo es una decisión independiente).
ViT-B/16 (Vision Transformer). Transformer de visión preentrenado en ImageNet-21k. En lugar de convoluciones, divide la imagen en parches de 16x16, los proyecta a vectores (tokens) y los procesa con auto-atención (self-attention), que pondera la relación entre todos los parches a la vez. Esto captura dependencias de largo alcance en la cara (relacionar ojos con mandíbula, por ejemplo) mejor que el campo receptivo local de una CNN. El coste es cuadrático en el número de parches y necesita más datos de preentrenamiento, por eso parte de ImageNet-21k. Aquí se configura como multi-task / multi-head: un tronco común genera el embedding de 256-dim y 17 cabezas softmax independientes, una por categoría, predicen en paralelo compartiendo la representación.
Estos modelos ya vienen entrenados por terceros. No se entrenan: solo se les pasa la imagen y se consume su salida.
MediaPipe Face Mesh es un modelo preentrenado de Google (no una librería de geometría manual). Internamente corre en dos etapas: primero un detector de rostro liviano tipo BlazeFace que localiza la cara, y después una red de regresión que predice las coordenadas de 478 landmarks faciales (468 de la malla base + 10 de iris con refinamiento). Fue entrenado sobre miles de caras anotadas. Sobre esas coordenadas se calculan rasgos con morfometría geométrica (proporciones entre puntos normalizadas por el ancho de la cara, invariantes a escala y distancia a la cámara).
DeepFace es una librería que envuelve varias redes preentrenadas. Para emoción usa una red liviana tipo mini-Xception entrenada sobre FER, que clasifica entre 7 emociones básicas; para edad usa una red basada en VGG-Face que la estima por regresión. A diferencia de la morfometría geométrica, estas redes aprenden patrones de textura y forma directamente de los píxeles.
MTCNN es una red en cascada (Multi-task Cascaded CNN) usada en el pipeline de inferencia de los modelos de deep learning para detectar y alinear la cara antes de pasarla al backbone. También es preentrenada.
- Transfer learning + fine-tuning: backbones preentrenados (ResNet-18, ViT-B/16) adaptados a la tarea.
- Multi-task learning: un embedding compartido alimenta múltiples cabezas (19 en la CNN propia, 17 en el ViT, 15 en el ResNet) que se entrenan juntas.
- Backbone freezing escalonado: las primeras epochs el backbone se congela y solo se entrenan las cabezas; después se descongela para fine-tune completo, evitando destruir los pesos preentrenados al inicio.
- Learning rates discriminativas: tasa alta para las cabezas nuevas y ~10x menor para el backbone, para no arruinar lo aprendido.
- Precisión mixta (AMP): cómputo en float16 donde es seguro, ~2x más rápido y con menos consumo de memoria en GPU.
- Cosine annealing scheduler: la learning rate baja suavemente a lo largo del entrenamiento.
- Data augmentation (Albumentations): flip horizontal, jitter de color, ruido gaussiano, para mejorar generalización.
- Funciones de pérdida: BCEWithLogitsLoss con
pos_weightpara el desbalance de clases en el ResNet; suma ponderada de cross-entropies conignore_index=-1en la CNN from scratch; suma de cross-entropy de las 17 cabezas en el ViT. - Pseudo-etiquetado con visión clásica: en CelebA se generan pseudo-labels de color de ojos (HSV con balance de blancos por esclera y máscara anular del iris) y tono de piel (canal L del espacio LAB), que después se usan para entrenar cabezas adicionales en la CNN from scratch.
- Early stopping + checkpointing: corte cuando la validación deja de mejorar y guardado del mejor modelo.
- Gradient clipping: norma del gradiente recortada a 1.0, habitual para estabilizar transformers.
- Morfometría geométrica: ratios entre landmarks normalizados por ancho de cara para derivar rasgos interpretables.
- Clasificación de color en espacios HSV/LAB: mediana del iris en HSV para color de ojos; canal L de LAB para tono de piel; ambas robustas frente a variaciones de iluminación.
- Condicionamiento por texto (CLIP / Stable Diffusion): los rasgos se traducen a prompt tokens en inglés que el encoder CLIP convierte en vectores de condición para la generación.
- Meta-learning / stacking (en curso): un metalearner combinará las salidas de todos los extractores (atributos ResNet, cabezas ViT, rasgos geométricos, color de ojos) en una predicción final.
La fase de generación reside en pet_generation/pet_generation.ipynb. Toma los rasgos extraídos en la Fase 1 y genera un familiar mágico — un animalito real y tierno (zorrito, búho, gatito, panda rojo, cervatillo...) — condicionado por la cara del usuario. El notebook articula el pipeline en tres bloques:
- Ensemble de cinco fuentes. Los tres modelos entrenados (
final_model-agus.pt,resnet-18-kat.pt,vit_multihead_best-kat.pt) predicen probabilidades sobre 19 rasgos. A esas tres distribuciones se suman dos votantes adicionales: una extracción geométrica con MediaPipe Face Mesh (478 landmarks, 11 rasgos derivados de ratios) y un verificador zero-shot con CLIP (openai/clip-vit-base-patch32). Las cinco se combinan por voto suave ponderado por rasgo, con una capa final de corrección manual (ipywidgets) que permite ajustar cualquier predicción antes de pasar a generación. - Selección determinista de la criatura. Un mapper convierte los rasgos consolidados en una especie, una paleta de colores y un conjunto de traits concretos. La función es determinista, así que la misma combinación de rasgos siempre desemboca en la misma especie.
- Generación con difusión. Stable Diffusion 1.5 (
Yntec/CuteFurry) genera la imagen final, condicionada por (a) un prompt enfático construido a partir de los rasgos y la especie elegida concompelpara ponderar tokens, (b) IP-Adapter plus-face (h94/IP-Adapter) que recibe la cara recortada del usuario para inyectar coloración y expresión, y (c) LCM-LoRA (latent-consistency/lcm-lora-sdv1-5) que reduce la inferencia a ~8 pasos en lugar de los 25-30 habituales.
Los checkpoints de los modelos de la Fase 1 que el notebook consume (final_model-agus.pt, resnet-18-kat.pt, vit_multihead_best-kat.pt) y las imágenes de prueba (rostro_agus.jpeg, rostro_prueba.jpg) no están en el repositorio porque superan el límite de tamaño de GitHub. Se distribuyen aparte y deben colocarse al mismo nivel que el .ipynb. Ver pet_generation/README.md para la lista completa de archivos externos.
petly/ es la aplicación web que envuelve todo el pipeline anterior en un producto utilizable: el usuario sube una foto desde el navegador y recibe una mascota generada. Internamente reutiliza la misma lógica que el notebook de la Fase 2 (Stable Diffusion 1.5 + IP-Adapter plus-face + LCM-LoRA para acelerar la inferencia) más una capa determinista que traduce los 18 rasgos extraídos a una especie, una paleta de colores y un conjunto de traits concretos.
- Backend (FastAPI,
petly/backend/): expone dos endpoints,POST /api/analyze(recibe la foto y devuelve la mascota propuesta con la PNG generada) yGET /api/pets(lista la colección). El pipeline de análisis facial es el mismo que el del notebook, portado a módulos Python (pipeline/core.py,mapper.py). - Frontend (Vite + React,
petly/frontend/): port hifi del prototipo Petly con una state machine de cinco pantallas (intro → captura → análisis → reveal → galería). Si la generación con Stable Diffusion está apagada o falla, cae a un SVG vectorial procedural construido a partir de los mismos traits. - Requisitos: Python 3.11 con los checkpoints
.ptdel notebook, Node 18+, y opcionalmente GPU para que la generación con SD vaya en segundos en vez de minutos.
Los detalles de arranque (uvicorn, npm run dev), variables de entorno (PETLY_MODELS_DIR, PETLY_GEN, PETLY_GEN_STEPS...) y el contrato de la API están en el README de petly.
unique-pet-generation/
├── README.md # este archivo
├── LICENSE
├── face_extractor/
│ ├── README.md # índice de las 4 aproximaciones
│ ├── cnn_from_scratch/ # 5.1 CNN multi-cabeza desde cero
│ │ ├── facial_attributes_extractor.ipynb
│ │ ├── inference.py
│ │ ├── finetune_extensions.py
│ │ ├── generate_pseudolabels.py
│ │ ├── generate_skin_pseudolabels.py
│ │ ├── models/ # backbone + heads
│ │ ├── utils/ # análisis HSV/LAB y label maps
│ │ ├── checkpoints/ # final_model.pt
│ │ ├── test_photos/
│ │ └── requirements.txt
│ ├── resnet/ # 5.2 Transfer learning con ResNet
│ │ ├── configs/base.yaml # hiperparámetros y configuración
│ │ ├── data/ # CelebA (descargado por script)
│ │ ├── scripts/
│ │ │ ├── download_celeba.py # descarga CelebA desde Kaggle (kagglehub)
│ │ │ ├── train.py # punto de entrada de entrenamiento
│ │ │ └── inference.py # webcam o archivo de imagen
│ │ ├── src/pet_gen/
│ │ │ ├── data/ # dataset CelebA, transforms, preprocessing
│ │ │ ├── models/ # backbone, heads, feature_model
│ │ │ └── training/ # trainer, losses, metrics
│ │ ├── notebooks/
│ │ │ ├── 01_explore_celeba.ipynb
│ │ │ ├── 04_comparison_resnet_vit.ipynb
│ │ │ └── 05_normalizacion_de_outputs.ipynb
│ │ ├── tests/
│ │ ├── pyproject.toml
│ │ ├── requirements.txt
│ │ └── uv.lock
│ ├── vit/ # 5.3 ViT multi-head en SageMaker
│ │ └── notebooks/
│ │ ├── 00_s3_setup.ipynb # sube CelebA a S3 para SageMaker
│ │ ├── 02_celeba_to_flags_mapping.ipynb
│ │ └── 03_sagemaker_vit_training.ipynb
│ └── clip_facemesh/ # 5.4 MediaPipe + DeepFace + CLIP
│ ├── notebooks/
│ │ └── 06_facemesh.ipynb
│ └── agus.jpg # imagen de ejemplo
├── pet_generation/ # Fase 2: generación con Stable Diffusion
│ ├── README.md
│ └── pet_generation.ipynb
└── petly/ # Aplicación web (FastAPI + React)
├── README.md
├── backend/ # FastAPI + pipeline portado del notebook
│ ├── app/
│ │ ├── main.py # app FastAPI, CORS, routers
│ │ ├── api/ # /api/analyze, /api/pets
│ │ ├── pipeline/core.py # lógica de análisis facial
│ │ ├── mapper.py # 18 rasgos → Pet (especie, color, traits)
│ │ ├── db.py # colección en SQLite
│ │ └── schemas.py # contrato Pet / Trait / AnalyzeResponse
│ └── requirements.txt
└── frontend/ # Vite + React
├── src/
│ ├── App.jsx # state machine + cliente
│ ├── api.js # llamadas al backend
│ ├── screens.jsx # intro / capture / analyze / reveal / gallery
│ └── pets.jsx # 6 mascotas SVG + registro
└── package.json
Cada subcarpeta de face_extractor/ y la de pet_generation/ es autocontenida con sus propias dependencias y notebooks. Lo que sigue son los pasos rápidos para cada aproximación; el README específico de cada subcarpeta da más detalle.
- Python 3.12.
- Para los pipelines basados en CelebA: credenciales de la API de Kaggle configuradas en
~/.kaggle/kaggle.json. - Para el ViT: cuenta de AWS con permisos para SageMaker y S3.
Los checkpoints .pt no están en el repositorio porque cada uno supera el límite de GitHub. Se distribuyen en un zip aparte que contiene una carpeta models/ con tres archivos:
models/
├── cnn_scratch.pt (~6 MB) — CNN multi-cabeza desde cero
├── resnet-18.pt (~136 MB) — ResNet-18 transfer learning
└── vit_multihead_best.pt (~344 MB) — ViT-B/16 multi-head
Cada subproyecto del repo espera los .pt en una carpeta y con un nombre concretos. La tabla siguiente indica dónde copiar cada archivo según qué se quiera ejecutar:
| Si quieres ejecutar... | Copia desde models/ |
a esta ruta del repo |
|---|---|---|
Demo principal de generación de mascotas (pet_generation/pet_generation.ipynb) |
cnn_scratch.pt |
pet_generation/final_model-agus.pt |
| ↑ | resnet-18.pt |
pet_generation/resnet-18-kat.pt |
| ↑ | vit_multihead_best.pt |
pet_generation/vit_multihead_best-kat.pt |
Análisis CNN from scratch (face_extractor/cnn_from_scratch/facial_attributes_extractor.ipynb) |
cnn_scratch.pt |
face_extractor/cnn_from_scratch/checkpoints/final_model.pt |
Comparativa ResNet vs ViT (face_extractor/resnet/notebooks/04_comparison_resnet_vit.ipynb) |
resnet-18.pt |
face_extractor/resnet/checkpoints/resnet-18.pt |
| ↑ | vit_multihead_best.pt |
face_extractor/resnet/checkpoints/vit_multihead_best.pt |
Para el caso más común (sólo el demo principal): basta con copiar los tres archivos a
pet_generation/renombrándolos como indica la tabla. Los notebooks de análisis individuales sólo son necesarios si se quieren reproducir los experimentos por separado.
Adicionalmente, el demo necesita dos imágenes de prueba (rostro_agus.jpeg y rostro_prueba.jpg) que también se distribuyen en el zip. Se colocan al lado del notebook, en pet_generation/.
cd face_extractor/cnn_from_scratch
pip install -r requirements.txtInferencia rápida sobre una imagen:
from inference import extract_face_attributes
attrs = extract_face_attributes("test_photos/foto1.jpeg",
checkpoint_path="checkpoints/final_model.pt")
print(attrs)El notebook facial_attributes_extractor.ipynb contiene el entrenamiento completo y los análisis post entrenamiento (distribuciones reales vs predichas, matrices de confusión, ejemplos cualitativos).
cd face_extractor/resnet
python -m venv .venv
source .venv/bin/activate # en Windows: .venv\Scripts\activate
pip install -r requirements.txt
python scripts/download_celeba.py # descarga CelebA (~1.4 GB)
python scripts/train.py # entrenamiento hasta 30 epochs con early stoppingInferencia:
python scripts/inference.py # webcam (SPACE para capturar, Q para salir)
python scripts/inference.py path/to/photo.jpgEn Apple Silicon (MPS) el entrenamiento tarda aproximadamente 1-2 horas.
Los notebooks de la subcarpeta vit/ están pensados para ejecutarse sobre AWS SageMaker. El flujo es: 00_s3_setup sube CelebA a S3, 02_celeba_to_flags_mapping define las 17 categorías softmax (flags.txt), y 03_sagemaker_vit_training lanza el job de entrenamiento.
cd face_extractor/clip_facemesh
jupyter notebook notebooks/06_facemesh.ipynbEl notebook carga MediaPipe Face Mesh y DeepFace, procesa una imagen (webcam o archivo) y produce un rasgos_checklist.json con los atributos y el prompt completo para Stable Diffusion.
Ver pet_generation/README.md. Requiere descargar los checkpoints externos y las imágenes de prueba listadas allí.
cd face_extractor/resnet
pytest tests/ -vCubren la carga del dataset, los splits y transforms, los forwards de los backbones y el flujo de gradientes con freeze/unfreeze.
Los siguientes notebooks llevan anotaciones celda por celda con teoría e interpretación de resultados:
face_extractor/cnn_from_scratch/facial_attributes_extractor.ipynb— diseño, entrenamiento, extensión con pseudo-etiquetas y análisis de sesgo del baseline desde cero.face_extractor/vit/notebooks/03_sagemaker_vit_training.ipynb— ViT-B/16 multi-head con 17 cabezas softmax sobre un embedding de 256-dim. Backbone congelado 5 epochs, luego fine-tune completo con learning rates discriminativas y precisión mixta. Early stopping en el epoch 12, mejor en el 9, ~84 min en una A10G.face_extractor/resnet/notebooks/04_comparison_resnet_vit.ipynb— comparación lado a lado de arquitectura, velocidad de inferencia, métricas guardadas, espacios de embedding (PCA / coseno) y acuerdo en conceptos solapados como el color de pelo.face_extractor/clip_facemesh/notebooks/06_facemesh.ipynb— webcam o imagen → MediaPipe Face Mesh (478 landmarks) → rasgos geométricos (forma de cara, ojos, tono de piel, simetría, cejas vía ratios de landmarks) + DeepFace (emoción, edad) → prompt tokens de Stable Diffusion →rasgos_checklist.jsonpara el paso de generación.
- Parámetros: 1.6M (1.57M backbone + 37K cabezas).
- Mejor epoch: 8.
- Accuracy media de validación: 0.872 sobre 15 cabezas activas de CelebA.
- Cabezas finales: 19 (17 entrenadas por el CNN + 2 entrenadas sobre pseudo-etiquetas de visión clásica).
- Comportamiento observado: predictor centrado en la moda del dataset en imágenes ambiguas; la cabeza CNN de tono de piel queda por debajo de la versión LAB, por lo que el sistema final se apoya en la versión clásica para esa decisión.
- Mejor epoch: 19 (early stopping en 24).
- mAP de validación: 0.7530.
- Accuracy de validación: 83.22%.
- Dimensión del embedding: 256.
- Mejor epoch: 9 (early stopping en 12).
- Accuracy media de validación: 0.883.
- Rasgos fuertes: barbilla, pecas, gafas, tono de piel, forma de cara (>0.95).
- Rasgos débiles: tamaño/forma de nariz, forma de ojos (~0.68–0.79), sutiles, dependientes del ángulo y la iluminación, con etiquetas más ruidosas.
Este pipeline no se entrena, así que no tiene métricas de validación; usa modelos preentrenados (MediaPipe + DeepFace) en inferencia directa y es determinista sobre una imagen dada. Sus resultados son cualitativos: el conjunto de rasgos extraídos y su traducción a prompt. Sobre la imagen de ejemplo (agus.jpg) extrae 9 rasgos y 6 prompt tokens:
| Rasgo | Valor | Fuente | Confianza |
|---|---|---|---|
| Forma de cara | redonda (ratio 0.827) | geométrico | alta |
| Tamaño de ojos | pequeños | geométrico | alta |
| Color de ojos | negro | iris HSV | media |
| Cejas | arqueadas | geométrico | media |
| Sonrisa | 1.0 | geométrico | media |
| Simetría | 0.0 | geométrico | alta |
| Expresión | neutral (67%) | DeepFace | media |
| Edad estimada | ~23 años | DeepFace | media |
| Tono de piel | oscuro | luminosidad | media |
Salida: outputs/rasgos_checklist.json con los rasgos, sus confianzas y el prompt completo concatenado, listo para el paso de generación.
Limitaciones observadas: la simetría dio 0.0 porque la cara no estaba perfectamente frontal respecto al eje central que asume la fórmula; el tono de piel se calcula de luminosidad cruda, así que una foto con poca luz lo empuja hacia oscuro. Son las limitaciones típicas de un método geométrico con umbrales fijos: rápido y explicable, pero sensible al encuadre y la iluminación.
MIT — ver LICENSE para más detalles.
