详解逆卷积操作–Up-sampling with Transposed Convolution

王 茂南 2019年5月29日07:07:062 7666字阅读25分33秒
摘要这篇文章详细介绍一下关于逆卷积的相关操作,查看卷积的系数与逆卷积的系数是否存在关系。

介绍

之前在介绍CNN的文章中,Convolutional Neural Networks(CNN)介绍–Pytorch实现,介绍了关于逆卷积的一些内容,但是感觉没有讲的很好。这次查到一篇文章,正好结合理解详细说一下,贴一下自己实验的结果。

参考链接 : Up-sampling with Transposed Convolution

建议可以看一下原文,我的理解可能还是会和原文有些偏差。

逆卷积介绍

上面文章,强调的卷积和逆卷积的核心是:

  • 卷积是有一种,多对一的关系;
  • 逆卷积是有一种,一对多的关系;

可以简单看一下下面的示意图:

下面是进行卷积的过程,如卷积结果中122会和原来的9个数字有关系。

详解逆卷积操作–Up-sampling with Transposed Convolution

下面是逆卷积的过程,我们希望2通过逆卷积能与后面的9个数字有关。也就是说,进行逆卷积操作后,我们希望左上角的数字2希望和2和1有关系。那么如何达到这样的效果呢。

详解逆卷积操作–Up-sampling with Transposed Convolution

对于卷积的运算,其实我们可以转换为下面的矩阵乘法。我们将kernel排成下面的矩阵(4x16)。

这是原始的kernel,我们将其重新进行排列。(这里的图像有些不清楚,可以查看上面链接的原文结合进行查看)

详解逆卷积操作–Up-sampling with Transposed Convolution

重新排列后得到下面的矩阵:

详解逆卷积操作–Up-sampling with Transposed Convolution

具体关于下面矩阵的生成方法,其实就是把原始的kernel每一行展平,横着进行放置。

详解逆卷积操作–Up-sampling with Transposed Convolution

接着,我们把图像数据也进行展平,得到下面的数据。

详解逆卷积操作–Up-sampling with Transposed Convolution

最后,原始的进行卷积的操作,就相当于是下面的矩阵运算的操作了。最后只需要将结果的41还原回22的即可。(图片不清楚,建议查看原文)

详解逆卷积操作–Up-sampling with Transposed Convolution

上面的卷积过程,是一个416 × 161 = 41的过程。且我们可以看到,输出的每一个值都与原来的9个值有关,可以看到416的矩阵中有9个数字不为0,即每一行红色的个数。

于是,我们想到逆卷积的操作,相当于是164 × 41 = 16*1的一个过程,我们将上面的矩阵进行转置操作,在进行乘法。

详解逆卷积操作–Up-sampling with Transposed Convolution

上面的结果相当于是一个逆卷积的过程,可以看到input中的4个数字,如2会影响到最后output的9个数字,因为第一列有9个值不是0.

上面的过程,主要还是再强调,我们逆卷积和卷积是为了保持一个一对多和多对一的关系。卷积和逆卷积的系数都是要分别进行学习的,不是简单的转置的关系(下面的实验会有讲到)

在实际进行逆卷积的操作的时候,我们是会在input周围填充0,我们可以看下面的动图。动图来源 : https://github.com/vdumoulin/conv_arithmetic

详解逆卷积操作–Up-sampling with Transposed Convolution

可以看到,在填充后,input左上角的方框会被计算9次,相当于是和output的9个数字是有关系的。

nn.MaxUnpool介绍

在使用MaxUnpool的时候要特别注意, 需要在maxpool的时候保存indices. 否则会出现如下的报错:

  1. typeError: forward() missing 1 required positional argument: 'indices'

同时, 我们不可以把nn.MaxUnpool2d写在nn.Sequential里面, 因为这个需要有两个输入的参数.

我们下面看一个例子, 有一个MaxUnpool的情况:

  1. class ConvDAE(nn.Module):
  2.     def __init__(self):
  3.         super().__init__()
  4.         # input: batch x 3 x 32 x 32 -> output: batch x 16 x 16 x 16
  5.         self.encoder = nn.Sequential(
  6.             nn.Conv2d(3, 16, 3, stride=1, padding=1), # batch x 16 x 32 x 32
  7.             nn.ReLU(),
  8.             nn.BatchNorm2d(16),
  9.             nn.MaxPool2d(2, stride=2, return_indices=True)
  10.         )
  11.         self.unpool = nn.MaxUnpool2d(2, stride=2, padding=0)
  12.         self.decoder = nn.Sequential(
  13.             nn.ConvTranspose2d(16, 16, 3, stride=2, padding=1, output_padding=1),
  14.             nn.ReLU(),
  15.             nn.BatchNorm2d(16),
  16.             nn.ConvTranspose2d(16, 3, 3, stride=1, padding=1, output_padding=0),
  17.             nn.ReLU()
  18.         )
  19.     def forward(self, x):
  20.         print(x.size())
  21.         out, indices = self.encoder(x)
  22.         out = self.unpool(out, indices)
  23.         out = self.decoder(out)
  24.         print(out.size())
  25.         return out

当有两个MaxUnpool的情况.

  1. class autoencoder(nn.Module):
  2.     """这就是原始论文的结构
  3.     """
  4.     def __init__(self):
  5.         super(autoencoder, self).__init__()
  6.         # -------
  7.         # encode
  8.         # -------
  9.         self.encode1 = nn.Sequential(
  10.             # 第一层
  11.             nn.Conv1d(kernel_size=25, in_channels=1, out_channels=32, stride=1, padding=12), # (1,784)->(32,784)
  12.             nn.BatchNorm1d(32), # 加上BN的结果
  13.             nn.ReLU(),
  14.             nn.MaxPool1d(kernel_size=3, stride=3, padding=1, return_indices=True), # (32,784)->(32,262)
  15.         )
  16.         self.encode2 = nn.Sequential(
  17.             # 第二层
  18.             nn.Conv1d(kernel_size=25, in_channels=32, out_channels=64, stride=1, padding=12), # (32,262)->(64,262)
  19.             nn.BatchNorm1d(64),
  20.             nn.ReLU(),
  21.             nn.MaxPool1d(kernel_size=3, stride=3, padding=1, return_indices=True), # (batchsize,64,262)->(batchsize,64,88)
  22.         )
  23.         self.encode3 = nn.Sequential(
  24.             nn.Linear(in_features=88*64, out_features=1024),
  25.             nn.Linear(in_features=1024, out_features=30)
  26.         )
  27.         # -------
  28.         # decode
  29.         # -------
  30.         self.unpooling1 = nn.MaxUnpool1d(kernel_size=3, stride=3, padding=1) # (batchsize,64,262)<-(batchsize,64,88)
  31.         self.unpooling2 = nn.MaxUnpool1d(kernel_size=3, stride=3, padding=1) # (32,784)<-(32,262)
  32.         self.decode1 = nn.Sequential(
  33.             # 第一层
  34.             nn.ReLU(),
  35.             nn.BatchNorm1d(64),
  36.             nn.ConvTranspose1d(kernel_size=25, in_channels=64, out_channels=32, stride=1, padding=12), # (32,262)<-(64,262)
  37.         )
  38.             # 第二层
  39.         self.decode2 = nn.Sequential(
  40.             nn.ReLU(),
  41.             nn.BatchNorm1d(32), # 加上BN的结果
  42.             nn.ConvTranspose1d(kernel_size=25, in_channels=32, out_channels=1, stride=1, padding=12), # (1,784)<-(32,784)
  43.         )
  44.         self.decode3 = nn.Sequential(
  45.             nn.Linear(in_features=30, out_features=1024),
  46.             nn.Linear(in_features=1024, out_features=88*64)
  47.         )
  48.     def forward(self, x):
  49.         # encode
  50.         x = x.view(x.size(0),1,-1) # 将图片摊平
  51.         x,indices1 = self.encode1(x) # 卷积层
  52.         x,indices2 = self.encode2(x) # 卷积层
  53.         x = x.view(x.size(0), -1) # 展开
  54.         x = self.encode3(x) # 全连接层
  55.         # decode
  56.         x = self.decode3(x)
  57.         x = x.view(x.size(0), 64, 88)
  58.         x = self.unpooling1(x, indices2)
  59.         x = self.decode1(x)
  60.         x = self.unpooling2(x, indices1)
  61.         x = self.decode2(x)
  62.         return x

 

参考资料

实现结果

其实具体的实现的方式可以参考使用CNN在MNIST上实现简单的攻击样本,这里就是进行了简单的修改。

加载数据集

首先我们加载这次测试使用的数据集,使用MNIST的数据集;

  1. import torch
  2. import torch.nn as nn
  3. import torchvision
  4. import torchvision.transforms as transforms
  5. # Device configuration
  6. device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
  7. # Hyper-parameters 
  8. num_epochs = 5
  9. batch_size = 100
  10. learning_rate = 0.001
  11. # MNIST dataset
  12. train_dataset = torchvision.datasets.MNIST(root='./',
  13.                                            train=True,
  14.                                            transform=transforms.ToTensor(),
  15.                                            download=True)
  16. test_dataset = torchvision.datasets.MNIST(root='./',
  17.                                           train=False,
  18.                                           transform=transforms.ToTensor())
  19. # Data loader
  20. train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
  21.                                            batch_size=batch_size,
  22.                                            shuffle=True)
  23. test_loader = torch.utils.data.DataLoader(dataset=test_dataset,
  24.                                           batch_size=batch_size,
  25.                                           shuffle=False)

 定义网络结构

我们使用下面一个简单的网络结构,我们先将图片通过卷积后池化进行压缩,接着希望通过逆卷积和逆池化进行还原。最后查看卷积的weight逆卷积的weight.

  • Conv->MaxPool->MaxUnpool->UnConv
  • 每一层图像大小的变化 28*28(input)->26*26(conv)->13*13(pool)->26*26(unpool)->28*28(output)
  1. # 搭建网络
  2. class CNNMNIST(nn.Module):
  3.     def __init__(self):
  4.         super(CNNMNIST,self).__init__()
  5.         self.conv1 = nn.Conv2d(in_channels=1,out_channels=1,kernel_size=3,stride=1,padding=0)
  6.         self.pool1 = nn.MaxPool2d(kernel_size=2,stride=2,padding=0,return_indices=True)
  7.         self.unpool1 = nn.MaxUnpool2d(kernel_size=2,stride=2,padding=0)
  8.         self.unconv1 = nn.ConvTranspose2d(in_channels=1, out_channels=1, kernel_size=3, stride=1, padding=0)
  9.     def forward(self,x):
  10.         # encode
  11.         out1 = self.conv1(x)
  12.         out = out1.clone()
  13.         out,indices = self.pool1(out)
  14.         # deocde
  15.         out = self.unpool1(out,indices,output_size=out1.size())
  16.         out = self.unconv1(out)
  17.         return out
  18. # 网络的初始化
  19. model = CNNMNIST().to(device)
  20. print(model)
详解逆卷积操作–Up-sampling with Transposed Convolution

网络的训练

  1. # 定义优化器和损失函数
  2. criterion = nn.MSELoss(reduction='mean')
  3. optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
  4. # 进行训练
  5. model.train()
  6. total_step = len(train_loader)
  7. for epoch in range(num_epochs):
  8.     for i, (images, labels) in enumerate(train_loader):
  9.         # Move tensors to the configured device
  10.         images = images.to(device)
  11.         # Forward pass
  12.         outputs = model(images)
  13.         loss = criterion(outputs, images)
  14.         # Backward and optimize
  15.         optimizer.zero_grad()
  16.         loss.backward()
  17.         optimizer.step()
  18.         if (i+1) % 100 == 0:
  19.             # 计算Loss
  20.             print ('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}'
  21.                    .format(epoch+1, num_epochs, i+1, total_step, loss.item()))

重构的效果

我们使用网络进行重构,看一下最终的效果如何。

详解逆卷积操作–Up-sampling with Transposed Convolution

看卷积和逆卷积的系数

我们验证一下卷积和逆卷积的系数是否是转置的关系。

详解逆卷积操作–Up-sampling with Transposed Convolution

可以看到逆卷积的系数也是要进行训练得到的。

最后,还是十分建议阅读原文的,原文的链接如下:Up-sampling with Transposed Convolution

  • 微信公众号
  • 关注微信公众号
  • weinxin
  • QQ群
  • 我们的QQ群号
  • weinxin
王 茂南
  • 本文由 发表于 2019年5月29日07:07:06
  • 转载请务必保留本文链接:https://mathpretty.com/10502.html
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

评论:2   其中:访客  1   博主  1
    • clatterrr
      clatterrr

      正被卷积玩得死去活来….
      话说卷积是组合数学的内容吧?还是具体数学…

        • 王 茂南
          王 茂南

          @ clatterrr 这个分类我也不是很懂, 我之前看卷积是与图像处理有关. 我这里写的是介绍关于深度学习中(CNN)中卷积的操作.