正在入坑DDPM,开源代码都看不懂,捣鼓了几天弄出来一个简易版。
有没有带着看开源代码的教程啥的,很需要
下面这些大部分是参考WGS.的Diffusion 扩散模型(DDPM)详解及torch复现这篇博客。
但是StanfordCars这个数据集失效了,不能直接用了,改成了可以自己设置数据集的版本
先导入包
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils import data
import torchvision
from torchvision import transforms
import math
import numpy as np
import matplotlib.pyplot as plt
import os
import glob
from PIL import Image
device = "cuda" if torch.cuda.is_available() else "cpu"
获取图片的路径,印象中glob这个函数好像是不同版本用法不同,有个顺序的问题,遇到问题了再百度
faces_path = glob.glob('hhy/*.jpg')
IMG_SIZE = 256
BATCH_SIZE = 8
注释那条换掉Lambda那条应该也是可以的,一直报错,撞了好多次,最后忘记改回来了
transform = transforms.Compose([
transforms.Resize((256,256)),
transforms.ToTensor(), #ToTensor写在操作之后
#transforms.Normalize(mean=[0.5,0.5,0.5],std=[0.5,0.5,0.5]) #数据强制缩放到(-1,1)
transforms.Lambda(lambda t: (t * 2) - 1) # Scale between [-1, 1]
])
定义数据集的类
class CMP_dataset(data.Dataset):
def __init__(self,faces_path):
self.faces_path = faces_path
def __getitem__(self,index):
face_path = self.faces_path[index]
pil_img = Image.open(face_path)
pil_img = transform(pil_img)
return pil_img
def __len__(self):
return len(self.faces_path)
加载数据集
dataset = CMP_dataset(faces_path)
dataloader = data.DataLoader(dataset,
batch_size=BATCH_SIZE,
shuffle=True,
drop_last=True)
定义一些用得到的方法,里面的get_index_from_list()和forward_diffusion_sample()这两个函数感觉还蛮有意思的,但其实就是 q ( x t ∣ x 0 ) = α ˉ x o + 1 − α ˉ ϵ q(x_t|x_0)=\sqrt{\bar\alpha}x_o+\sqrt{1-\bar\alpha}\epsilon q(xt∣x0)=αˉxo+1−αˉϵ这一步的意思
import torch.nn.functional as F
def linear_beta_schedule(timesteps, start=0.0001, end=0.02):
return torch.linspace(start, end, timesteps)
def get_index_from_list(vals, t, x_shape):
batch_size = t.shape[0] #1
out = vals.gather(-1, t.cpu())
return out.reshape(batch_size, *((1,) * (len(x_shape)-1))).to(t.device)
def forward_diffusion_sample(x_0, t, device=device):
noise = torch.randn_like(x_0)
sqrt_alphas_cumprod_t = get_index_from_list(sqrt_alphas_cumprod, t, x_0.shape)
sqrt_one_minus_alphas_cumprod_t = get_index_from_list(sqrt_one_minus_alphas_cumprod, t, x_0.shape )
return sqrt_alphas_cumprod_t.to(device) * x_0.to(device) + sqrt_one_minus_alphas_cumprod_t.to(device)* noise.to(device),noise.to(device)
# 界定测试时间表
T = 300
betas = linear_beta_schedule(timesteps=T)
# 预先计算闭合形式的不同项
alphas = 1. - betas
alphas_cumprod = torch.cumprod(alphas, axis=0)
alphas_cumprod_prev = F.pad(alphas_cumprod[:-1], (1, 0), value=1.0)
sqrt_recip_alphas = torch.sqrt(1.0 / alphas)
sqrt_alphas_cumprod = torch.sqrt(alphas_cumprod)
sqrt_one_minus_alphas_cumprod = torch.sqrt(1. - alphas_cumprod)
posterior_variance = betas * (1. - alphas_cumprod_prev) / (1. - alphas_cumprod)
将tensor显示为图片
def show_tensor_image(image):
reverse_transforms = transforms.Compose([
transforms.Lambda(lambda t: (t + 1) / 2),
transforms.Lambda(lambda t: t.permute(1, 2, 0)), # CHW to HWC
transforms.Lambda(lambda t: t * 255.),
transforms.Lambda(lambda t: t.cpu().numpy().astype(np.uint8)),
transforms.ToPILImage(),
])
# Take first image of batch
if len(image.shape) == 4:
image = image[0, :, :, :]
plt.imshow(reverse_transforms(image))
定义model
class Block(nn.Module):
def __init__(self, in_ch, out_ch, time_emb_dim, up=False):
super().__init__()
self.time_mlp = nn.Linear(time_emb_dim, out_ch)
if up:
self.conv1 = nn.Conv2d(2*in_ch, out_ch, 3, padding=1)
self.transform = nn.ConvTranspose2d(out_ch, out_ch, 4, 2, 1)
else:
self.conv1 = nn.Conv2d(in_ch, out_ch, 3, padding=1)
self.transform = nn.Conv2d(out_ch, out_ch, 4, 2, 1)
self.conv2 = nn.Conv2d(out_ch, out_ch, 3, padding=1)
self.bnorm1 = nn.BatchNorm2d(out_ch)
self.bnorm2 = nn.BatchNorm2d(out_ch)
self.relu = nn.ReLU()
def forward(self, x, t, ):
# 第一次卷积
h = self.bnorm1(self.relu(self.conv1(x)))
# 时间嵌入
time_emb = self.relu(self.time_mlp(t))
# 扩展到最后2个维度
time_emb = time_emb[(..., ) + (None, ) * 2]
# 添加时间通道
h = h + time_emb
# 第二次卷积
h = self.bnorm2(self.relu(self.conv2(h)))
# 上采样或者下采样
return self.transform(h)
class SinusoidalPositionEmbeddings(nn.Module):
def __init__(self, dim):
super().__init__()
self.dim = dim
def forward(self, time):
device = time.device
half_dim = self.dim // 2
embeddings = math.log(10000) / (half_dim - 1)
embeddings = torch.exp(torch.arange(half_dim, device=device) * -embeddings)
embeddings = time[:, None] * embeddings[None, :]
embeddings = torch.cat((embeddings.sin(), embeddings.cos()), dim=-1)
return embeddings
class SimpleUnet(nn.Module):
"""
Unet架构的一个简化版本
"""
def __init__(self):
super().__init__()
image_channels = 3
down_channels = (64, 128, 256, 512, 1024)
up_channels = (1024, 512, 256, 128, 64)
out_dim = 1
time_emb_dim = 32
# 时间嵌入
self.time_mlp = nn.Sequential(
SinusoidalPositionEmbeddings(time_emb_dim),
nn.Linear(time_emb_dim, time_emb_dim),
nn.ReLU()
)
# 初始预估
self.conv0 = nn.Conv2d(image_channels, down_channels[0], 3, padding=1)
# 下采样
self.downs = nn.ModuleList([Block(down_channels[i], down_channels[i+1], \
time_emb_dim) \
for i in range(len(down_channels)-1)])
# 上采样
self.ups = nn.ModuleList([Block(up_channels[i], up_channels[i+1], \
time_emb_dim, up=True) \
for i in range(len(up_channels)-1)])
self.output = nn.Conv2d(up_channels[-1], 3, out_dim)
def forward(self, x, timestep):
# 时间嵌入
t = self.time_mlp(timestep)
# 初始卷积
x = self.conv0(x)
# Unet
residual_inputs = []
for down in self.downs:
x = down(x, t)
residual_inputs.append(x)
for up in self.ups:
residual_x = residual_inputs.pop()
# 添加残差结构作为额外的通道
x = torch.cat((x, residual_x), dim=1)
x = up(x, t)
return self.output(x)
model = SimpleUnet()
def get_loss(model, x_0, t):
x_noisy, noise = forward_diffusion_sample(x_0, t, device)
noise_pred = model(x_noisy, t)
return F.l1_loss(noise, noise_pred)
@torch.no_grad()#防止内存爆炸
def sample_timestep(x, t):
"""
调用模型来预测图像中的噪声,并返回
去噪后的图像。
如果我们还没有进入最后一步,则对该图像施加噪声。
"""
betas_t = get_index_from_list(betas, t, x.shape)
sqrt_one_minus_alphas_cumprod_t = get_index_from_list(
sqrt_one_minus_alphas_cumprod, t, x.shape
)
sqrt_recip_alphas_t = get_index_from_list(sqrt_recip_alphas, t, x.shape)
# 调用模型(当前图像--噪声预测)。
model_mean = sqrt_recip_alphas_t * (
x - betas_t * model(x, t) / sqrt_one_minus_alphas_cumprod_t
)
posterior_variance_t = get_index_from_list(posterior_variance, t, x.shape)
if t == 0:
return model_mean
else:
noise = torch.randn_like(x)
return model_mean + torch.sqrt(posterior_variance_t) * noise
@torch.no_grad()
def sample_plot_image():
# 样本噪声
img_size = IMG_SIZE
img = torch.randn((1, 3, img_size, img_size), device=device)
plt.figure(figsize=(15,15))
plt.axis('off')
num_images = 10
stepsize = int(T/num_images)
for i in range(0,T)[::-1]:
t = torch.full((1,), i, device=device, dtype=torch.long)
img = sample_timestep(img, t)
if i % stepsize == 0:
plt.subplot(1, num_images, int(i/stepsize+1))
show_tensor_image(img.detach().cpu())
plt.show()
开始训练
from torch.optim import Adam
device = "cuda" if torch.cuda.is_available() else "cpu"
model.to(device)
optimizer = Adam(model.parameters(), lr=0.001)
epochs = 1000 # Try more!
for epoch in range(epochs):
for step, batch in enumerate(dataloader):
optimizer.zero_grad()
t = torch.randint(0, T, (BATCH_SIZE,), device=device).long()
loss = get_loss(model, batch, t)
loss.backward()
optimizer.step()
if epoch % 5 == 0 and step == 0:
print(f"Epoch {epoch} | step {step:03d} Loss: {loss.item()} ")
sample_plot_image()
我这边用的数据集是celeb-A 256的数据集,一开始用64*64做的时候效果感觉已经比较不错了,然后改成256之后不知道为啥就很慢,用的4060ti,感觉也不应该啊。