扩散模型课程第一单元第二部分:扩散模型从零到一

前言

于 11 月底正式开课的扩散模型课程正在火热进行中,在中国社区成员们的帮助下,我们组织了「抱抱脸中文本地化志愿者小组」并完成了扩散模型课程的中文翻译,感谢 @darcula1993、@XhrLeokk、@hoi2022、@SuSung-boy 对课程的翻译!

如果你还没有开始课程的学习,我们建议你从 第一单元:扩散模型简介 开始。

扩散模型从零到一

这个 Notebook 我们将展示相同的步骤(向数据添加噪声、创建模型、训练和采样),并尽可能简单地在 PyTorch 中从头开始实现。然后,我们将这个「玩具示例」与 diffusers 版本进行比较,并关注两者的区别以及改进之处。这里的目标是熟悉不同的组件和其中的设计决策,以便在查看新的实现时能够快速确定关键思想。

让我们开始吧!

有时,只考虑一些事务最简单的情况会有助于更好地理解其工作原理。我们将在本笔记本中尝试这一点,从“玩具”扩散模型开始,看看不同的部分是如何工作的,然后再检查它们与更复杂的实现有何不同。

你将跟随本文的 Notebook 学习到

  • 损坏过程(向数据添加噪声)

  • 什么是 UNet,以及如何从零开始实现一个极小的 UNet

  • 扩散模型训练

  • 抽样理论

然后,我们将比较我们的版本与 diffusers 库中的 DDPM 实现的区别

  • 对小型 UNet 的改进

  • DDPM 噪声计划

  • 训练目标的差异

  • timestep 调节

  • 抽样方法

这个笔记本相当深入,如果你对从零开始的深入研究不感兴趣,可以放心地跳过!

还值得注意的是,这里的大多数代码都是出于说明的目的,我不建议直接将其用于您自己的工作(除非您只是为了学习目的而尝试改进这里展示的示例)。

准备环境与导入:

!pip install -q diffusers
import torch
import torchvision
from torch import nn
from torch.nn import functional as F
from torch.utils.data import DataLoader
from diffusers import DDPMScheduler, UNet2DModel
from matplotlib import pyplot as plt

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f'Using device: {device}')

数据

在这里,我们将使用一个非常小的经典数据集 mnist 来进行测试。如果您想在不改变任何其他内容的情况下给模型一个稍微困难一点的挑战,请使用 torchvision.dataset,FashionMNIST 应作为替代品。

dataset = torchvision.datasets.MNIST(root="mnist/", train=True, download=True, transform=torchvision.transforms.ToTensor())
train_dataloader = DataLoader(dataset, batch_size=8, shuffle=True)
x, y = next(iter(train_dataloader))
print('Input shape:', x.shape)
print('Labels:', y)
plt.imshow(torchvision.utils.make_grid(x)[0], cmap='Greys');

该数据集中的每张图都是一个数字的 28x28 像素的灰度图,像素值的范围是从 0 到 1。

损坏过程

假设你没有读过任何扩散模型的论文,但你知道这个过程会增加噪声。你会怎么做?

我们可能想要一个简单的方法来控制损坏的程度。那么,如果我们要引入一个参数来控制输入的“噪声量”,那么我们会这么做:

noise = torch.rand_like(x)

noisy_x = (1-amount)*x + amount*noise

如果 amount = 0,则返回输入而不做任何更改。如果 amount = 1,我们将得到一个纯粹的噪声。通过这种方式将输入与噪声混合,我们将输出保持在相同的范围(0 to 1)。

我们可以很容易地实现这一点(但是要注意 tensor 的 shape,以防被广播 (broadcasting) 机制不正确的影响到):

def corrupt(x, amount):
  """Corrupt the input `x` by mixing it with noise according to `amount`"""
  noise = torch.rand_like(x)
  amount = amount.view(-1, 1, 1, 1) # Sort shape so broadcasting works
  return x*(1-amount) + noise*amount

让我们来可视化一下输出的结果,以了解是否符合我们的预期:

# Plotting the input data
fig, axs = plt.subplots(2, 1, figsize=(12, 5))
axs[0].set_title('Input data')
axs[0].imshow(torchvision.utils.make_grid(x)[0], cmap='Greys')

# Adding noise
amount = torch.linspace(0, 1, x.shape[0]) # Left to right -> more corruption
noised_x = corrupt(x, amount)

# Plottinf the noised version
axs[1].set_title('Corrupted data (-- amount increases -->)')
axs[1].imshow(torchvision.utils.make_grid(noised_x)[0], cmap='Greys');

当噪声量接近 1 时,我们的数据开始看起来像纯随机噪声。但对于大多数的噪声情况下,您还是可以很好地识别出数字。你认为这是最佳的吗?

模型

我们想要一个模型,它可以接收 28px 的噪声图像,并输出相同形状的预测。一个比较流行的选择是一个叫做 UNet 的架构。最初被发明用于医学图像中的分割任务,UNet 由一个“压缩路径”和一个“扩展路径”组成。“压缩路径”会使通过该路径的数据被压缩,而通过“扩展路径”会将数据扩展回原始维度(类似于自动编码器)。模型中的残差连接也允许信息和梯度在不同层级之间流动。

一些 UNet 的设计在每个阶段都有复杂的 blocks,但对于这个玩具 demo,我们只会构建一个最简单的示例,它接收一个单通道图像,并通过下行路径上的三个卷积层(图和代码中的 down_layers)和上行路径上的 3 个卷积层,在下行和上行层之间具有残差连接。我们将使用 max pooling 进行下采样和 nn.Upsample 用于上采样。某些比较复杂的 UNets 的设计会使用带有可学习参数的上采样和下采样 layer。下面的结构图大致展示了每个 layer 的输出通道数:

9fd3a230e3fb10416ff431dcbec6d421.jpeg

代码实现如下:

class BasicUNet(nn.Module):
  """A minimal UNet implementation."""
  def __init__(self, in_channels=1, out_channels=1):
    super().__init__()
    self.down_layers = torch.nn.ModuleList([ 
      nn.Conv2d(in_channels, 32, kernel_size=5, padding=2),
      nn.Conv2d(32, 64, kernel_size=5, padding=2),
      nn.Conv2d(64, 64, kernel_size=5, padding=2),
    ])
    self.up_layers = torch.nn.ModuleList([
      nn.Conv2d(64, 64, kernel_size=5, padding=2),
      nn.Conv2d(64, 32, kernel_size=5, padding=2),
      nn.Conv2d(32, out_channels, kernel_size=5, padding=2), 
    ])
    self.act = nn.SiLU() # The activation function
    self.downscale = nn.MaxPool2d(2)
    self.upscale = nn.Upsample(scale_factor=2)

  def forward(self, x):
    h = []
    for i, l in enumerate(self.down_layers):
      x = self.act(l(x)) # Through the layer n the activation function
      if i < 2: # For all but the third (final) down layer:
        h.append(x) # Storing output for skip connection
        x = self.downscale(x) # Downscale ready for the next layer
              
    for i, l in enumerate(self.up_layers):
      if i > 0: # For all except the first up layer
        x = self.upscale(x) # Upscale
        x += h.pop() # Fetching stored output (skip connection)
        x = self.act(l(x)) # Through the layer n the activation function
            
    return x

我们可以验证输出 shape 是否如我们期望的那样与输入相同:

net = BasicUNet()
x = torch.rand(8, 1, 28, 28)
net(x).shape
torch.Size([8, 1, 28, 28])

该网络有 30 多万个参数:

sum([p.numel() for p in net.parameters()])
309057

您可以尝试更改每个 layer 中的通道数或尝试不同的结构设计。

训练模型

那么,模型到底应该做什么呢?同样,对这个问题有各种不同的看法,但对于这个演示,让我们选择一个简单的框架:给定一个损坏的输入 noisy_x,模型应该输出它对原本 x 的最佳猜测。我们将通过均方误差将预测与真实值进行比较。

我们现在可以尝试训练网络了。

  • 获取一批数据

  • 添加随机噪声

  • 将数据输入模型

  • 将模型预测与干净图像进行比较,以计算 loss

  • 更新模型的参数

你可以自由进行修改来尝试获得更好的结果!

# Dataloader (you can mess with batch size)
batch_size = 128
train_dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)

# How many runs through the data should we do?
n_epochs = 3

# Create the network
net = BasicUNet()
net.to(device)

# Our loss finction
loss_fn = nn.MSELoss()

# The optimizer
opt = torch.optim.Adam(net.parameters(), lr=1e-3) 

# Keeping a record of the losses for later viewing
losses = []

# The training loop
for epoch in range(n_epochs):

  for x, y in train_dataloader:
    # Get some data and prepare the corrupted version
    x = x.to(device) # Data on the GPU
    noise_amount = torch.rand(x.shape[0]).to(device) # Pick random noise amounts
    noisy_x = corrupt(x, noise_amount) # Create our noisy x

    # Get the model prediction
    pred = net(noisy_x)

    # Calculate the loss
    loss = loss_fn(pred, x) # How close is the output to the true 'clean' x?

    # Backprop and update the params:
    opt.zero_grad()
    loss.backward()
    opt.step()

    # Store the loss for later
    losses.append(loss.item())

    # Print our the average of the loss values for this epoch:
    avg_loss = sum(losses[-len(train_dataloader):])/len(train_dataloader)
    print(f'Finished epoch {epoch}. Average loss for this epoch: {avg_loss:05f}')

# View the loss curve
plt.plot(losses)
plt.ylim(0, 0.1);
Finished epoch 0. Average loss for this epoch: 0.026736
Finished epoch 1. Average loss for this epoch: 0.020692
Finished epoch 2. Average loss for this epoch: 0.018887
17acb12c64d1d0f8707203e031a67015.png

我们可以尝试通过抓取一批数据,以不同的数量损坏数据,然后喂进模型获得预测来观察结果:

#@markdown Visualizing model predictions on noisy inputs:

# Fetch some data
x, y = next(iter(train_dataloader))
x = x[:8] # Only using the first 8 for easy plotting

# Corrupt with a range of amounts
amount = torch.linspace(0, 1, x.shape[0]) # Left to right -> more corruption
noised_x = corrupt(x, amount)

# Get the model predictions
with torch.no_grad():
  preds = net(noised_x.to(device)).detach().cpu()

# Plot
fig, axs = plt.subplots(3, 1, figsize=(12, 7))
axs[0].set_title('Input data')
axs[0].imshow(torchvision.utils.make_grid(x)[0].clip(0, 1), cmap='Greys')
axs[1].set_title('Corrupted data')
axs[1].imshow(torchvision.utils.make_grid(noised_x)[0].clip(0, 1), cmap='Greys')
axs[2].set_title('Network Predictions')
axs[2].imshow(torchvision.utils.make_grid(preds)[0].clip(0, 1), cmap='Greys');
ca91b4eb81be7a25f3f0c15a4382c52e.png

你可以看到,对于较低的噪声水平数量,预测的结果相当不错!但是,当噪声水平非常高时,模型能够获得的信息就开始逐渐减少。而当我们达到 amount = 1 时,模型会输出一个模糊的预测,该预测会很接近数据集的平均值。模型通过这样的方式来猜测原始输入。

取样(采样)

如果我们在高噪声水平下的预测不是很好,我们如何才能生成图像呢?

如果我们从完全随机的噪声开始,检查一下模型预测的结果,然后只朝着预测方向移动一小部分,比如说 20%。现在我们有一个噪声很多的图像,其中可能隐藏了一些关于输入数据的结构的提示,我们可以将其输入到模型中以获得新的预测。希望这个新的预测比第一个稍微好一点(因为我们这一次的输入稍微减少了一点噪声),所以我们可以用这个新的更好的预测再往前迈出一小步。

如果一切顺利的话,以上过程重复几次以后我们就会得到一个新的图像!以下图例是迭代了五次以后的结果,左侧是每个阶段的模型输入的可视化,右侧则是预测的去噪图像。请注意,即使模型在第 1 步就预测了去噪图像,我们也只是将输入向去噪图像变换了一小部分。重复几次以后,图像的结构开始逐渐出现并得到改善 , 直到获得我们的最终结果为止。

#@markdown Sampling strategy: Break the process into 5 steps and move 1/5'th of the way there each time:
n_steps = 5
x = torch.rand(8, 1, 28, 28).to(device) # Start from random
step_history = [x.detach().cpu()]
pred_output_history = []

for i in range(n_steps):
  with torch.no_grad(): # No need to track gradients during inference
    pred = net(x) # Predict the denoised x0
  pred_output_history.append(pred.detach().cpu()) # Store model output for plotting
  mix_factor = 1/(n_steps - i) # How much we move towards the prediction
  x = x*(1-mix_factor) + pred*mix_factor # Move part of the way there
  step_history.append(x.detach().cpu()) # Store step for plotting

fig, axs = plt.subplots(n_steps, 2, figsize=(9, 4), sharex=True)
axs[0,0].set_title('x (model input)')
axs[0,1].set_title('model prediction')
for i in range(n_steps):
  axs[i, 0].imshow(torchvision.utils.make_grid(step_history[i])[0].clip(0, 1), cmap='Greys')
  axs[i, 1].imshow(torchvision.utils.make_grid(pred_output_history[i])[0].clip(0, 1), cmap='Greys')
078f8a7f0729fcab5348b0278fd773a7.png

我们可以将流程分成更多步骤,并希望通过这种方式获得更好的图像:

#@markdown Showing more results, using 40 sampling steps
n_steps = 40
x = torch.rand(64, 1, 28, 28).to(device)
for i in range(n_steps):
  noise_amount = torch.ones((x.shape[0], )).to(device) * (1-(i/n_steps)) # Starting high going low
  with torch.no_grad():
    pred = net(x)
  mix_factor = 1/(n_steps - i)
  x = x*(1-mix_factor) + pred*mix_factor
fig, ax = plt.subplots(1, 1, figsize=(12, 12))
ax.imshow(torchvision.utils.make_grid(x.detach().cpu(), nrow=8)[0].clip(0, 1), cmap='Greys')
<matplotlib.image.AxesImage at 0x7f27567d8210>
0acee447898c766251527b25b3b6c4f3.png

结果并不是非常好,但是已经出现了一些可以被认出来的数字!您可以尝试训练更长时间(例如,10 或 20 个 epoch),并调整模型配置、学习率、优化器等。此外,如果您想尝试稍微困难一点的数据集,您可以尝试一下 fashionMNIST,只需要一行代码的替换就可以了。

与 DDPM 做比较

在本节中,我们将看看我们的“玩具”实现与其他笔记本中使用的基于 DDPM 论文的方法有何不同: 扩散器简介 Notebook。

  • 扩散器简介 Notebook:
    http://github.com/huggingface/diffusion-models-class/blob/main/unit1/01_introduction_to_diffusers.ipynb

我们将会看到的

  • 模型的表现受限于随迭代周期 (timesteps) 变化的控制条件,在前向传导中时间步 (t) 是作为一个参数被传入的

  • 有很多不同的取样策略可选择,可能会比我们上面所使用的最简单的版本更好

  • diffusers UNet2DModel 比我们的 BasicUNet 更先进

  • 损坏过程的处理方式不同

  • 训练目标不同,包括预测噪声而不是去噪图像

  • 该模型通过调节 timestep 来调节噪声水平 , 其中 t 作为一个附加参数传入前向过程中。

  • 有许多不同的采样策略可供选择,它们应该比我们上面简单的版本更有效。

自 DDPM 论文发表以来,已经有人提出了许多改进建议,但这个例子对于不同的可用设计决策具有指导意义。读完这篇文章后,你可能会想要深入了解这篇论文《Elucidating the Design Space of Diffusion-Based Generative Models》,它对所有这些组件进行了详细的探讨,并就如何获得最佳性能提出了新的建议。

Elucidating the Design Space of Diffusion-Based Generative Models 论文链接:
http://arxiv.org/abs/2206.00364

如果你觉得这些内容对你来说太过深奥了,请不要担心!你可以随意跳过本笔记本的其余部分或将其保存以备不时之需。

UNet

diffusers 中的 UNet2DModel 模型比上述基本 UNet 模型有许多改进:

  • GroupNorm 层对每个 blocks 的输入进行了组标准化(group normalization)

  • Dropout 层能使训练更平滑

  • 每个块有多个 resnet 层(如果 layers_per_block 未设置为 1)

  • 注意机制(通常仅用于输入分辨率较低的 blocks)

  • timestep 的调节。

  • 具有可学习参数的下采样和上采样块

让我们来创建并仔细研究一下 UNet2DModel:

model = UNet2DModel(
  sample_size=28,           # the target image resolution
  in_channels=1,            # the number of input channels, 3 for RGB images
  out_channels=1,           # the number of output channels
  layers_per_block=2,       # how many ResNet layers to use per UNet block
  block_out_channels=(32, 64, 64), # Roughly matching our basic unet example
  down_block_types=( 
    "DownBlock2D",        # a regular ResNet downsampling block
    "AttnDownBlock2D",    # a ResNet downsampling block w/ spatial self-attention
    "AttnDownBlock2D",
  ), 
  up_block_types=(
    "AttnUpBlock2D", 
    "AttnUpBlock2D",      # a ResNet upsampling block with spatial self-attention
    "UpBlock2D",          # a regular ResNet upsampling block
  ),
)
print(model)
UNet2DModel(
  (conv_in): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (time_proj): Timesteps()
  (time_embedding): TimestepEmbedding(
    (linear_1): Linear(in_features=32, out_features=128, bias=True)
    (act): SiLU()
    (linear_2): Linear(in_features=128, out_features=128, bias=True)
  )
  (down_blocks): ModuleList(
    (0): DownBlock2D(
      (resnets): ModuleList(
        (0): ResnetBlock2D(
          (norm1): GroupNorm(32, 32, eps=1e-05, affine=True)
          (conv1): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (time_emb_proj): Linear(in_features=128, out_features=32, bias=True)
          (norm2): GroupNorm(32, 32, eps=1e-05, affine=True)
          (dropout): Dropout(p=0.0, inplace=False)
          (conv2): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (nonlinearity): SiLU()
        )
        (1): ResnetBlock2D(
          (norm1): GroupNorm(32, 32, eps=1e-05, affine=True)
          (conv1): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (time_emb_proj): Linear(in_features=128, out_features=32, bias=True)
          (norm2): GroupNorm(32, 32, eps=1e-05, affine=True)
          (dropout): Dropout(p=0.0, inplace=False)
          (conv2): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (nonlinearity): SiLU()
        )
      )
      (downsamplers): ModuleList(
        (0): Downsample2D(
          (conv): Conv2d(32, 32, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))
        )
      )
    )
    (1): AttnDownBlock2D(
      (attentions): ModuleList(
        (0): AttentionBlock(
          (group_norm): GroupNorm(32, 64, eps=1e-05, affine=True)
          (query): Linear(in_features=64, out_features=64, bias=True)
          (key): Linear(in_features=64, out_features=64, bias=True)
          (value): Linear(in_features=64, out_features=64, bias=True)
          (proj_attn): Linear(in_features=64, out_features=64, bias=True)
        )
        (1): AttentionBlock(
          (group_norm): GroupNorm(32, 64, eps=1e-05, affine=True)
          (query): Linear(in_features=64, out_features=64, bias=True)
          (key): Linear(in_features=64, out_features=64, bias=True)
          (value): Linear(in_features=64, out_features=64, bias=True)
          (proj_attn): Linear(in_features=64, out_features=64, bias=True)
        )
      )
      (resnets): ModuleList(
        (0): ResnetBlock2D(
          (norm1): GroupNorm(32, 32, eps=1e-05, affine=True)
          (conv1): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (time_emb_proj): Linear(in_features=128, out_features=64, bias=True)
          (norm2): GroupNorm(32, 64, eps=1e-05, affine=True)
          (dropout): Dropout(p=0.0, inplace=False)
          (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (nonlinearity): SiLU()
          (conv_shortcut): Conv2d(32, 64, kernel_size=(1, 1), stride=(1, 1))
        )
        (1): ResnetBlock2D(
          (norm1): GroupNorm(32, 64, eps=1e-05, affine=True)
          (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (time_emb_proj): Linear(in_features=128, out_features=64, bias=True)
          (norm2): GroupNorm(32, 64, eps=1e-05, affine=True)
          (dropout): Dropout(p=0.0, inplace=False)
          (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (nonlinearity): SiLU()
        )
      )
      (downsamplers): ModuleList(
        (0): Downsample2D(
          (conv): Conv2d(64, 64, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))
        )
      )
    )
    (2): AttnDownBlock2D(
      (attentions): ModuleList(
        (0): AttentionBlock(
          (group_norm): GroupNorm(32, 64, eps=1e-05, affine=True)
          (query): Linear(in_features=64, out_features=64, bias=True)
          (key): Linear(in_features=64, out_features=64, bias=True)
          (value): Linear(in_features=64, out_features=64, bias=True)
          (proj_attn): Linear(in_features=64, out_features=64, bias=True)
        )
        (1): AttentionBlock(
          (group_norm): GroupNorm(32, 64, eps=1e-05, affine=True)
          (query): Linear(in_features=64, out_features=64, bias=True)
          (key): Linear(in_features=64, out_features=64, bias=True)
          (value): Linear(in_features=64, out_features=64, bias=True)
          (proj_attn): Linear(in_features=64, out_features=64, bias=True)
        )
      )
      (resnets): ModuleList(
        (0): ResnetBlock2D(
          (norm1): GroupNorm(32, 64, eps=1e-05, affine=True)
          (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (time_emb_proj): Linear(in_features=128, out_features=64, bias=True)
          (norm2): GroupNorm(32, 64, eps=1e-05, affine=True)
          (dropout): Dropout(p=0.0, inplace=False)
          (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (nonlinearity): SiLU()
        )
        (1): ResnetBlock2D(
          (norm1): GroupNorm(32, 64, eps=1e-05, affine=True)
          (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (time_emb_proj): Linear(in_features=128, out_features=64, bias=True)
          (norm2): GroupNorm(32, 64, eps=1e-05, affine=True)
          (dropout): Dropout(p=0.0, inplace=False)
          (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (nonlinearity): SiLU()
        )
      )
    )
  )
  (up_blocks): ModuleList(
    (0): AttnUpBlock2D(
      (attentions): ModuleList(
        (0): AttentionBlock(
          (group_norm): GroupNorm(32, 64, eps=1e-05, affine=True)
          (query): Linear(in_features=64, out_features=64, bias=True)
          (key): Linear(in_features=64, out_features=64, bias=True)
          (value): Linear(in_features=64, out_features=64, bias=True)
          (proj_attn): Linear(in_features=64, out_features=64, bias=True)
        )
        (1): AttentionBlock(
          (group_norm): GroupNorm(32, 64, eps=1e-05, affine=True)
          (query): Linear(in_features=64, out_features=64, bias=True)
          (key): Linear(in_features=64, out_features=64, bias=True)
          (value): Linear(in_features=64, out_features=64, bias=True)
          (proj_attn): Linear(in_features=64, out_features=64, bias=True)
        )
        (2): AttentionBlock(
          (group_norm): GroupNorm(32, 64, eps=1e-05, affine=True)
          (query): Linear(in_features=64, out_features=64, bias=True)
          (key): Linear(in_features=64, out_features=64, bias=True)
          (value): Linear(in_features=64, out_features=64, bias=True)
          (proj_attn): Linear(in_features=64, out_features=64, bias=True)
        )
      )
      (resnets): ModuleList(
        (0): ResnetBlock2D(
          (norm1): GroupNorm(32, 128, eps=1e-05, affine=True)
          (conv1): Conv2d(128, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (time_emb_proj): Linear(in_features=128, out_features=64, bias=True)
          (norm2): GroupNorm(32, 64, eps=1e-05, affine=True)
          (dropout): Dropout(p=0.0, inplace=False)
          (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (nonlinearity): SiLU()
          (conv_shortcut): Conv2d(128, 64, kernel_size=(1, 1), stride=(1, 1))
        )
        (1): ResnetBlock2D(
          (norm1): GroupNorm(32, 128, eps=1e-05, affine=True)
          (conv1): Conv2d(128, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (time_emb_proj): Linear(in_features=128, out_features=64, bias=True)
          (norm2): GroupNorm(32, 64, eps=1e-05, affine=True)
          (dropout): Dropout(p=0.0, inplace=False)
          (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (nonlinearity): SiLU()
          (conv_shortcut): Conv2d(128, 64, kernel_size=(1, 1), stride=(1, 1))
        )
        (2): ResnetBlock2D(
          (norm1): GroupNorm(32, 128, eps=1e-05, affine=True)
          (conv1): Conv2d(128, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (time_emb_proj): Linear(in_features=128, out_features=64, bias=True)
          (norm2): GroupNorm(32, 64, eps=1e-05, affine=True)
          (dropout): Dropout(p=0.0, inplace=False)
          (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (nonlinearity): SiLU()
          (conv_shortcut): Conv2d(128, 64, kernel_size=(1, 1), stride=(1, 1))
        )
      )
      (upsamplers): ModuleList(
        (0): Upsample2D(
          (conv): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
        )
      )
    )
    (1): AttnUpBlock2D(
      (attentions): ModuleList(
        (0): AttentionBlock(
          (group_norm): GroupNorm(32, 64, eps=1e-05, affine=True)
          (query): Linear(in_features=64, out_features=64, bias=True)
          (key): Linear(in_features=64, out_features=64, bias=True)
          (value): Linear(in_features=64, out_features=64, bias=True)
          (proj_attn): Linear(in_features=64, out_features=64, bias=True)
        )
        (1): AttentionBlock(
          (group_norm): GroupNorm(32, 64, eps=1e-05, affine=True)
          (query): Linear(in_features=64, out_features=64, bias=True)
          (key): Linear(in_features=64, out_features=64, bias=True)
          (value): Linear(in_features=64, out_features=64, bias=True)
          (proj_attn): Linear(in_features=64, out_features=64, bias=True)
        )
        (2): AttentionBlock(
          (group_norm): GroupNorm(32, 64, eps=1e-05, affine=True)
          (query): Linear(in_features=64, out_features=64, bias=True)
          (key): Linear(in_features=64, out_features=64, bias=True)
          (value): Linear(in_features=64, out_features=64, bias=True)
          (proj_attn): Linear(in_features=64, out_features=64, bias=True)
        )
      )
      (resnets): ModuleList(
        (0): ResnetBlock2D(
          (norm1): GroupNorm(32, 128, eps=1e-05, affine=True)
          (conv1): Conv2d(128, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (time_emb_proj): Linear(in_features=128, out_features=64, bias=True)
          (norm2): GroupNorm(32, 64, eps=1e-05, affine=True)
          (dropout): Dropout(p=0.0, inplace=False)
          (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (nonlinearity): SiLU()
          (conv_shortcut): Conv2d(128, 64, kernel_size=(1, 1), stride=(1, 1))
        )
        (1): ResnetBlock2D(
          (norm1): GroupNorm(32, 128, eps=1e-05, affine=True)
          (conv1): Conv2d(128, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (time_emb_proj): Linear(in_features=128, out_features=64, bias=True)
          (norm2): GroupNorm(32, 64, eps=1e-05, affine=True)
          (dropout): Dropout(p=0.0, inplace=False)
          (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (nonlinearity): SiLU()
          (conv_shortcut): Conv2d(128, 64, kernel_size=(1, 1), stride=(1, 1))
        )
        (2): ResnetBlock2D(
          (norm1): GroupNorm(32, 96, eps=1e-05, affine=True)
          (conv1): Conv2d(96, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (time_emb_proj): Linear(in_features=128, out_features=64, bias=True)
          (norm2): GroupNorm(32, 64, eps=1e-05, affine=True)
          (dropout): Dropout(p=0.0, inplace=False)
          (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (nonlinearity): SiLU()
          (conv_shortcut): Conv2d(96, 64, kernel_size=(1, 1), stride=(1, 1))
        )
      )
      (upsamplers): ModuleList(
        (0): Upsample2D(
          (conv): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
        )
      )
    )
    (2): UpBlock2D(
      (resnets): ModuleList(
        (0): ResnetBlock2D(
          (norm1): GroupNorm(32, 96, eps=1e-05, affine=True)
          (conv1): Conv2d(96, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (time_emb_proj): Linear(in_features=128, out_features=32, bias=True)
          (norm2): GroupNorm(32, 32, eps=1e-05, affine=True)
          (dropout): Dropout(p=0.0, inplace=False)
          (conv2): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (nonlinearity): SiLU()
          (conv_shortcut): Conv2d(96, 32, kernel_size=(1, 1), stride=(1, 1))
        )
        (1): ResnetBlock2D(
          (norm1): GroupNorm(32, 64, eps=1e-05, affine=True)
          (conv1): Conv2d(64, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (time_emb_proj): Linear(in_features=128, out_features=32, bias=True)
          (norm2): GroupNorm(32, 32, eps=1e-05, affine=True)
          (dropout): Dropout(p=0.0, inplace=False)
          (conv2): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (nonlinearity): SiLU()
          (conv_shortcut): Conv2d(64, 32, kernel_size=(1, 1), stride=(1, 1))
        )
        (2): ResnetBlock2D(
          (norm1): GroupNorm(32, 64, eps=1e-05, affine=True)
          (conv1): Conv2d(64, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (time_emb_proj): Linear(in_features=128, out_features=32, bias=True)
          (norm2): GroupNorm(32, 32, eps=1e-05, affine=True)
          (dropout): Dropout(p=0.0, inplace=False)
          (conv2): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (nonlinearity): SiLU()
          (conv_shortcut): Conv2d(64, 32, kernel_size=(1, 1), stride=(1, 1))
        )
      )
    )
  )
  (mid_block): UNetMidBlock2D(
    (attentions): ModuleList(
      (0): AttentionBlock(
        (group_norm): GroupNorm(32, 64, eps=1e-05, affine=True)
        (query): Linear(in_features=64, out_features=64, bias=True)
        (key): Linear(in_features=64, out_features=64, bias=True)
        (value): Linear(in_features=64, out_features=64, bias=True)
        (proj_attn): Linear(in_features=64, out_features=64, bias=True)
      )
    )
    (resnets): ModuleList(
      (0): ResnetBlock2D(
        (norm1): GroupNorm(32, 64, eps=1e-05, affine=True)
        (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
        (time_emb_proj): Linear(in_features=128, out_features=64, bias=True)
        (norm2): GroupNorm(32, 64, eps=1e-05, affine=True)
        (dropout): Dropout(p=0.0, inplace=False)
        (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
        (nonlinearity): SiLU()
      )
      (1): ResnetBlock2D(
        (norm1): GroupNorm(32, 64, eps=1e-05, affine=True)
        (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
        (time_emb_proj): Linear(in_features=128, out_features=64, bias=True)
        (norm2): GroupNorm(32, 64, eps=1e-05, affine=True)
        (dropout): Dropout(p=0.0, inplace=False)
        (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
        (nonlinearity): SiLU()
      )
    )
  )
  (conv_norm_out): GroupNorm(32, 32, eps=1e-05, affine=True)
  (conv_act): SiLU()
  (conv_out): Conv2d(32, 1, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
)

正如你所看到的,还有更多!它比我们的 BasicUNet 有多得多的参数量:

sum([p.numel() for p in model.parameters()]) # 1.7M vs the ~309k parameters of the BasicUNet
1707009

我们可以用这个模型代替原来的模型来重复一遍上面展示的训练过程。我们需要将 x 和 timestep 传递给模型(这里我会传递 t = 0,以表明它在没有 timestep 条件的情况下工作,并保持采样代码简单,但您也可以尝试输入 (amount*1000),使 timestep 与噪声水平相当)。如果要检查代码,更改的行将显示为“#<<<

#@markdown Trying UNet2DModel instead of BasicUNet:

# Dataloader (you can mess with batch size)
batch_size = 128
train_dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)

# How many runs through the data should we do?
n_epochs = 3

# Create the network
net = UNet2DModel(
  sample_size=28,  # the target image resolution
  in_channels=1,  # the number of input channels, 3 for RGB images
  out_channels=1,  # the number of output channels
  layers_per_block=2,  # how many ResNet layers to use per UNet block
  block_out_channels=(32, 64, 64),  # Roughly matching our basic unet example
  down_block_types=( 
    "DownBlock2D",  # a regular ResNet downsampling block
    "AttnDownBlock2D",  # a ResNet downsampling block with spatial self-attention
    "AttnDownBlock2D",
  ), 
  up_block_types=(
    "AttnUpBlock2D", 
    "AttnUpBlock2D",  # a ResNet upsampling block with spatial self-attention
    "UpBlock2D",   # a regular ResNet upsampling block
  ),
) #<<<
net.to(device)

# Our loss finction
loss_fn = nn.MSELoss()

# The optimizer
opt = torch.optim.Adam(net.parameters(), lr=1e-3) 

# Keeping a record of the losses for later viewing
losses = []

# The training loop
for epoch in range(n_epochs):

  for x, y in train_dataloader:

    # Get some data and prepare the corrupted version
    x = x.to(device) # Data on the GPU
    noise_amount = torch.rand(x.shape[0]).to(device) # Pick random noise amounts
    noisy_x = corrupt(x, noise_amount) # Create our noisy x

    # Get the model prediction
    pred = net(noisy_x, 0).sample #<<< Using timestep 0 always, adding .sample

    # Calculate the loss
    loss = loss_fn(pred, x) # How close is the output to the true 'clean' x?

    # Backprop and update the params:
    opt.zero_grad()
    loss.backward()
    opt.step()

    # Store the loss for later
    losses.append(loss.item())

    # Print our the average of the loss values for this epoch:
    avg_loss = sum(losses[-len(train_dataloader):])/len(train_dataloader)
    print(f'Finished epoch {epoch}. Average loss for this epoch: {avg_loss:05f}')

# Plot losses and some samples
fig, axs = plt.subplots(1, 2, figsize=(12, 5))

# Losses
axs[0].plot(losses)
axs[0].set_ylim(0, 0.1)
axs[0].set_title('Loss over time')

# Samples
n_steps = 40
x = torch.rand(64, 1, 28, 28).to(device)
for i in range(n_steps):
  noise_amount = torch.ones((x.shape[0], )).to(device) * (1-(i/n_steps)) # Starting high going low
  with torch.no_grad():
    pred = net(x, 0).sample
  mix_factor = 1/(n_steps - i)
  x = x*(1-mix_factor) + pred*mix_factor

axs[1].imshow(torchvision.utils.make_grid(x.detach().cpu(), nrow=8)[0].clip(0, 1), cmap='Greys')
axs[1].set_title('Generated Samples');
Finished epoch 0. Average loss for this epoch: 0.018925
Finished epoch 1. Average loss for this epoch: 0.012785
Finished epoch 2. Average loss for this epoch: 0.011694
729646f2824dfb0dfc4e49cd9b95bcbe.png

这看起来比我们的第一组结果好多了!您可以尝试调整 UNet 配置或更长时间的训练,以获得更好的性能。

损坏过程

DDPM 论文描述了一个为每个“timestep”添加少量噪声的损坏过程。为某些 timestep 给定 , 我们可以得到一个噪声稍稍增加的 :

bc69ad4a77d3f62bc35b4c92799990ae.png

这就是说,我们取 , 给他一个 的系数,然后加上带有 系数的噪声。这里 是根据一些管理器来为每一个 t 设定的,来决定每一个迭代周期中添加多少噪声。现在,我们不想把这个推演进行 500 次来得到 ,所以我们用另一个公式来根据给出的 计算得到任意 t 时刻的 :

86d4ab7941f0cbaf6161951d03bedadf.png

数学符号看起来总是很吓人!幸运的是,调度器为我们处理了所有这些(取消下一个单元格的注释以检查代码)。我们可以画出 (标记为 sqrt_alpha_prod) 和 (标记为 sqrt_one_minus_alpha_prod) 来看一下输入 (x) 与噪声是如何在不同迭代周期中量化和叠加的 :

#??noise_scheduler.add_noise
noise_scheduler = DDPMScheduler(num_train_timesteps=1000)
plt.plot(noise_scheduler.alphas_cumprod.cpu() ** 0.5, label=r"${\sqrt{\bar{\alpha}_t}}$")
plt.plot((1 - noise_scheduler.alphas_cumprod.cpu()) ** 0.5, label=r"$\sqrt{(1 - \bar{\alpha}_t)}$")
plt.legend(fontsize="x-large");
b50bc076e0ed233b71fd51af41e2263d.png

一开始 , 噪声 x 里绝大部分都是 x 自身的值  (sqrt_alpha_prod ~= 1),但是随着时间的推移,x 的成分逐渐降低而噪声的成分逐渐增加。与我们根据 amount 对 x 和噪声进行线性混合不同,这个噪声的增加相对较快。我们可以在一些数据上看到这一点:

#@markdown visualize the DDPM noising process for different timesteps:

# Noise a batch of images to view the effect
fig, axs = plt.subplots(3, 1, figsize=(16, 10))
xb, yb = next(iter(train_dataloader))
xb = xb.to(device)[:8]
xb = xb * 2. - 1. # Map to (-1, 1)
print('X shape', xb.shape)

# Show clean inputs
axs[0].imshow(torchvision.utils.make_grid(xb[:8])[0].detach().cpu(), cmap='Greys')
axs[0].set_title('Clean X')

# Add noise with scheduler
timesteps = torch.linspace(0, 999, 8).long().to(device)
noise = torch.randn_like(xb) # << NB: randn not rand
noisy_xb = noise_scheduler.add_noise(xb, noise, timesteps)
print('Noisy X shape', noisy_xb.shape)

# Show noisy version (with and without clipping)
axs[1].imshow(torchvision.utils.make_grid(noisy_xb[:8])[0].detach().cpu().clip(-1, 1),  cmap='Greys')
axs[1].set_title('Noisy X (clipped to (-1, 1)')
axs[2].imshow(torchvision.utils.make_grid(noisy_xb[:8])[0].detach().cpu(),  cmap='Greys')
axs[2].set_title('Noisy X');
X shape torch.Size([8, 1, 28, 28])
Noisy X shape torch.Size([8, 1, 28, 28])
cf4f255bcab19ede884db3ed1c4d483e.png

在运行中的另一个变化:在 DDPM 版本中,加入的噪声是取自一个高斯分布(来自均值 0 方差 1 的 torch.randn),而不是在我们原始 corrupt 函数中使用的 0-1 之间的均匀分布(torch.rand),当然对训练数据做正则化也可以理解。在另一篇笔记中,你会看到 Normalize(0.5, 0.5) 函数在变化列表中,它把图片数据从 (0, 1) 区间映射到 (-1, 1),对我们的目标来说也‘足够用了’。我们在此篇笔记中没使用这个方法,但在上面的可视化中为了更好的展示添加了这种做法。

训练目标

在我们的玩具示例中,我们让模型尝试预测去噪图像。在 DDPM 和许多其他扩散模型实现中,模型则会预测损坏过程中使用的噪声(在缩放之前,因此是单位方差噪声)。在代码中,它看起来像是这样:

noise = torch.randn_like(xb) # << NB: randn not rand
noisy_x = noise_scheduler.add_noise(x, noise, timesteps)
model_prediction = model(noisy_x, timesteps).sample
loss = mse_loss(model_prediction, noise) # noise as the target

你可能认为预测噪声(我们可以从中得出去噪图像的样子)等同于直接预测去噪图像。那么,为什么要这么做呢?这仅仅是为了数学上的方便吗?

这里其实还有另一些精妙之处。我们在训练过程中,会计算不同(随机选择)timestep 的 loss。这些不同的目标将导致这些 loss 的不同的“隐含权重”,其中预测噪声会将更多的权重放在较低的噪声水平上。你可以选择更复杂的目标来改变这种“隐性损失权重”。或者,您选择的噪声管理器将在较高的噪声水平下产生更多的示例。也许你让模型设计成预测 “velocity” v,我们将其定义为由噪声水平影响的图像和噪声组合(请参阅“扩散模型快速采样的渐进蒸馏”- 'PROGRESSIVE DISTILLATION FOR FAST SAMPLING OF DIFFUSION MODELS')。也许你将模型设计成预测噪声,然后基于某些因子来对 loss 进行缩放:比如有些理论指出可以参考噪声水平(参见“扩散模型的感知优先训练”-'Perception Prioritized Training of Diffusion Models'),或者基于一些探索模型最佳噪声水平的实验(参见“基于扩散的生成模型的设计空间说明”-'Elucidating the Design Space of Diffusion-Based Generative Models')。

一句话解释:选择目标对模型性能有影响,现在有许多研究者正在探索“最佳”选项是什么。目前,预测噪声(epsilon 或 eps)是最流行的方法,但随着时间的推移,我们很可能会看到库中支持的其他目标,并在不同的情况下使用。

迭代周期(Timestep)调节

UNet2DModel 以 x 和 timestep 为输入。后者被转化为一个嵌入(embedding),并在多个地方被输入到模型中。

这背后的理论支持是这样的:通过向模型提供有关噪声水平的信息,它可以更好地执行任务。虽然在没有这种 timestep 条件的情况下也可以训练模型,但在某些情况下,它似乎确实有助于性能,目前来说绝大多数的模型实现都包括了这一输入。

取样(采样)

有一个模型可以用来预测在带噪样本中的噪声(或者说能预测其去噪版本),我们怎么用它来生成图像呢?

我们可以给入纯噪声,然后就希望模型能一步就输出一个不带噪声的好图像。但是,就我们上面所见到的来看,这通常行不通。所以,我们在模型预测的基础上使用足够多的小步,迭代着来每次去除一点点噪声。

具体我们怎么走这些小步,取决于使用上面取样方法。我们不会去深入讨论太多的理论细节,但是一些顶层想法是这样:

  • 每一步你想走多大?也就是说,你遵循什么样的“噪声计划(噪声管理)”?

  • 你只使用模型当前步的预测结果来指导下一步的更新方向吗(像 DDPM,DDIM 或是其他的什么那样)?你是否要使用模型来多预测几次来估计一个更高阶的梯度来更新一步更大更准确的结果(更高阶的方法和一些离散 ODE 处理器)?或者保留历史预测值来尝试更好的指导当前步的更新(线性多步或遗传取样器)?

  • 你是否会在取样过程中额外再加一些随机噪声,或你完全已知的(deterministic)来添加噪声?许多取样器通过参数(如 DDIM 中的 'eta')来供用户选择。

对于扩散模型取样器的研究演进的很快,随之开发出了越来越多可以使用更少步就找到好结果的方法。勇敢和有好奇心的人可能会在浏览 diffusers library 中不同部署方法时感到非常有意思,可以查看 Schedulers 代码 或看看 Schedulers 文档,这里经常有一些相关的论文。

  • Schedulers 代码:
    http://github.com/huggingface/diffusers/tree/main/src/diffusers/schedulers

  • Schedulers 文档:
    http://huggingface.co/docs/diffusers/main/en/api/schedulers

结语

希望这可以从一些不同的角度来审视扩散模型提供一些帮助。这篇笔记是 Jonathan Whitaker 为 Hugging Face 课程所写的,如果你对从噪声和约束分类来生成样本的例子感兴趣。问题与 bug 可以通过 GitHub issues 或 Discord 来交流。

致谢第一单元第二部分社区贡献者

感谢社区成员们对本课程的贡献:

@darcula1993、@XhrLeokk:魔都强人工智能孵化者,二里街调参记录保持人,一切兴趣使然的 AIGC 色图创作家的庇护者,图灵神在五角场的唯一指定路上行走。

感谢茶叶蛋蛋对本文贡献设计素材!

欢迎通过链接加入我们的本地化小组与大家共同交流:
http://bit.ly/3G40j6U

本文转载于网络 如有侵权请联系删除

相关文章

  • C++实现卷积操作

    大家好,又见面了,我是你们的朋友全栈君。卷积操作的C++实现#include<opencv2/opencv.hpp> #include<opencv2/highgui/highgui.hpp> #include<opencv2/core/core.hpp> usingnamespacestd; usingnamespacecv; MatKernel_test_3_3=( Mat_<double>(3,3)<< 0,-1,0, -1,5,-1, 0,-1,0); voidConvlution(MatInputImage,MatOutputImage,Matkernel) { //计算卷积核的半径 intsub_x=kernel.cols/2; intsub_y=kernel.rows/2; //遍历图片 for(intimage_y=0;image_y<InputImage.rows-2*sub_y;image_y++) { for(intimage_x=0;image_x<InputIm

  • 平台代码检查工具:sonarLint+sonarqube安装教程

    加强对代码质量的管控要求,不允许新增代码部分出现严重、重要、主要等sonar扫描问题。大家按以下文档安装开发工具对应的代码检测插件,请大家务必重视、执行.这里只介绍前端开发VSCode,后端开发Eclipse,两种代表性的编译器安装sonarLint+sonarqube方法,其它编译器同理.VSCode安装sonarlint方法插件介绍SonarLint是⼀个免费的IDE扩展,允许您在编码问题存在之前修复它们;像拼写检查器一样,SonarLint在编写代码时会突出显示错误和安全漏洞,并提供明确的修复指导,以便在代码提交之前修复它们。通过流⾏的ide(Eclipse、Idea、VSCode)和流⾏的编程语⾔,SonarLint帮助开发⼈员编写更好、更安全的代码!SonarQube是⼀个开源的代码分析平台,⽤来持续分析和评测项目源代码的质量。通过SonarQube我们可以检测出项⽬中重复代码,潜在bug,代码规范,安全性漏洞等问题,并通过SonarQubewebUI展⽰出来。⽬前公司持续集成环境中已部署该服务,自动构建过程中对项⽬代码进⾏扫描、并输出报告。SonarQube提供SonarL

  • 原创 | leader总让我干杂活,我很不满,该怎么办?

    众所周知互联网公司当中充满了套路,什么把年终奖摊平到每个月跟你说薪水的,什么把年终的月份说得特别多,等你发现已经来不及的。明明跟你说的是A岗位结果让你去做B事情的,这些事情数不胜数,简直都不叫个事。要说其中最让人不能忍受的,我个人觉得还是无休无止的杂活。杂活文化虽然大家都没有放在明面上说,但其实我个人认为互联网圈子里是有一个杂活文化的。其实也不是互联网,各行各业都差不多,总有些类似端茶递水谁都不愿意但是总得有人做的活,这些活就被称为杂活。当我们作为新人刚入职的时候,尤其是校招生,几乎很难躲过干杂活的厄运。我简单列举下我自己干过的杂活,当年我还是后端工程师的时候,接收过别人的烂系统,帮别人修bug找bug。找了一堆bug,结果被老板认为是我写的。除此之外,还读过别人一个函数就长达三四千行的又臭又长的代码。要说这些也都不叫事,最让我不能接受的还是无休无止的增删改查,和线上oncall。明明是别人系统的问题,大半夜的有人喊你起床。这种滋味真的是一言难尽。后来转了算法之后,也没好多少。做过一些边角的需求,整理过好几百个的特征,因为老板要看一个数据,写过上千行的SQL。当然也对着同一个模型一遍又

  • 对话Ampere产品高级副总裁Jeff Wittich

    云计算市场渴求新算力,服务器端CPU应尽快变革图|Ampere产品高级副总裁JeffWittich市场需求的变化要求云计算产业链变革,Ampere抓住的正是这样的机会。策划&撰写:Lynn云计算市场越来越大。2019年开始,随着全球各大传统企业,包括商业银行的全面上云,云计算开启了高速发展时代。从人类历史上的第一张黑洞照片到疫情期间的全民网课、线上会议与药物研发,它在科研探索、教育等场景中扮演的角色越来越重要。然而在应用需求猛增的情况下,产业底层厂商需要面临的问题也随之而来:算力无止境加持下,如何顺应市场需求,搭建新型大规模数据中心?以及如何最大程度控制成本?作为业内资深工程师,Ampere产品高级副总裁JeffWittich对此也是感触颇多,“如今的数据中心对延时、散热、功耗等性能的需求已大不相同,机器学习、边缘计算等需求日益强烈,市场需要一种新型的算力了。”大势所趋,云计算与传统计算差异化愈加明显提起云计算,从互联网时代到如今AI时代,它的发展历程就是共享经济在计算领域的演进史。如今因为各类场景数字化升级的推进,其应用领域和应用层次也一直在不断扩大。如Jeff提到,因近两年

  • Linux经典面试题,了解一下!

    问题一: 绝对路径用什么符号表示?当前目录、上层目录用什么表示?主目录用什么表示?切换目录用什么命令? 答案: 绝对路径:如/etc/init.d 当前目录和上层目录:./../ 主目录:~/ 切换目录:cd 问题二:怎么查看当前进程?怎么执行退出?怎么查看当前路径? 答案: 查看当前进程:ps 执行退出:exit 查看当前路径:pwd 问题三:怎么清屏?怎么退出当前命令?怎么执行睡眠?怎么查看当前用户id?查看指定帮助用什么命令? 答案: 清屏:clear 退出当前命令:ctrl+c彻底退出 执行睡眠:ctrl+z挂起当前进程fg恢复后台 查看当前用户id:”id“:查看显示目前登陆账户的uid和gid及所属分组及用户名 查看指定帮助:如manadduser这个很全而且有例子;adduser--help这个告诉你一些常用参数;infoadduesr; 问题四:Ls命令执行什么功能?可以带哪些参数,有什么区别? 答案: ls执行的功能:列出指定目录中的目录,以及文件 哪些参数以及区别:a所有文件l详细信息,包括大小字节数,可读可写可执行的权限等 问题五:建立软链接(快捷方式),

  • Java 8的Stream代码,你能看懂吗?

    本文来源公众号:Hollis本文作者:Hollis 今天在读公司代码的时候,发现用了Java8Stream的特性,而且比较复杂(即便用了Stream也写了15行代码)。然后,我就看不懂了…(真菜!) 记得我在之前写过一篇Optional的文章,回顾了一下又稍微能看懂一点了(给女朋友讲解什么是Optional)。而前几天正好mark住了Hollis大佬的Stream文章,阅读完感觉又顺畅了一些。这里给大家推荐一下,建议阅读。以下为正文: 在Java中,集合和数组是我们经常会用到的数据结构,需要经常对他们做增、删、改、查、聚合、统计、过滤等操作。相比之下,关系型数据库中也同样有这些操作,但是在Java8之前,集合和数组的处理并不是很便捷。不过,这一问题在Java8中得到了改善,Java8API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。本文就来介绍下如何使用Stream。特别说明一下,关于Stream的性能及原理不是本文的重点,如果大家感兴趣后面会出文章单独介绍。1 Stream介绍Stream使用一种类似用SQL语句从数据库查询数据的直观方式来提供一种对Jav

  • 一大波DeepMind专利来袭,AI圈瑟瑟发抖:连RNN都是你家的?

    夏乙发自学院路 量子位出品|公众号QbitAI最近,专门研究各界专利的博主IPKat发现,一大波DeepMind申请的专利出现了,共有12个。国外各大社交网站都炸了锅。DeepMind?专利?对。而且都是看名字都非常吓人,强化学习系统、循环神经网络尽在其中:专利号专利名称优先权日WO2018/048934用神经网络生成音频6Sep2016WO2018/048945用卷积神经网络处理序列6Sep2016WO2018064591用神经网络生成视频帧6Sep2016WO2018071392用于为机器人智能体选择要执行行为的神经网络10Oct2016WO2018/081089用神经网络处理文本序列26Oct2016WO2018/083532用神经网络训练行为选择3Nov2016WO2018/083667强化学习系统4Nov2016WO2018/083668用神经网络实现场景理解与生成4Nov2016WO2018/083669循环神经网络(RNN)4Nov2016WO2018083670序列转换神经网络4Nov2016WO2018083671带有辅助任务的强化学习4Nov2016WO2018/0

  • 如何“勾引”一只母青蛙

    方栗子发自凹非寺 量子位报道|公众号QbitAI△这可能是一个错误示范春分已在今日凌晨安静地来了。值此生机蓬勃的时节,北半球的动物们正享受着炽热的爱情。如果,你听到青蛙满载雄性荷尔蒙的鸣声,那可能是雄蛙在向远处娇柔的雌蛙示爱,也可能是以假乱真的机器蛙在测试雌蛙的反应。蛙类澎湃的爱意△机器蛙之南美泡蟾(TúngaraFrog)当选择约会对象的时候,雌蛙会听辨雄蛙求偶的声音,那是“呜呜”和“咕咕”两种声音组成的奇妙旋律。如果双方相互可见,雄蛙声囊鼓起的样子极富魅力,将增加它成功的几率。德克萨斯大学奥斯汀分校和索尔兹伯里大学的研究人员,利用两种声音的不同排列组合,搭配能够鼓起声囊的南美泡蟾机器人(上图真是假蛙),来测试雌蟾的反应。得出的简要结果如下——△meh=无聊,oohlala=不错哟,Hotdamn!=无法自拔研究人员发现,对雌蟾来说,单调“呜”声加上声囊鼓起,已可以媲美一段优雅的“呜咕”纯音乐。另外,将两声无效的求爱信号前后相连,便能够拨动雌蟾的心弦,触发积极的回应。团队将蛙类声音交流中的特点与人类的“连续性错觉”做了类比。当一串“哔”声之间加入音量足够大的白噪音,人类听到的将是连贯

  • CCAI 2017 | AAAI 执委 Toby Walsh: AI 是中性技术,如何用它更好地为人类服务

    澳大利亚新南威尔士大学教授、AAAI执行委员会成员TobyWalsh文/CSDN胡永波7月22-23日,在中国科学技术协会、中国科学院的指导下,由中国人工智能学会、阿里巴巴集团&蚂蚁金服主办,CSDN、中国科学院自动化研究所承办的2017中国人工智能大会(CCAI2017)在杭州国际会议中心盛大召开。在本次大会上,澳大利亚新南威尔士大学教授、AAAI执行委员会成员TobyWalsh发表了主题为《人工智能如何造福人类》的演讲。在TobyWalsh看来,虽然埃隆马斯克、霍金等大佬对人工智能心存怀疑,但这并不影响人工智能造福人类。TobyWalsh用“食物银行”和“器官银行”两个案例证明,人工智能在解决贫穷、医疗等问题时,大有可为。本文根据TobyWalsh主题演讲整理,略有删减:在中国,人工智能技术非常有潜力,而且我相信这种潜力是无法预估的,因为每次我来到中国,都会惊异于中国人工智能的发展是多么如火如荼。今年的IJCAI会议在澳大利亚举办,我是会议主席,今天早上我看了会议投稿系统,发现现在向IJCAI提交论文数量最多的国家是中国,中国学者向IJCAI提交的论文数量已经超过了欧美。会

  • 化繁为简的企业级 Git 管理实践(一):多分支子模块依赖管理

    介绍面向复杂工程的简单化Git分支依赖管理方案。我们对子模块的使用进行了简化,避免了由于漏提交子模块commitid或子模块代码导致无法更新或更新错误的情况。需求描述我们尝试使用Git来维护一个项目的代码。这个项目的结构比较复杂:项目包含由多个子模块,每个子模块是一个独立的Git仓库,子模块还允许继续嵌套包含子模块。例如,主工程依赖common、framework、react_native等多个子模块,而react_native子模块又依赖node_modules、HFCommon、HFModules等多个嵌套子模块。[-]app_android/ |-[+]HFUIKit |-[+]channel |-[+]common |-[+]framework |-[+]hybrid |-[+]messagecenter |-[-]react_native |-[+]HFCommon |-[+]HFModules |-[+]node_modules 复制主工程和子模块允许存在多个分支,且相互之间有依赖关系。例如,主工程的jilin分支同时依赖common子模块的master分支,以及frame

  • 单体中心代码库 vs. 分布式代码库|洞见

    去年中旬两位Google工程师在《美国计算机学会通讯》发表了一篇论文“WhyGoogleStoresBillionsofLinesofCodeinaSingleRepository”,它介绍了谷歌为什么采用一个定制的大型单体中心代码库,并且在多个大会上分享了这个话题。InfoQ中文网站也发表了一篇较为客观的文章”Google为什么要把数十亿行代码放到一个库中?”来评论Google这种代码管理方法,其中总结了Google宣称的这种唯一中心库代码管理方式的优势,包括: 统一版本控制广泛地代码共享和重用简化依赖管理,避免菱形依赖原子修改大规模重构跨团队协作灵活的团队边界和代码所有权代码可见性以及清晰的树形结构提供了隐含的团队命名空间并且也总结了Google这种唯一中心库代码管理方式的一些问题,包括:工具投入(Google开发了自己专用的EclipseID插件)代码库复杂性(需要有依赖重构和代码清理辅助工具)代码健康(专用工具可以自动检测和删除无用代码、分派代码评审任务等)更多的问题讨论点击【阅读原文】查看。对于Google这样的大型团队或者公司,他们的代码管理看起来是简单的单体代码库管理方式

  • Java之面向对象例子(三) 多态,重写,重载,equals()方法和toString()方法的重写

    重写(继承关系)子类得成员方法和父类的成员方法,方法名,参数类型,参数个数完全相同,这就是子类的方法重写了父类的方法。重载在一个类里有两个方法,方法名是完全一样的,参数类型或参数个数不同。例子://父类 publicclassPet{ publicvoideat(){ System.out.println("peteat"); } publicvoideat(Stringa){//重载,同一个类,方法名相同,参数类型或个数不同 System.out.println("petStringeat"); } }复制//子类 publicclassCatextendsPet{ publicvoideat(){//重写 System.out.println("cateat"); } publicvoideat(inta){ System.out.println("catinteat"); } }复制toString()方法重写返回该对象的字符串表示。通常, toString 方法会返回一个“以文本方式表示”此对象的字

  • 最具同情心的勒索软件套件Philadelphia

    前言最近,一种新型的勒索软件服务(RaaS)名为Philadelphia,开始向外以400美元的价格售卖,这个恶意软件作者的名为Rainmaker。据Rainmaker所说,该程序套件提供一种低成本的勒索软件服务,它允许任何一个有犯罪意图的人发动高级的勒索软件攻击,并且它操作简单,成本低。同时,据Rainmaker介绍,Philadelphia“创新”勒索软件服务市场,它具有自动检测功能:当付款已经完成,然后自动解密;感染USB驱动器,并通过网络感染其他计算机。特别值得注意的是,“同情按钮”将提供给赋有同情心的犯罪分子解密特定受害者的文件。为了演示这种新的勒索软件服务,Rainmaker创建了一个显示其功能的PDF和视频。感染症状当文件被加密后,文件名称将被更改为.lock后缀的文件名。例如文件test.jpg可能会变成7B205C09B88C57ED8AB7C913263CCFBE296C8EA9938A.locked.加密完成后会显示下面的锁屏图像:如上图所示,受害者被要求支付0.3比特币,并且勒索软件声称加密文件的算法使用了AES-256和RSA-2048。最后勒索软件作者将重要

  • 五段式流水线_cpu流水线工作原理

    大家好,又见面了,我是你们的朋友全栈君这个流水线应该是我大二上的时候的最高水平了,现在看起来确实很简单,代码风格也不是很好,没有模块化,而且这些指令也不是严格的mips的所有指令,是自己定义的一些。但是放在博客里也算是对自己过去的一个总结吧! 现在再看这个代码,我觉得写得太恶心了,没有注释,没有说清楚关键的地方。我自己都忘了为什么这么写~~后来发现有非常坑爹的Bug!!!!祝好!!!我没有改过来了~~~•实验步骤1.先写好CPU的指令集。2.根据指令集先把一些基本的指令实现,比如LOAD,STORE等,把大概的流水线先画出框图。画出框图后,把基本指令实现。调试,仿真。3.在2的基础上加入别的指令,考虑好hazard的问题4.优化代码代码,调试,simulation。•实验原理流水线是数字系统中一种提高系统稳定性和工作速度的方法,广泛应用于高档CPU的架构中。根据MIPS处理器的特点,将整体的处理过程分为取指令(IF)、指令译码(ID)、执行(EX)、存储器访问(MEM)和寄存器会写(WB)五级,对应多周期的五个处理阶段。一个指令的执行需要5个时钟周期,每个时钟周期的上升沿来临时,此指令

  • xp系统http服务器,WinXP HTTP500内部服务器错误的解决方法

    大家好,又见面了,我是你们的朋友全栈君。在系统造成500内部服务器错误有很多原因,而系统中出现的故障是用户最不想看到的,有时候就是因为这些故障让我们折腾个大半天才能够搞定。打开网站出现了HTTP500内部服务器错误的提示,这究竟是怎么一回事呢?下面我们就来说说WinXPHTTP500内部服务器错误的解决方法。具体解决方法如下:一、造成500错误常见原因有:ASP语法出错、ACCESS数据库连接语句出错、文件引用与包含路径出错、使用了服务器不支持的组件如FSO等。最新win7系统下载:http://xt.299229.com/windows7/二、让IE显示详细的出错信息:菜单–工具–Internet选项–高级–显示友好的HTTP错误信息,去掉这个选择吧,然后刷新出错页,就可以看到详细的出错信息!三、设置IIS显示详细错误信息:1、打开IIS,点全局设置中的“错误页”(注意必须是全局网站!)2、点右上角的编辑功能设置。3、在错误响应中,选第二项的“详细错误”4、点确定后刷新IE就会显示详细的出错信息。上面就是WinXPHTTP500内部服务器错误的解决方法,有遇到这样问题的用户们可以按照

  • 基于台积电3nm工艺,Alphawave网络芯片流片成功

    10月27日消息,网络芯片设计公司Alphawave日前宣佈,旗下ZeusCORE1001-112GbpsNRZ/PAM4成功流片。该芯片将支持800G以太网、OIF112G-CEI、PCIe6.0和CXL3.0在内的众多标准,同时这也是台积电3nm家族N3E制程的首个测试芯片。Alphawave也预计成为台积电N3E制程的首批客户。目前该芯片已通过所有必要的测试,预计将会在台积电的OIP论坛上展示。AlphawaveCEOTonyPialis表示,很自豪成为首批使用台积电最先进N3E制程技术的公司之一,双方的合作伙伴关係将继续带来创新的高速连接技术,为最先进的资料中心提供动力。根据台积电先前的官方说法,比较N3和N5制程技术,N3在相同功号和複杂度的情况下,预计会带来10%到15%的性能提升,或者在相同频率和电晶体数量中降低25%-30%的功耗,同时会将逻辑密度提高约1.6倍。至于,更新的N3E是台积电第二代3nm节点制程技术,相较N5节点制程,性能提升幅度大概为18%,或者降低34%的功耗,逻辑密度提高约1.7倍。而相较首代N3制程技术,台积电预计N3E将会更广泛地被採用,量产时间

  • JavaScript DES 加密

    最近做网页数据加密工作,使用CryptoJSv3.1.2这个JavaScript脚本,网上比较有质量的文章实在太少,经验证加密结果与Asp.netDES加密结果一致 参考文章 https://gist.github.com/ufologist/5581486 CryptoJSv3.1.2下载地址 https://code.google.com/p/crypto-js/downloads/list 例子如下 <html> <head> <metahttp-equiv="content-type"content="text/html;charset=UTF-8"> <title>JS设置DES加密处理</title> <scripttype="text/javascript"src="js/jquery.min.js"></script> <scriptsrc="js/rollups/tripledes.js"></script> <scriptsrc="js/compone

  • sts bug SpringJUnit4ClassRunner

    SpringJUnit4ClassRunner找不到,不会自动修复,复制只能复制引用过去复制import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;复制null

  • 个人博客week3

      一调研测评:找不到bug 采访   1.程序员查词典   2.用户的确正在使用   3.解决了,没有缺点,没问题   4.没问题   非常推荐 二分析   16周   优点好看   没有提高部分 三建议和规划   建议同上   规划:2开发1测试1美工   第1周:需求分析   第2周:生成设计文档,设计复审。   第3周:具体设计。   第4-12周:具体编码。   第13周:代码复审。   第14-15周:测试。   第16周:美工。

  • [编程题] lk [231. 2的幂]

    [编程题]lk231.2的幂 题目 输入输出 方法1:位运算 //方法3:使用位运算消除1 /*思想:如果是2的n次方,那么它的二进制肯定是0000000100000这种样子,其中是只有一个1(有可能是0000001),我们用位运算消除一次,看能否消除为0,就判断是否是2的幂次方*/ publicbooleanisPowerOfTwo1(intn){ returnn>0&&(n&(n-1))==0; } 复制 方法2:循环取模 //一直模2 publicbooleanisPowerOfTwo(intn){ if(n==0){returnfalse;} while(n%2==0){ n=n/2; } returnn==1; } 复制

  • 蓝桥杯 穿越雷区 (Java广搜+字符串处理)

    蓝桥杯穿越雷区 X星的坦克战车很奇怪,它必须交替地穿越正能量辐射区和负能量辐射区才能保持正常运转,否则将报废。某坦克需要从A区到B区去(A,B区本身是安全区,没有正能量或负能量特征),怎样走才能路径最短?已知的地图是一个方阵,上面用字母标出了A,B区,其它区都标了正号或负号分别表示正负能量辐射区。例如:A+-+--+--+-+++-+-+-+B+-+-坦克车只能水平或垂直方向上移动到相邻的区。数据格式要求:输入第一行是一个整数n,表示方阵的大小,4<=n<100接下来是n行,每行有n个数据,可能是A,B,+,-中的某一个,中间用空格分开。A,B都只出现一次。要求输出一个整数,表示坦克从A区到B区的最少移动步数。如果没有方案,则输出-1例如:用户输入:5A+-+--+--+-+++-+-+-+B+-+-则程序应该输出:10资源约定:峰值内存消耗(含虚拟机)<512MCPU消耗 <2000ms请严格按要求输出,不要画蛇添足地打印类似:“请您输入...”的多余内容。所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。注意:不要使用package语句。不要

相关推荐

推荐阅读