作业四:经典卷积神经网络

一、AlexNet网络

(1)学习特征:在具有一定复杂度的神经网络中,图像特征应该是由多个共同学习的神经网络组成,每一层都会有可学参数,例如在机器视觉中,最底层可能会检测出边缘颜色等信息,更高层则建立在这些底层表示的基础上提取更负责的特征,最终学习出图像的综合表示。

(2)数据:lenet训练的数据集共有7万,对于深度神经网络训练明显不足,能够提取许多特征的深度模型需要大量的有标签数据。ImageNet数据集发布提供了深度模型发展的条件。

(3)网络模型:与lenet网络相似,ALexNet由五个卷积三个全连接组成。实现与结构如图所示:

net = nn.Sequential(
    nn.Conv2d(1, 96, kernel_size=11, stride=4, padding=1), nn.ReLU(),
    nn.MaxPool2d(kernel_size=3, stride=2),
    nn.Conv2d(96, 256, kernel_size=5, padding=2), nn.ReLU(),
    nn.MaxPool2d(kernel_size=3, stride=2),
    nn.Conv2d(256, 384, kernel_size=3, padding=1), nn.ReLU(),
    nn.Conv2d(384, 384, kernel_size=3, padding=1), nn.ReLU(),
    nn.Conv2d(384, 256, kernel_size=3, padding=1), nn.ReLU(),
    nn.MaxPool2d(kernel_size=3, stride=2),
    nn.Flatten(),
    nn.Linear(6400, 4096), nn.ReLU(),
    nn.Dropout(p=0.5),
    nn.Linear(4096, 4096), nn.ReLU(),
    nn.Dropout(p=0.5),
    nn.Linear(4096, 10))

  

 (4)激活函数、容量控制和预处理

 与lenet不同,该模型选用的relu函数,ReLU激活函数的计算更简单,当使用不同的参数初始化方法时,ReLU激活函数使训练模型更加容易。

为了控制模型复杂度,该模型在全连接层后加入了Dropout层。

为了使得模型更健壮,AlexNet在训练时增加了大量的图像增强数据,如裁剪等,更大的样本量有效地减少了过拟合。

二、VGG神经网络

基于AlexNet的后三层卷积层和模块化的思想设计出VGG网络。

(1)VGG块:由指定卷积层数的网络块最后加窗口为2X2、步幅为2的最大值池化层构成。每一个网络块由一个3X3的卷积核、padding为1的卷积层再加relu激活函数组成。实现与结构如下:

def vgg_block(num_convs, in_channels, out_channels):
    layers = []
    for _ in range(num_convs):
        layers.append(nn.Conv2d(in_channels, out_channels,
                                kernel_size=3, padding=1))
        layers.append(nn.ReLU())
        in_channels = out_channels
    layers.append(nn.MaxPool2d(kernel_size=2,stride=2))
    return nn.Sequential(*layers)

 

 (2)VGG网络:由指定数的VGG块组成,最后加三个全连接层。实现与结构如下:

conv_arch = ((1, 64), (1, 128), (2, 256), (2, 512), (2, 512))#指定每个块的参数
def vgg(conv_arch):
    conv_blks = []#存储卷积层
    in_channels = 1
    for (num_convs, out_channels) in conv_arch:
        conv_blks.append(vgg_block(num_convs, in_channels, out_channels))
        in_channels = out_channels
    return nn.Sequential(
        *conv_blks, nn.Flatten(),
        nn.Linear(out_channels * 7 * 7, 4096), nn.ReLU(), nn.Dropout(0.5),
        nn.Linear(4096, 4096), nn.ReLU(), nn.Dropout(0.5),
        nn.Linear(4096, 10))

net = vgg(conv_arch)

 

 三、NiN神经网络

与VGG相似,NIN使用了1X1的卷积神经网络,更加多的表示了空间结构。

(1)NiN块:在每个像素位置应用一个全连接层( 1×1 卷积层),做到将空间维度中的每个像素视为单个样本,将通道维度视为不同特征。与VGG块设计思路相同,其由一个指定卷积核大小的卷积层和两个1×1 卷积层组成。实现与结构如下:

def nin_block(in_channels, out_channels, kernel_size, strides, padding):
    return nn.Sequential(
        nn.Conv2d(in_channels, out_channels, kernel_size, strides, padding),
        nn.ReLU(),
        nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU(),
        nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU())

 

 (2)NiN网络:由指定NiN块组成,在每一块(除最后)后加上窗口形状为 3×3,步幅为 2的最大值池化层,最后一块的输出通道必须与标签类别数相同,最后使用全局平均池化层。实现与结构如下:

net = nn.Sequential(
    nin_block(1, 96, kernel_size=11, strides=4, padding=0),
    nn.MaxPool2d(3, stride=2),
    nin_block(96, 256, kernel_size=5, strides=1, padding=2),
    nn.MaxPool2d(3, stride=2),
    nin_block(256, 384, kernel_size=3, strides=1, padding=1),
    nn.MaxPool2d(3, stride=2),
    nn.Dropout(0.5),
    nin_block(384, 10, kernel_size=3, strides=1, padding=1),
    nn.AdaptiveAvgPool2d((1, 1)),
    nn.Flatten())

 四、GoogLeNet神经网络

结合NiN中串联网络的思想,有时不同的卷积核大小效果不同,该模型在每一层则使用了不同的卷积核。

(1) Inception块:由四条并行路径组成,前三条路径使用窗口大小为 1×1、3×3 和 5×5 的卷积层,从不同空间大小中提取信息。中间的两条路径在输入上执行 1×1 卷积,以减少通道数,从而降低模型的复杂性。第四条路径使用 3×3 最大池化层,然后使用 1×1 卷积层来改变通道数。 最后四条路在通道维度合并。实现与结构如下:

class Inception(nn.Module):
    def __init__(self, in_channels, c1, c2, c3, c4, **kwargs):
        super(Inception, self).__init__(**kwargs)
        self.p1_1 = nn.Conv2d(in_channels, c1, kernel_size=1)
        self.p2_1 = nn.Conv2d(in_channels, c2[0], kernel_size=1)
        self.p2_2 = nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1)
        self.p3_1 = nn.Conv2d(in_channels, c3[0], kernel_size=1)
        self.p3_2 = nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2)
        self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)
        self.p4_2 = nn.Conv2d(in_channels, c4, kernel_size=1)

    def forward(self, x):
        p1 = F.relu(self.p1_1(x))
        p2 = F.relu(self.p2_2(F.relu(self.p2_1(x))))
        p3 = F.relu(self.p3_2(F.relu(self.p3_1(x))))
        p4 = F.relu(self.p4_2(self.p4_1(x)))
        return torch.cat((p1, p2, p3, p4), dim=1)

 

 (2)GoogLeNet模型: 9 个Inception块和全局平均池化层,最后使用全连接层输出。实现与结构如下:

b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
                   nn.ReLU(),
                   nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
b2 = nn.Sequential(nn.Conv2d(64, 64, kernel_size=1),
                   nn.ReLU(),
                   nn.Conv2d(64, 192, kernel_size=3, padding=1),
                   nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
b3 = nn.Sequential(Inception(192, 64, (96, 128), (16, 32), 32),
                   Inception(256, 128, (128, 192), (32, 96), 64),
                   nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
b4 = nn.Sequential(Inception(480, 192, (96, 208), (16, 48), 64),
                   Inception(512, 160, (112, 224), (24, 64), 64),
                   Inception(512, 128, (128, 256), (24, 64), 64),
                   Inception(512, 112, (144, 288), (32, 64), 64),
                   Inception(528, 256, (160, 320), (32, 128), 128),
                   nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
b5 = nn.Sequential(Inception(832, 256, (160, 320), (32, 128), 128),
                   Inception(832, 384, (192, 384), (48, 128), 128),
                   nn.AdaptiveAvgPool2d((1,1)),
                   nn.Flatten())

net = nn.Sequential(b1, b2, b3, b4, b5, nn.Linear(1024, 10))

 五、批量归一化

 在深层的神经网络中,反向传播过程中顶层网络梯度合适能够有效收敛,但是底层网络因为梯度的传递在不断缩小导致可能会梯度消失,最终导致底层网络收敛过慢,而且底层网络一边,顶层就会跟着一起变。批量归一化就是解决这个问题:批量归一化利用小批量的均值和标准差,不断调整神经网络的中间输出,使整个神经网络各层的中间输出值更加稳定。

(1)原理:在每次训练迭代中,我们首先归一化输入,即通过减去其均值并除以其标准差,其中两者均基于当前小批量处理。 接下来,我们应用比例系数和比例偏移。BN(x)=γ*(x−μ^B)σ^B+β.注意的是使用大小为 1 的小批量应用批量归一化,我们将无法学到任何东西。 这是因为在减去均值之后,每个隐藏单元将为 0。所以,只有使用足够大的小批量,批量归一化这种方法才是有效且稳定的。

(2)作用在全连接层和卷积层的输出上,激活函数前,也可作用在全连接层和卷积层输入上。对于全连接层作用在特征维,对与卷积层作用在通道维。

(3)实现

#从零实现
# batch_norm函数
def batch_norm(X, gamma, beta, moving_mean, moving_var, eps, momentrun):
    # momentrun更新mean和var,eps为了避免除0
    if not torch.is_grad_enabled():
        X_hat = (X - moving_mean) / torch.sqrt(moving_var + eps)
    else:
        assert len(X.shape) in (2, 4)  # 要么全连接层,要么卷积层
        if len(X.shape) == 2:
            mean = X.mean(dim=0)
            var = ((X - mean) ** 2).mean(dim=0)
        else:
            mean = X.mean(dim=(0, 2, 3), keepdim=True)  # 每个通道求,得出(1,n,1,1)
            var = ((X - mean) ** 2).mean(dim=(0, 2, 3), keepdim=True)

        X_hat = (X - mean) / torch.sqrt(var + eps)
        moving_mean = momentrun * moving_mean + (1.0 - momentrun) * mean
        moving_var = momentrun * moving_var + (1.0 - momentrun) * var
    Y = gamma * X_hat + beta
    return Y, moving_mean.data, moving_var.data


# batch_norm层
class BatchNorm(nn.Module):
    def __init__(self, num_features, num_dims):
        super().__init__()
        if num_dims == 2:
            shape = (1, num_features)
        else:
            shape = (1, num_features, 1, 1)
        self.gamma = nn.Parameter(torch.ones(shape))
        self.beta = nn.Parameter(torch.zeros(shape))
        self.moving_mean = torch.zeros(shape)
        self.moving_var = torch.ones(shape)

    def forward(self, X):
        if self.moving_mean.device != X.device:
            self.moving_mean = self.moving_mean.to(X.device)
            self.moving_var = self.moving_var.to(X.device)
        Y, self.moving_mean, self.moving_var = batch_norm(X, self.gamma, self.beta, self.moving_mean, self.moving_var,
                                                          eps=1e-5, momentrun=0.9)
        return Y


net = nn.Sequential(
    nn.Conv2d(1, 6, kernel_size=5), BatchNorm(6, num_dims=4),
    nn.Sigmoid(), nn.MaxPool2d(kernel_size=2, stride=2),
    nn.Conv2d(6, 16, kernel_size=5), BatchNorm(16, num_dims=4),
    nn.Sigmoid(), nn.MaxPool2d(kernel_size=2, stride=2),
    nn.Flatten(), nn.Linear(16 * 4 * 4, 120), BatchNorm(120, num_dims=2),
    nn.Sigmoid(), nn.Linear(120, 84), BatchNorm(84, num_dims=2),
    nn.Sigmoid(), nn.Linear(84, 10)

)
#简洁实现
net = nn.Sequential(
    nn.Conv2d(1, 6, kernel_size=5), nn.BatchNorm2d(6),
    nn.Sigmoid(), nn.MaxPool2d(kernel_size=2, stride=2),
    nn.Conv2d(6, 16, kernel_size=5), nn.BatchNorm2d(16),
    nn.Sigmoid(), nn.MaxPool2d(kernel_size=2, stride=2),
    nn.Flatten(), nn.Linear(16 * 4 * 4, 120), nn.BatchNorm2d(120),
    nn.Sigmoid(), nn.Linear(120, 84), nn.BatchNorm2d(84),
    nn.Sigmoid(), nn.Linear(84, 10)

)

 六、残差网络ResNet

 (1)原理:对于一个具有更深层次的卷积神经网络,因为模型过于复杂导致过拟合,训练效果不如浅一些的神经网络,根据这一问题提出残差网络,使用嵌套模式增加一条线路使得当前训练效果不低于之前的训练。

(2)残差快:2 个有相同输出通道数的 3×3 卷积层,每个卷积层后接一个批量归一化层和 ReLU 激活函数。然后通过跨层数据通路,跳过这 2 个卷积运算,将输入直接加在最后的 ReLU 激活函数前。如果想改变通道数,就需要引入一个额外的 1×1 卷积层来将输入变换成需要的形状后再做相加运算。实现与结构如下:

class Residual(nn.Module):  
    def __init__(self, input_channels, num_channels,
                 use_1x1conv=False, strides=1):
        #use_1x1conv=False是否改变通道数
        super().__init__()
        self.conv1 = nn.Conv2d(input_channels, num_channels,
                               kernel_size=3, padding=1, stride=strides)
        self.conv2 = nn.Conv2d(num_channels, num_channels,
                               kernel_size=3, padding=1)
        if use_1x1conv:
            self.conv3 = nn.Conv2d(input_channels, num_channels,
                                   kernel_size=1, stride=strides)
        else:
            self.conv3 = None
        self.bn1 = nn.BatchNorm2d(num_channels)
        self.bn2 = nn.BatchNorm2d(num_channels)
        self.relu = nn.ReLU(inplace=True)

    def forward(self, X):
        Y = F.relu(self.bn1(self.conv1(X)))
        Y = self.bn2(self.conv2(Y))
        if self.conv3:
            X = self.conv3(X)
        Y += X
        return F.relu(Y)

 

 (3)模型:前两层跟之前介绍的 GoogLeNet 中的一样: 在输出通道数为 64、步幅为 2 的 7×7 卷积层后,接步幅为 2 的 3×3 的最大池化层。 不同之处在于 ResNet 每个卷积层后增加了批量归一化层。 然后使用 4 个由残差块组成的模块,每个模块使用若干个同样输出通道数的残差块。 第一个模块的通道数同输入通道数一致。之后的每个模块在第一个残差块里将上一个模块的通道数翻倍,并将高和宽减半。最后,与 GoogLeNet 一样,在 ResNet 中加入全局平均汇聚层,以及全连接层输出。实现与结构如下:

def resnet_block(input_channels, num_channels, num_residuals,
                 first_block=False):
    blk = []
    for i in range(num_residuals):
        if i == 0 and not first_block:
            blk.append(Residual(input_channels, num_channels,
                                use_1x1conv=True, strides=2))
        else:
            blk.append(Residual(num_channels, num_channels))
    return blk
b2 = nn.Sequential(*resnet_block(64, 64, 2, first_block=True))
b3 = nn.Sequential(*resnet_block(64, 128, 2))
b4 = nn.Sequential(*resnet_block(128, 256, 2))
b5 = nn.Sequential(*resnet_block(256, 512, 2))

 (4)resnet能训练深层次的原因:传统的深度网络计算梯度时,如图中y所示,是通过相乘,这就导致有一个模块梯度特别小时,会导致之后的梯度也会消失。resnet则将乘法换成了加法,如图y'所示,当某一块特别小时后边的任然可以由一个相对合适的梯度用来更新。

七、猫狗大战

在上一次的基础上,修改网络模型,因为gpu内存太小,batchsize改为32,参照resnet18使用了14层网络,取消了第二个残差块(调试使用前10层时,网络效果不好,猜测时因为前10层最终通道数只有128,提取特征不够)。修改代码如下:

class Residual(nn.Module):
    def __init__(self, input_channels, num_channels,
                 use_1x1conv=False, strides=1):
        super().__init__()
        self.conv1 = nn.Conv2d(input_channels, num_channels,
                               kernel_size=3, padding=1, stride=strides)
        self.conv2 = nn.Conv2d(num_channels, num_channels,
                               kernel_size=3, padding=1)
        if use_1x1conv:
            self.conv3 = nn.Conv2d(input_channels, num_channels,
                                   kernel_size=1, stride=strides)
        else:
            self.conv3 = None
        self.bn1 = nn.BatchNorm2d(num_channels)
        self.bn2 = nn.BatchNorm2d(num_channels)
        self.relu = nn.ReLU(inplace=True)

    def forward(self, X):
        Y = F.relu(self.bn1(self.conv1(X)))
        Y = self.bn2(self.conv2(Y))
        if self.conv3:
            X = self.conv3(X)
        Y += X
        return F.relu(Y)


def resnet_block(input_channels, num_channels, num_residuals,
                 first_block=False):
    blk = []
    for i in range(num_residuals):
        if i == 0 and not first_block:
            blk.append(Residual(input_channels, num_channels,
                                use_1x1conv=True, strides=2))
        else:
            blk.append(Residual(num_channels, num_channels))
    return blk


class net(nn.Module):
    def __init__(self):
        super().__init__()
        self.b1 = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3),
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        )
        # self.b2 = nn.Sequential(*resnet_block(64, 64, 2, first_block=True))
        self.b3 = nn.Sequential(*resnet_block(64, 128, 2))
        self.b4 = nn.Sequential(*resnet_block(128, 256, 2))
        self.b5 = nn.Sequential(*resnet_block(256, 512, 2))
        self.bf = nn.Sequential(
            nn.AdaptiveAvgPool2d((1, 1)),
            nn.Flatten(),
            nn.Linear(512, 2)
        )

    def forward(self, X):
        X = self.b1(X)
        # X = self.b2(X)
        X = self.b3(X)
        X = self.b4(X)
        X = self.b5(X)
        X = self.bf(X)
        return X

如果图:跑了50次,最终准确率在90%上下

 

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值