27.8 C
Colombia
miércoles, enero 22, 2025

Weblog de Posit AI: Straightforward PixelCNN con tfprobability


Hemos visto bastantes ejemplos de aprendizaje no supervisado (o aprendizaje autosupervisado, para elegir el término más correcto pero menos common) en este weblog.

A menudo, estos involucran Autocodificadores variacionales (VAE)cuyo atractivo radica en que permiten modelar una espacio latente de factores subyacentes e independientes (preferiblemente) que determinan las características visibles. Una posible desventaja puede ser la calidad inferior de las muestras generadas. Las redes generativas adversarias (GAN) son otro enfoque common. Conceptualmente, estos son muy atractivos debido a su encuadre de teoría de juegos. Sin embargo, pueden resultar difíciles de entrenar. PixelCNN Las variantes, por otro lado (las incluiremos todas aquí en PixelCNN) son generalmente conocidas por sus buenos resultados. Sin embargo, parecen implicar algo más de alquimia. En esas circunstancias, ¿qué podría ser más bienvenido que una forma sencilla de experimentar con ellos? A través de TensorFlow Likelihood (TFP) y su contenedor R, probabilidadahora tenemos esa manera.

Esta publicación primero ofrece una introducción a PixelCNN, concentrándose en conceptos de alto nivel (dejando los detalles para que los curiosos los busquen en los artículos respectivos). Luego mostraremos un ejemplo del uso tfprobability experimentar con la implementación de la PTF.

Principios de PixelCNN

Autoregresividad, o: Necesitamos (algo de) orden

La concept básica en PixelCNN es la autorregresividad. Cada píxel se modela dependiendo de todos los píxeles anteriores. Formalmente:

[p(mathbf{x}) = prod_{i}p(x_i|x_0, x_1, …, x_{i-1})]

Ahora espera un segundo – ¿qué pasa? son píxeles anteriores? La última vez que vi una imagen period bidimensional. Esto significa que tenemos que imponer una orden en los píxeles. Comúnmente esto será escaneo rasterizado Orden: fila tras fila, de izquierda a derecha. Pero cuando se trata de imágenes en coloration, hay algo más: en cada posición, en realidad tenemos tres valores de intensidad, uno para cada uno de rojo, verde y azul. El artículo authentic de PixelCNN(Oord, Kalchbrenner y Kavukcuoglu 2016) Aquí también se lleva a cabo la autorregresividad, con la intensidad de un píxel para el rojo dependiendo de los píxeles anteriores, los del verde dependiendo de estos mismos píxeles anteriores pero, además, el valor precise del rojo, y los del azul dependiendo de los píxeles anteriores, así como del valores actuales para rojo y verde.

[p(x_i|mathbf{x}<i) = p(x_{i,R}|mathbf{x}<i) p(x_{i,G}|mathbf{x}<i, x_{i,R}) p(x_{i,B}|mathbf{x}<i, x_{i,R}, x_{i,G})]

Aquí, la variante implementada en TFP, PixelCNN++(Salimans et al. 2017) introduce una simplificación; factoriza la distribución conjunta de una manera menos intensiva en computación.

Entonces, técnicamente sabemos cómo se realiza la autorregresividad; Intuitivamente, todavía puede parecer sorprendente que imponer un orden de escaneo ráster “simplemente funcione” (para mí, al menos, lo es). Quizás este sea uno de esos puntos en los que la potencia informática compensa con éxito la falta de un equivalente de un conocimiento previo cognitivo.

Enmascaramiento, o: dónde no mirar

Ahora bien, PixelCNN termina en “CNN” por una razón: como es ordinary en el procesamiento de imágenes, están involucradas capas convolucionales (o bloques de las mismas). Pero, ¿no es la propia naturaleza de una convolución que calcule un promedio de algún tipo, mirando, para cada píxel de salida, no sólo la entrada correspondiente sino también su entorno espacial (o temporal)? ¿Cómo rima eso con la estrategia de mirar solo los píxeles anteriores?

Sorprendentemente, este problema es más fácil de resolver de lo que parece. Al aplicar el núcleo convolucional, simplemente multiplique con una máscara que ponga a cero los “píxeles prohibidos”, como en este ejemplo para un núcleo de 5×5, donde estamos a punto de calcular el valor convolucional para la fila 3, columna 3:

[left[begin{array}
{rrr}
1 & 1 & 1 & 1 & 1
1 & 1 & 1 & 1 & 1
1 & 1 & 1 & 0 & 0
0 & 0 & 0 & 0 & 0
0 & 0 & 0 & 0 & 0
end{array}right]
]

Esto hace que el algoritmo sea honesto, pero introduce un problema diferente: con cada capa convolucional sucesiva consumiendo la salida de su predecesor, hay un crecimiento continuo. punto ciego (llamado así en analogía con el punto ciego de la retina, pero ubicado en la parte superior derecha) de píxeles que nunca son visto por el algoritmo. Van den Oord et al. (2016)(Oord et al. 2016) solucione este problema utilizando dos pilas convolucionales diferentes, una de arriba a abajo y la otra de izquierda a derecha.

Fig. 1: Izquierda: Punto ciego, creciendo sobre capas. Derecha: Usar dos pilas diferentes (una vertical y otra horizontal) resuelve el problema. Fuente: van den Oord et al., 2016.

Condicionamiento, o: Muéstrame un gatito

Hasta ahora siempre hemos hablado de “generar imágenes” de forma puramente genérica. Pero el verdadero atractivo radica en la creación de muestras de algún tipo específico: una de las clases en las que hemos estado entrenando o información ortogonal alimentada a la pink. Aquí es donde se convierte PixelCNN PixelCNN condicional(Oord et al. 2016)y es también donde resurge esa sensación de magia. Una vez más, como “matemática basic” no es difícil de concebir. Aquí, (mathbf{h}) es la entrada adicional a la que estamos condicionando:

[p(mathbf{x}| mathbf{h}) = prod_{i}p(x_i|x_0, x_1, …, x_{i-1}, mathbf{h})]

Pero, ¿cómo se traduce esto en operaciones de redes neuronales? Es solo otra multiplicación de matrices ((V^T mathbf{h})) agregado a las salidas convolucionales ((W mathbf{x})).

[mathbf{y} = tanh(W_{k,f} mathbf{x} + V^T_{k,f} mathbf{h}) odot sigma(W_{k,g} mathbf{x} + V^T_{k,g} mathbf{h})]

(Si se pregunta acerca de la segunda parte a la derecha, después del letrero del producto Hadamard, no entraremos en detalles, pero en pocas palabras, es otra modificación introducida por (Oord et al. 2016)una transferencia del principio de “puerta” de redes neuronales recurrentes, como GRU y LSTM, al entorno convolucional).

Entonces vemos lo que implica la decisión de tomar un valor de píxel para muestrear. ¿Pero cómo es realmente esa decisión? hecho?

Probabilidad de mezcla logística, o: Ningún píxel es una isla

Nuevamente, aquí es donde la implementación de TFP no sigue el artículo authentic, sino el último de PixelCNN++. Originalmente, los píxeles se modelaban como valores discretos, determinados por un softmax sobre 256 (0-255) valores posibles. (Que esto realmente haya funcionado parece otro ejemplo de magia del aprendizaje profundo. Imagínese: en este modelo, 254 está tan lejos de 255 como de 0).

Por el contrario, PixelCNN++ asume una distribución continua subyacente de intensidad de coloration y redondea al número entero más cercano. Esa distribución subyacente es una mezcla de distribuciones logísticas, lo que permite la multimodalidad:

[nu sim sum_{i} pi_i logistic(mu_i, sigma_i)]

Arquitectura basic y distribución de PixelCNN.

En basic, PixelCNN++, como se describe en (Salimans et al. 2017)consta de seis bloques. Los bloques juntos forman una estructura comparable a UNet, reduciendo sucesivamente el tamaño de la entrada y luego aumentando el muestreo nuevamente:

Fig. 2: Estructura general de PixelCNN++. De: Salimans et al., 2017.

En la distribución PixelCNN de TFP, el número de bloques se puede configurar como num_hierarchiesel valor predeterminado es 3.

Cada bloque consta de un número personalizable de capas, llamadas Capas ResNet debido a la conexión residual (seen a la derecha) que complementa las operaciones convolucionales en la pila horizontal:

Fig. 3: Uno de los llamados "capa ResNet"que presenta una pila convolucional vertical y horizontal. Fuente: van den Oord et al., 2017.

En TFP, el número de estas capas por bloque es configurable como num_resnet.

num_resnet y num_hierarchies son los parámetros con los que es más possible que experimentes, pero hay algunos más que puedes consultar en el documentación. El número de distribuciones logísticas en la mezcla también es configurable, pero según mis experimentos es mejor mantener ese número bastante bajo para evitar producir NaNs durante el entrenamiento.

Veamos ahora un ejemplo completo.

Ejemplo de un extremo a otro

Nuestro patio de recreo será Dibujo rápidoun conjunto de datos (que sigue creciendo) que se obtiene pidiendo a las personas que dibujen un objeto en veinte segundos como máximo, utilizando el ratón. (Para comprobarlo usted mismo, simplemente consulte el sitio internet). A día de hoy, existen más de cincuenta millones de instancias, de 345 clases diferentes.

En primer lugar, estos datos se eligieron para tomar un descanso del MNIST y sus variantes. Pero al igual que esos (¡y muchos más!), QuickDraw se puede obtener, en tfdatasets-formulario listo, vía tfdsel contenedor de R para los conjuntos de datos de TensorFlow. Sin embargo, a diferencia de la “familia” MNIST, las “muestras reales” son en sí mismas muy irregulares y, a menudo, incluso les faltan partes esenciales. Entonces, para fundamentar el criterio, cuando mostramos muestras generadas siempre mostramos ocho dibujos reales con ellas.

Preparando los datos

Como el conjunto de datos es gigantesco, instruimos tfds para cargar los primeros 500.000 dibujos “sólo”.

Para acelerar aún más el entrenamiento, nos centramos en veinte clases. Esto efectivamente nos deja con ~ 1100 – 1500 dibujos por clase.

# bee, bicycle, broccoli, butterfly, cactus,
# frog, guitar, lightning, penguin, pizza,
# rollerskates, sea turtle, sheep, snowflake, solar,
# swan, The Eiffel Tower, tractor, practice, tree
lessons <- c(26, 29, 43, 49, 50,
             125, 134, 172, 218, 225,
             246, 255, 258, 271, 295,
             296, 308, 320, 322, 323
)

classes_tensor <- tf$solid(lessons, tf$int64)

train_ds <- train_ds %>%
  dataset_filter(
    perform(report) tf$reduce_any(tf$equal(classes_tensor, report$label), -1L)
  )

La distribución de PixelCNN espera valores en el rango de 0 a 255, no se requiere normalización. El preprocesamiento consiste entonces en simplemente proyectar píxeles y etiquetas cada uno para float:

preprocess <- perform(report) {
  report$picture <- tf$solid(report$picture, tf$float32) 
  report$label <- tf$solid(report$label, tf$float32)
  record(tuple(report$picture, report$label))
}

batch_size <- 32

practice <- train_ds %>%
  dataset_map(preprocess) %>%
  dataset_shuffle(10000) %>%
  dataset_batch(batch_size)

Creando el modelo

ahora usamos tfd_pixel_cnn para definir cuál será la logverosimilitud utilizada por el modelo.

dist <- tfd_pixel_cnn(
  image_shape = c(28, 28, 1),
  conditional_shape = record(),
  num_resnet = 5,
  num_hierarchies = 3,
  num_filters = 128,
  num_logistic_mix = 5,
  dropout_p =.5
)

image_input <- layer_input(form = c(28, 28, 1))
label_input <- layer_input(form = record())
log_prob <- dist %>% tfd_log_prob(image_input, conditional_input = label_input)

Esta probabilidad logarítmica personalizada se agrega como una pérdida al modelo y luego, el modelo se compila solo con una especificación del optimizador. Durante el entrenamiento, las pérdidas al principio disminuyeron rápidamente, pero las mejoras en épocas posteriores fueron menores.

mannequin <- keras_model(inputs = record(image_input, label_input), outputs = log_prob)
mannequin$add_loss(-tf$reduce_mean(log_prob))
mannequin$compile(optimizer = optimizer_adam(lr = .001))

mannequin %>% match(practice, epochs = 10)

Para mostrar conjuntamente imágenes reales y falsas:

for (i in lessons) {
  
  real_images <- train_ds %>%
    dataset_filter(
      perform(report) report$label == tf$solid(i, tf$int64)
    ) %>% 
    dataset_take(8) %>%
    dataset_batch(8)
  it <- as_iterator(real_images)
  real_images <- iter_next(it)
  real_images <- real_images$picture %>% as.array()
  real_images <- real_images[ , , , 1]/255
  
  generated_images <- dist %>% tfd_sample(8, conditional_input = i)
  generated_images <- generated_images %>% as.array()
  generated_images <- generated_images[ , , , 1]/255
  
  photos <- abind::abind(real_images, generated_images, alongside = 1)
  png(paste0("draw_", i, ".png"), width = 8 * 28 * 10, peak = 2 * 28 * 10)
  par(mfrow = c(2, 8), mar = c(0, 0, 0, 0))
  photos %>%
    purrr::array_tree(1) %>%
    purrr::map(as.raster) %>%
    purrr::iwalk(plot)
  dev.off()
}

De nuestras veinte clases, aquí tienes una selección de seis, cada una de las cuales muestra dibujos reales en la fila superior y dibujos falsos debajo.

Fig. 4: Bicicletas tiradas por personas (fila superior) y la red (fila inferior).
Fig. 5: Brócoli dibujado por personas (fila superior) y la red (fila inferior).
Fig. 6: Mariposas dibujadas por personas (fila superior) y la red (fila inferior).
Fig. 7: Guitarras dibujadas por personas (fila superior) y la red (fila inferior).
Fig. 8: Pingüinos, dibujados por personas (fila superior) y la red (fila inferior).
Fig. 9: Patines, tirados por personas (fila superior) y la red (fila inferior).

Probablemente no confundiríamos la primera y la segunda fila, pero los dibujos humanos reales también exhiben una enorme variación. Y nadie dijo nunca que PixelCNN fuera una arquitectura para el aprendizaje de conceptos. Siéntase libre de jugar con otros conjuntos de datos de su elección: la distribución PixelCNN de TFP lo hace fácil.

Concluyendo

En esta publicación, tuvimos tfprobability / TFP hace todo el trabajo pesado por nosotros y, por lo tanto, podría centrarse en los conceptos subyacentes. Dependiendo de sus gustos, esta puede ser una situación supreme: los árboles no pierden de vista el bosque. Por otro lado: si descubre que cambiar los parámetros proporcionados no logra lo que desea, tiene una implementación de referencia para comenzar. Entonces, cualquiera que sea el resultado, la adición de dicha funcionalidad de nivel superior a TFP es una ganancia para los usuarios. (Si eres desarrollador de TFP y lees esto: Sí, nos gustaría más :-)).

Pero a todos, ¡gracias por leer!

Oord, Aaron van den, Nal Kalchbrenner y Koray Kavukcuoglu. 2016. “Redes neuronales recurrentes de píxeles”. CORR abs/1601.06759. http://arxiv.org/abs/1601.06759.

Oord, Aaron van den, Nal Kalchbrenner, Oriol Vinyals, Lasse Espeholt, Alex Graves y Koray Kavukcuoglu. 2016. “Generación de imágenes condicional con decodificadores PixelCNN”. CORR abs/1606.05328. http://arxiv.org/abs/1606.05328.

Salimans, Tim, Andrej Karpathy, Xi Chen y Diederik P. Kingma. 2017. “PixelCNN++: una implementación de PixelCNN con probabilidad de mezcla logística discretizada y otras modificaciones”. En ICLR.

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles