本次包含3个部分:
- 简单的自编码器
- 卷积自编码器
- 去噪自编码器
目录
1 简单的自编码器
首先,我们将构建一个简单的自动编码器来压缩MNIST数据集。使用自动编码器,我们通过编码器传递输入数据,该编码器对输入数据进行了压缩表示。然后,这个表示通过一个解码器来重建输入数据。通常,编码器和解码器将结合神经网络一同使用,然后训练示例数据。
压表示法缩
压缩表示非常适合以比存储原始数据更高效的方式保存和共享任何类型的数据。在实践中,压缩表示通常保存有关输入图像的关键信息,我们可以用它去噪图像或其他类型的重建和变换!
在本笔记本中,我们将为编码器和解码器构建一个简单的网络架构。让我们开始导入库并获取数据集。
可视化数据
线性自编码器
我们将用这些图像训练一个自动编码器,将它们拉平成784长度的向量。来自该数据集的图像已经标准化,其值在0到1之间。让我们从构建一个简单的自动编码器开始。编码器和解码器应由一个线性层构成。连接编码器和解码器的单元将是压缩表示(compressed representation)。
由于图像是在0到1之间标准化的,我们需要在输出层上使用sigmoid激活来获得与输入值范围匹配的值。
待办事项:在下面的单元格中为自动编码器构建图表。
- 输入的图像将被拉平为784长度的向量。目标与输入是相同的。编码器和解码器将由两层线性层组成。深度维度应更改如下:784 inputs > encoding_dim > 784 outputs。所有层都将应用ReLu激活,除了最终输出层,它有一个sigmoid激活。
compressed representation应该是一个维度为 encoding_dim=32 的向量。
训练
在这里,我将编写一些代码来训练网络。我在这里对验证不太感兴趣,所以我会在之后监控训练损失和测试损失。
在本例中,我们不关心标签,只关心图像,我们可以从train_loader中获得图像。因为我们正在比较输入和输出图像中的像素值,所以最好使用用于回归任务的损失函数。回归是关于比较数量而不是概率值。在本例中,我将使用MSELoss。并将输出图像和输入图像进行如下比较:
另外,这是非常简单的PyTorch训练。我们将图像拉平,将其输入自动编码器,并记录下训练过程中的损失。
检查结果
下面我绘制了一些测试图像及其重建图。在大多数情况下,这些看起来很好,除了一些模糊的部分。
下一节内容
我们这里处理的是图像,所以我们(通常)可以使用卷积层获得更好的性能。所以,接下来我们将使用卷积层构建一个更好的自动编码器。
2 卷积自编码器
继续使用MNIST数据集,让我们使用卷积层来改进自动编码器的性能。我们将建立一个卷积自动编码器来压缩MNIST数据集。
编码器部分将由卷积和池化层组成,解码器将由学习“上采样”压缩表示的转置卷积层组成。
Encoder
网络的编码器部分将是一个典型的卷积金字塔。每个卷积层后面都有一个最大池化层,以降低层的维数。
Decoder
不过这个解码器对你来说可能是新的。解码器需要从窄表示转换为宽的、重建的图像。例如,表示可以是一个7x7x4的max-pool层。这是编码器的输出,也是解码器的输入。我们想从解码器中得到一个28x28x1的图像,所以我们需要从压缩的表示返回。网络的原理图如下所示。
这里我们最终的编码器层的大小是7x7x4 = 196。原始图像的大小是28x28 = 784,所以编码后的向量是原始图像大小的25%。这些只是每一层的建议尺寸。你可以随意改变深度和大小,事实上,我们鼓励你添加额外的层来让这个表现更小!记住,我们这里的目标是找到输入数据的一个小表示。
解码器:转置卷积
该解码器使用转置卷积层来增加输入层的宽度和高度。它们的工作原理与卷积层几乎完全相同,只是方式相反。输入层的步幅导致转置卷积层的步幅。例如,如果你有一个3x3的内核,那么输入层中的一个3x3 patch就会被简化为一个卷积层中的一个单元。相比较而言,输入层中的一个单元将扩展为转置卷积层中的3x3 patch。PyTorch为我们提供了一种创建图层的简单方法nn.ConvTranspose2d。
值得注意的是,转置卷积层可能导致最终图像中的伪影,比如棋盘图案。这是由于内核中的重叠,可以通过设置步幅和内核大小相等来避免重叠。在Augustus Odena等人的这篇Distill文章中,作者指出,通过使用最近邻或双线性插值(上采样)然后使用卷积层来调整图层大小,可以避免这些棋盘效应。
- 我们将在另一个笔记本中展示这种方法,以便您可以试验它并看到区别。
待办事项:建立如上所示的网络。
- 用一系列卷积层和池化层构建编码器。在构建解码器时,记得转置卷积层可以使用stride和kernel_size=2来对输入进行向上采样。
训练
# specify loss function
criterion = nn.MSELoss()
# specify loss function
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# number of epochs to train the model
n_epochs = 30
for epoch in range(1, n_epochs+1):
# monitor training loss
train_loss = 0.0
###################
# train the model #
###################
for data in train_loader:
# _ stands in for labels, here
# no need to flatten images
images, _ = data
# clear the gradients of all optimized variables
optimizer.zero_grad()
# forward pass: compute predicted outputs by passing inputs to the model
outputs = model(images)
# calculate the loss
loss = criterion(outputs, images)
# backward pass: compute gradient of the loss with respect to model parameters
loss.backward()
# perform a single optimization step (parameter update)
optimizer.step()
# update running training loss
train_loss += loss.item()*images.size(0)
# print avg training statistics
train_loss = train_loss/len(train_loader)
print('Epoch: {} \tTraining Loss: {:.6f}'.format(
epoch,
train_loss
))
结果:
Epoch: 1 Training Loss: 0.536067
Epoch: 2 Training Loss: 0.296300
Epoch: 3 Training Loss: 0.271992
Epoch: 4 Training Loss: 0.257491
Epoch: 5 Training Loss: 0.248846
Epoch: 6 Training Loss: 0.242581
Epoch: 7 Training Loss: 0.238418
Epoch: 8 Training Loss: 0.235051
Epoch: 9 Training Loss: 0.232203
Epoch: 10 Training Loss: 0.230150
Epoch: 11 Training Loss: 0.228493
Epoch: 12 Training Loss: 0.226843
Epoch: 13 Training Loss: 0.225579
Epoch: 14 Training Loss: 0.224565
Epoch: 15 Training Loss: 0.223704
Epoch: 16 Training Loss: 0.222844
Epoch: 17 Training Loss: 0.221671
Epoch: 18 Training Loss: 0.220569
Epoch: 19 Training Loss: 0.219486
Epoch: 20 Training Loss: 0.218675
Epoch: 21 Training Loss: 0.218025
Epoch: 22 Training Loss: 0.217432
Epoch: 23 Training Loss: 0.216864
Epoch: 24 Training Loss: 0.216343
Epoch: 25 Training Loss: 0.215857
Epoch: 26 Training Loss: 0.215377
Epoch: 27 Training Loss: 0.214942
Epoch: 28 Training Loss: 0.214570
Epoch: 29 Training Loss: 0.214245
Epoch: 30 Training Loss: 0.213977
检查结果
下面我绘制了一些测试图像及其重建图。这些看起来有点粗糙的边缘,可能是由于我们上面提到的棋盘效应,往往发生在转置层。
训练结果:
Epoch: 1 Training Loss: 0.323222
Epoch: 2 Training Loss: 0.167930
Epoch: 3 Training Loss: 0.150233
Epoch: 4 Training Loss: 0.141811
Epoch: 5 Training Loss: 0.136143
Epoch: 6 Training Loss: 0.131509
Epoch: 7 Training Loss: 0.126820
Epoch: 8 Training Loss: 0.122914
Epoch: 9 Training Loss: 0.119928
Epoch: 10 Training Loss: 0.117524
Epoch: 11 Training Loss: 0.115594
Epoch: 12 Training Loss: 0.114085
Epoch: 13 Training Loss: 0.112878
Epoch: 14 Training Loss: 0.111946
Epoch: 15 Training Loss: 0.111153
Epoch: 16 Training Loss: 0.110411
Epoch: 17 Training Loss: 0.109753
Epoch: 18 Training Loss: 0.109152
Epoch: 19 Training Loss: 0.108625
Epoch: 20 Training Loss: 0.108119
Epoch: 21 Training Loss: 0.107637
Epoch: 22 Training Loss: 0.107156
Epoch: 23 Training Loss: 0.106703
Epoch: 24 Training Loss: 0.106221
Epoch: 25 Training Loss: 0.105719
Epoch: 26 Training Loss: 0.105286
Epoch: 27 Training Loss: 0.104917
Epoch: 28 Training Loss: 0.104582
Epoch: 29 Training Loss: 0.104284
Epoch: 30 Training Loss: 0.104016
编解码效果:
(额外)解码器:上采样层 + 卷积层
该解码器使用最近邻上采样层和正常卷积层的组合来增加输入层的宽度和厚度。
待办事项:建立如下所示的网络。
- 用一系列卷积层和池化层构建编码器。在构建解码器时,使用上采样和正常卷积层的组合。
3 去噪自编码器
继续使用MNIST数据集,让我们给数据添加噪声,看看我们是否可以定义和训练一个自动编码器去噪。
让我们开始导入库并获取数据集。
去噪
正如我之前提到的,像您目前所构建的自动编码器在实践中并不是太有用。然而,只要在有噪声的图像上训练网络,就可以很成功地用于图像去噪。我们可以通过在训练图像中添加高斯噪声来创建噪声图像,然后将其裁剪到0到1之间。
- 我们将使用噪声图像作为输入,原始的、干净的图像作为标签。
下面是我生成的一些噪声图像和相关的去噪图像的例子。
因为这对网络来说是一个更难的问题,所以我们想在这里使用更深的卷积层;层与更多的功能地图。您也可以考虑添加额外的层。我建议将编码器中的卷积层的深度设为32,并将相同的深度倒推到解码器中。
TODO:构建去噪自编码器的网络。与上面的模型相比,添加更深的和/或更多的层。
训练
我们只关心可以从train_loader中获得的训练图像。
在本例中,我们实际上给这些图像添加了一些噪声,并将这些噪声y_imgs提供给我们的模型。该模型将产生基于噪声输入的重建图像。但是,我们希望它产生正常的无噪声图像,所以,当我们计算损失时,我们仍然会将重建的输出与原始图像进行比较!
因为我们正在比较输入和输出图像中的像素值,所以最好使用用于回归任务的损失函数。在本例中,我将使用MSELoss。并将输出图像和输入图像进行如下比较: