Tutorial: Difusión estable de Scratch II

Read this article in:
html { font-size: 20px; } code { font-size: 0.8rem; } .center { text-align: center; } .a20251225-container { margin: 2em auto 5em; width: 800px; } .a20251225-container img { max-width: calc( 100% - 1em ); padding: 0.5em; display: flex; margin: auto; } .a20251225-container img.bordered { border: 1px solid #999; } .a20251225-container table { margin: auto; } .a20251225-container table thead td { text-align: center; } .a20251225-container table.bordered td { border-bottom: 1px solid #999; padding: 0.5em; }

Esta es una regurgitación de la anterior Tutorial: Difusión estable de ScratchMe está tomando mucho más de 1 día para entender e implementar el código en ese artículo. Accidentalmente, los artículos originales han sido eliminados de la web - tanto como es cuestionable que he copiado el trabajo de otra persona - bueno, el original ya no está disponible, así que la mía puede convertirse en una copia autorizada. Y para ser claro: Tengo profunda admiración por los autores que copio. Estoy celoso de su agilidad y fuerza mental (y el tiempo y los recursos que tienen disponibles para perseguir estos temas de investigación). Admiro a las personas que inventaron la difusión estable, y otros algoritmos en sus alrededores. Debajo está mi humilde intento de reimplementar el código de ese papel, y conseguir una instancia de difusión estable, escrita desde cero, arriba y corriendo.

Además, me centraré principalmente en cosas con las que estoy luchando, así que esto puede no ser un tutorial completo. Además, para el lector, recomiendo firmemente este libro: Dive into Deep LearningMuy claramente escrito; me ha ayudado mucho. Su explicación de la codificación del tiempo más furioso hizo que el concepto fuera claro para mí. Ahora bien, una tabla rápida de contenidos para este artículo:

Cuadro de contenidos

  • básica 1D adelante/difusión reversa
  • una arquitectura U-Net para trabajar con imágenes
  • la pérdida asociada con el aprendizaje de la función de puntuación
  • un modelo de atención para la generación condicional
  • un autoencoder

lightning

Difusión inicial e inversa básica

Para la arquitectura de la difusión estable. Digamos que tenemos nuestros datos (imagenes, o un único punto de datos) y le agregamos ruido. Entonces capacitamos una red neuronal para denoizar nuestros datos. Por lo tanto, tendremos una difusión avanzada ( ruido de la cama) y difisión inversa (removiendo el ruido). Para un primer paso, somos un punto de datos unidimensional muy simple (y) que se difunde como una función del tiempo (x).

$$ x(t + \Delta t) = x(t) + \sigma(t) \sqrt{ \Delta t} \; r $$

Donde \( \sigma(t) √ 0 \) es la fuerza de ruido, \( \Delta t \) es el tamaño del paso, y \( r \sim \mathcal{N} (0, 1) \) es una variable normal al azar. Añadimos repetidamente el ruido normalmente distribuido a nuestra muestra. A menudo, la fuerza de ruido \( \sigma(t) > 0 \) es elegida para depender del tiempo, y se aumenta a medida que t se hace más grande. Este es un pase adelante, por lo que \( \sigma(t) \) se hace más grande con el tiempo al añadir ruido, y se vuelve más pequeño con el tiempo al quitarlo.

Vamos a implementar el pase adelante en pitón.


## Simulate forward diffusion for N steps.
def forward_diffusion_1d(x0, noise_strength_fn, t0, nsteps, dt):
  """x0: initial sample value, scalar
  noise_strength_fn: function of time, outputs scalar noise strength
  t0: initial time
  nsteps: number of diffusion steps
  dt: time step size
  """

  # Initialize trajectory
  x = np.zeros(nsteps + 1); x[0] = x0
  t = t0 + np.arange(nsteps + 1)*dt

  # Perform many Euler-Maruyama time steps
  for i in range(nsteps):
    noise_strength = noise_strength_fn(t[i])
    random_normal = np.random.randn()
    x[i+1] = x[i] + random_normal
  return x, t

## Example noise strength function: always equal to 1
def noise_strength_constant(t):
  return 1

Vamos a ejecutarlo y visualizarlo:


nsteps = 100
t0 = 0
dt = 0.1
noise_strength_fn = noise_strength_constant
x0 = 0

num_tries = 5
for i in range(num_tries):
  x, t = forward_diffusion_1d(x0, noise_strength_fn, t0, nsteps, dt)

  plt.plot(t, x)
  plt.xlabel('time', fontsize=20)
  plt.ylabel('$x$', fontsize=20)
plt.title('Forward diffusion visualized', fontsize=20)
plt.show()
1d stable diffusion

Podemos revertir este proceso de difusión por una regla de actualización similar:

$$ x(t + \Delta t) = x(t) + \sigma(T - t)^2 \frac{d}{dx}\left[ \log p(x, T-t) \right] \Delta t + \sigma(T-t) \sqrt{\Delta t} \ r $$

Donde

$$ s(x, t) := \frac{d}{dx} \log p(x, t) $$

  • x = la imagen ruidosa a la vez t
  • \( p(x, t) \) = densidad de probabilidad de x a la vez t
  • \( \frac{d}{dx} \) = gradiente con respecto a x
  • \( s(x, t) \) = función de puntuación

Este es un punto conceptual clave en los modelos de difusión y la difusión estable. El objetivo del modelo es aprender a denoizar x moviéndolo hacia regiones de mayor probabilidad de la distribución de datos. ¿Por qué log(p) en lugar de p? Esto tiene ventajas numéricas. Las probabilidades p(x) son a menudo muy pequeñas, especialmente en dimensiones altas.

log d dx

El derivado de un tronco de un pequeño valor es bastante grande. Además, tomar el registro convierte los productos en sumas, haciendo gradientes más estables. El gradiente de registro da una dirección en lugar de una magnitud que depende del valor absoluto de p(x). log p(x) y p(x) son equivalentes en optimización, pero el primero es mucho más estable numéricamente. Muchos algoritmos de aprendizaje (MLE, marcador coincidente) utiliza naturalmente el registro.

¿Y qué significa tener una distribución de probabilidad de una imagen? Para ello, te remito a los auto-encoders (VAE), que codifican una entrada (imagen) como una distribución de probabilidad (1) centro y (2) desviación estándar, en un espacio latente. La representación espacial latente de una imagen en un VAE es exactamente lo que es una distribución de probabilidad aprendida de una imagen.

En la práctica, no sabemos ya la función de puntuación; en cambio, tenemos que aprenderla. Una manera de aprender es entrenar una red neuronal para denoizar las muestras a través del objetivo denoizante

$$ J := \mathbb{E} {t\in (0, T), x 0 \sim p 0(x 0)}\left[ \ \ Vert s(x {noised}, t) \sigma^2(t) + (x {noised} - x 0) \Vert^2 2 \right] $$

Donde \(p 0(x 0)\) es nuestra distribución objetivo (por ejemplo, imágenes de gatos y perros), y donde \(x {noised}\) es la muestra de distribución objetivo \(x 0\) después de un paso adelante de difusión, es decir.\(x {noised} - x 0\) es sólo una variable aleatoria normalmente distribuida.

Aquí hay otra forma de escribir lo mismo, que está más cerca de la implementación real. Por sustitución \[\begin{equation} x {noised} = x 0 + \sigma(t) \epsilon, \; \epsilon\sim \mathcal N(0,I) \end{equation}\] Tenemos esta función objetiva \[\begin{equation} J := \mathbb{E} {t\in (0, T), x 0 \sim p 0(x 0), \epsilon \sim \mathcal N(0,I)}\left[ \Vert s(x 0 + \sigma(t) \epsilon, t) \sigma(t) +

SignaturaSignificado\( J \)La función de pérdida para entrenar la red de puntuación \( s \theta \)\( \mathbb{E} {t \in (0,T), x 0 \sim p 0(x 0)}[\cdot] \)Expectativa con el tiempo \(t\) y muestras de datos \(x 0\)\( x 0 \sim p 0(x 0) \)\(x 0\) se extrae de la distribución de datos (por ejemplo, imágenes)\( x {noised} \)Versión ruidosa de \(x 0\) a la vez (t)\( s(x {\text{noised}}, t) \)La salida de la red de puntuación, una aproximación de \( \nabla x \log p t(x {\text{noised}) \)\( \sigma(t) \)Función de horario ruidoso (desviación estándar del ruido gaisiano a la vez t )\( \Vert \cdot \Vert 2^2 \)Norma L2 cuadrada (a poca distancia Euclidiana cuadrada)

Estamos aprendiendo a predecir cuánto ruido se agregó a cada parte de nuestra muestraDeberíamos ser capaces de hacer esto bien a cada vez \(t\) en el proceso de difusión y por cada \(x 0\) en nuestra distribución original (dogs/cats/etc).

Todo el término es esencialmente: ruido predicho − ruido real , Así que squaring it and taking expectation da la pérdida de error medio cuadrado para entrenar la red de puntuación.

¿Por qué multiplicarse por \( \sigma^2(t) \)? En los derivados de la puntuación, la puntuación óptima se relaciona con el ruido añadido escalado por \( \sigma^2(t) \):

\[ s \theta(x {\rm noised}, t) \approx - \frac{x {\rm noised} - x 0}{\sigma^2(t)} \]

Multiplying by \( \sigma^2(t) \) remove el escalado, por lo que la red aprende a predecir el ruido real. explicación intuitiva:

  • Tome un punto de datos limpio \( x {0} \)
  • Agregue el ruido gausiano para obtener \( x {\text{noised}\)
  • Pase \( x {\text{noised}} \) a la red de puntuación \( s \theta \)
  • Compute how close \( s \theta(x {\rm noised}, t) \, \sigma^2(t) \) es al ruido real \( (x {\rm noised} - x 0) \)
  • Promedio sobre todos los puntos de datos y todo el tiempo t

Esencialmente, usted está enseñando a la red cómo “denoizar” una muestra ruidosa en cualquier momento paso.

U-nets

Please login to post comments: