Compare commits

...
12 Commits
4 changed files with 132 additions and 28 deletions
+10 -6
View File
@@ -1,9 +1,11 @@
<img src="./denoising-diffusion.png" width="500px"></img> <img src="./denoising-diffusion.png" width="500px"></img>
## Denoising Diffusion Probabilistic Model, in Pytorch (wip) ## Denoising Diffusion Probabilistic Model, in Pytorch
Implementation of <a href="https://arxiv.org/abs/2006.11239">Denoising Diffusion Probabilistic Model</a> in Pytorch. It is a new approach to generative modeling that may <a href="https://ajolicoeur.wordpress.com/the-new-contender-to-gans-score-matching-with-langevin-sampling/">have the potential</a> to rival GANs. It uses denoising score matching to estimate the gradient of the data distribution, followed by Langevin sampling to sample from the true distribution. This implementation was transcribed from the official Tensorflow version <a href="https://github.com/hojonathanho/diffusion">here</a>. Implementation of <a href="https://arxiv.org/abs/2006.11239">Denoising Diffusion Probabilistic Model</a> in Pytorch. It is a new approach to generative modeling that may <a href="https://ajolicoeur.wordpress.com/the-new-contender-to-gans-score-matching-with-langevin-sampling/">have the potential</a> to rival GANs. It uses denoising score matching to estimate the gradient of the data distribution, followed by Langevin sampling to sample from the true distribution. This implementation was transcribed from the official Tensorflow version <a href="https://github.com/hojonathanho/diffusion">here</a>.
<img src="./sample.png" width="500px"><img>
## Install ## Install
```bash ```bash
@@ -34,8 +36,8 @@ loss = diffusion(training_images)
loss.backward() loss.backward()
# after a lot of training # after a lot of training
sampled_images = diffusion.p_sample_loop((1, 3, 128, 128)) sampled_images = diffusion.sample(128, batch_size = 4)
sampled_images.shape # (1, 3, 128, 128) sampled_images.shape # (4, 3, 128, 128)
``` ```
Or, if you simply want to pass in a folder name and the desired image dimensions, you can use the `Trainer` class to easily train a model. Or, if you simply want to pass in a folder name and the desired image dimensions, you can use the `Trainer` class to easily train a model.
@@ -61,9 +63,11 @@ trainer = Trainer(
'path/to/your/images', 'path/to/your/images',
image_size = 128, image_size = 128,
train_batch_size = 32, train_batch_size = 32,
train_lr = 3e-4, train_lr = 2e-5,
train_num_steps = 100000, train_num_steps = 100000, # total training steps
gradient_accumulate_every = 1 gradient_accumulate_every = 2, # gradient accumulation steps
ema_decay = 0.995, # exponential moving average decay
fp16 = True # turn on mixed precision training with apex
) )
trainer.train() trainer.train()
@@ -1,4 +1,5 @@
import math import math
import copy
import torch import torch
from torch import nn, einsum from torch import nn, einsum
import torch.nn.functional as F import torch.nn.functional as F
@@ -15,9 +16,16 @@ import numpy as np
from tqdm import tqdm from tqdm import tqdm
from einops import rearrange from einops import rearrange
try:
from apex import amp
APEX_AVAILABLE = True
except:
APEX_AVAILABLE = False
# constants # constants
SAVE_AND_SAMPLE_EVERY = 1000 SAVE_AND_SAMPLE_EVERY = 1000
UPDATE_EMA_EVERY = 10
EXTS = ['jpg', 'png'] EXTS = ['jpg', 'png']
# helpers functions # helpers functions
@@ -35,8 +43,30 @@ def cycle(dl):
for data in dl: for data in dl:
yield data yield data
def loss_backwards(fp16, loss, optimizer, **kwargs):
if fp16:
with amp.scale_loss(loss, optimizer) as scaled_loss:
scaled_loss.backward(**kwargs)
else:
loss.backward(**kwargs)
# small helper modules # small helper modules
class EMA():
def __init__(self, beta):
super().__init__()
self.beta = beta
def update_model_average(self, ma_model, current_model):
for current_params, ma_params in zip(current_model.parameters(), ma_model.parameters()):
old_weight, up_weight = ma_params.data, current_params.data
ma_params.data = self.update_average(old_weight, up_weight)
def update_average(self, old, new):
if old is None:
return new
return old * self.beta + (1 - self.beta) * new
class Residual(nn.Module): class Residual(nn.Module):
def __init__(self, fn): def __init__(self, fn):
super().__init__() super().__init__()
@@ -90,7 +120,7 @@ class Rezero(nn.Module):
# building block modules # building block modules
class Block(nn.Module): class Block(nn.Module):
def __init__(self, dim, dim_out, groups = 32): def __init__(self, dim, dim_out, groups = 8):
super().__init__() super().__init__()
self.block = nn.Sequential( self.block = nn.Sequential(
nn.Conv2d(dim, dim_out, 3, padding=1), nn.Conv2d(dim, dim_out, 3, padding=1),
@@ -101,7 +131,7 @@ class Block(nn.Module):
return self.block(x) return self.block(x)
class ResnetBlock(nn.Module): class ResnetBlock(nn.Module):
def __init__(self, dim, dim_out, *, time_emb_dim, groups = 32): def __init__(self, dim, dim_out, *, time_emb_dim, groups = 8):
super().__init__() super().__init__()
self.mlp = nn.Sequential( self.mlp = nn.Sequential(
Mish(), Mish(),
@@ -140,7 +170,7 @@ class LinearAttention(nn.Module):
# model # model
class Unet(nn.Module): class Unet(nn.Module):
def __init__(self, dim, out_dim = None, dim_mults=(1, 2, 4, 8), groups = 32): def __init__(self, dim, out_dim = None, dim_mults=(1, 2, 4, 8), groups = 8):
super().__init__() super().__init__()
dims = [3, *map(lambda m: dim * m, dim_mults)] dims = [3, *map(lambda m: dim * m, dim_mults)]
in_out = list(zip(dims[:-1], dims[1:])) in_out = list(zip(dims[:-1], dims[1:]))
@@ -161,6 +191,7 @@ class Unet(nn.Module):
self.downs.append(nn.ModuleList([ self.downs.append(nn.ModuleList([
ResnetBlock(dim_in, dim_out, time_emb_dim = dim), ResnetBlock(dim_in, dim_out, time_emb_dim = dim),
ResnetBlock(dim_out, dim_out, time_emb_dim = dim),
Residual(Rezero(LinearAttention(dim_out))), Residual(Rezero(LinearAttention(dim_out))),
Downsample(dim_out) if not is_last else nn.Identity() Downsample(dim_out) if not is_last else nn.Identity()
])) ]))
@@ -175,6 +206,7 @@ class Unet(nn.Module):
self.ups.append(nn.ModuleList([ self.ups.append(nn.ModuleList([
ResnetBlock(dim_out * 2, dim_in, time_emb_dim = dim), ResnetBlock(dim_out * 2, dim_in, time_emb_dim = dim),
ResnetBlock(dim_in, dim_in, time_emb_dim = dim),
Residual(Rezero(LinearAttention(dim_in))), Residual(Rezero(LinearAttention(dim_in))),
Upsample(dim_in) if not is_last else nn.Identity() Upsample(dim_in) if not is_last else nn.Identity()
])) ]))
@@ -191,8 +223,9 @@ class Unet(nn.Module):
h = [] h = []
for resnet, attn, downsample in self.downs: for resnet, resnet2, attn, downsample in self.downs:
x = resnet(x, t) x = resnet(x, t)
x = resnet2(x, t)
x = attn(x) x = attn(x)
h.append(x) h.append(x)
x = downsample(x) x = downsample(x)
@@ -201,9 +234,10 @@ class Unet(nn.Module):
x = self.mid_attn(x) x = self.mid_attn(x)
x = self.mid_block2(x, t) x = self.mid_block2(x, t)
for resnet, attn, upsample in self.ups: for resnet, resnet2, attn, upsample in self.ups:
x = torch.cat((x, h.pop()), dim=1) x = torch.cat((x, h.pop()), dim=1)
x = resnet(x, t) x = resnet(x, t)
x = resnet2(x, t)
x = attn(x) x = attn(x)
x = upsample(x) x = upsample(x)
@@ -222,11 +256,15 @@ def noise_like(shape, device, repeat=False):
return repeat_noise() if repeat else noise() return repeat_noise() if repeat else noise()
class GaussianDiffusion(nn.Module): class GaussianDiffusion(nn.Module):
def __init__(self, denoise_fn, beta_start=0.0001, beta_end=0.02, num_diffusion_timesteps=1000, loss_type='l1'): def __init__(self, denoise_fn, beta_start=0.0001, beta_end=0.02, num_diffusion_timesteps=1000, loss_type='l1', betas = None):
super().__init__() super().__init__()
self.denoise_fn = denoise_fn self.denoise_fn = denoise_fn
self.np_betas = betas = np.linspace(beta_start, beta_end, num_diffusion_timesteps).astype(np.float64) if exists(betas):
self.np_betas = betas.detach().cpu().numpy() if isinstance(betas, torch.Tensor) else betas
else:
self.np_betas = betas = np.linspace(beta_start, beta_end, num_diffusion_timesteps).astype(np.float64)
timesteps, = betas.shape timesteps, = betas.shape
self.num_timesteps = int(timesteps) self.num_timesteps = int(timesteps)
self.loss_type = loss_type self.loss_type = loss_type
@@ -309,6 +347,26 @@ class GaussianDiffusion(nn.Module):
img = self.p_sample(img, torch.full((b,), i, device=device, dtype=torch.long)) img = self.p_sample(img, torch.full((b,), i, device=device, dtype=torch.long))
return img return img
@torch.no_grad()
def sample(self, image_size, batch_size = 16):
return self.p_sample_loop((16, 3, image_size, image_size))
@torch.no_grad()
def interpolate(self, x1, x2, t = None, lam = 0.5):
b, *_, device = *x1.shape, x1.device
t = default(t, self.num_timesteps - 1)
assert x1.shape == x2.shape
t_batched = torch.stack([torch.tensor(t, device=device)] * b)
xt1, xt2 = map(lambda x: self.q_sample(x, t=t_batched), (x1, x2))
img = (1 - lam) * xt1 + lam * xt2
for i in tqdm(reversed(range(0, t)), desc='interpolation sample time step', total=t):
img = self.p_sample(img, torch.full((b,), i, device=device, dtype=torch.long))
return img
def q_sample(self, x_start, t, noise=None): def q_sample(self, x_start, t, noise=None):
noise = default(noise, lambda: torch.randn_like(x_start)) noise = default(noise, lambda: torch.randn_like(x_start))
@@ -370,14 +428,19 @@ class Trainer(object):
diffusion_model, diffusion_model,
folder, folder,
*, *,
ema_decay = 0.995,
image_size = 128, image_size = 128,
train_batch_size = 32, train_batch_size = 32,
train_lr = 3e-4, train_lr = 2e-5,
train_num_steps = 100000, train_num_steps = 100000,
gradient_accumulate_every = 1 gradient_accumulate_every = 2,
fp16 = False
): ):
super().__init__() super().__init__()
self.model = diffusion_model self.model = diffusion_model
self.ema = EMA(ema_decay)
self.ema_model = copy.deepcopy(self.model)
self.image_size = image_size self.image_size = image_size
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
@@ -386,25 +449,62 @@ class Trainer(object):
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)
def train(self): self.step = 0
ind = 0
while ind < self.train_num_steps: assert not fp16 or fp16 and APEX_AVAILABLE, 'Apex must be installed in order for mixed precision training to be turned on'
self.fp16 = fp16
if fp16:
(self.model, self.ema_model), self.opt = amp.initialize([self.model, self.ema_model], self.opt, opt_level='O1')
self.reset_parameters()
def reset_parameters(self):
self.ema_model.load_state_dict(self.model.state_dict())
def step_ema(self):
if self.step < 2000:
self.reset_parameters()
return
self.ema.update_model_average(self.ema_model, self.model)
def save(self, milestone):
data = {
'step': self.step,
'model': self.model.state_dict(),
'ema': self.ema_model.state_dict()
}
torch.save(data, f'./model-{milestone}.pt')
def load(self, milestone):
data = torch.load(f'./model-{milestone}.pt')
self.step = data['step']
self.model.load_state_dict(data['model'])
self.ema_model.load_state_dict(data['ema'])
def train(self):
backwards = partial(loss_backwards, self.fp16)
while self.step < self.train_num_steps:
for i in range(self.gradient_accumulate_every): for i in range(self.gradient_accumulate_every):
data = next(self.dl).cuda() data = next(self.dl).cuda()
loss = self.model(data) loss = self.model(data)
print(f'{ind}: {loss.item()}') print(f'{self.step}: {loss.item()}')
loss.backward() backwards(loss / self.gradient_accumulate_every, self.opt)
self.opt.step() self.opt.step()
self.opt.zero_grad() self.opt.zero_grad()
if ind % SAVE_AND_SAMPLE_EVERY == 0: if self.step % UPDATE_EMA_EVERY == 0:
milestone = ind // SAVE_AND_SAMPLE_EVERY self.step_ema()
all_images = self.model.p_sample_loop((64, 3, self.image_size, self.image_size))
utils.save_image(all_images, f'./sample-{milestone}.png', nrow=8)
torch.save(model.state_dict(), f'./model-{milestone}.pt')
ind += 1 if self.step % SAVE_AND_SAMPLE_EVERY == 0:
milestone = self.step // SAVE_AND_SAMPLE_EVERY
all_images = self.ema_model.p_sample_loop((64, 3, self.image_size, self.image_size))
utils.save_image(all_images, f'./sample-{milestone}.png', nrow=8)
self.save(milestone)
self.step += 1
print('training completed') print('training completed')
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

+2 -2
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.1.0', version = '0.2.3',
license='MIT', license='MIT',
description = 'Denoising Diffusion Probabilistic Models - Pytorch', description = 'Denoising Diffusion Probabilistic Models - Pytorch',
author = 'Phil Wang', author = 'Phil Wang',
@@ -17,7 +17,7 @@ setup(
'einops', 'einops',
'numpy', 'numpy',
'pillow', 'pillow',
'torch', 'torch>=1.6',
'torchvision', 'torchvision',
'tqdm' 'tqdm'
], ],