全连接、卷积网络优化 + MobilenetV2网络复现+小项目

首先,我们得清楚,为什么要对网络进行优化?这是基于什么样的场景下才需要考虑到的工作。

其次,需要知道,网络优化具体应该怎么做。

模型优化的目的:为了解决深度学习中的容易遇到的 过拟合、梯度爆炸、梯度消失,三类问题,提出的解决办法。

一、过拟合(overfitting)

        模型在训练集上的表现非常好,但是在测试集上的表现很差。通常是由于网络的参数量太大而任务本身难度太小、数据量小,导致模型相对比任务本身来说,太过于聪明,出现模型过度学习到非目标特征本身的特征,使模型输入 x 和模型权重 w 产生了线性相关性,从而导致了过拟合的出现。

        解决模型过拟合的办法①:模型搭建时中加入Dropout

                通过使部分模型权重失活的方式,来减少模型的参数量,以此防止过拟合的出现,需要对任务难度评估和对应的模型参数搭配非常熟悉。

              使用 demo如下,可以看见在0.5的权重失活下,有0.5范围左右的输出变为了0,且失活目标位是随机的。

import torch
from torch import nn


x = torch.randn(1,1,2,10)
dropout = nn.Dropout(0.5)
out = dropout(x)
print(out)

第一次运行 输出:

tensor([[[[ 0.0000, -1.1751, -1.0928, -0.6260,  0.0000, -0.0000, -0.0000,
           -0.0000,  0.0000, -0.0000],
          [-0.0000,  2.9594, -2.7653, -0.0000, -2.6314,  0.0000, -0.0000,
           -0.0000, -3.9002, -0.0000]]]])

第二次运行 输出:

tensor([[[[ 1.2695, -2.5052,  0.0000, -0.0000, -1.9676,  0.0000,  0.0483,
           -0.0000,  4.4935, -2.6391],
          [ 0.0000, -0.0000,  4.3747, -4.0744,  0.5882,  0.0000,  0.0000,
           -0.0000, -0.5639,  0.3098]]]])
可以看见,两次输出的结果失活目标存在变化,证实了Drpout的失活目标是根据失活范围随机选定的。

模型搭建中如何使用Droputout, 下面提供DNN和CNN的demo:

import torch
from torch import nn


class DnnNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.Linear_layers = nn.Sequential(
            nn.Linear(784, 512),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(512, 256),
            nn.ReLU(),
            nn.Linear(256, 10),
            nn.Dropout(0.5)
        )

    def forward(self, x):
        h = self.Linear_layers(x)
        return h


class CnnNet(nn.Module):
    def __init__(self):
        super().__init__()

        self.conv2d_layers = nn.Sequential(
            nn.Conv2d(3, 3, 3),
            nn.ReLU(),
            nn.Dropout2d(0.5),
            nn.Conv2d(3, 3, 3),
            nn.ReLU(),
            nn.Dropout2d(0.5),
            nn.Conv2d(3, 3, 3),
            nn.Dropout2d(0.5)
        )

    def forward(self, x):
        h = self.conv2d_layers(x)
        return h


if __name__ == '__main__':
    x1 = torch.randn(1, 28*28)
    net = DnnNet()
    y1 = net(x1)
    print("y1.shape: ", y1.shape)
    print("y1: ", y1)

    x2 = torch.randn(1, 3, 10, 10)
    net = CnnNet()
    y2 = net(x2)
    print("y2.shape: ", y2.shape)
    print("y2: ", y2)

注意:卷积使用的是:nn.Dropout2d()    而全连接使用的是:   nn.Dropout()

输出结果如下:

y1.shape:  torch.Size([1, 3, 10])
y1:  tensor([[[ 0.0000,  0.1649, -0.0000, -0.0653, -0.0000, -0.0000,  0.0000,
          -0.0000,  0.0709,  0.0000],
         [ 0.0000,  0.0000, -0.1149,  0.0885,  0.0980, -0.0000,  0.0000,
          -0.0000,  0.0000,  0.0000],
         [ 0.0000,  0.0000,  0.0000, -0.0494,  0.2310, -0.0000,  0.0000,
          -0.4139, -0.4306,  0.3792]]], grad_fn=<MulBackward0>)
y2.shape:  torch.Size([1, 3, 4, 4])
y2:  tensor([[[[-0.3224, -0.3224, -0.3224, -0.3224],
          [-0.3224, -0.3224, -0.3224, -0.3224],
          [-0.3224, -0.3224, -0.3224, -0.3224],
          [-0.3224, -0.3224, -0.3224, -0.3224]],

         [[ 0.0000,  0.0000,  0.0000,  0.0000],
          [ 0.0000,  0.0000,  0.0000,  0.0000],
          [ 0.0000,  0.0000,  0.0000,  0.0000],
          [ 0.0000,  0.0000,  0.0000,  0.0000]],

         [[ 0.1046,  0.1046,  0.1046,  0.1046],
          [ 0.1046,  0.1046,  0.1046,  0.1046],
          [ 0.1046,  0.1046,  0.1046,  0.1046],
          [ 0.1046,  0.1046,  0.1046,  0.1046]]]], grad_fn=<MulBackward0>)

        解决模型过拟合的办法②:正则化

        L1正则化:会直接把数据压到0上, 更多用在模型的稀疏化处理,模型过于复杂,需要减小时使用。

        L2正则化:L2正则化可以将数据限制到无限接近0的位置,但是并不会等于0

        总结:如果是为了防止过拟合,就用L2,希望把W变小,减弱模型的学习能力就行了,但是不希望变成0,变成0,会导致模型的退化,Relu激活函数的缺陷就是,会把结果变为0.

        如何给模型添加正则化?在模型的优化器中已经加入了正则化参数,如下:

from torchvision import models
import torch

m = models.inception_v3()
opt = torch.optim.Adam(m.parameters(), weight_decay=0.2)

print("torch.optim.Adam(weight_decay=0.2): ", opt)

        默认使用L2正则化,默认给了一个值是0,可以给0-1之间的值 ,而在0 - 1之间的原因是:        

               1. 数值稳定性:过大的 weight_decay 值可能会导致数值不稳定或权重过快减小,从而影响模型训练的效果。小的 weight_decay 值能够平滑地调整权重,避免剧烈的权重变化。

               2. 正则化效果weight_decay 值过大可能会过度惩罚权重,使得模型的表达能力降低,导致欠拟合。适中的 weight_decay 值能够有效地防止过拟合,同时又不会过多地限制模型的表达能力。

               3. 常见实践:在实践中,weight_decay 通常被设为一个较小的值(例如 0.0001 到 0.01),这已经证明在许多任务中能够有效地改善模型性能。

        注意:测试模型的时候,一定要加net.eval()

二、梯度爆炸

链式法则:两个函数组合起来的复合函数,导数等于里面函数代入外函数值的导乘以里面函数之导数。

由于链式法则在计算梯度时的影响,在神经网络的层数较多时,意味着连乘越多,因此很容易导致其中某一个比较大的值在连乘过程中被快速放大,导致模型出现了梯度爆炸,也就是超出了计算机所能支持的最大计算精度。

解决梯度爆炸的办法:

BatchNomal:批归一化

在模型中加入BatchNomal的作用:

1.计算时压力就不会太大,就不容易精度溢出。

2.用sigmold举例,如果数据做了归一化在激活的时候,数据很容易落在梯度比较大的区间,增大学习梯度。

3.我们希望模型的输入、w、输出的h,都能在 -1 - 1之间,这样自始至终,计算都不会太大或太小,有利于计算机计算,且网络也会学的又快又好。

4.凸显数据的差异性,提升特征提取能力,加速学习。

5.可以解决梯度爆炸,梯度爆炸:是由于梯度过大导致的,为了保证不容易出现梯度消失和梯度爆炸,首先就是把梯度限定到某个范围之间,这个范围就是-1 - 1之间。因为把每一层的特征都抑制在了-1 - 1之间,权重初始化的时候也被限制在一定范围内,x和w都被限制到类似于做了归一化的分布中,得到的结果h就不会大,反向求导的梯度就不会大。因为BachNomal是把大的分布抑制在-1 - 1之间,有可能会出0,因此就会出现梯度消失,因此,BachNomal不会解决梯度消失。全连接或者卷积,推荐每一层网络都加上BachNomal。

注意:如果加了BachNomal,最好将网络中的bias设置为False,因为BachNomal中带了滑动系数,如果网络再进行偏移,有可能偏移会过大了。
模型设计中使用BatchNomal的demo如下:

import torch
from torch import nn


class DnnNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.Linear_layers = nn.Sequential(
            nn.Linear(784, 512),
            nn.BatchNorm1d(512),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(512, 256),
            nn.BatchNorm1d(256),
            nn.ReLU(),
            nn.Linear(256, 10),
            nn.BatchNorm1d(10),
            nn.Dropout(0.5)
        )

    def forward(self, x):
        h = self.Linear_layers(x)
        return h


class CnnNet(nn.Module):
    def __init__(self):
        super().__init__()

        self.conv2d_layers = nn.Sequential(
            nn.Conv2d(3, 16, 3),
            nn.BatchNorm2d(16),
            nn.ReLU(),
            nn.Dropout2d(0.5),
            nn.Conv2d(16, 32, 3),
            nn.BatchNorm2d(32),
            nn.ReLU(),
            nn.Dropout2d(0.5),
            nn.Conv2d(32, 64, 3),
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.Dropout2d(0.5)
        )

    def forward(self, x):
        h = self.conv2d_layers(x)
        return h


if __name__ == '__main__':
    x1 = torch.randn(3, 784)
    net = DnnNet()

    y1 = net(x1)
    print("y1.shape: ", y1.shape)
    print("y1: ", y1)

    x2 = torch.randn(1, 3, 10, 10)
    net = CnnNet()
    y2 = net(x2)
    print("y2.shape: ", y2.shape)
    print("y2: ", y2)

三、梯度消失

链式法则:两个函数组合起来的复合函数,导数等于里面函数代入外函数值的导乘以里面函数之导数。

由于链式法则在计算梯度时的影响,在神经网络的层数较多时,意味着连乘越多,因此很容易导致其中某一个比较小的值在连乘过程中被快速缩小,直至变为0,从而导致模型出现了梯度爆炸,也就是超出了计算机所能支持的最小计算精度。

抑制梯度消失的方法:

        1.从激活函数角度出发,不要用sigold和tanh, 用ReLU和PReLU也可以延缓

        2.模型中加BachNomal一定程度上也可以抑制梯度消失,因为缩小的没有那么快了

解决梯度爆炸的办法:

只有给模型加残差,将链式法则由原本的连乘变为连加,确保权重不会等于0.

残差网络:

resnet18复现代码

import torch
from torch import nn


class ResBlock(nn.Module):
    def __init__(self, in_c):
        super().__init__()
        self.layers = nn.Sequential(
            nn.Conv2d(in_c, in_c, 3, 1, padding=1, bias=False),
            nn.BatchNorm2d(in_c),
            nn.ReLU(),
            nn.Conv2d(in_c, in_c, 3, 1, padding=1, bias=False),
            nn.BatchNorm2d(in_c)
        )

    def forward(self, x):
        return self.layers(x) + x


class down_sample(nn.Module):
    def __init__(self, in_c):
        super().__init__()
        self.layers = nn.Sequential(
            nn.Conv2d(in_c, 2 * in_c, 3, 2, padding=1, bias=False),
            nn.BatchNorm2d(2 * in_c),
            nn.ReLU()
        )

    def forward(self, x):
        return self.layers(x)


class resNet18(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False),
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.MaxPool2d(3, 2, 1)
        )
        self.hidden_layer = nn.Sequential(
            ResBlock(64),
            ResBlock(64),
            down_sample(64),
            ResBlock(128),
            ResBlock(128),
            down_sample(128),
            ResBlock(256),
            ResBlock(256),
            down_sample(256),
            ResBlock(512),
            ResBlock(512)
        )

    def forward(self, x):
        return self.hidden_layer(self.conv1(x))


if __name__ == '__main__':
    net = resNet18()
    x = torch.randn(1,3,224, 224)
    y = net(x)
    print(y.shape)

 resnet34复现代码:

from torch import nn
import torch


class ResBlock(nn.Module):
    def __init__(self, in_c):
        super().__init__()
        self.layer = nn.Sequential(
            nn.Conv2d(in_c, in_c, 3, 1, padding=1, bias=False),
            nn.BatchNorm2d(in_c),
            nn.ReLU(),
            nn.Conv2d(in_c, in_c, 3, 1, padding=1, bias=False),
            nn.BatchNorm2d(in_c)
        )

    def forward(self, x):
        return self.layer(x) + x


class down_sample(nn.Module):
    def __init__(self, in_c):
        super().__init__()
        self.layer = nn.Sequential(
            nn.Conv2d(in_c, 2 * in_c, 3, 2, padding=1, bias=False),
            nn.BatchNorm2d(2 * in_c),
            nn.ReLU()
        )

    def forward(self, x):
        return self.layer(x)


class resNet34(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Sequential(
            nn.Conv2d(3, 64, 7, 2, padding=3, bias=False),
            nn.BatchNorm2d(64),
            nn.ReLU()
        )
        self.hidden_layer = nn.Sequential(
            ResBlock(64),
            ResBlock(64),
            ResBlock(64),
            down_sample(64),
            ResBlock(128),
            ResBlock(128),
            ResBlock(128),
            ResBlock(128),
            down_sample(128),
            ResBlock(256),
            ResBlock(256),
            ResBlock(256),
            ResBlock(256),
            ResBlock(256),
            ResBlock(256),
            down_sample(256),
            ResBlock(512),
            ResBlock(512),
            ResBlock(512),
        )

    def forward(self, x):
        print(self.conv1(x).shape)
        return self.hidden_layer(self.conv1(x))


if __name__ == '__main__':
    net = resNet34()
    x = torch.randn(1,3,228,228)
    y = net(x)
    print(y.shape)

总结:基于以上,结合Dropout 、 BatchNomal 、 残差三种方式,就可以完美的避免模型搭建经常遇到的三类问题,过拟合、梯度爆炸、梯度消失。

网络优化技巧:

1*1 和 3*3 的卷积对比:

 可以发现,1*1的卷积并不会做像素融合

分离卷积:

分离卷积的前提:

1.组数需要能被输入和输出通道整除
2.卷积至少要保证存在通道融合,否则通道完全分离,拿到的特征是十分片面的。

深度可分离卷积:输入通道,输出通道,分组,都一样

通道混洗:

1.将通道转为深度可分离卷积,输入通道、输出通道、组数完全 一 一 对应
2.交叉的方法:①神经网络 ②reshape,一般第二种比较多,因为1*1的卷积也会有一个学习的过程,而通道混洗,只是为了改变位置,而不是特征本身,因此,reshape更合适。

高效的网络模块: 

ResNet瓶颈结构:

MobilenetV1: 

MobilenetV2:

ShuffleNet:

​​​​​​​

下面附上MobilenetV2的复现代码:

import torch
from torch import nn


config = [
    [-1, 32, 1, 2],
    [1, 16, 1, 1],
    [6, 24, 2, 2],
    [6, 32, 3, 2],
    [6, 64, 4, 2],
    [6, 96, 3, 1],
    [6, 160, 3, 2],
    [6, 320, 1, 1]
]


class Block(nn.Module):
    def __init__(self, p_c, i, t, c, n, s):  # i代表当前第几个block
        super().__init__()
        # 每个重复的最后一次负责下采样
        # 所以i = n-1 的时候进行操作
        self.i = i
        self.n = n

        _s = s if i == n-1 else 1  # 判断是否是最后一次重复的步长,是最后一次重复的步长为2
        # 判断是否是最后一次重复,最后一次重复负责通道变换为下一层的输入
        _c = c if i == n-1 else p_c

        _p_c = p_c * t  # 输入通道扩增倍数

        self.layer = nn.Sequential(
            nn.Conv2d(p_c, _p_c, 1, 1, bias=False),
            nn.BatchNorm2d(_p_c),
            nn.ReLU6(),
            nn.Conv2d(_p_c, _p_c, 3, _s, padding=1, groups=_p_c, bias=False),
            nn.BatchNorm2d(_p_c),
            nn.ReLU6(),
            nn.Conv2d(_p_c, _c, 1, 1, bias=False),
            nn.BatchNorm2d(_c)
        )

    def forward(self, x):
        if self.i == self.n-1:
            return self.layer(x)
        else:
            return self.layer(x) + x


class MobilenetV2(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.input_layer = nn.Sequential(
            nn.Conv2d(3, 32, 3, 2, padding=1, bias=False),
            nn.BatchNorm2d(32),
            nn.ReLU6()
        )
        self.blocks = []
        p_c = config[0][1]
        for t, c, n, s in config[1:]:
            for i in range(n):
                self.blocks.append(Block(p_c, i, t, c, n, s))
            p_c = c

        self.hidden_layer = nn.Sequential(*self.blocks)

        self.output_layer = nn.Sequential(
            nn.Conv2d(320, 1280, 1, 1, bias=False),
            nn.BatchNorm2d(1280),
            nn.ReLU6(),
            nn.AvgPool2d(7, 1),
            nn.Conv2d(1280, 10, 1, 1, bias=False)
        )

    def forward(self, x):
        h = self.input_layer(x)
        print(h.shape)
        h = self.hidden_layer(h)
        print(h.shape)
        h = self.output_layer(h)
        h = h.reshape(-1, 10)
        return h


if __name__ == '__main__':
    net = MobilenetV2(config)
    x = torch.randn(1,3, 224, 224)
    y = net(x)
    print(y.shape)



还差使用复现的MobileNet网络实现小项目的训练、测试代码,后面抽空补上~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

埋头苦干小金哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值