DnCNN论文阅读

前言

DnCNN的论文属于本人学习的第一篇论文,本文整理记录相关内容,若有后人也从这篇论文开始学习,可以参考此文

论文原文和代码

论文原文:https://ederodan.com/paper/DnCNN.pdf
论文代码:https://github.com/cszn/DnCNN

论文主要目的与贡献

这篇文章主要在传统的去噪神经网络上进行了了改良,提出了新的前馈降噪卷积神经网络(DnCNNs)。
主要的改良方法包括:改用残差学习、加入批量归一化。
效果:提升特定噪声水平的高斯去噪的效果、进一步扩展到一般性的图像去噪任务,即盲高斯去噪、单图像超分辨率、JPEG图像去块。

传统方式与本文创新

传统方式

(1)通过建模图像先验,建立去噪模型:
缺点:涉及复杂的优化,耗时;
模型一般非凸,并且需要手动设计参数,很难达到最优去噪性能。
(2)辨别学习方法学习图像先验模型:
优点:能够摆脱迭代优化过程;
CSF和TNRD在计算效率和去噪质量之间的差距有了很好的效果
缺点:在捕获图像结构的所有特征上受到限制;
许多手动参数被涉及到;
模型被盲图像去噪限制

本文创新

图像去噪:从一个由噪声的图像y=x+v中,恢复干净的图像x。
图像去噪视为一种判别学习问题:(DnCNN)通过卷积神经网络将图像与噪声分离。
CNN优点:
第一:非常深的结构提高利用图像特征的容量和灵活方面是非常有效的
第二:训练CNN的正则化和学习方面取得了相当大的进展,ReLU、批量归一化、残差学习,提高去噪性能
第三:适合GPU的并行运算。提高运行时的性能
DnCNN优点:
第一:在特定的噪声水平下训练比最先进的方法如BM3D,WNNM和TNRD有更好的高斯去噪效果。
第二:对于盲高斯去噪,比为特定噪声水平训练的BM3D和TNRD表现的更好。
第三:当被延伸到一些一般的图像去噪任务时,也能获得很好的结果。
第四:一个DnCNN模型对于三个一般图像去噪任务的高效性,即高斯去噪,有着多重升级因子的SISR和有着不同质量因子的JPEG去块问题。
这项工作的贡献:
第一:为高斯去噪提出了一个端到端的可训练的很深的卷积神经网络
第二:发现残差学习和批量归一化可以使卷积神经网络学习极大的获益
第三:DnCNN可以轻松扩展以处理一般的图像去噪任务
other:已经存在通过深度神经网络来处理去噪问题的模型,如:MLP和TNRD。但是,他们都是针对特定噪声水平训练特定模型。开发CNN进行一般图像去噪仍然没有研究。

Bathch Normalization 批归一化

因为深层神经网络在做非线性变换前的激活输入值(就是那个x=WU+B,U是输入)随着网络深度加深或者在训练过程中,其分布逐渐发生偏移或者变动,之所以训练收敛慢,一般是整体分布逐渐往非线性函数的取值区间的上下限两端靠近(对于Sigmoid函数来说,意味着激活输入值WU+B是大的负值或正值),所以这导致反向传播时低层神经网络的梯度消失,这是训练深层神经网络收敛越来越慢的本质原因,而BN就是通过一定的规范化手段,把每层神经网络任意神经元这个输入值的分布强行拉回到均值为0方差为1的标准正态分布,其实就是把越来越偏的分布强制拉回比较标准的分布,这样使得激活输入值落在非线性函数对输入比较敏感的区域,这样输入的小变化就会导致损失函数较大的变化,意思是这样让梯度变大,避免梯度消失问题产生,而且梯度变大意味着学习收敛速度快,能大大加快训练速度。

Bathch Normalization 相关文章

https://www.cnblogs.com/king-lps/p/8378561.html
https://www.cnblogs.com/guoyaohua/p/8724433.html

ResNets 残差网络

更深的网络可以进行更加复杂的特征模式的提取,从而理论上更深的网络可以得到更好的结果。但是通过简单的叠加层的方式来增加网络深度,可能引来梯度消失/梯度爆炸的问题,随着网络深度的增加,也会带来两个问题:

1、梯度消失或梯度爆炸
2、网络退化问题

ResNet是在2015年有何凯明,张翔宇,任少卿,孙剑共同提出的,ResNet使用了一个新的思想,ResNet的思想是假设我们涉及一个网络层,存在最优化的网络层次,那么往往我们设计的深层次网络是有很多网络层为冗余层的。那么我们希望这些冗余层能够完成恒等映射,保证经过该恒等层的输入和输出完全相同。具体哪些层是恒等层,这个会有网络训练的时候自己判断出来。

可以看到X是这一层残差块的输入,也称作F(x)为残差,x为输入值,F(X)是经过第一层线性变化并激活后的输出,该图表示在残差网络中,第二层进行线性变化之后激活之前,F(x)加入了这一层输入值X,然后再进行激活后输出。在第二层输出值激活前加入X,这条路径称作shortcut连接。

ResNet最终更新某一个节点的参数时,由于h(x)=F(x)+x,使得链式求导后的结果如图所示,不管括号内右边部分的求导参数有多小,因为左边的1的存在,并且将原来的链式求导中的连乘变成了连加状态,都能保证该节点参数更新不会发生梯度消失或梯度爆炸现象。

ResNets 相关文章

https://cnblogs.com/gczr/p/10127723.html

网络设计

y = x + v

预测出噪声v,其实是在隐藏层中将干净图片移除 v is additive white Gaussian noise(加性高斯白噪声)


DcCN 基于modify the VGG network

Conv+ReLU:使用64个大小为3×3×c的滤波器被用于生成64个特征图。然后将整流的线性单元(ReLU)用于非线性。
Conv+BN+ReLU:64个大小的过滤器,并且将批量归一化加在卷积和ReLU之间。
Conv:c个大小为3×3×64的滤波器被用于重建输出。
特征:采用残差学习公式来学习R(y),并结合批量归一化来加速训练以及提高去噪性能。
边界伪影:采用0填充的方法解决了边界伪影

参数设计

感受野receptive field大小设置为35x35,相应的深度为17。对于其他一般图像的去噪任务,我们将采用更大的感受野并将深度设置为20.

rfsize=f(out,stride,ksize)=(out−1)∗stride+ksizer
https://blog.csdn.net/bojackhosreman/article/details/70162018

实验部分

DnCNN-S
已知noise level的高斯去噪。设置 σ=15,25,50,patch size为40×40,裁剪128×1600个patch来训练模型

DnCNN-B
盲高斯去噪。设置noise level为σ∈[0,55],patch size为50×50, 裁剪128×3000个patch来训练模型

CDnCNN-B
盲彩色图像去噪模型。用Berkeley segmentation数据集的用432张彩色图像训练,BSD68数据集的彩色图像进行测试。设置noise level在[0,55],patch size为50x50,裁剪128×3000块训练模型

DnCNN-3
这是针对去噪、SISR和JEPG图片解压缩三种任务训练的网络

针对已知noise level 的高斯噪声,DnCNN-S和DnCNN-B都可以获得比竞争方法更好的PSNR结果。我们的DnCNN-S模型在三种噪声水平上均优于BM3D 0.6dB。 特别是,即使是没有已知噪声水平的单一模型,我们的DnCNN-B仍然可以胜过针对已知特定噪声水平进行训练的竞争方法。

对3种分辨率的图片进行降噪处理所需要的时间,DnCNN在CPU实现方面仍然非常具有竞争力。对于GPU时间,所提出的DnCNN实现了非常吸引人的计算效率。

DnCNN-3在三种不同任务与其他方法的对比,可以看出该网络在3种任务中都优于之前的方法,在高斯去噪和JPEG图片解压的效果尤为明显。这说明用一种网络结构处理多种不同类型的图片噪声是可行的。

实验代码

核心代码加注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# 声明一个类,并继承自nn.Module
class DnCNN(nn.Module):
# 定义构造函数
# 构建网络最开始写一个class,然后def _init_(输入的量),然后super(DnCNN,self).__init__()这三句是程式化的存在,
# 初始化
def __init__(self, depth=17, n_channels=64, image_channels=1, use_bnorm=True, kernel_size=3):
##初始化方法使用父类的方法即可,super这里指的就是nn.Module这个基类,第一个参数是自己创建的类名
super(DnCNN, self).__init__()
# 定义自己的网络

kernel_size = 3 # 卷积核的大小 3*3
padding = 1 ##padding表示的是在图片周围填充0的多少,padding=0表示不填充,padding=1四周都填充1维
layers = []
# 四个参数 输入的通道 输出的通道 卷积核大小 padding
# 构建一个输入通道为channels,输出通道为64,卷积核大小为3*3,四周进行1个像素点的零填充的conv1层 #bias如果bias=True,添加偏置
layers.append(
nn.Conv2d(in_channels=image_channels, out_channels=n_channels, kernel_size=kernel_size, padding=padding,
bias=True))
##增加网络的非线性——激活函数nn.ReLU(True) 在卷积层(或BN层)之后,池化层之前,添加激活函数
layers.append(nn.ReLU(inplace=True))
for _ in range(depth - 2):
##构建卷积层
layers.append(
nn.Conv2d(in_channels=n_channels, out_channels=n_channels, kernel_size=kernel_size, padding=padding,
bias=False))
# 加快收敛速度一一批标准化层nn.BatchNorm2d() 输入通道为64的BN层 与卷积层输出通道数64对应
# eps为保证数值稳定性(分母不能趋近或取0),给分母加上的值。默认为1e-4
# momentum: 动态均值和动态方差所使用的动量。默认为0.1
layers.append(nn.BatchNorm2d(n_channels, eps=0.0001, momentum=0.95))
# 增加网络的非线性——激活函数nn.ReLU(True) 在卷积层(或BN层)之后,池化层之前,添加激活函数
layers.append(nn.ReLU(inplace=True))
# 构建卷积层
layers.append(
nn.Conv2d(in_channels=n_channels, out_channels=image_channels, kernel_size=kernel_size, padding=padding,
bias=False))
# 利用nn.Sequential()按顺序构建网络
self.dncnn = nn.Sequential(*layers)
self._initialize_weights() # 调用初始化权重函数

# 定义自己的前向传播函数
def forward(self, x):
y = x
out = self.dncnn(x)
return y - out

def _initialize_weights(self):
for m in self.modules():
## 使用isinstance来判断m属于什么类型【卷积操作】
if isinstance(m, nn.Conv2d):
# 正交初始化(Orthogonal Initialization)主要用以解决深度网络下的梯度消失、梯度爆炸问题
init.orthogonal_(m.weight)
print('init weight')
if m.bias is not None:
# init.constant_常数初始化
init.constant_(m.bias, 0)
## 使用isinstance来判断m属于什么类型【批量归一化操作】
elif isinstance(m, nn.BatchNorm2d):
# init.constant_常数初始化
init.constant_(m.weight, 1)
init.constant_(m.bias, 0)


# 定义损失函数类
class sum_squared_error(_Loss): # PyTorch 0.4.1
"""
Definition: sum_squared_error = 1/2 * nn.MSELoss(reduction = 'sum')
The backward is defined as: input-target
"""

def __init__(self, size_average=None, reduce=None, reduction='sum'):
super(sum_squared_error, self).__init__(size_average, reduce, reduction)

# MSELoss 计算input和target之差的平方
# reduce(bool)- 返回值是否为标量,默认为True size_average(bool)- 当reduce=True时有效。为True时,返回的loss为平均值;为False时,返回的各样本的loss之和。
def forward(self, input, target):
# return torch.sum(torch.pow(input-target,2), (0,1,2,3)).div_(2)
return torch.nn.functional.mse_loss(input, target, size_average=None, reduce=None, reduction='sum').div_(2)

参考文章

论文翻译 :https://blog.csdn.net/qq_40716944/article/details/80098005
他人学习笔记:
https://blog.csdn.net/npu15222808378/article/details/80376929
https://blog.csdn.net/qq_39594939/article/details/96433522

文章作者: EderOdan
文章链接: zaihit.com/2019/11/16/DnCNN%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 丁香树下