Compare commits

..
11 Commits
4 changed files with 130 additions and 22 deletions
+12
View File
@@ -6,6 +6,8 @@ Implementation of <a href="https://arxiv.org/abs/2006.11239">Denoising Diffusion
This implementation was transcribed from the official Tensorflow version <a href="https://github.com/hojonathanho/diffusion">here</a> This implementation was transcribed from the official Tensorflow version <a href="https://github.com/hojonathanho/diffusion">here</a>
<a href="https://huggingface.co/blog/annotated-diffusion">Annotated code</a> by Research Scientists / Engineers from <a href="https://huggingface.co/">🤗 Huggingface</a>
<img src="./sample.png" width="500px"><img> <img src="./sample.png" width="500px"><img>
[![PyPI version](https://badge.fury.io/py/denoising-diffusion-pytorch.svg)](https://badge.fury.io/py/denoising-diffusion-pytorch) [![PyPI version](https://badge.fury.io/py/denoising-diffusion-pytorch.svg)](https://badge.fury.io/py/denoising-diffusion-pytorch)
@@ -119,3 +121,13 @@ Samples and model checkpoints will be logged to `./results` periodically
url = {https://openreview.net/forum?id=2LdBqxc1Yv} url = {https://openreview.net/forum?id=2LdBqxc1Yv}
} }
``` ```
```bibtex
@article{Choi2022PerceptionPT,
title = {Perception Prioritized Training of Diffusion Models},
author = {Jooyoung Choi and Jungbeom Lee and Chaehun Shin and Sungwon Kim and Hyunwoo J. Kim and Sung-Hoon Yoon},
journal = {ArXiv},
year = {2022},
volume = {abs/2204.00227}
}
```
@@ -5,7 +5,8 @@ import torch.nn.functional as F
from torch.special import expm1 from torch.special import expm1
from tqdm import tqdm from tqdm import tqdm
from einops import rearrange, repeat from einops import rearrange, repeat, reduce
from einops.layers.torch import Rearrange
# helpers # helpers
@@ -33,6 +34,24 @@ def right_pad_dims_to(x, t):
return t return t
return t.view(*t.shape, *((1,) * padding_dims)) return t.view(*t.shape, *((1,) * padding_dims))
# neural net helpers
class Residual(nn.Module):
def __init__(self, fn):
super().__init__()
self.fn = fn
def forward(self, x):
return x + self.fn(x)
class MonotonicLinear(nn.Module):
def __init__(self, *args, **kwargs):
super().__init__()
self.net = nn.Linear(*args, **kwargs)
def forward(self, x):
return F.linear(x, self.net.weight.abs(), self.net.bias.abs())
# continuous schedules # continuous schedules
# equations are taken from https://openreview.net/attachment?id=2LdBqxc1Yv&name=supplementary_material # equations are taken from https://openreview.net/attachment?id=2LdBqxc1Yv&name=supplementary_material
@@ -40,17 +59,54 @@ def right_pad_dims_to(x, t):
# log(snr) that approximates the original linear schedule # log(snr) that approximates the original linear schedule
def beta_linear_log_snr(t): def log(t, eps = 1e-20):
return -torch.log(expm1(1e-4 + 10 * (t ** 2))) return torch.log(t.clamp(min = eps))
def alpha_cosine_log_snr(t): def beta_linear_log_snr(t):
raise NotImplementedError return -log(expm1(1e-4 + 10 * (t ** 2)))
def alpha_cosine_log_snr(t, s = 0.008):
return -log((torch.cos((t + s) / (1 + s) * torch.pi * 0.5) ** -2) - 1, eps = 1e-5)
class learned_noise_schedule(nn.Module): class learned_noise_schedule(nn.Module):
def __init__(self): """ described in section H and then I.2 of the supplementary material for variational ddpm paper """
def __init__(
self,
*,
log_snr_max,
log_snr_min,
hidden_dim = 1024,
frac_gradient = 1.
):
super().__init__() super().__init__()
raise NotImplementedError self.slope = log_snr_min - log_snr_max
# learned noise schedule, using learned monotonic MLP (weights kept positive) in the paper self.intercept = log_snr_max
self.net = nn.Sequential(
Rearrange('... -> ... 1'),
MonotonicLinear(1, 1),
Residual(nn.Sequential(
MonotonicLinear(1, hidden_dim),
nn.Sigmoid(),
MonotonicLinear(hidden_dim, 1)
)),
Rearrange('... 1 -> ...'),
)
self.frac_gradient = frac_gradient
def forward(self, x):
frac_gradient = self.frac_gradient
device = x.device
out_zero = self.net(torch.zeros_like(x))
out_one = self.net(torch.ones_like(x))
x = self.net(x)
normed = self.slope * ((x - out_zero) / (out_one - out_zero)) + self.intercept
return normed * frac_gradient + normed.detach() * (1 - frac_gradient)
class ContinuousTimeGaussianDiffusion(nn.Module): class ContinuousTimeGaussianDiffusion(nn.Module):
def __init__( def __init__(
@@ -59,10 +115,14 @@ class ContinuousTimeGaussianDiffusion(nn.Module):
*, *,
image_size, image_size,
channels = 3, channels = 3,
cond_scale = 500,
loss_type = 'l1', loss_type = 'l1',
noise_schedule = 'linear', noise_schedule = 'linear',
num_sample_steps = 500 num_sample_steps = 500,
clip_sample_denoised = True,
learned_schedule_net_hidden_dim = 1024,
learned_noise_schedule_frac_gradient = 1., # between 0 and 1, determines what percentage of gradients go back, so one can update the learned noise schedule more slowly
p2_loss_weight_gamma = 0., # p2 loss weight, from https://arxiv.org/abs/2204.00227 - 0 is equivalent to weight of 1 across time
p2_loss_weight_k = 1
): ):
super().__init__() super().__init__()
assert not denoise_fn.sinusoidal_cond_mlp assert not denoise_fn.sinusoidal_cond_mlp
@@ -76,17 +136,36 @@ class ContinuousTimeGaussianDiffusion(nn.Module):
# continuous noise schedule related stuff # continuous noise schedule related stuff
self.cond_scale = cond_scale # the log(snr) will be scaled by this value
self.loss_type = loss_type self.loss_type = loss_type
if noise_schedule == 'linear': if noise_schedule == 'linear':
self.log_snr = beta_linear_log_snr self.log_snr = beta_linear_log_snr
elif noise_schedule == 'cosine':
self.log_snr = alpha_cosine_log_snr
elif noise_schedule == 'learned':
log_snr_max, log_snr_min = [beta_linear_log_snr(torch.tensor([time])).item() for time in (0., 1.)]
self.log_snr = learned_noise_schedule(
log_snr_max = log_snr_max,
log_snr_min = log_snr_min,
hidden_dim = learned_schedule_net_hidden_dim,
frac_gradient = learned_noise_schedule_frac_gradient
)
else: else:
raise ValueError(f'unknown noise schedule {noise_schedule}') raise ValueError(f'unknown noise schedule {noise_schedule}')
# sampling # sampling
self.num_sample_steps = num_sample_steps self.num_sample_steps = num_sample_steps
self.clip_sample_denoised = clip_sample_denoised
# p2 loss weight
# proposed https://arxiv.org/abs/2204.00227
assert p2_loss_weight_gamma <= 2, 'in paper, they noticed any gamma greater than 2 is harmful'
self.p2_loss_weight_gamma = p2_loss_weight_gamma # recommended to be 0.5 or 1
self.p2_loss_weight_k = p2_loss_weight_k
@property @property
def device(self): def device(self):
@@ -105,9 +184,6 @@ class ContinuousTimeGaussianDiffusion(nn.Module):
# reviewer found an error in the equation in the paper (missing sigma) # reviewer found an error in the equation in the paper (missing sigma)
# following - https://openreview.net/forum?id=2LdBqxc1Yv&noteId=rIQgH0zKsRt # following - https://openreview.net/forum?id=2LdBqxc1Yv&noteId=rIQgH0zKsRt
# todo - derive x_start from the posterior mean and do dynamic thresholding
# assumed that is what is going on in Imagen
log_snr = self.log_snr(time) log_snr = self.log_snr(time)
log_snr_next = self.log_snr(time_next) log_snr_next = self.log_snr(time_next)
c = -expm1(log_snr - log_snr_next) c = -expm1(log_snr - log_snr_next)
@@ -115,10 +191,21 @@ class ContinuousTimeGaussianDiffusion(nn.Module):
squared_alpha, squared_alpha_next = log_snr.sigmoid(), log_snr_next.sigmoid() squared_alpha, squared_alpha_next = log_snr.sigmoid(), log_snr_next.sigmoid()
squared_sigma, squared_sigma_next = (-log_snr).sigmoid(), (-log_snr_next).sigmoid() squared_sigma, squared_sigma_next = (-log_snr).sigmoid(), (-log_snr_next).sigmoid()
alpha, sigma, alpha_next = map(sqrt, (squared_alpha, squared_sigma, squared_alpha_next))
batch_log_snr = repeat(log_snr, ' -> b', b = x.shape[0]) batch_log_snr = repeat(log_snr, ' -> b', b = x.shape[0])
pred_noise = self.denoise_fn(x, batch_log_snr) pred_noise = self.denoise_fn(x, batch_log_snr)
model_mean = sqrt(squared_alpha_next / squared_alpha) * (x - c * sqrt(squared_sigma) * pred_noise) if self.clip_sample_denoised:
x_start = (x - sigma * pred_noise) / alpha
# in Imagen, this was changed to dynamic thresholding
x_start.clamp_(-1., 1.)
model_mean = alpha_next * (x * (1 - c) / alpha + c * x_start)
else:
model_mean = alpha_next / alpha * (x - c * sigma * pred_noise)
posterior_variance = squared_sigma_next * c posterior_variance = squared_sigma_next * c
return model_mean, posterior_variance return model_mean, posterior_variance
@@ -178,9 +265,17 @@ class ContinuousTimeGaussianDiffusion(nn.Module):
noise = default(noise, lambda: torch.randn_like(x_start)) noise = default(noise, lambda: torch.randn_like(x_start))
x, log_snr = self.q_sample(x_start = x_start, times = times, noise = noise) x, log_snr = self.q_sample(x_start = x_start, times = times, noise = noise)
model_out = self.denoise_fn(x, log_snr)
model_out = self.denoise_fn(x, log_snr * self.cond_scale) losses = self.loss_fn(model_out, noise, reduction = 'none')
return self.loss_fn(model_out, noise) losses = reduce(losses, 'b ... -> b', 'mean')
if self.p2_loss_weight_gamma >= 0:
# following eq 8. in https://arxiv.org/abs/2204.00227
loss_weight = (self.p2_loss_weight_k + log_snr.exp()) ** -self.p2_loss_weight_gamma
losses = losses * loss_weight
return losses.mean()
def forward(self, img, *args, **kwargs): def forward(self, img, *args, **kwargs):
b, c, h, w, device, img_size, = *img.shape, img.device, self.image_size b, c, h, w, device, img_size, = *img.shape, img.device, self.image_size
@@ -541,7 +541,7 @@ class GaussianDiffusion(nn.Module):
# dataset classes # dataset classes
class Dataset(data.Dataset): class Dataset(data.Dataset):
def __init__(self, folder, image_size, exts = ['jpg', 'jpeg', 'png']): def __init__(self, folder, image_size, exts = ['jpg', 'jpeg', 'png'], augment_horizontal_flip = False):
super().__init__() super().__init__()
self.folder = folder self.folder = folder
self.image_size = image_size self.image_size = image_size
@@ -549,7 +549,7 @@ class Dataset(data.Dataset):
self.transform = transforms.Compose([ self.transform = transforms.Compose([
transforms.Resize(image_size), transforms.Resize(image_size),
transforms.RandomHorizontalFlip(), transforms.RandomHorizontalFlip() if augment_horizontal_flip else nn.Identity(),
transforms.CenterCrop(image_size), transforms.CenterCrop(image_size),
transforms.ToTensor() transforms.ToTensor()
]) ])
@@ -580,7 +580,8 @@ class Trainer(object):
step_start_ema = 2000, step_start_ema = 2000,
update_ema_every = 10, update_ema_every = 10,
save_and_sample_every = 1000, save_and_sample_every = 1000,
results_folder = './results' results_folder = './results',
augment_horizontal_flip = True
): ):
super().__init__() super().__init__()
self.model = diffusion_model self.model = diffusion_model
@@ -596,7 +597,7 @@ class Trainer(object):
self.gradient_accumulate_every = gradient_accumulate_every self.gradient_accumulate_every = gradient_accumulate_every
self.train_num_steps = train_num_steps self.train_num_steps = train_num_steps
self.ds = Dataset(folder, image_size) self.ds = Dataset(folder, image_size, augment_horizontal_flip = augment_horizontal_flip)
self.dl = cycle(data.DataLoader(self.ds, batch_size = train_batch_size, shuffle=True, pin_memory=True)) self.dl = cycle(data.DataLoader(self.ds, batch_size = train_batch_size, shuffle=True, pin_memory=True))
self.opt = Adam(diffusion_model.parameters(), lr=train_lr) self.opt = Adam(diffusion_model.parameters(), lr=train_lr)
+1 -1
View File
@@ -3,7 +3,7 @@ from setuptools import setup, find_packages
setup( setup(
name = 'denoising-diffusion-pytorch', name = 'denoising-diffusion-pytorch',
packages = find_packages(), packages = find_packages(),
version = '0.16.6', version = '0.18.2',
license='MIT', license='MIT',
description = 'Denoising Diffusion Probabilistic Models - Pytorch', description = 'Denoising Diffusion Probabilistic Models - Pytorch',
author = 'Phil Wang', author = 'Phil Wang',