目录
[1、forward process](#1、forward process)
[2、backward process](#2、backward process)
[3、diffusion model](#3、diffusion model)
[1、pixel RNN](#1、pixel RNN)
摘要
本周学习非监督学习的其中一种类型------生成式模型,其中包含pixel RNN、VAE和GAN三种模型。简单的了解了三种模型的提出和生成数据的方式以及其优缺点。还进行了代码实践------李宏毅机器学习HW6:生成二次元图片。该代码实例是扩散模型,学习了该模型的一些代表性的辅助块和组件,以及添加噪声的数据处理。
Abstract
This week, we learn about one of the types of unsupervised learning, generative models, which contains three models, pixel RNN, VAE and GAN. Briefly learned how the three models are proposed and generate data as well as their advantages and disadvantages. Code practice was also conducted - Hongyi Li Machine Learning HW6: Generating Quadratic Images. The code example is the diffusion model, and some representative auxiliary blocks and components of the model were learned, as well as data processing for adding noise.
一、HW6------生成二次元图片
1、forward process
python
def linear_beta_schedule(timesteps):
"""
linear schedule, proposed in original ddpm paper
"""
scale = 1000 / timesteps
beta_start = scale * 0.0001
beta_end = scale * 0.02
return torch.linspace(beta_start, beta_end, timesteps, dtype = torch.float64)
def extract(a, t, x_shape):
b, *_ = t.shape
out = a.gather(-1, t)
return out.reshape(b, *((1,) * (len(x_shape) - 1)))
linear_beta_schedule()函数:生成扩散模型中的线性β(beta)时间表
extract()函数:从张量中提取特定索引值
数据集的处理
python
class Dataset(Dataset):
def __init__(
self,
folder,
image_size
):
self.folder = folder
self.image_size = image_size
self.paths = [p for p in Path(f'{folder}').glob(f'**/*.jpg')]
#################################
## TODO: Data Augmentation ##
#################################
self.transform = T.Compose([
T.Resize(image_size),
T.ToTensor()
])
def __len__(self):
return len(self.paths)
def __getitem__(self, index):
path = self.paths[index]
img = Image.open(path)
return self.transform(img)
三个内置方法与HW3相同CSDN
2、backward process
神经网络中的辅助函数
残差块
python
class Residual(nn.Module):
def __init__(self, fn):
super().__init__()
self.fn = fn
def forward(self, x, *args, **kwargs):
return self.fn(x, *args, **kwargs) + x
在forward方法中,它将输入 x
通过 fn
处理后,再与原始输入 x
相加,实现了残差连接。
上/下采样
python
def Upsample(dim, dim_out = None):
return nn.Sequential(
nn.Upsample(scale_factor = 2, mode = 'nearest'),
nn.Conv2d(dim, default(dim_out, dim), 3, padding = 1)
)
def Downsample(dim, dim_out = None):
return nn.Sequential(
Rearrange('b c (h p1) (w p2) -> b (c p1 p2) h w', p1 = 2, p2 = 2),
nn.Conv2d(dim * 4, default(dim_out, dim), 1)
)
上采样:首先使用upsample进行最近邻插值,将特征图的尺寸放大两倍;然后,使用conv2d进行卷积操作,以调整通道数。
下采样:首先使用rearrange操作来重新排列数据,以准备进行下采样;然后,使用conv2d进行卷积操作,以调整通道数。
权重标准化的二维卷积
python
class WeightStandardizedConv2d(nn.Conv2d): #权重标准化的二维卷积
def forward(self, x):
eps = 1e-5 if x.dtype == torch.float32 else 1e-3
weight = self.weight
mean = reduce(weight, 'o ... -> o 1 1 1', 'mean')
var = reduce(weight, 'o ... -> o 1 1 1', partial(torch.var, unbiased = False)) #计算均值和方差
normalized_weight = (weight - mean) * (var + eps).rsqrt()
return F.conv2d(x, normalized_weight, self.bias, self.stride, self.padding, self.dilation, self.groups) #最后对标准化的结果进行二维卷积
在forward方法中,首先计算了卷积核的均值和方差,并使用这些统计数据来标准化权重。其中的reduce()函数是为了计算均值和方差。
层归一化
python
class LayerNorm(nn.Module): #层归一化:是在单个样本的特征图上的操作(通过规范化层的输入来减少内部协变量偏移)
def __init__(self, dim):
super().__init__()
self.g = nn.Parameter(torch.ones(1, dim, 1, 1))
def forward(self, x):
eps = 1e-5 if x.dtype == torch.float32 else 1e-3
var = torch.var(x, dim = 1, unbiased = False, keepdim = True)
mean = torch.mean(x, dim = 1, keepdim = True)
return (x - mean) * (var + eps).rsqrt() * self.g
初始化方法中g用于在归一化后缩放特征;在forward方法中,计算了输入x的均值和方差,并使用这些统计数据来归一化输入 。
前规范化:位于激活函数和权重之前
python
class PreNorm(nn.Module):
def __init__(self, dim, fn):
super().__init__()
self.fn = fn
self.norm = LayerNorm(dim) #每个样本的特征都被独立地规范化,使得每个特征的均值为0,标准差为1
def forward(self, x):
x = self.norm(x)
return self.fn(x)
在初始化方法中dim定了归一化的维度,fn是一个神经网络层或层的序列;在forward方法中,首先对输入x进行归一化,然后将归一化后的特征传递给fn进行处理。
正弦位置嵌入
python
class SinusoidalPosEmb(nn.Module): #生成正弦位置嵌入
def __init__(self, dim):
super().__init__()
self.dim = dim #表示嵌入位置的维度
def forward(self, x):
device = x.device
half_dim = self.dim // 2
emb = math.log(10000) / (half_dim - 1)
emb = torch.exp(torch.arange(half_dim, device=device) * -emb)
emb = x[:, None] * emb[None, :]
emb = torch.cat((emb.sin(), emb.cos()), dim=-1)
return emb
class RandomOrLearnedSinusoidalPosEmb(nn.Module):
def __init__(self, dim, is_random = False):
super().__init__()
assert (dim % 2) == 0
half_dim = dim // 2
self.weights = nn.Parameter(torch.randn(half_dim), requires_grad = not is_random)
def forward(self, x):
x = rearrange(x, 'b -> b 1')
freqs = x * rearrange(self.weights, 'd -> 1 d') * 2 * math.pi
fouriered = torch.cat((freqs.sin(), freqs.cos()), dim = -1)
fouriered = torch.cat((x, fouriered), dim = -1)
return fouriered
为模型提供序列中每个元素的位置信息。以上两个类都是用于生成正弦位置嵌入。
神经网络的组件
神经网络块
python
class Block(nn.Module): #块:权重标准化卷积+层归一化+silu激活函数
def __init__(self, dim, dim_out, groups = 8):
super().__init__()
self.proj = WeightStandardizedConv2d(dim, dim_out, 3, padding = 1) #卷积层进行卷积操作
self.norm = nn.GroupNorm(groups, dim_out) #层归一化对卷积后的输出结果进行层归一化
self.act = nn.SiLU() #激活函数为了引入非线性
def forward(self, x, scale_shift = None):
x = self.proj(x)
x = self.norm(x)
if exists(scale_shift):
scale, shift = scale_shift
x = x * (scale + 1) + shift
x = self.act(x)
return x
包含了权重标准化卷积+层归一化+silu激活函数
ResNet块
python
class ResnetBlock(nn.Module): #包含时间嵌入和残差连接的ResNet块
def __init__(self, dim, dim_out, *, time_emb_dim = None, groups = 8):
super().__init__()
self.mlp = nn.Sequential( #MLP是通过非线性激活函数来学习和模拟输入数据与输出数据之间的复杂关系和映射
nn.SiLU(),
nn.Linear(time_emb_dim, dim_out * 2)
) if exists(time_emb_dim) else None
#如果 time_emb_dim存在,创建序列模型(激活函数+非线性函数)用于将时间嵌入转换为缩放和平移参数
#两个 Block 实例,用于构建ResNet块中的两个卷积块
self.block1 = Block(dim, dim_out, groups = groups)
self.block2 = Block(dim_out, dim_out, groups = groups)
self.res_conv = nn.Conv2d(dim, dim_out, 1) if dim != dim_out else nn.Identity()#一个卷积层或恒等映射,用于实现残差连接
def forward(self, x, time_emb = None):
scale_shift = None
if exists(self.mlp) and exists(time_emb):
time_emb = self.mlp(time_emb) #用MLP来处理时间嵌入
time_emb = rearrange(time_emb, 'b c -> b c 1 1') #调整形状来满足卷积层的输入要求
scale_shift = time_emb.chunk(2, dim = 1) #将输出分割为缩放和平移参数
h = self.block1(x, scale_shift = scale_shift) #使用缩放和平移参数
h = self.block2(h)
return h + self.res_conv(x) #将第二个块的输出与通过 self.res_conv 处理的原始输入相加,实现残差连接
包含时间嵌入和残差连接的块。forward方法中首先判断是否包含时间嵌入并生成缩放和平移参数,然后返回值处实现残差连接。
线性的注意力机制
python
class LinearAttention(nn.Module): #线性注意力机制,比transformer注意力更加高效
def __init__(self, dim, heads = 4, dim_head = 32):
super().__init__()
self.scale = dim_head ** -0.5 #注意力因子
self.heads = heads #注意力头的数量
hidden_dim = dim_head * heads #每个注意力头的维度
self.to_qkv = nn.Conv2d(dim, hidden_dim * 3, 1, bias = False) #一个二维卷积层,将输入x转化为q、k、v
self.to_out = nn.Sequential( #一个序列模型:二维卷积层+归一化层
nn.Conv2d(hidden_dim, dim, 1),
LayerNorm(dim)
)
def forward(self, x):
b, c, h, w = x.shape
qkv = self.to_qkv(x).chunk(3, dim = 1)
q, k, v = map(lambda t: rearrange(t, 'b (h c) x y -> b h c (x y)', h = self.heads), qkv)
q = q.softmax(dim = -2)
k = k.softmax(dim = -1) #对q、k应用softmax函数,从而获得注意力分散
q = q * self.scale
v = v / (h * w)
context = torch.einsum('b h d n, b h e n -> b h d e', k, v) #计算注意力上下文,这是通过 K 和 V 的外积实现的
out = torch.einsum('b h d e, b h d n -> b h e n', context, q)
out = rearrange(out, 'b h c (x y) -> b (h c) x y', h = self.heads, x = h, y = w)
return self.to_out(out)
forward方法实现了线性注意力的计算过程,包括对Q和K应用softmax函数,计算注意力上下文,以及通过注意力机制增强输入特征。比传统的transformer注意力机制更加高效。
3、diffusion model
高斯分布模型包含了很多个方法。如下:
python
def predict_start_from_noise(self, x_t, t, noise):
return (
extract(self.sqrt_recip_alphas_cumprod, t, x_t.shape) * x_t -
extract(self.sqrt_recipm1_alphas_cumprod, t, x_t.shape) * noise
)
def predict_noise_from_start(self, x_t, t, x0):
return (
(extract(self.sqrt_recip_alphas_cumprod, t, x_t.shape) * x_t - x0) / \
extract(self.sqrt_recipm1_alphas_cumprod, t, x_t.shape)
)
def q_posterior(self, x_start, x_t, t):
posterior_mean = (
extract(self.posterior_mean_coef1, t, x_t.shape) * x_start +
extract(self.posterior_mean_coef2, t, x_t.shape) * x_t
)
posterior_variance = extract(self.posterior_variance, t, x_t.shape)
posterior_log_variance_clipped = extract(self.posterior_log_variance_clipped, t, x_t.shape)
return posterior_mean, posterior_variance, posterior_log_variance_clipped
def model_predictions(self, x, t, clip_x_start = False, rederive_pred_noise = False):
model_output = self.model(x, t)
maybe_clip = partial(torch.clamp, min = -1., max = 1.) if clip_x_start else identity
pred_noise = model_output
x_start = self.predict_start_from_noise(x, t, pred_noise)
x_start = maybe_clip(x_start)
if clip_x_start and rederive_pred_noise:
pred_noise = self.predict_noise_from_start(x, t, x_start)
return pred_noise, x_start
def p_mean_variance(self, x, t, clip_denoised = True):
noise, x_start = self.model_predictions(x, t)
if clip_denoised:
x_start.clamp_(-1., 1.)
model_mean, posterior_variance, posterior_log_variance = self.q_posterior(x_start = x_start, x_t = x, t = t)
return model_mean, posterior_variance, posterior_log_variance, x_start
predict_start_from_noise
和 predict_noise_from_start
方法用于从噪声预测干净的数据点和从干净的数据点预测噪声;q_posterior
方法用于计算后验分布的均值和方差;model_predictions
方法使用模型进行预测,并根据需要裁剪预测的噪声;p_mean_variance
方法计算模型的均值和方差。
生成图像的逐步过程如下:
二、非监督学习------生成式模型
**非监督学习:**就是训练数据没有标签
**生成模型:**给定训练集,产生与训练集同分布的新样本。首先学习训练样本中的数据分布,然后再在该分布中采样出一个点,通过这个点就可以生成想要的类似于真样本的假数据。
1、pixel RNN
可以看出,每次生成一张图片都需要一个个像素依次生成。上图3*3的可以看成是RNN预留了9个维度的像素生成。
多个维度的像素处理如下:
在生成图像的时候,第一种方法是像素的输入是3维向量,分别代表RGB,缺陷就是RGB的权重很接近导致生成图片近乎灰色;第二种方法是将不同颜色的像素块以"独热码"的形式表示出来,缺陷就是颜色太多导致维度过大;所以提出第三种方法------聚类
**问题:**无法确定每个pixel出现的正确顺序
2、VAE
auto-encoder提出是为了让机器通过输入的数据,经过神经网络的计算生成新数据。
自编码器(Auto-encoder) 是一种用于无监督学习的神经网络模型,其主要目的是学习数据的低维表示(或压缩表示),同时能够从这些低维表示中重构原始输入数据。由encoder和decoder组成。
**问题:**auto-encoder一般生成图片的效果不好
**解决:**VAE
实际上,VAE就是在anto-encoder的基础上进行了改进,主要是使得encoder和decoder中间的code计算更加复杂了。和是decoder给出的两组3维向量,则代表code。最终要使得重构误差最小。简单来说,VAE就是加了噪声的code。
重构误差reconstruction error: x→y→z,y是被污染的x,z是从y中试图对x进行的重构。重构误差是用来衡量重构的好坏。
一般来说,我们可以把encoder输出的n维向量选取2个维度进行调整,固定其他(n-2)维。这样就能明显的看出图像生成的具体变换。如下所示:
**问题:**为什么生成模型VAE要优于auto-encoder
回答: VAE能生成一些介于几张不同真实图片之间的图片,如下:
可以看出,左图当中是auto-encoder模型,给定的database中只有满月和半月的图像,所以生成的图像也只有这两种;右图中VAE模型在code中添加了噪声,能够生成3/4月的图像,使得机器有一定的想象力。
**混合高斯模型GaussianMixture Model:**假设我们有m个权重、方差、均值各不相同的高斯模型,那我们分布出x的概率就是每个高斯分布的权重P(m)和这个正态分布分布出x的概率的成绩的求和。
假设是正态分布,并且也是关于x的不同均值方差的正态分布。在z分布曲线上取一些点,就能对应到P(x)上的一个正态分布。VAE的高斯分布图如下图所示:
已知P(z)是正态分布,想要估计关于z的均值方差,就需要使观测对象x的可能性得到最大,如下面的最大似然函数的推导过程:
**问题:**该模型从来没有真正的学习生成新的数据,而是模仿原有database中的旧数据,在其基础上做一些线性变换。
**解决:**GAN
3、GAN
GAN可以做到生成全新的数据,它的训练过程就是:固定discriminator,使得generator生成的数据更加接近真实数据;固定generator,使得discriminator很大程度上区分真实图片和虚假的生成图片。
总结
本周的代码实践仅仅是扩散模型的simple版本,后续还将添加数据增强以及其他参数的调整实现更好的生成效果。并且继续学习flow-based GAN的相关内容。