Pytorch: 图像自编码器-卷积自编码网络(转置卷积解码)和图像去噪

Copyright: Jingmin Wei, Pattern Recognition and Intelligent System, School of Artificial and Intelligence, Huazhong University of Science and Technology

Pytorch教程专栏链接


文章目录

  • Pytorch: 图像自编码器-卷积自编码网络(转置卷积解码)和图像去噪
    • Reference
    • 介绍
    • 数据预处理
    • 数据集构建
    • 基于转置卷积编码的网络构建
    • 网络训练和预测
    • 评价去噪效果

本教程不商用,仅供学习和参考交流使用,如需转载,请联系本人。

Reference

Sparse Auto-Encoders

Convolutional Auto-Encoders

Stacked Auto-Encoders(Denoising)

介绍

利用卷积自编码网络去噪,利用其进行图像的编码和解码,是因为卷积操作在提取图像的信息上有较好的效果,而且可以对图像中隐藏的空间信息等内容进行较好的提取。该网络可以用于去噪,分割等。

在网络的输入图像带有噪声,而输出图像则为原始的去噪图像。在编码器阶段,会经过多个卷积、池化、激活和 BN 层操作,逐渐降低每个特征映射尺寸,如此降低至 24 × 24 24\times24 24×24 ,即图像缩小为原来的 1 16 \frac{1}{16} 161​ 。而解码器阶段,则通过多个转置卷积,激活和 BN 层操作,逐渐将其解码为原始图像大小并且包含 3 3 3 通道,即 3 × 96 × 96 3\times96\times96 3×96×96 的图像。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from skimage.util import random_noise
from skimage.measure import compare_psnr
from skimage.metrics import peak_signal_noise_ratio
import hiddenlayer as hlimport torch
import torch.nn as nn
import torch.nn.functional as F
import torch.utils.data as Data
import torch.optim as optim
from torchvision import transforms
from torchvision.datasets import STL10
# 模型加载选择GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# device = torch.device('cpu')
print(device)
print(torch.cuda.device_count())
print(torch.cuda.get_device_name(0))
cuda
1
NVIDIA GeForce RTX 3070

数据预处理

使用到的图像数据集为 - STL10 ,包含三种类型的数据,分别是带有标签的训练集和验证集,分别包含 5000 5000 5000 和 8000 8000 8000 张图像,共 10 10 10 类数据。还有一个类型包含 10 10 10 万张无标签图像,均为 96 × 96 96\times96 96×96 的 RGB 图像,可以用于无监督学习。

虽然可以直接用 torchvision.datasets.STL10() 下载,但是数据大小仅 2.5GB ,且为二进制。所以最好直接到本身网址:https://cs.stanford.edu/~acoates/stl10/ 下载,并保存到指定文件夹后解压。

为了节省时间并提高训练速度,在搭建 的网络中只使用 5000 5000 5000 张图片,其中 4000 4000 4000 张作为训练集, 1000 1000 1000 张作为验证集。

首先对数据预处理,定义一个从 .bin 文件中读取数据的函数,并对数据进行增强。

我们首先把文件和数据上传至服务器(我用的是 MistGPU,有关使用方式请自行在官网查询)。注意,带空格的文件夹如果无法识别,可以加上双引号。

# 传输代码文件到服务器端
scp "E:\\Jupyter WorkSpace\\PytorchLearn\\torch_autoencoder_conv(transpose).ipynb" mist@gpu193.mistgpu.xyz:/home/mist/
# 传输数据到服务器端
scp "E:\\Jupyter WorkSpace\\PytorchLearn\\data\\stl10_binary.tar.gz" mist@gpu193.mistgpu.xyz:/home/mist/data/
#  z解压文件
cd ~/data
tar xf stl10_binary.tar.gz # 解压到当前目录下

然后打开 MistGPU ,继续运行代码。

# 定义一个将bin文件处理为图像数据的函数
def read_image(data_path):with open(data_path, 'rb') as f:datal = np.fromfile(f, dtype=np.uint8)# 图像[num, channels, width, height]images = np.reshape(datal, (-1, 3, 96, 96))# 转为RGBimages = np.transpose(images, (0, 3, 2, 1))# 图像标准化到0-1return images / 255.0

上述函数处理后,第一位表示图像的数量,后面表示图像的 RGB 像素值(方便 matplotlib 可视化)。

用该函数读取 STL10 数据的训练数据集 train_X.bin 程序如下:

# 读取训练集,5000张96*96*3的图像
data_path = './data/stl10_binary/train_X.bin'
images = read_image(data_path)
print('images.shape:', images.shape)
images.shape: (5000, 96, 96, 3)

接下来定义一张为图像数据添加高斯噪声的函数,为每一张图像都添加随机噪声。

# 为数据添加高斯噪声
def gaussian_noise(images, sigma):# sigma: 噪声标准差sigma2 = sigma**2 / (255**2) # 噪声方差images_noisy = np.zeros_like(images) # 0矩阵初始化for i in range(images.shape[0]):image = images[i]# 添加噪声image_noise = random_noise(image, mode='gaussian', var=sigma2, clip=True)images_noisy[i] = image_noisereturn images_noisy
images_noise = gaussian_noise(images, 30)
print('images_noise:', images_noise.min(), '~', images_noise.max())
images_noise: 0.0 ~ 1.0

以上代码通过 random_noise 为每张图像添加指定方差为 sigma2 的图像噪声,并且将像素值范围处理到 0 − 1 0-1 0−1 之间。从输出可知,所有像素值最大为 1 1 1 ,最小为 0 0 0 。

下面可视化其中部分图像,对比添加噪声前后的内容

# 可视化其中部分原图像和高斯噪声图
plt.figure(figsize=(14, 8))
for i in np.arange(36):plt.subplot(6, 12, i+1)plt.imshow(images[i, ...])plt.axis('off')plt.subplot(6, 12, i+37)plt.imshow(images_noise[i, ...])plt.axis('off')
plt.show()


数据集构建

接下来对图像切分为训练集和验证集,并进行图像增强和数据类型标准化,处理为张量格式:

# 转为[num, channels, height, width]
data_Y = np.transpose(images, (0, 3, 2, 1)) # 原图作为Labels
data_X = np.transpose(images_noise, (0, 3, 2, 1)) # 噪声图作为Inputs
# 分割为训练集和验证集
X_train, X_val, y_train, y_val = train_test_split(data_X, data_Y, test_size=0.2, random_state=123)
# 转为张量数据
X_train = torch.tensor(X_train, dtype=torch.float32)
X_val = torch.tensor(X_val, dtype=torch.float32)
y_train = torch.tensor(y_train, dtype=torch.float32)
y_val = torch.tensor(y_val, dtype=torch.float32)
# 将X, Y转化为数据集
train_data = Data.TensorDataset(X_train, y_train)
val_data = Data.TensorDataset(X_val, y_val)
print('X_train.shape:', X_train.shape)
print('X_val.shape:', X_val.shape)
print('y_train.shape:', y_train.shape)
print('y_val.shape:', y_val.shape)
X_train.shape: torch.Size([4000, 3, 96, 96])
X_val.shape: torch.Size([1000, 3, 96, 96])
y_train.shape: torch.Size([4000, 3, 96, 96])
y_val.shape: torch.Size([1000, 3, 96, 96])

接下来使用 Data.Dataloader() 方法将二者处理为数据加载器,每个 batch 包含 32 32 32 张图像。

# 定义训练集和验证集的数据加载器
train_loader = Data.DataLoader(dataset=train_data, # 使用的数据集batch_size=32, # 批处理样本大小shuffle=True, # 每次训练迭代时都打乱数据num_workers=0 # windows上只能0个进程,linux可设置为4
)
val_loader = Data.DataLoader(dataset=val_data,batch_size=32,shuffle=True,num_workers=0
)

基于转置卷积编码的网络构建

class Denoise_AutoEncoders(nn.Module):def __init__(self):super(Denoise_AutoEncoders, self).__init__()# 定义Encoder编码层self.Encoder = nn.Sequential(nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3, stride=1, padding=1), # [, 64, 96, 96]nn.ReLU(True),nn.BatchNorm2d(64),nn.Conv2d(64, 64, 3, stride=1, padding=1), # [, 64, 96, 96]nn.ReLU(True),nn.BatchNorm2d(64),nn.Conv2d(64, 64, 3, stride=1, padding=1), # [, 64, 96, 96]nn.ReLU(True),nn.MaxPool2d(kernel_size=2, stride=2), # [, 64, 48, 48]nn.BatchNorm2d(64),nn.Conv2d(64, 128, 3, stride=1, padding=1), # [, 128, 48, 48]nn.ReLU(True),nn.BatchNorm2d(128),nn.Conv2d(128, 128, 3, stride=1, padding=1), # [, 128, 48, 48]nn.ReLU(True),nn.BatchNorm2d(128),nn.Conv2d(128, 256, 3, stride=1, padding=1), # [, 256, 48, 48]nn.ReLU(True),nn.MaxPool2d(2, 2), # [, 256, 24, 24]nn.BatchNorm2d(256),)# 定义Decoder解码层,使用转置卷积self.Decoder = nn.Sequential(nn.ConvTranspose2d(in_channels=256, out_channels=128, kernel_size=3, stride=1, padding=1), # [, 256, 24, 24]nn.ReLU(True),nn.BatchNorm2d(128),nn.ConvTranspose2d(128, 128, 3, stride=2, padding=1, output_padding=1), # [, 128, 48, 48]nn.ReLU(True),nn.BatchNorm2d(128),nn.ConvTranspose2d(128, 64, 3, stride=1, padding=1), # [, 64, 48, 48]nn.ReLU(True),nn.BatchNorm2d(64),nn.ConvTranspose2d(64, 32, 3, stride=1, padding=1), # [, 32, 48, 48]nn.ReLU(True),nn.BatchNorm2d(32),nn.ConvTranspose2d(32, 32, 3, stride=1, padding=1), # [, 32, 48, 48]nn.ConvTranspose2d(32, 16, 3, stride=2, padding=1, output_padding=1), # [, 16, 96, 96]nn.ReLU(True),nn.BatchNorm2d(16),nn.ConvTranspose2d(16, 3, 3, stride=1, padding=1), # [, 3, 96, 96]nn.Sigmoid(),)def forward(self, x):encoder = self.Encoder(x)decoder = self.Decoder(encoder)return encoder, decoder

上述网络定义了一个卷积自编码器。在编码层,卷积核大小均为 3 × 3 3\times3 3×3 ,激活函数为 ReLU,采用最大值池化,并使用了 Batch Normalization 。编码后,尺寸从 96 × 96 96\times96 96×96 缩小为 24 × 24 24\times24 24×24 ,并且通道数从 3 3 3 增加到 256 256 256 。

在解码层,操作相反,对特征映射做转置卷积,从而放大了特征映射,从 24 × 24 24\times24 24×24 放大到 96 × 96 96\times96 96×96 ,并且通道从 256 256 256 逐渐过渡为 3 3 3 ,对应着原始 RGB 的通道数。

接下来定义一个网络实体:

# 定义网络对象
DAEmodel = Denoise_AutoEncoders().to(device)
from torchsummary import summary
summary(DAEmodel, input_size=(3, 96, 96))
----------------------------------------------------------------Layer (type)               Output Shape         Param #
================================================================Conv2d-1           [-1, 64, 96, 96]           1,792ReLU-2           [-1, 64, 96, 96]               0BatchNorm2d-3           [-1, 64, 96, 96]             128Conv2d-4           [-1, 64, 96, 96]          36,928ReLU-5           [-1, 64, 96, 96]               0BatchNorm2d-6           [-1, 64, 96, 96]             128Conv2d-7           [-1, 64, 96, 96]          36,928ReLU-8           [-1, 64, 96, 96]               0MaxPool2d-9           [-1, 64, 48, 48]               0BatchNorm2d-10           [-1, 64, 48, 48]             128Conv2d-11          [-1, 128, 48, 48]          73,856ReLU-12          [-1, 128, 48, 48]               0BatchNorm2d-13          [-1, 128, 48, 48]             256Conv2d-14          [-1, 128, 48, 48]         147,584ReLU-15          [-1, 128, 48, 48]               0BatchNorm2d-16          [-1, 128, 48, 48]             256Conv2d-17          [-1, 256, 48, 48]         295,168ReLU-18          [-1, 256, 48, 48]               0MaxPool2d-19          [-1, 256, 24, 24]               0BatchNorm2d-20          [-1, 256, 24, 24]             512ConvTranspose2d-21          [-1, 128, 24, 24]         295,040ReLU-22          [-1, 128, 24, 24]               0BatchNorm2d-23          [-1, 128, 24, 24]             256ConvTranspose2d-24          [-1, 128, 48, 48]         147,584ReLU-25          [-1, 128, 48, 48]               0BatchNorm2d-26          [-1, 128, 48, 48]             256ConvTranspose2d-27           [-1, 64, 48, 48]          73,792ReLU-28           [-1, 64, 48, 48]               0BatchNorm2d-29           [-1, 64, 48, 48]             128ConvTranspose2d-30           [-1, 32, 48, 48]          18,464ReLU-31           [-1, 32, 48, 48]               0BatchNorm2d-32           [-1, 32, 48, 48]              64ConvTranspose2d-33           [-1, 32, 48, 48]           9,248ConvTranspose2d-34           [-1, 16, 96, 96]           4,624ReLU-35           [-1, 16, 96, 96]               0BatchNorm2d-36           [-1, 16, 96, 96]              32ConvTranspose2d-37            [-1, 3, 96, 96]             435Sigmoid-38            [-1, 3, 96, 96]               0
================================================================
Total params: 1,143,587
Trainable params: 1,143,587
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.11
Forward/backward pass size (MB): 80.86
Params size (MB): 4.36
Estimated Total Size (MB): 85.33
----------------------------------------------------------------/usr/local/lib/python3.6/dist-packages/torch/nn/functional.py:718: UserWarning: Named tensors and all their associated APIs are an experimental feature and subject to change. Please do not use them for anything important until they are released as stable. (Triggered internally at  /home/mist/pytorch/c10/core/TensorImpl.h:1156.)return torch.max_pool2d(input, kernel_size, stride, padding, dilation, ceil_mode)
# 输出网络结构
from torchviz import make_dotx = torch.randn(1, 3, 96, 96).requires_grad_(True)
y = DAEmodel(x.to(device))
myDAENet_vis = make_dot(y, params=dict(list(DAEmodel.named_parameters()) + [('x', x)]))
myDAENet_vis


网络训练和预测

定义优化算法为 Adam,损失函数采用均方误差,并可视化损失大小的变化。

LR = 0.0003
optimizer = optim.Adam(DAEmodel.parameters(), lr=LR) # Adam优化器
loss_fuc = nn.MSELoss().to(device) # 损失函数# 使用Hiddenlayer可视化
historyl = hl.History()
# 使用Canvas
canvasl = hl.Canvas()
train_num = 0
val_num = 0
# 对模型迭代训练,对所有数据训练epoch轮
for epoch in range(10):train_loss_epoch = 0val_loss_epoch = 0# 对训练加载器的数据迭代优化DAEmodel.train()for step, (b_x, b_y) in enumerate(train_loader):b_x, b_y = b_x.to(device), b_y.to(device)_, output = DAEmodel(b_x)loss = loss_fuc(output, b_y) # 均方根误差optimizer.zero_grad() # 清空过往梯度loss.backward() # 梯度反向传播optimizer.step() # 根据梯度更新参数train_loss_epoch += loss.item() * b_x.size(0)train_num = train_num + b_x.size(0)# 对验证加载器的数据进行模型验证DAEmodel.eval()for step, (b_x, b_y) in enumerate(val_loader):b_x, b_y = b_x.to(device), b_y.to(device)_, output = DAEmodel(b_x)loss = loss_fuc(output, b_y)val_loss_epoch += loss.item() * b_x.size(0)val_num = val_num + b_x.size(0)# 计算一个epoch的损失train_loss = train_loss_epoch / train_numval_loss = val_loss_epoch / val_num# 保存每个epoch的输出losshistoryl.log(epoch, train_loss=train_loss, val_loss=val_loss)# 可视化网络训练过程with canvasl:canvasl.draw_plot([historyl['train_loss'], historyl['val_loss']])


评价去噪效果

下面针对验证集中的一张图像使用训练好的降噪器去噪,并与原始图像对比效果

# 输入
imageindex = 1 # 图像索引
im = X_val[imageindex, ...]
im = im.unsqueeze(0)
im_nose = np.transpose(im.data.numpy(), (0, 3, 2, 1))
im_nose = im_nose[0, ...]
# 网络去噪结果
DAEmodel.eval()
_, output = DAEmodel(im.cuda())
im_de = np.transpose(output.data.cpu().numpy(), (0, 3, 2, 1))
im_de = im_de[0, ...]
# 标签(无噪声图像)
im = y_val[imageindex, ...]
im_or = im.unsqueeze(0)
im_or = np.transpose(im_or.data.numpy(), (0, 3, 2, 1))
im_or = im_or[0, ...]
# 计算去噪后的PSNR
print('加噪后的PSNR:', peak_signal_noise_ratio(im_or, im_nose), 'dB')
print('去噪后的PSNR:', peak_signal_noise_ratio(im_or, im_de), 'dB')
加噪后的PSNR: 19.41260585144029 dB
去噪后的PSNR: 24.97685547084317 dB

上述代码是对降噪前后的图像分别计算出 PSNR,即峰值信噪比,值越大说明两个图像之间越相似,可用于表示去噪效果。

接下来对原始图像、带噪图像和去噪图像分别可视化:

# 结果可视化
plt.figure(figsize=(12, 4))
plt.subplot(1, 3, 1)
plt.imshow(im_or)
plt.axis('off')
plt.title('Original Image')
plt.subplot(1, 3, 2)
plt.imshow(im_nose)
plt.axis('off')
plt.title('Noise Image $\sigma$=30')
plt.subplot(1, 3, 3)
plt.imshow(im_de)
plt.axis('off')
plt.title('Denoising Image')
plt.show()


从图中可以看出,去噪效果显著,已经看不到图像的噪声点了,而且去噪图像非常平滑,和原始图像很相近。

下面针对整个验证数据集使用降噪器,计算出所有图像降噪前后的 PSNR 提升大小的均值,来衡量多张数据上的降噪情况。

# 计算整个验证集去噪后的PSNR提升的均值
PSNR_val = []
DAEmodel.eval()
for i in range(X_val.shape[0]):imageindex = i# 输入im = X_val[imageindex, ...]im = im.unsqueeze(0)im_nose = np.transpose(im.data.numpy(), (0, 3, 2, 1))im_nose = im_nose[0, ...]# 去噪_, output = DAEmodel(im.cuda())im_de = np.transpose(output.data.cpu().numpy(), (0, 3, 2, 1))im_de = im_de[0, ...]# 输出im = y_val[imageindex, ...]im_or = im.unsqueeze(0)im_or = np.transpose(im_or.data.numpy(), (0, 3, 2, 1))im_or = im_or[0, ...]# 计算去噪后的PSNRPSNR_val.append(peak_signal_noise_ratio(im_or, im_de) - peak_signal_noise_ratio(im_or, im_nose))
print('PSNR的平均提升量为:', np.mean(PSNR_val), 'dB')
PSNR的平均提升量为: 5.813400503195555 dB

可知,峰值信噪比提升了 5.81 d B 5.81dB 5.81dB ,去噪的效果非常显著。

受于训练时间和设备的约束,并没有使用更多的图像以及不同类型的噪声训练,所以得到的降噪器还有一定的局限性,但是从降噪后的图像已经反映出了基于卷积神经网络的降噪自编码器在图像去噪方面的有效性。

Pytorch:基于转置卷积解码的卷积自编码网络相关推荐

  1. 彩色星球图片生成4:转置卷积+插值缩放+卷积收缩(pytorch版)

    彩色星球图片生成4:转置卷积层+插值缩放+卷积收缩(pytorch版) 1. 改进方面 1.1 优化器与优化步长 1.2 交叉熵损失函数 1.3 Patch判别器 1.4 输入分辨率 1.5 转置卷积 ...

  2. pytorch基于卷积层通道剪枝的方法

    pytorch基于卷积层通道剪枝的方法 原文:https://blog.csdn.net/yyqq7226741/article/details/78301231 本文基于文章:Pruning Con ...

  3. 【Pytorch神经网络理论篇】 26 基于空间域的图卷积GCNs(ConvGNNs):定点域+谱域+图卷积的操作步骤

    图卷积网络(Graph Convolutional Network,GCN)是一种能对图数据进行深度学习的方法.图卷积中的"图"是指数学(图论)中用顶点和边建立的有相关联系的拓扑图 ...

  4. 一文搞懂转置卷积(反卷积)

    ↑ 点击蓝字 关注极市平台 作者丨土豆@知乎 来源丨https://zhuanlan.zhihu.com/p/158933003 极市导读 转置卷积在一些文献中也被称为反卷积,人们如果希望网络学习到上 ...

  5. 基于U-Net的递归残差卷积神经网络在医学图像分割中的应用

    转载: 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/weixin_45723705/ ...

  6. 卷积,特征图,转置卷积和空洞卷积的计算细节

    六月 北京 | 高性能计算之GPU CUDA培训 6月22-24日三天密集式学习  快速带你入门阅读全文> 正文共2029个字,13张图,预计阅读时间13分钟. 最近在做姿态估计的项目,在定制和 ...

  7. 论文解读丨基于局部特征保留的图卷积神经网络架构(LPD-GCN)

    本文分享自华为云社区<论文解读:基于局部特征保留的图卷积神经网络架构(LPD-GCN)>,原文作者:PG13 . 近些年,很多研究者开发了许多基于图卷积网络的方法用于图级表示学习和分类应用 ...

  8. 转置卷积和膨胀卷积详细讲解

    目录 转置卷积(Transposed Convolution) 作用 转置卷积的输出 转置卷积过程 转置卷积运算过程 pytorch中转置卷积对应参数 膨胀卷积(空洞卷积,Atrous convolu ...

  9. pytorch 入门:GPU加速,卷积层,池化层

    GPU 加速 深度学习设计很多向量和多矩阵运算,比如BP , CNN 等深层模型都可以写成矩阵运算的格式,不用写成循环运算.但是CPU 上矩阵的运算会被展成循环的形式,CPU 是串行执行的.而GPU ...

最新文章

  1. 前端面试-综合问题版
  2. ABP(现代ASP.NET样板开发框架)系列之2、ABP入门教程
  3. POJ - 1185 炮兵阵地(状压dp)
  4. xcode10 自定义代码块
  5. 一台微型计算机的处理速度主要取决于,2017年答案计算机等级考试题库「附答案」...
  6. 迅雷前CEO陈磊涉嫌职务侵占罪已被立案侦查,股价周四下跌超7%
  7. 网易:层次遍历二叉树
  8. linux系统下的权限知识梳理
  9. angular2+ 中封装调用递归tree
  10. gradle mysql方言_基于Spring Boot技术栈的博客系统企业级实战教程分享
  11. 给产品经理讲技术:微信的openid和unionid
  12. 突破Dr.com校园网客户端对于热点和路由器的限制
  13. java面试题--算法集锦(建议收藏)
  14. Photoshop 入门教程「1」如何在Mac版 Photoshop 中打开图像?
  15. HTML中字体属性、文本属性使用说明
  16. str.count 统计
  17. SSM 之 MyBatis
  18. Ubuntu下同时搭建支持匿名、本地、虚拟用户的ftp服务器
  19. epoll_create详解
  20. 菜猪的JAVA 网络编程学习之Socket用法详解(上)

热门文章

  1. 群晖显示服务器错误代码21,群晖 DSM 6.2.3 升级 25426 错误 21 的解决办法 | 智享阁...
  2. 黑客攻防技术宝典Web实战篇(第二版)_读书笔记(第一章~第三章)
  3. 《Java标准教程》pdf 附下载链接
  4. 欧拉定理及扩展(附证明)
  5. 5e显示非vac服务器,CSGO出现VAC无法验证的解决方法
  6. ORA-00600: internal error code 内部错误代码
  7. SecureCRT连接后乱码问题!!!
  8. Java经典面试题答案解析(1-80题)
  9. js中计算两个时间差
  10. 2021年焊工(初级)考试及焊工(初级)免费试题