Archivo de la etiqueta: GAN

Modelos Generativos para Síntesis y Mejora de Imágenes Médicas

Introducción

En el campo de la imagen médica, la posibilidad de transformar una modalidad en otra, como generar imágenes de tomografía computarizada (CT) a partir de resonancia magnética (MRI) o tomografía por emisión de positrones (PET) se está convirtiendo en una herramienta revolucionaria. Modelos como las redes generativas antagónicas (GANs) permiten esta conversión de forma precisa y sin necesidad de someter al paciente a múltiples escaneos, lo que reduce la exposición a radiación [1], ahorra costes clínicos y facilita el uso de sistemas diseñados específicamente para trabajar con imágenes CT. Además, estas imágenes sintéticas permiten ampliar conjuntos de datos para entrenar algoritmos, mejorar la planificación quirúrgica o radioterápica y avanzar hacia una medicina más personalizada y eficiente [2]. La generación de CT sintéticas a partir de MRI o PET representa así una solución elegante y práctica a varios de los retos actuales en diagnóstico por imagen.

Para entender la aplicación de esta GAN en la transformación de imágenes médicas lo primero que tendremos que hacer será aprender las características del formato de imágenes que vamos a utilizar:

Imágenes por Resonancia Magnética (MRI)

La resonancia magnética se utiliza para visualizar tejidos blandos con gran detalle, cómo el cerebro, los músculos o los ligamentos, sin necesidad de radiación ionizante. Es especialmente útil en neurología y traumatología.

Tomografía por Emisión de Positrones (PET)

Las imágenes PET permiten observar procesos metabólicos en el cuerpo, siendo muy eficaces para detectar tumores activos y hacer seguimiento en tratamientos oncológicos. Proporcionan una visión funcional más que estructural.

Tomografía Computarizada (CT)

La CT permite visualizar con alta resolución huesos, pulmones y vasos sanguíneos, siendo fundamental en urgencias médicas y en la planificación de tratamientos como la radioterapia. Emplea rayos X y produce imágenes detalladas del cuerpo.

Arquitectura

La arquitectura de MedCycleGAN surge como una combinación estratégica entre la estructura de entrenamiento no pareado de CycleGAN y la potencia generativa de MedGAN [3]. El objetivo es obtener imágenes sintéticas de alta calidad sin la necesidad de datasets emparejados, una limitación importante en entornos médicos reales.

Dataset y configuración

En esta implementación, los modelos fueron entrenados con 1595 imágenes por dominio, sin emparejamiento directo. El entrenamiento sigue el flujo estándar de CycleGAN, pero se beneficia de la mayor capacidad de modelado de CasNet para refinar progresivamente los detalles anatómicos de las imágenes generadas

Generadores con arquitectura CasNet

El elemento central del modelo generador es CasNet (Cascaded U-Net), introducido originalmente en MedGAN. Esta arquitectura consiste en una secuencia de U-Nets encadenadas, donde la salida de una U-Net es usada como entrada de la siguiente. Cada U-Net tiene la capacidad de preservar detalles espaciales mediante skip connections, lo que favorece la reconstrucción estructural en imágenes médicas. En el modelo original, se empleaban 6 U-Nets, pero en esta implementación se ha reducido a 3 U-Nets para disminuir la carga computacional sin comprometer drásticamente la calidad [3].

Formalmente, si denotamos una U-Net como UUU, y una entrada xxx del dominio fuente (por ejemplo, MR), el generador actúa como:

donde GGG representa el generador completo. Cada U-Net es un autoencoder con convoluciones convolutivas y deconvolutivas simétricas, seguidas de normalización y activaciones ReLU o LeakyReLU.

El código que utilizamos para el generador está compuesto por las siguientes celdas las cuales las explicaremos una a una tras mostrarlas:

Esto reduce el tamaño espacial sin sesgo pero aumenta la profundidad también. Se utiliza la normalización por lotes para estabilizar el entrenamiento.

Esta función ayuda a reconstruir detalles finos que se pierden en el encoding. Utiliza Conv2DTranspose para aumentar la resolución.

Construimos una U-Net completa aplicando 8 bloques encoder (el último sin conexión skip), reconstruyendo la imagen pasando por 7 bloques decoder y rehusando los skip connections.

Define los tamaños de los encoder y decoder, y construye un modelo de Keras.

Funciona de manera que, si tienes una imagen de entrada de tamaño 256×256, este generador la transforma mediante 3 pasos U-Net, comprimiendo y descomprimiendo con saltos de conexión, buscando generar una imagen sintética coherente con los datos reales.

Discriminador

El discriminador en MedCycleGAN se mantiene deliberadamente ligero, con tres capas convolucionales, lo que recuerda a la arquitectura de PatchGAN utilizada en CycleGAN. Su función no es clasificar la imagen entera como real o falsa, sino verificar patches locales, lo cual es más efectivo en imágenes médicas donde los detalles estructurales finos son relevantes [4]. Esta elección arquitectónica reduce significativamente el tiempo de entrenamiento y evita el sobreajuste, especialmente con datasets pequeños.

Flujo cíclico y consistencia

Como en CycleGAN, se utiliza un enfoque bidireccional:

  • Un generador G: X→ Y : X (por ejemplo, MR → CT)
  • Un generador inverso F: Y→X
  • Dos discriminadores Dx y Dy ​ para cada dominio.

Se aplica una pérdida adversarial sobre cada par (G, Dy) y (F, Dx), y una pérdida de consistencia cíclica

Esto obliga al sistema a mantener información relevante entre las transformaciones, asegurando que la ida y vuelta entre dominios no distorsione los datos originales.

Nuestro código del discriminador aplica una arquitectura tipo PatchGAN, evaluando pequeñas regiones de la imagen para determinar si son reales o generadas.

Usa convoluciones con normalización y activación LeakyReLU para extraer características, terminando con una salida sigmoid que da probabilidades por parche.

Esta combinación permite generar imágenes de alta calidad con coherencia visual y detalles consistentes.

Entrenamiento

El entrenamiento se basó en una arquitectura tipo CyvleGAN con dos generadores y dos discriminadores, permitiendo la conversión bidireccional entre dominios de imagen.

Se utilizaron pérdidas adversariales junto con pérdidas de ciclo y de identidad para asegurar coherencia estructural y fidelidad en la transformación. 

El optimizador Adam, con parámetros ajustados, garantiza una convergencia estable. Cada paso de entrenamiento actualiza todos los modelos simultáneamente usando tf.GradientTape y los checkpoints permiten continuar el entrenamiento sin pérdidas. 

Los resultados visuales por época muestran una mejora progresiva en la calidad de las imágenes generadas.

Testing MR images

La figura muestra imágenes reales de resonancia magnética frente a las generadas por el modelo. Se aprecia que el modelo reproduce con buena fidelidad la estructura cerebral, lo que demuestra su capacidad para generar imágenes médicamente coherentes a partir de datos sintéticos.

Testing PET images

Las imágenes muestran comparaciones entre datos PET reales (izquierda) y generados (derecha). El modelo logra replicar correctamente las regiones de mayor actividad metabólica, conservando la estructura general y la distribución de intensidades, clave para el análisis funcional.

Discusión

Los resultados obtenidos con imágenes MR y PET muestran que el modelo MedCycleGAN es capaz de generar imágenes sintéticas con una estructura anatómica y funcional coherente en comparación con las imágenes reales. En el caso de las MR, las imágenes generadas preservan bien los contornos cerebrales y detalles relevantes, lo que sugiere una buena capacidad del modelo para mantener la integridad espacial.

Para las PET, aunque la resolución original es menor y el ruido es más evidente, las imágenes generadas reproducen con acierto las zonas de alta actividad metabólica, lo cual es esencial en contextos oncológicos o neurológicos. Esto indica que el modelo no solo transfiere el estilo, sino también información clave para el análisis clínico.

En conjunto, estos resultados apoyan la utilidad del modelo como herramienta para aumentar la resolución, generar datos sintéticos para entrenamiento y reducir la necesidad de escaneos múltiples, con un impacto potencial en la mejora de calidad diagnóstica y eficiencia clínica.

BIBLIOGRAFÍA

1. Osuala, R. et al. (2022). medigan: a Python library of pretrained generative models for medical image synthesis. arXiv. https://arxiv.org/abs/2203.13765

2. Khader, F. et al. (2022). Medical diffusion: Denoising diffusion probabilistic models for 3D medical image generation. arXiv. https://arxiv.org/abs/2211.07804

3. Armanious, K., Jiang, C., Fischer, M., Küstner, T., Hepp, T., Nikolaou, K., … & Yang, B. (2020). MedGAN: Medical Image Translation using GANs. Computerized Medical Imaging and Graphics, 79, 101684. https://doi.org/10.1016/j.compmedimag.2020.101684 

4. Zhu, J. Y., Park, T., Isola, P., & Efros, A. A. (2017). Unpaired Image-to-Image Translation using Cycle-Consistent Adversarial Networks. In ICCV 2017. https://arxiv.org/abs/1703.10593

Datasets

Imágenes MRI y PET descargadas en OASIS 3, requiere un registro para descargarlas https://sites.wustl.edu/oasisbrains/home/oasis-3

Imágenes CT descargadas de Qure.ai https://web.archive.org/web/20220816011051/http://headctstudy.qure.ai/


Este blog ha sido creado por Antonio Verdú, José Ángel Bello y Jaime Sichar


GANs para traducción Imagen a Imagen: Coloración

INTRODUCCIÓN

Las GANs (o Generative Adversarial Networks) son una familia de modelos de Inteligencia Artificial en los que dos redes neuronales, conocidas como Generador y Discriminador, compiten entre sí siguiendo una dinámica de “policía y falsificador” para generar datos sintéticos, los cuales ofrecen resultados muy prometedores.

Entre los usos de estas redes se encuentran la generación de imágenes, la simulación de datos médicos o la traducción de imagen a imagen. Es sobre esto último sobre lo que nos centraremos a partir de ahora.

Más concretamente, exploraremos la utilidad de un tipo de GANs más avanzado (Conditional GANs) para colorear imágenes en escala de grises, con el posible objetivo de restauración de imágenes históricas.

ESTADO DEL ARTE

GANs y CGANs

Desde su aparición en 2014, las GANs se han impuesto como una de las formas más eficaces de entrenar modelos generativos. Estas se componen de dos redes adversarias:

  • Un generador G, que crea datos sintéticos a partir de ruido.
  • Un discriminador D, que estima la probabilidad de que los datos pertenezcan al dataset de entrenamiento frente a que sean generados por G.

Ambas redes se entrenan simultáneamente en un juego de suma cero, en el que el G aprende la distribución de los datos de entrada para generar salidas lo más factibles posible y D aprende a diferenciar los datos sintéticos de los reales. El Discriminador intenta minimizar sus errores de detección y el Generador intenta maximizarlos para imágenes sintéticas:

Aunque en la práctica rara vez es así, este juego tiene un equilibrio de Nash en el que los datos generados son perfectamente indistinguibles de los reales.

Un problema de las GANs es que, aunque el generador aprende la distribución de los datos de entrada, no tenemos control sobre los datos generados.

Para abordar este problema surgieron las GANs condicionales (CGANs por sus siglas en inglés), que introducen tanto en el generador como en el discriminador una nueva condición como dato de entrada.

Esta condición bien puede ser una etiqueta de clase, una parte de los datos o datos de otro tipo (por ejemplo, usar un prompt de texto para generar imágenes).

En las CGANs, el generador no solo aprende la distribución de los datos de entrada, sino que también genera datos consistentes con la condición de entrada al combinar ésta con la prior (normalmente ruido gaussiano) en un nuevo espacio latente conjunto. El entrenamiento sigue la misma dinámica que en las GANs tradicionales, aunque en la función de pérdida se añade la condición y:

Este tipo de arquitecturas son especialmente eficaces para la traducción de imagen a imagen, en las que dada una imagen de entrada la tarea es generar una imagen relacionada pero con otras características. En nuestro ejemplo, dada una imagen en escala de grises queremos obtener la misma imagen a color.

Pix2Pix

Una red muy conocida en el ámbito de la traducción de imagen a imagen es Pix2Pix.

Esta fue presentada en 2016 como una red de propósito general. Es decir, que la misma arquitectura se puede entrenar para distintas tareas y con distintos dominios de entrada.

Veamos más detalladamente de qué se compone esta red:

Arquitectura del generador

El generador consiste en una U-Net, una red convolucional originalmente desarrollada para la segmentación de imágenes biomédicas. Sigue una estructura codificador-decodificador (capas de reducción para representar la entrada en un espacio latente y capas de expansión para reconstruir la imagen) modificada en el que se hace uso de skip connections para mantener la información más detallada de capas anteriores.

Nota: en este diagrama, x es la imagen de entrada con la condición, e y es la imagen de salida.

Arquitectura del discriminador

Para el discriminador se hace uso de una Patch GAN, que evalúa la imagen en pequeños trozos (patches) y luego calcula la media de todos los trozos para obtener la probabilidad de que la imagen sea real.

Función de pérdida

Sea la función de pérdida de una GAN Condicional:

Pix2Pix añade un nuevo término de regularización que mide la diferencia entre la imágen generada y la imágen original:

Finalmente, se pondera esta regularización L1 y se combina con la función de pérdida de la GAN para obtener la función de coste final.

DESARROLLO

Para nuestra tarea, hemos hecho uso de una red Pix2Pix adaptada para poder entrenarse con cualquier dataset de imágenes.

Primero, tomamos una colección de imágenes (estas será nuestra ground truth) y la duplicamos en otro fichero con las imágenes a escala de grises.

Empezamos haciendo un resample para que las imágenes tengan todas el mismo tamaño (512×512). Después, dividimos en un conjunto de entrenamiento y en uno de test con proporción 80-20 y guardamos las imágenes en un dataset. Ahora que ya tenemos los datos con los que vamos a trabajar, procedemos a la creación del modelo:

Definimos las capas de upsample y downsample:

Definimos el generador:

Y el discriminador:

Por último, definimos sus funciones de pérdida:

Y ya tenemos todo lo necesario para comenzar el entrenamiento.

Hemos entrenado el modelo dos veces para probar la generalidad de la arquitectura:

-Un dataset con 702 fotos de perros

-Un dataset de 2032 fotos de arte

Por motivos prácticos y de limitaciones temporales, entrenamos los modelos con 20 épocas, lo que tardó un par de horas con la T4 de Google Collab. Los resultados los comentaremos a continuación.

RESULTADOS

Probaremos ahora a pasarle nuevas imágenes al modelo. Aquí se muestran algunas comparaciones entre la imagen original (ground truth), y la imagen generada a color a partir de la muestra en escala de grises.

DISCUSIÓN

Teniendo en cuenta las limitaciones del dataset y del entorno de Google Colab (561 imágenes para un entrenamiento con 20 épocas), los resultados son bastante satisfactorios. Más allá de métricas y errores cuantitativos, visualmente se puede apreciar que las imágenes se colorean de forma razonable, incluso cuando ve imágenes ajenas al dataset original. Esto Indica una buena generalización del modelo para fotos de perros y no solo limitada a los datos vistos durante el entrenamiento.

No obstante, las imágenes resultantes son bastante oscuras y con una paleta muy homogénea. Esta escasez de color podría deberse a una falta de datos/épocas durante el entrenamiento o a una falta de diversidad de color en los datos de entrada, y no lo abordaremos aquí. Sin embargo, el problema de la oscuridad se puede mitigar de manera directa, aplicando una transformación logarítmica a las imágenes de salida.

Como podremos comprobar, los resultados han mejorado. Si queremos resultados aún mejores, deberíamos ajustar la transformación logarítmica para cada imagen hasta lograr el resultado óptimo. Por otra parte, el dataset de obras de arte cuenta con más imágenes y una variedad de colores más amplia, así que no encontramos estos problemas en la misma medida.

A la vista de estos resultados, podemos concluir que Pix2Pix es un modelo adecuado para tareas de coloración de imágenes, y que con más recursos es razonable esperar unos resultados mucho mejores.

MATERIALES

BIBLIOGRAFÍA

Aparte de los recursos ya mencionados en el apartado de Materiales:

  • Mirza, M., & Osindero, S. (2014). Conditional Generative Adversarial Nets [Preprint]. arXiv. [Última vez visitado: 15 de mayo 2025] https://arxiv.org/pdf/1411.1784

Este artículo ha sido desarrollado por Jorge Martínez, Sergio Manrique, Lijun Zhou y David Fernández

Colorización de imágenes usando Generative Adversarial Networks

Introducción

A lo largo de este trabajo vamos a intentar llevar a cabo la colorización de imágenes en blanco y negro de manera que se parezcan lo máximo posible a la realidad. Para la realización de esta investigación, hemos decidido utilizar un dataset llamado PETS, que contiene 7390 imágenes de mascotas en diversos ambientes. El motivo que nos llevó a utilizar este dataset es, simplemente, que no hemos encontrado ningún trabajo previo que lo utilizase con esta finalidad. Además, observamos que contenía una gran variedad de imágenes diferentes entre sí, por lo que es apto para el objetivo de esta investigación.

Para la colorización de las imágenes en blanco y negro, hemos utilizado como inspiración el código propuesto en el artículo de Medium llamado Colorizing black & white images with U-Net and Conditional GAN – a tutorial.

Estado del arte

La colorización de imágenes usando sistemas automáticos no es un proceso sencillo por la gran subjetividad de colores que se pueden apreciar en una imagen y el rango de sensibilidad que pueda medir en cada píxel. Con las Generative Adversarial Networks se puede aproximar al problema desde un punto de vista novedoso con grandes resultados.

La arquitectura pix2pix (Isola et al, 2017) de las GAN permite el aprendizaje directo entre la imagen de entrada y la de salida lo que agiliza el proceso y mejora los resultados obtenidos, esto no solo se ha visto reflejado en la colorización de imágenes sino en todos aquellos procesos de aprendizaje automático relativos a imágenes o vídeos.

Kamyar Nazeri, Eric Ng y Mehran Ebrahimi (Nazeri, Ng & Ebrahimi, 2018) crearon las bases de una Deep Convolutional GAN para el proceso de colorear imágenes automáticamente usando como entrada imágenes en escalas de grises de los conjuntos de entrenamientos públicos CIFAR-10 y Places365. La mayor ventaja de este modelo se basa en la función de coste para limitar el entrenamiento y que la parte generadora no cree colores irreales.

Rahul Reddy Pasham, Sameer Md y Ashwini K. (Ashwini, Pasham & Sameer, 2022) también utilizaron GANs en el problema de colorear imágenes y en su caso destacaron las capas de activaciones LeakyReLu y tanh y la capacidad de colorear imágenes a pesar de ser datos no etiquetados.

El mayor reto de las GANS en el proyecto de colorización de imágenes actualmente es la generación precisa de colores.

Desarrollo

LAB

En el espacio LAB (Shariatnia, 2020), al igual que en RGB, cada píxel se representa con tres valores. El primer canal, L, codifica la luminosidad (Lightness) del píxel. El segundo, A, representa la mezcla entre verde y rojo, y el tercero, B, indica la mezcla entre amarillo y azul.

Se utiliza este espacio de color en lugar de RGB porque la imagen que se le proporciona al modelo para entrenar debe estar en blanco y negro. Al usar LAB, se puede enviar directamente el canal correspondiente a la luminosidad, sin necesidad de convertir la imagen a escala de grises. De este modo, el modelo solo necesita predecir los canales A y B. Esto reduce el número de combinaciones posibles: de 256³ (alrededor de 17 millones) en el espacio RGB a 256² (65.536) al predecir únicamente dos canales de color.

GAN

Las GANs (Generative Adversarial Networks) son una red neuronal compuesta por dos modelos que compiten entre sí: un generador, que aprende a crear datos falsos que imitan los datos reales, y un discriminador, que intenta distinguir entre datos reales y generados. 

Ambos se entrenan simultáneamente en un proceso de competencia: el generador mejora para engañar al discriminador, y el discriminador mejora para no ser engañado. Con el tiempo, esta dinámica hace que el generador produzca datos cada vez más realistas.

Si el entrenamiento del generador es muy bueno puede generar imágenes que al discriminador le sean difíciles de clasificar, el objetivo es que la precisión no disminuya a medida que mejora la generación de imágenes (Google Developers, s.f.).

Generador

Nuestro generador U-Net sigue una arquitectura encoder-decoder con skip-connections. Podemos destacar varias cosas de la arquitectura. En primer lugar, el uso de LeakyReLU en la parte del encoder con una slope de 0.2 y de ReLU normal en la parte del decoder (Nazeri, Ng & Ebrahimi, 2018). Por otro lado, el uso de función de activación tanh en la última capa (Isola et al., 2017; Ashwini, Pasham & Sameer, 2022) y, por último, la utilización de Batch Normalization, que nos ayuda a evitar que el entrenamiento de la GAN converja a una serie de parámetros que siempre devuelven el mismo output.  Este escenario se llama mode-collapse o the Helvetica scenario (Nazeri, Ng & Ebrahimi, 2018).

Patch Discriminator

Un Patch Discriminator es un tipo de discriminador usado en GANs (por ejemplo en la arquitectura Pix2Pix) que evalúa si pequeñas subregiones de la imagen parecen realistas.

En lugar de dar un único valor de salida , el Patch Discriminator produce una matriz de valores, donde cada valor indica si una pequeña subregión de la imagen se considera real o falso.

Es una buena idea usarlo para colorización por varios motivos: hace que el discriminador se enfoque en los detalles locales, lo que provoca que el generador acierte en cada parte de la imagen. Además, tiene menos parámetros que un discriminador global y permite un entrenamiento más estable al evaluar diferentes regiones de la imagen.

Función de pérdida

En nuestra función de pérdida, debemos destacar la aplicación de One Sided Label Smoothing; ya que, de esta manera, evitamos que la red emita outputs extremadamente seguros y aprenda mejor a la hora de realizar el entrenamiento (Nazeri, Ng & Ebrahimi, 2018). El smoothing lo realizamos en la etiqueta positiva (pasamos de 1 a 0.9) y la etiqueta negativa la dejamos intacta.

La función de pérdida establece dos modos. El modo vanilla introduce el uso de Binary Cross Entropy con Logistic Loss, mientras que el modo lsgan introduce Mean Squared Error Loss.

Esta función de pérdida guía tanto al discriminador como al generador en su entrenamiento: el discriminador aprende a diferenciar imágenes reales de generadas, mientras que el generador intenta engañarlo generando imágenes que el discriminador clasifique como reales.

Nuestro modelo

El primer paso es obtener las imágenes en el formato adecuado; en este caso, utilizamos imágenes de tamaño 256×256 en el espacio de color LAB en lugar de RGB. 

En el método __init__, se definen e inicializan el generador y el discriminador. Se configuran las pérdidas GAN y L1 (coherencia de color), y se establecen los optimizadores Adam para ambos modelos.

El método optimize gestiona el entrenamiento. Primero, se pasa la imagen en escala de grises al generador, obteniendo fake_color.

Luego, se entrena el discriminador (backward_D):

  • Se le alimentan las imágenes generadas (fake_color), marcadas como “falsas” .
  • Se le alimentan imágenes reales del conjunto de entrenamiento, marcadas como “reales”.
  • Se calcula la pérdida promedio de “falsas” y “reales”, y se retropropaga para actualizar los pesos del discriminador.

A continuación, se entrena el generador (backward_G):

  • Se alimentan al discriminador las imágenes generadas, pero con etiquetas “reales” para calcular la pérdida adversarial (engañar al discriminador).
  • Se calcula la pérdida L1, que mide la diferencia a nivel de píxel con las imágenes reales (multiplicada por 100).
  • La suma de la pérdida adversarial y la L1 es la pérdida total del generador, que se utiliza para actualizar sus pesos mediante retropropagación.

Finalmente, optimize coordina todo el ciclo de entrenamiento: primero genera la predicción, luego optimiza el discriminador y, a continuación, optimiza el generador, repitiendo este proceso para cada lote del conjunto de entrenamiento. Este proceso debe evitar el mode-collapse, un escenario en el que el generador converge a una serie de parámetros que siempre devuelven el mismo output, lo que limita la diversidad de las imágenes generadas (Shariatnia, 2020).
El código entrena el modelo durante 50 épocas utilizando el conjunto de datos de entrenamiento. En cada época, se procesan los lotes (batches) del conjunto de entrenamiento, y en cada iteración, el modelo optimiza tanto el generador como el discriminador. El generador crea imágenes coloreadas a partir de las imágenes en escala de grises, mientras que el discriminador evalúa si las imágenes generadas son reales o no. Las pérdidas de ambos modelos se registran, y cada 350 iteraciones se visualiza el rendimiento del modelo y se imprime el estado de las pérdidas. El objetivo es que, con el tiempo, el generador aprenda a crear imágenes más realistas y el discriminador mejore su capacidad para distinguir entre imágenes reales y generadas.

Materiales

En este apartado, vamos a compartir el código más importante y necesario para realizar esta tarea de colorización de imágenes en blanco y negro.

El generador U-Net de nuestra GAN (se necesita tener instalada la librería PyTorch de Python, entre otras):

class UnetBlock(nn.Module):
    def __init__(self, nf, ni, submodule=None, input_c=None, dropout=False,
                 innermost=False, outermost=False):
        super().__init__()
        self.outermost = outermost
        if input_c is None: input_c = nf
        downconv = nn.Conv2d(input_c, ni, kernel_size=4,
                             stride=2, padding=1, bias=False)
        downrelu = nn.LeakyReLU(0.2, True)
        downnorm = nn.BatchNorm2d(ni)
        uprelu = nn.ReLU(True)
        upnorm = nn.BatchNorm2d(nf)

        if outermost:
            upconv = nn.ConvTranspose2d(ni * 2, nf, kernel_size=4,
                                        stride=2, padding=1)
            down = [downconv]
            up = [uprelu, upconv, nn.Tanh()] #Utilizamos función de activación tanh en la última capa como se indica en Image Colorization using Generative Adversarial Networks
            model = down + [submodule] + up
        elif innermost:
            upconv = nn.ConvTranspose2d(ni, nf, kernel_size=4,
                                        stride=2, padding=1, bias=False)
            down = [downrelu, downconv]
            up = [uprelu, upconv, upnorm]
            model = down + up
        else:
            upconv = nn.ConvTranspose2d(ni * 2, nf, kernel_size=4,
                                        stride=2, padding=1, bias=False)
            down = [downrelu, downconv, downnorm]
            up = [uprelu, upconv, upnorm]
            if dropout: up += [nn.Dropout(0.5)]
            model = down + [submodule] + up
        self.model = nn.Sequential(*model)

    def forward(self, x):
        if self.outermost:
            return self.model(x)
        else:
            return torch.cat([x, self.model(x)], 1)

class Unet(nn.Module):
    def __init__(self, input_c=1, output_c=2, n_down=8, num_filters=64):
        super().__init__()
        unet_block = UnetBlock(num_filters * 8, num_filters * 8, innermost=True)
        for _ in range(n_down - 5):
            unet_block = UnetBlock(num_filters * 8, num_filters * 8, submodule=unet_block, dropout=True)
        out_filters = num_filters * 8
        for _ in range(3):
            unet_block = UnetBlock(out_filters // 2, out_filters, submodule=unet_block)
            out_filters //= 2
        self.model = UnetBlock(output_c, out_filters, input_c=input_c, submodule=unet_block, outermost=True)

    def forward(self, x):
        return self.model(x)

Nuestro Patch Discriminator:

class PatchDiscriminator(nn.Module):
    def __init__(self, input_c, num_filters=64, n_down=3):
        super().__init__()
        model = [self.get_layers(input_c, num_filters, norm=False)]
        model += [self.get_layers(num_filters * 2 ** i, num_filters * 2 ** (i + 1), s=1 if i == (n_down-1) else 2)
                          for i in range(n_down)] 
        model += [self.get_layers(num_filters * 2 ** n_down, 1, s=1, norm=False, act=False)] 
        self.model = nn.Sequential(*model)

    def get_layers(self, ni, nf, k=4, s=2, p=1, norm=True, act=True): 
        layers = [nn.Conv2d(ni, nf, k, s, p, bias=not norm)]          
        if norm: layers += [nn.BatchNorm2d(nf)]
        if act: layers += [nn.LeakyReLU(0.2, True)]
        return nn.Sequential(*layers)

    def forward(self, x):
        return self.model(x)

La función de pérdida:

class GANLoss(nn.Module):
    def __init__(self, gan_mode='vanilla', real_label=0.9, fake_label=0.0): #Aplicamos un One-Sided Label Smoothing como se indica en Image Colorizarion using Generative Adversarial Networks
        super().__init__()
        self.register_buffer('real_label', torch.tensor(real_label))
        self.register_buffer('fake_label', torch.tensor(fake_label))
        if gan_mode == 'vanilla':
            self.loss = nn.BCEWithLogitsLoss()
        elif gan_mode == 'lsgan':
            self.loss = nn.MSELoss()

    def get_labels(self, preds, target_is_real):
        if target_is_real:
            labels = self.real_label
        else:
            labels = self.fake_label
        return labels.expand_as(preds)

    def __call__(self, preds, target_is_real):
        labels = self.get_labels(preds, target_is_real)
        loss = self.loss(preds, labels)
        return loss

La inicialización del modelo y la definición del mismo:

def init_weights(net, init='norm', gain=0.02):

    def init_func(m):
        classname = m.__class__.__name__
        if hasattr(m, 'weight') and 'Conv' in classname:
            if init == 'norm':
                nn.init.normal_(m.weight.data, mean=0.0, std=gain)
            elif init == 'xavier':
                nn.init.xavier_normal_(m.weight.data, gain=gain)
            elif init == 'kaiming':
                nn.init.kaiming_normal_(m.weight.data, a=0, mode='fan_in')

            if hasattr(m, 'bias') and m.bias is not None:
                nn.init.constant_(m.bias.data, 0.0)
        elif 'BatchNorm2d' in classname:
            nn.init.normal_(m.weight.data, 1., gain)
            nn.init.constant_(m.bias.data, 0.)

    net.apply(init_func)
    print(f"model initialized with {init} initialization")
    return net

def init_model(model, device):
    model = model.to(device)
    model = init_weights(model)
    return model

class MainModel(nn.Module):
    def __init__(self, net_G=None, lr_G=2e-4, lr_D=2e-4, #Utilizamos initial learning rate de 2x10-4 como se indica en Image Colorization using Generative Adversarial Networks
                 beta1=0.5, beta2=0.999, lambda_L1=100.): #Utilizamos lambda = 100 y beta1 = 0.5 (para reducir el momento) como indica la misma fuente
        super().__init__()

        self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
        print(self.device)
        self.lambda_L1 = lambda_L1

        if net_G is None:
            self.net_G = init_model(Unet(input_c=1, output_c=2, n_down=8, num_filters=64), self.device)
        else:
            self.net_G = net_G.to(self.device)
        self.net_D = init_model(PatchDiscriminator(input_c=3, n_down=3, num_filters=64), self.device)
        self.GANcriterion = GANLoss(gan_mode='vanilla').to(self.device)
        self.L1criterion = nn.L1Loss()
        self.opt_G = optim.Adam(self.net_G.parameters(), lr=lr_G, betas=(beta1, beta2))
        self.opt_D = optim.Adam(self.net_D.parameters(), lr=lr_D, betas=(beta1, beta2))

    def set_requires_grad(self, model, requires_grad=True):
        for p in model.parameters():
            p.requires_grad = requires_grad

    def setup_input(self, data):
        self.L = data['L'].to(self.device)
        self.ab = data['ab'].to(self.device)

    def forward(self):
        self.fake_color = self.net_G(self.L)

    def backward_D(self):
        fake_image = torch.cat([self.L, self.fake_color], dim=1)
        fake_preds = self.net_D(fake_image.detach())
        self.loss_D_fake = self.GANcriterion(fake_preds, False)
        real_image = torch.cat([self.L, self.ab], dim=1)
        real_preds = self.net_D(real_image)
        self.loss_D_real = self.GANcriterion(real_preds, True)
        self.loss_D = (self.loss_D_fake + self.loss_D_real) * 0.5
        self.loss_D.backward()

    def backward_G(self):
        fake_image = torch.cat([self.L, self.fake_color], dim=1)
        fake_preds = self.net_D(fake_image)
        self.loss_G_GAN = self.GANcriterion(fake_preds, True)
        self.loss_G_L1 = self.L1criterion(self.fake_color, self.ab) * self.lambda_L1
        self.loss_G = self.loss_G_GAN + self.loss_G_L1
        self.loss_G.backward()

    def optimize(self):
        self.forward()
        self.net_D.train()
        self.set_requires_grad(self.net_D, True)
        self.opt_D.zero_grad()
        self.backward_D()
        self.opt_D.step()

        self.net_G.train()
        self.set_requires_grad(self.net_D, False)
        self.opt_G.zero_grad()
        self.backward_G()
        self.opt_G.step()

El bucle de entrenamiento (con 50 épocas):

def train_model(model, train_dl, epochs, display_every=350):
    data = next(iter(val_dl)) 
    for e in range(epochs):
        loss_meter_dict = create_loss_meters() 
        i = 0                                 
        for data in tqdm(train_dl):
            model.setup_input(data)
            model.optimize()
            update_losses(model, loss_meter_dict, count=data['L'].size(0)) 
            i += 1
            if i % display_every == 0:
                print(f"\nEpoch {e+1}/{epochs}")ç
                print(f"Iteration {i}/{len(train_dl)}")
                log_results(loss_meter_dict) 
                visualize(model, data, save=False) 

model = MainModel()
train_model(model, train_dl, 50)

Resultados

La primera fila son las imágenes que se introducen al generador en blanco y negro. La segunda fila son el resultado de la colorización por parte del generador y la tercera fila son las imágenes originales.

Discusión

Podemos observar que la colorización se ha realizado correctamente en todas las imágenes, aunque existen ligeros defectos en algunas de ellas. En la segunda imagen desde la izquierda, el suelo no se ha coloreado correctamente en ciertas zonas. Por otro lado, en la segunda imagen desde la derecha, el perro presenta una mancha verde en la cabeza. Esto se puede deber a que la imagen presenta una gran cantidad de este color y pueden ocurrir errores por este motivo. Por último, en la imagen de la derecha, parece que se ha aplicado un filtro sepia y el resultado queda entre medias de la imagen real y de la imagen en blanco y negro.

Referencias


Este entrada ha sido realizada por Mario Gutiérrez, Diego Rivero y Alejandro Tapia.

Avances en la Generación de Caras Realistas mediante GANs: Mejoras en la Diversidad y el Control de Atributos

Introducción

En el campo del procesamiento avanzado de señales y datos, una de las tecnologías más innovadoras y prometedoras que ha surgido en los últimos años son las Redes Adversarias Generativas, comúnmente conocidas como GANs. Estas redes neuronales han revolucionado la manera en que generamos y procesamos información, abriendo un abanico de posibilidades en campos como la visión por computadora, la generación de contenido creativo y la simulación de datos realistas.

La generación de imágenes realistas de rostros humanos ha sido durante mucho tiempo un desafío en el campo del aprendizaje automático. Sin embargo, gracias a los avances en las GANs, ha surgido una técnica revolucionaria que ha permitido la creación de imágenes faciales sorprendentemente realistas a partir de datos aleatorios. En esta entrada de blog, exploraremos el enfoque específico de la generación de caras mediante GANs, examinando los avances más destacados, las técnicas utilizadas y los desafíos pendientes. Nos centraremos en aspectos clave, como el uso de arquitecturas específicas como StyleGAN, la mejora de la calidad de las imágenes generadas y la manipulación de atributos faciales.

Generación de Caras Realistas mediante GANs

Estado del arte

Antes de sumergirnos en el funcionamiento y las aplicaciones de las GANs, es importante realizar una breve revisión del estado del arte en este campo. Desde su introducción por Ian Goodfellow en 2014, las GANs han captado rápidamente la atención de la comunidad científica y tecnológica. Diversos avances han permitido mejorar la estabilidad y la calidad de las generaciones, así como la capacidad de aprender y representar distribuciones complejas de datos. Estos avances han dado lugar a aplicaciones sorprendentes en áreas como la síntesis de imágenes, el procesamiento de voz y el diseño de fármacos, entre otros. [1]

Una arquitectura que ha destacado es StyleGAN, que permite el control y la manipulación fina de los atributos faciales en las imágenes generadas. Además, la incorporación de técnicas como la normalización por lotes estilizada (Style-based Batch Normalization) y el uso de redes generadoras y discriminadoras de múltiples escalas ha mejorado aún más la calidad y la diversidad de las imágenes generadas. [2]

Desarrollo

Las GANs se basan en un enfoque novedoso que involucra la interacción entre dos redes neuronales: el generador y el discriminador. El generador tiene como objetivo crear muestras sintéticas que se asemejen a las muestras reales, mientras que el discriminador busca distinguir entre las muestras generadas y las reales. Ambas redes se entrenan de manera simultánea, en un proceso de competencia adversarial, donde el generador busca engañar al discriminador y, este último, trata de mejorar su capacidad de discriminación.

Esquema de una GAN básica [12]

En el proceso de generación de caras mediante GANs, el generador mapea un espacio latente de datos aleatorios a imágenes de caras realistas. El discriminador, por su parte, se entrena para distinguir entre imágenes generadas y reales.

El enfoque de StyleGAN ha permitido avances notables en la generación de caras realistas. Al introducir una descomposición de estilo en el generador, se puede controlar de manera más precisa el aspecto de las imágenes generadas, como la edad, el género, la expresión facial y otros atributos específicos. Además, la incorporación de técnicas de normalización por lotes estilizada y la generación en múltiples escalas ha llevado a una mejora significativa en la calidad visual y la diversidad de las imágenes generadas.

Materiales

El uso de derivaciones matemáticas es fundamental para comprender el funcionamiento de las GANs y optimizar su entrenamiento. La minimización de una función de pérdida, como la divergencia de Kullback-Leibler o la pérdida de Wasserstein, permite establecer una dinámica de aprendizaje entre el generador y el discriminador. Estos conceptos matemáticos fundamentales se ven reflejados en el código de implementación de las GANs, que consta de la definición de las arquitecturas de las redes, la elección de la función de pérdida y la configuración de los hiperparámetros.

La generación de imágenes realistas se logra mediante la minimización de la función de pérdida adversarial. La función objetivo se define como:

Función de pérdida adversarial

Donde D(x) es la estimación del discriminador de la probabilidad de que la instancia de datos reales x sea real, Ex es el valor esperado sobre todas las instancias de datos reales, G(z) es la salida del generador cuando se da ruido z, D(G(z)) es la estimación del discriminador de la probabilidad de que una instancia falsa sea real, Ez es el valor esperado sobre todas las entradas aleatorias al generador (en efecto, el valor esperado sobre todas las instancias falsas generadas G(z)). pdata es la distribución real de los datos y p(z) es la distribución de ruido latente. [11]

El generador intenta maximizar esta función de pérdida mientras que el discriminador intenta minimizarla, creando la competencia adversarial.

A continuación, se presenta un ejemplo de estructura de una implementación básica de una GAN para la generación de caras utilizando la biblioteca de Python TensorFlow:

Y, usando la arquitectura StyleGAN:

Discusión de los resultados

La potencia de las GANs se hace evidente al observar los resultados obtenidos en diversos experimentos. Mediante la aplicación de GANs, se ha logrado generar imágenes hiperrealistas, imitar el estilo de artistas famosos, completar imágenes dañadas o faltantes, e incluso simular la evolución de enfermedades en datos médicos.

Se han logrado imágenes faciales altamente realistas, con detalles finos y una calidad visual sorprendente. Además, la capacidad de controlar los atributos faciales ha permitido la creación de imágenes personalizadas y la manipulación de características específicas.

Sin embargo, a pesar de estos avances, aún existen desafíos importantes que deben abordarse en la generación de caras mediante GANs. Uno de estos es lograr una mayor estabilidad en el entrenamiento de los modelos. A veces, las GANs pueden ser difíciles de entrenar y pueden sufrir de problemas como el colapso del modo, donde el generador produce imágenes similares y de baja diversidad. También es fundamental mejorar la interpretabilidad de los modelos y garantizar la ética en la generación de caras, evitando posibles sesgos y discriminación involuntaria. [6]

A continuación, se muestran algunos ejemplos de imágenes generadas utilizando una GAN entrenada para la generación de caras. Estas imágenes demuestran la capacidad de las GANs para producir resultados visualmente atractivos. Además, se pueden proporcionar métricas de evaluación, como el puntaje de Inception (Inception Score), para cuantificar la calidad y la diversidad de las imágenes generadas.

Visualización de la salida del programa de SAYAK en Kaggle [11]

Conclusiones

Las Redes Adversarias Generativas han revolucionado el procesamiento de señales y datos, ofreciendo una poderosa herramienta para la generación y simulación de información compleja. Su capacidad de aprender y representar distribuciones de datos complejas ha permitido avances significativos en campos como la visión por computadora, la creatividad computacional y la generación de contenido realista.

La generación de caras mediante GANs ha alcanzado niveles sorprendentes de realismo y control de atributos faciales. La arquitectura StyleGAN ha sido un avance significativo en este campo, permitiendo una generación más precisa y diversa. Sin embargo, aún existen desafíos a superar, como la estabilidad del entrenamiento y la ética en la generación de imágenes faciales. La investigación continua se centra en mejorar estos aspectos y abrir nuevas posibilidades en la generación de caras.

Los avances en esta área de investigación continúan abriendo nuevas posibilidades en el campo del procesamiento avanzado de señales y datos. A medida que se superan los desafíos restantes, se espera que las GANs sigan evolucionando y proporcionando resultados aún más impresionantes.

Referencias

[1] Goodfellow, I., Pouget-Abadie, J., Mirza, M., Xu, B., Warde-Farley, D., Ozair, S., … & Bengio, Y. (2014). Generative adversarial nets. In Advances in neural information processing systems (pp. 2672-2680). [Enlace]

[2] Karras, T., Laine, S., & Aila, T. (2019). A style-based generator architecture for generative adversarial networks. In Proceedings of the IEEE conference on computer vision and pattern recognition (pp. 4401-4410). [Enlace]

[3] Karras, T., Aila, T., Laine, S., & Lehtinen, J. (2020). Training generative adversarial networks with limited data. In Proceedings of the IEEE/CVF conference on computer vision and pattern recognition (pp. 5937-5947). [Enlace]

[4] Zhao, S., Liu, Z., & Shen, X. (2021). Learning to Generate Faces: A Survey. arXiv preprint arXiv:2103.01763. [Enlace]

[5] Brock, A., Donahue, J., & Simonyan, K. (2018). Large scale GAN training for high fidelity natural image synthesis. arXiv preprint arXiv:1809.11096. [Enlace]

[6] Brock, A., Donahue, J., & Simonyan, K. (2019). Large scale GAN training for high fidelity natural image synthesis. In International Conference on Learning Representations. [Enlace]

[7] Arjovsky, M., & Bottou, L. (2017). Towards principled methods for training generative adversarial networks. arXiv preprint arXiv:1701.04862. [Enlace]

[8] Zhang, H., Xu, T., Li, H., Zhang, S., Wang, X., Huang, X., & Metaxas, D. N. (2018). Self-attention generative adversarial networks. In International Conference on Machine Learning (pp. 7354-7363). [Enlace]

[9] Huang, X., Li, Y., Poursaeed, O., Hopcroft, J., & Belongie, S. (2017). Stacked generative adversarial networks. In IEEE Conference on Computer Vision and Pattern Recognition (pp. 6487-6495). [Enlace]

[10] Arjovsky, M., Chintala, S., & Bottou, L. (2017). Wasserstein generative adversarial networks. In International conference on machine learning (pp. 214-223). [Enlace]

[11] Kaggle – Fake Faces with DCGANs. [Enlace]

[12] Moodle – Signals and Data Advanced Processing. Part1, Chapter 3: Deep Generative Models. 3.4. Generative Adversarial Networks. [Enlace]

About

Equipo formado por Daniel Gómez, Alejandro Blas y Daniel Tijeras