Tutorial: Difusão estável 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 é uma regurgitação do anterior Tutorial: Difusão estável de ScratchEstá me levando muito mais de 1 dia para entender e implementar o código naquele artigo. Acidentalmente, os artigos originais foram removidos da web - tanto quanto é questionável que eu tenha copiado o trabalho de outra pessoa - bem, o original não está mais disponível, então o meu pode se tornar uma cópia autoritária. E para ser claro: Tenho profunda admiração pelos autores que copio. Tenho inveja de sua agilidade mental e força (e do tempo e recursos que eles têm disponíveis para perseguir esses tópicos de pesquisa). Admiro as pessoas que inventaram a difusão estável e outros algoritmos nas suas proximidades. Abaixo está minha humilde tentativa de reimplementar o código daquele papel, e obter uma instância de difusão estável, escrita do zero, em funcionamento.

Além disso, estarei focando principalmente em coisas com as quais estou lutando, então isso pode não ser um tutorial completo e completo. Além disso, para o leitor, recomendo fortemente este livro: Mergulhe em Profundo AprendizagemMuito claramente escrito, ajudou-me muito. A explicação deles da codificação de tempo mais furada tornou o conceito bem claro para mim. Agora, então, um índice rápido para este artigo:

Sumário

  • difusão básica 1D para frente/reverso
  • uma arquitetura U-Net para trabalhar com imagens
  • a perda associada à aprendizagem da função escore
  • um modelo de atenção para a geração condicional
  • um codificador automático

lightning

Distribuição inicial e inversa

Para a arquitetura de difusão estável. Digamos que temos nossos dados (imagens, ou um único ponto de dados) e adicionamos ruído a ele. Treinamos então uma rede neural para denoizar os nossos dados. Assim, teremos difusão para a frente (ruído adicional) e diferenciação reversa (retirando ruído). Para um primeiro passo, nós um ponto de dados unidimensional muito simples (y) que é dificultado em função do tempo (x).

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

Onde \( \sigma(t) > 0 \) é a resistência ao ruído, \( \Delta t \) é o tamanho do passo, e \( r \sim \mathcal{N} (0, 1) \) é uma variável aleatória normal padrão. Nós repetidamente adicionamos ruído normalmente distribuído à nossa amostra. Frequentemente, a resistência ao ruído \( \sigma(t) > 0 \) é escolhida para depender do tempo, e fica maior à medida que t fica maior. Este é um passe para frente, então \( \sigma(t) \) fica maior com o tempo ao adicionar ruído, e fica menor com o tempo ao removê-lo.

Vamos implementar o passe em python.


## 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 executá-lo e visualizá-lo:


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 reverter este processo de difusão por uma regra de atualização semelhante:

$$ 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 $$

Onde

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

  • x = imagem ruidosa no momento t
  • \( p(x, t) \) = densidade de probabilidade de x no tempo t
  • \( \frac{d}{dx} \) = gradiente em relação a x
  • \( s( x, t) \) = a função de pontuação

Este é um ponto conceitual chave em modelos de difusão e Difusão estável. O objetivo do modelo é aprender a desruir x movendo-o para regiões de maior probabilidade da distribuição de dados. Por que log( p) em vez de p? Isto tem vantagens numéricas. As probabilidades p(x) são muitas vezes muito pequenas, especialmente em dimensões elevadas.

log d dx

A derivada de um log de um pequeno valor é bastante grande. Além disso, tomar o log transforma produtos em somas, tornando gradientes mais estáveis. O gradiente de log dá uma direção em vez de uma magnitude que depende do valor absoluto de p(x). log p(x) e p(x) são equivalentes em otimização, mas o primeiro é muito mais estável numericamente. Muitos algoritmos de aprendizagem (MLE, correspondência de pontuação) usam naturalmente log.

E o que significa ter uma distribuição de probabilidade de uma imagem? Para isso, me refiro a autocodificadores variacionais (VAE), que codificam uma entrada (imagem) como uma distribuição de probabilidade (1) centro e (2) desvio padrão, em um espaço latente. A representação do espaço latente de uma imagem em um VAE é exatamente o que uma distribuição de probabilidade aprendida de uma imagem é.

Na prática, ainda não sabemos a função escore; em vez disso, temos que aprender. Uma forma de aprender é treinar uma rede neural para “denoizar” amostras através do objetivo de denoir

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

em que \( p 0( x 0)\) é a nossa distribuição alvo (por exemplo, fotos de gatos e cães), e onde \(x {noised}\) é a amostra de distribuição alvo \(x 0\) após uma etapa de difusão para frente, ou seja,\(x {noised} - x 0\) é apenas uma variável aleatória normalmente distribuída.

Aqui está outra maneira de escrever a mesma coisa, que está mais perto da implementação real. Substituindo \[\begin{equação} x {noised} = x 0 + \sigma(t) \epsilon, \; \epsilon\sim\mathcal N(0,I) \end{equação}\] Temos esta função objetiva \[\begin{equação} 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) + \epsilon \Vert^2 2 \\right] \end{equação}\]

SímboloSignificado\( J \)A função de perda para treinar a rede de pontuação \( s \theta \)\(\mathbb{E} {t\in (0,T), x 0 \sim p 0(x 00)[\cdot] \)Expectativa ao longo do tempo \(t\) e amostras de dados \(x 0\)\( x 0 \sim p 0( x 0) \)\(x 0\) é desenhada a partir da distribuição de dados (por exemplo, imagens)\( x {\text{ noised}} \)Versão ruidosa de \(x 0\) no momento (t)\( s( x {\text{ noised}}, t) \)A saída da rede de pontuação, uma aproximação de \( \nabla x \log p t(x {\text{noised}}) \)\( \sigma( t) \)Função de programação do ruído (desvio-padrão do ruído gaussiano no momento t )\( \Vert \cdot \Vert 2^2 \)Norma L2 quadrada (distância euclidiana ao quadrado)

Estamos aprendendo a prever quanto ruído foi adicionado a cada parte de nossa amostraDevemos ser capazes de fazer isso bem em todas as vezes \(t\) no processo de difusão, e para cada \(x 0\) em nossa distribuição original (dogs/cats/etc).

Todo o termo é essencialmente: ruído predito − ruído real , Então equilibrá-lo e tomar expectativa dá a perda média de erro ao quadrado para treinar a rede de pontuação.

Por que multiplicar por \( \sigma^2(t) \)? Nas derivações de correspondência de pontuação, a pontuação ótima está relacionada com o ruído adicionado escalado por \( \sigma^2(t) \):

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

Multiplicando por \( \sigma^2(t) \) remove a escala, assim a rede aprende a prever o ruído real. Explicação intuitiva:

  • Pegue um ponto de dados limpo \( x {0} \)
  • Adicionar ruído gaussiano para obter \( x {\text{ noised} \)
  • Passe \( x {\text{noised}} \) para a rede de pontuação \( s \theta \)
  • Calcular quão próximo \( s \theta( x {\rm ruidod}, t) \, \sigma^2( t) \) é do ruído real \( (x {\rm ruidod} - x 0) \)
  • Média sobre todos os pontos de dados e todas as vezes t

Essencialmente, você está ensinando a rede como “denoizar” uma amostra ruidosa a qualquer momento.

Redes U

Please login to post comments: