天天看点

Pytorch实验3:实现卷积神经网络、空洞卷积、残差网络实验内容实验环境及实验数据集实验过程实验结果实验心得体会参考文献

作者:北京交通大学计算机学院 秦梓鑫

学号:21120390

代码仅供参考,欢迎交流;请勿用于任何形式的课程作业。

  • Pytorch实验一:从零实现Logistic回归和Softmax回归
  • Pytorch实验2:手动实现前馈神经网络、Dropout、正则化、K折交叉验证,解决多分类、二分类、回归任务

目录:实现卷积神经网络、空洞卷积、残差网络

  • 实验内容
    • 二维卷积实验
    • 空洞卷积实验
    • 残差网络实验
  • 实验环境及实验数据集
  • 实验过程
    • 二维卷积实验
      • 手动实现二维卷积
      • 使用torch实现二维卷积
      • 对不同超参数的实验结果对比分析
      • 复现AlexNet模型
      • 与前馈神经网络的效果进行对比
    • 空洞卷积实验
    • 残差网络实验
  • 实验结果
    • 二维卷积实验
      • 手动实现二维卷积
      • 使用torch实现二维卷积
      • 对不同超参数的实验结果对比分析
        • 卷积核大小的影响
        • 卷积层数的影响
      • 复现AlexNet模型
      • 与前馈神经网络的效果进行对比
    • 空洞卷积实验
      • 空洞卷积实验结果
      • 空洞卷积与普通卷积的效果对比
      • 超参数的对比
        • 卷积核大小的影响
        • 卷积层数的影响
    • 残差网络实验
      • 残差网络实验结果
      • 残差空洞卷积实验结果
  • 实验心得体会
  • 参考文献

实验内容

二维卷积实验

卷积神经网络是一种具有局部连接、权重共享特性的深层前馈神经网络。局部连接是指通过引入卷积核,使得每次运算学习的特征只和局部输入相关,极大地减少了计算量和连接层数;权值共享是指学习的特征具有局部不变性,使得对此类特征提取更加充分。

在深度学习中,卷积的定义和分析数学、信号处理中的定义略有不同。一般地,卷积通过互相关(cross-correlation)计算。给定输入 X ∈ R M × N \textbf{X}\in R^{M\times N} X∈RM×N 和卷积核 W ∈ R U × V \textbf{W}\in R^{U\times V} W∈RU×V,它们的互相关为

y i , j = ∑ u = 1 U ∑ v = 1 V w u , v x i + u − 1 , j + v + 1 y_{i,j}= \sum_{u=1}^{U}{ \sum_{v=1}^{V}{w_{u,v}x_{i+u-1,j+v+1}}} yi,j​=u=1∑U​v=1∑V​wu,v​xi+u−1,j+v+1​

也记作:

Y = W ⊗ X \textbf{Y}=\textbf{W}⊗\textbf{X} Y=W⊗X

在考虑特征抽取问题时,互相关和卷积的运算是等价的。在卷积的基础上,可以引入卷积核的步长(stride)和填充(padding)来控制提取的特征图的尺寸。

一般地,卷积的输出大小由参数:卷积核数目( F F F)、卷积核大小( K K K)、步长( S S S)、填充( P P P),和输入图片的通道数( D D D)、宽度( W W W)、高度( H H H)共同决定;其中输出的通道数、宽度、高度分别是:

D o u t = F D_{out}=F Dout​=F

W o u t = W + 2 P − K S + 1 W_{out}=\frac{W+2P-K}{S}+1 Wout​=SW+2P−K​+1

H o u t = H + 2 P − K S + 1 H_{out}=\frac{H+2P-K}{S}+1 Hout​=SH+2P−K​+1

对于每个卷积核,共引入 K × K   × D   + 1 K\times K\ \times D\ +1 K×K ×D +1个参数;由于有F个卷积核,所以共有 F × D × K 2 + F F\times D\times K^2+F F×D×K2+F个参数。

卷积层(Convolution Layer)通常与汇聚层(Pooling Layer)混合使用。汇聚层是一种下采样(down sample)操作,作用是减少参数和特征的数量、加快运算速度和增加模型的鲁棒性。常用的汇聚包括:最大汇聚和平均汇聚。

二维卷积实验的目的是:(1)手动实现二维卷积;(2)使用torch实现二维卷积;(3)进行实验结果的分析、超参数的对比分析;(4)复现AlexNet模型;(5)与前馈神经网络的效果进行对比。

空洞卷积实验

空洞卷积是指通过在卷积核的元素间插入空隙来增加感受野的方法。对于大小为 K × K K\times K K×K的卷积核,在每两个元素中插入 D − 1 D-1 D−1个元素,则空洞卷积核的有效大小是:

K ′ = K + ( K − 1 ) × ( D − 1 ) K'=K+(K-1)×(D-1) K′=K+(K−1)×(D−1)

其中:D是空洞卷积的膨胀率。例如:原卷积核大小为 3 × 3 3\times3 3×3,则空洞卷积核的单边大小为 3 + ( 3 − 1 ) × ( 2 − 1 ) = 5 3+\left(3-1\right)\times\left(2-1\right)=5 3+(3−1)×(2−1)=5。普通卷积可以视为 D = 1 D=1 D=1时的空洞卷积。

Pytorch实验3:实现卷积神经网络、空洞卷积、残差网络实验内容实验环境及实验数据集实验过程实验结果实验心得体会参考文献

由于空洞卷积引入了空隙,有一部分特征没有参与计算,且捕获具有一定间隔的特征可能是不相关的。Panqu Wang等人在2018年提出混合空洞卷积条件(Hybrid Dilated Convolution, HDC),对网络中每一层的膨胀率变化规律做出规定。满足此条件的网络可以规避特征空隙、同时捕获远程和近距离信息。

对于卷积核大小均为K\times K的N层卷积神经网络,其每一层膨胀率组成的序列分别为: [ r 1 , … , r i , … , r n ] \left[r_1,\ldots,r_i,\ldots,r_n\right] [r1​,…,ri​,…,rn​],需满足:

∀ 1 < i < n , M i ≤ K ∀1<i<n,M_i≤K ∀1<i<n,Mi​≤K

其中:

M i = m a x [ r i + 1 − 2 r i , 2 r i − r i + 1 , r i ] Mi=max[r_{i+1}-2r_i,2r_i-r_{i+1},r_i] Mi=max[ri+1​−2ri​,2ri​−ri+1​,ri​]

本实验的目的是:(1)利用torch.nn实现空洞卷积,其中:膨胀率序列(dilation)满足HDC条件,在车辆分类数据集上实验并输出结果;(2)比较空洞卷积和普通卷积的结果;(3)选取1-2个超参数进行对比分析。

残差网络实验

在深度神经网络中,如果期望非线性单元 f ( x ; θ ) f\left(\textbf{x};\theta\right) f(x;θ)去逼近目标函数 h ( x ) h(\textbf{x}) h(x);可以考虑将目标函数分解为:

h ( x ) = x + h ( x ) − x h(\textbf{x})=\textbf{x}+h(\textbf{x})-\textbf{x} h(x)=x+h(x)−x

其中, x \textbf{x} x称为恒等函数(identity function), ( h ( x ) − x ) (h(\textbf{x})-\textbf{x}) (h(x)−x)称为残差函数(residue function)。

根据通用近似定理,非线性单元可以在理论上逼近任意函数。因此,原问题可以进行如下转化:期望 f ( x ) f\left(\textbf{x}\right) f(x)逼近残差函数 ( h ( x ) − x ) (h(\textbf{x})-\textbf{x}) (h(x)−x),使得 f ( x ) + x f\left(\textbf{x}\right)+\textbf{x} f(x)+x逼近目标函数 h ( x ) h(\textbf{x}) h(x)。

一般地,残差网络可以通过跳层连接(shortcut connection)实现。过往的实验表明:残差网络可以解决深层神经网络的梯度爆炸、梯度消失和网络退化问题。

本实验的目的是:(1)复现给定的残差网络架构,分析结果;(2)结合空洞卷积,对比分析实验结果。

实验环境及实验数据集

数据集采用车辆分类数据集,共1358张车辆图片,三个类别。其中:随机抽取70%作训练集,30%作测试集。

实验环境为Linux 3.10.0-1062.el7.x86_64;运算器为NVIDIA GeForce RTX 2080;框架为:Pytorch 1.6.0;采用Pycharm内置的SSH连接进行交互。

实验过程

二维卷积实验

手动实现二维卷积

要实现卷积,需要依次实现:单通道互相关运算、多通道互相关运算、多卷积核互相关运算。需要说明的是,在Open CV读入的图片格式中,通道(channel)是最后一个维度,因此需要对读取的数据进行通道变换。代码段分别如下。

1.	#单通道互相关运算
2.	def corr2d(X,K):
3.	    batch_size,H,W = X.shape
4.	    h,w = K.shape[0],K.shape[1]
5.	    Y = torch.zeros(batch_size,H-h+1,W-w+1).to(device)
6.	    for i in range(Y.shape[1]):
7.	        for j in range(Y.shape[2]):
8.	            area = X[:,i:i+h, j:j+w]
9.	            Y[:,i,j] = (area* K).sum()
10.	    return Y
11.	
12.	#多通道互相关运算
13.	def corr2d_multi_in(X, K):
14.	    res = corr2d(X[:,:,:,0],K[:,:,0])
15.	    for i in range(0,X.shape[3]):
16.	       res += corr2d(X[:,:,:,i],K[:,:,i])
17.	    return res
18.	
19.	#多卷积核互相关运算
20.	def corr2d_multi_in_out(X, K):
21.	    return torch.stack([corr2d_multi_in(X,k) for k in K],dim=1)

           

定义了互相关运算、二维池化运算后,我们可以把卷积封装成卷积层模块。

1.	#定义池化层
2.	def pool2d(X, pool_size, mode='max'):
3.	    p_h, p_w = pool_size,pool_size
4.	    Y = torch.zeros((X.shape[0],X.shape[1],X.shape[2] - p_h + 1, X.shape[3] - p_w + 1))
5.	    for i in range(Y.shape[2]):
6.	        for j in range(Y.shape[3]):
7.	            if mode == 'max':
8.	                Y[:,:,i, j] = X[:,:,i: i + p_h, j: j + p_w].max()
9.	            elif mode == 'avg':
10.	                Y[:,:,i, j] = X[:,:,i: i + p_h, j: j + p_w].mean()
11.	    return Y
12.	
13.	#定义卷积模块
14.	class MyConv2D(nn.Module):
15.	    def __init__(self,in_channels,out_channels,kernel_size):
16.	        super(MyConv2D,self).__init__()
17.	        if isinstance(kernel_size,int):
18.	            kernel_size = (kernel_size,kernel_size)
19.	        self.weight = nn.Parameter(torch.randn((out_channels,in_channels)+kernel_size))
20.	        self.bias = nn.Parameter(torch.randn(out_channels,1,1))
21.	    def forward(self,x):
22.	        y = corr2d_multi_in_out(x,self.weight) +self.bias
23.	        return y

           

使用torch实现二维卷积

使用Pytorch可以直接定义模型完成训练。值得注意的是,由于卷积层的参数和输入图片大小会影响全连接层的参数设置,每一次调整网络架构时,全连接层的维度都需要重新计算。

此次实验中,我们把数据集中的图片压缩为200*100的尺寸,根据公式(3)~(5),我们可以得出全连接层的维度是15532.

1.	class ConvModule(nn.Module):
2.	    def __init__(self):
3.	        super(ConvModule,self).__init__()
4.	        self.conv = nn.Sequential(
5.	            nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, stride=1, padding=0),
6.	            nn.BatchNorm2d(32),
7.	            nn.ReLU(inplace=True)
8.	        )
9.	        self.pool = nn.MaxPool2d(2,2)
10.	        self.fc = nn.Linear(155232, num_classes)
11.	    def forward(self,X):
12.	        out = self.conv(X.float())
13.	        out = self.pool(out)
14.	        out = out.view(out.size(0), -1) # flatten each instance
15.	        out = self.fc(out)
16.	        return out

           

对不同超参数的实验结果对比分析

此部分,我们试验了不同的卷积层层数(在ConvModule中定义)和卷积核大小。

复现AlexNet模型

AlexNet中引入了Dropout、ReLU激活和局部相应归一化技巧,以提升模型的泛化能力。AlexNet中的局部响应归一化(Local Response Normalization, LRN)是一种对于同层的部分神经元的归一化方法。它受到生物学中侧抑制现象的启发,即:活跃神经元对邻近神经元由平衡和约束作用。数学上,可视作在激活函数的基础上,再做一次简单线性回归。此方法现已被池化层取代,很少应用。此处,我们直接采用[1]中的LRN实现.

AlexNet是第一个现代深度网络模型,由Krizhevsky等人在2012年参与ImageNet竞赛中提出。由于当时GPU的运算性能限制,当时被拆分为两个子网络进行分别训练。它包含5个卷积层、3个汇聚层和3个全连接层。

在本实验中,我们主要参照[1]中的AlexNet类进行实验。需要注意的是,多层网络、LRN、ReLU激活可能会带来大规模的梯度爆炸问题,造成损失为NaN。因此,我们在每一层卷积的末尾额外添加了一个BatchNorm层进行归一化处理。

与前馈神经网络的效果进行对比

我们将卷积网络与实验2中的网络(代码如下)进行对比。

1.	layers = collections.OrderedDict([
2.	    ('L1',nn.Linear(154587,192)),
3.	    ('A1',nn.Hardswish()),
4.	    ('drop1', nn.Dropout(p=0.1)),
5.	    ('L2', nn.Linear(192, 96)),
6.	    ('A2', nn.LeakyReLU()),
7.	    ('drop2', nn.Dropout(p=0.1)),
8.	    ('FC', nn.Linear(96,3))
9.	])
10.	AnnNet = nn.Sequential(layers)

           

空洞卷积实验

在torch中,定义空洞卷积较为简便,只需要修改卷积层的dilation参数即可。

残差网络实验

Pytorch实验3:实现卷积神经网络、空洞卷积、残差网络实验内容实验环境及实验数据集实验过程实验结果实验心得体会参考文献

由于复现的残差网络具有一定的子结构相似性,此处,我们进行了模块化处理。首先,我们定义两层卷积的残差网络:

1.	class Residual(nn.Module):
2.	    def __init__(self, input_channels,num_channels, use_1x1conv=False, strides=1, **kwargs):
3.	        super(Residual,self).__init__()
4.	        self.conv1 = nn.Conv2d(input_channels,num_channels, kernel_size=3, padding=1,stride=strides,dilation=2)
5.	        self.conv2 = nn.Conv2d(num_channels,num_channels, kernel_size=3, padding=1,dilation=5)
6.	        if use_1x1conv:
7.	            self.conv3 = nn.Conv2d(num_channels,num_channels, kernel_size=1,stride=strides)
8.	        else:
9.	            self.conv3 = None
10.	        self.bn1 = nn.BatchNorm2d(num_channels)
11.	        self.bn2 = nn.BatchNorm2d(num_channels)
12.	
13.	    def forward(self, X):
14.	        Y = self.bn1(self.conv1(X))
15.	        Y = F.relu(Y)
16.	        Y = self.bn2(self.conv2(Y))
17.	        if self.conv3:
18.	            X = self.conv3(X)
19.	            return F.relu(Y + X)
20.	        return F.relu(Y)

           

之后,我们定义一层上采样、一层卷积的残差网络:

1.	class UpsampleResidual(nn.Module):
2.	    def __init__(self, in_channel,out_channel, kernel_size=3,strides=1, **kwargs):
3.	        super(UpsampleResidual, self).__init__()
4.	        self.l1 = nn.Conv2d(in_channels=in_channel, out_channels=out_channel, kernel_size=kernel_size, stride=strides,padding=1)
5.	        self.l2 = nn.Conv2d(in_channels=out_channel, out_channels=out_channel, kernel_size=kernel_size,stride=1,padding=1)
6.	        self.shortcut = nn.Conv2d(in_channels=in_channel,out_channels=out_channel,kernel_size=1,stride=strides)
7.	    def forward(self,X):
8.	        Y = self.l1(X)
9.	        Y = F.relu(Y)
10.	        Y = self.l2(Y)
11.	        shortcut = self.shortcut(X)
12.	        return F.relu(Y+ shortcut)

           

最终,我们将以上两个模块进行多次复合,得到需要复现的网络:

class ResidualModule(nn.Module):
    def __init__(self):
        super(ResidualModule,self).__init__()
        self.conv1 = nn.Conv2d(3,64,3)
        self.conv2 = Residual(64,64,True)
        self.conv3 = UpsampleResidual(64,128,3)
        self.conv4 = Residual(128,128, True)
        self.conv5 = UpsampleResidual(128,256,3)
        self.conv6 = Residual(256,256, True)
        self.conv7 = UpsampleResidual(256,512,3)
        self.conv8 = Residual(512,512,True)
        self.pool = torch.nn.AvgPool2d(kernel_size=3)
        self.fc = nn.Linear(460800, num_classes)
        self.process = nn.Sequential(self.conv1,self.conv2,self.conv3,
                                     self.conv4,self.conv5,self.conv6,self.conv7,self.conv8)
    def forward(self,X):
        out = self.process(X)
        out = out.view(out.size(0), -1) # flatten each instance
        out = self.fc(out)
        return out
           

实验结果

二维卷积实验

手动实现二维卷积

手动定义的卷积没有进行矩阵优化,时间复杂度为 O ( n 4 ) \mathcal{O}\left(n^4\right) O(n4),对运算时间要求较高。经过手动验证,反向传播过程耗费时间过长,无法预计完成模型训练的时间;因此,这里不能提供具体的手动实现的卷积的结果图表,但是可以观察到loss的计算过程。

Pytorch实验3:实现卷积神经网络、空洞卷积、残差网络实验内容实验环境及实验数据集实验过程实验结果实验心得体会参考文献

使用torch实现二维卷积

Pytorch实验3:实现卷积神经网络、空洞卷积、残差网络实验内容实验环境及实验数据集实验过程实验结果实验心得体会参考文献

实验迭代次数为50次时,在测试集、训练集上的loss和准确率变化如上图所示。可以观察到loss的震荡变化和准确率的逐渐上升;但训练集上的表现始终优于测试集,说明模型存在一定的过拟合现象。在GPU环境下,训练两层卷积神经网络模型训练50次epoch的时间为7分钟左右。

对不同超参数的实验结果对比分析

卷积核大小的影响

Pytorch实验3:实现卷积神经网络、空洞卷积、残差网络实验内容实验环境及实验数据集实验过程实验结果实验心得体会参考文献

可以观察到:尺寸相对较大的卷积核在训练时收敛更快、总体震荡较小,收敛时精度更高、loss更小。尺寸较小的卷积核在训练过程种震荡趋势明显。总体而言,不同尺寸卷积核的模型在此次训练时都存在着过拟合的趋势;可见,是否过拟合和卷积核大小无关。

卷积层数的影响

Pytorch实验3:实现卷积神经网络、空洞卷积、残差网络实验内容实验环境及实验数据集实验过程实验结果实验心得体会参考文献

可以观察到:随着卷积层数的增加:在训练阶段,训练集上的震荡更小;在收敛阶段,loss更低,在训练集上的准确率更高。在测试集上,三种网络的精确度表现近似,但卷积层数更多的网络相对收敛更快。

复现AlexNet模型

Pytorch实验3:实现卷积神经网络、空洞卷积、残差网络实验内容实验环境及实验数据集实验过程实验结果实验心得体会参考文献

在AlexNet上迭代50次的结果如上。可以观察到,随着层数的加深,网络收敛所需的epoch数明显增加。

与前馈神经网络的效果进行对比

Pytorch实验3:实现卷积神经网络、空洞卷积、残差网络实验内容实验环境及实验数据集实验过程实验结果实验心得体会参考文献

实验结果表明,在本实验所用的车辆分类数据集上,迭代20次时,前馈神经网络的效果适中,效果最优的是三层卷积神经网络。AlexNet在预测准确度的效果不佳,可能是原因是:(1)数据集过小,模型无法充分学习特征;(2)训练次数不够,模型没有收敛。但值得注意的是,AlexNet在测试集和训练集上的表现都非常接近,说明没有出现过拟合现象。

空洞卷积实验

空洞卷积实验结果

Pytorch实验3:实现卷积神经网络、空洞卷积、残差网络实验内容实验环境及实验数据集实验过程实验结果实验心得体会参考文献

空洞卷积的实验结果如上。可以观察到:相比于振荡现象明显的普通卷积,空洞卷积模型没有出现过拟合现象,可能是由于空洞卷积能很好地同时提取远近距离特征。

空洞卷积与普通卷积的效果对比

Pytorch实验3:实现卷积神经网络、空洞卷积、残差网络实验内容实验环境及实验数据集实验过程实验结果实验心得体会参考文献

从图像可以看出:空洞卷积的准确度变化更平稳、在测试集上的精度和普通卷积类似;在同样训练次数下,空洞卷积模型训练集上的精度略低于普通卷积。可能原因是:空洞卷积提取了更多的层次特征,需要更多的迭代次数才能收敛。

空洞卷积模型相比于普通卷积在时间上略有增加,每次训练平均实际在8分钟左右;但空洞卷积在不同超参数设定下,运行时间变化较大。经过实验验证,可以发现主要耗时的模块在于反向传播部分。

超参数的对比

卷积核大小的影响

Pytorch实验3:实现卷积神经网络、空洞卷积、残差网络实验内容实验环境及实验数据集实验过程实验结果实验心得体会参考文献

实验结果表明:在空洞卷积模型训练前期,卷积核大小分别设置为3、3、2时在测试集上的表现较优、且变化趋势较稳定;在训练后期,模型逐渐收敛,卷积核大小对实验结果的影响减少。可能原因是:卷积核的尺寸增加等价于模型参数量增加,所以在训练前期可能会有更好的表达力。在空洞卷积中,尺寸大的卷积核并没有表现出普通卷积中出现的巨大性能提升。

卷积层数的影响

Pytorch实验3:实现卷积神经网络、空洞卷积、残差网络实验内容实验环境及实验数据集实验过程实验结果实验心得体会参考文献

可以观察到:在收敛阶段,深层的网络在训练集上准确度和loss上表现更好,但在测试集上没有突出的优势。

残差网络实验

残差网络实验结果

Pytorch实验3:实现卷积神经网络、空洞卷积、残差网络实验内容实验环境及实验数据集实验过程实验结果实验心得体会参考文献

观察实验结果可知:残差网络在训练集上迭代20次即可达到接近99%的准确率;此外,模型收敛速度非常快。可见,转化后的函数逼近问题更容易学习。

残差空洞卷积实验结果

Pytorch实验3:实现卷积神经网络、空洞卷积、残差网络实验内容实验环境及实验数据集实验过程实验结果实验心得体会参考文献

实验结果显示,在训练集上,空洞残差卷积具有更高的精度;在测试集上,空洞残差卷积和普通残差卷积表现相似,两者均具有较高的精度。

实验心得体会

本次实验遇到的最大的问题是计算效率的问题;此外,在实验中,还有一些细节可以优化::

  • 进入卷积神经网络部分后,定义的网络在CPU上的训练时间已远远超出预期;因此,模型迁移到显存、使用GPU进行训练是必须的。在获取服务器使用权限后、进行模型训练后,我直观地体会到GPU带来的效率提升。
  • 手动实现的卷积时间复杂度太高,目前仍然无法使用。本实验主要使用torch的实现,没有采用手动卷积。如果后续有可能对此部分进行优化,需要对矩阵乘法部分进行Image to Column的并行化处理。
  • 数据集的生成。实验中采用的是OpenCV框架进行读取,但其实OpenCV的通道和RGB颜色的定义和torch中有较大的区别。因此,后续可以考虑使用其它的图片读取框架。
  • 超参数的导入。实验中每一层的超参数(卷积层数、卷积核通道数等)是固定的,但这不方便对超参数进行网格搜索。后续可以针对此问题,设置层数的传参。

参考文献

[1] Pytorch手撕经典网络之AlexNet. https://zhuanlan.zhihu.com/p/29786939

[2] 邱锡鹏,神经网络与深度学习,机械工业出版社,https://nndl.github.io/ , 2020.

[3] Krizhevsky A, Sutskever I, Hinton G E. Wang P, Chen P, Yuan Y, et al. Understanding convolution for semantic segmentation[C]//2018 IEEE Winter Conference on Applications of Computer Vision (WACV). IEEE, 2018: 1451-1460.

[4] Zhang, Aston and Lipton, Zachary C. and Li, Mu and Smola, Alexander J. Dive into Deep Learning

继续阅读