Pytorch使用Inception及Residual 提高模型精确度 解决代码冗余 梯度为零问题

初学者学习Pytorch系列

第一篇 Pytorch初学简单的线性模型 代码实操
第二篇 Pytorch实现逻辑斯蒂回归模型 代码实操
第三篇 Pytorch实现多特征输入的分类模型 代码实操
第四篇 Pytorch实现Dataset数据集导入 必要性解释及代码实操
第五篇 Pytorch实现多分类问题 样例解释 通俗易懂 新手必看
第六篇 Pytorch使用CNN实现基本的MNIST数据集学习 通俗理解CNN
第七篇 Pytorch使用CNN实现Inception及Residual 解决代码冗余 梯度为零



前言

  1. 本文依旧使用Mnist数据集+CNN卷积神经网络学习多分类问题。但是本文会加入CNN的进阶内容。Mnist数据集相当于C语言的Hello World,是初学者必学的一个集合。
    MNIST数据集
    上图为MNIST数据集样图

一、先上代码

使用Residual的代码如下,重点在Residual类

import torch
from torchvision import transforms
from torchvision import datasets
import torch.nn.functional as F
from torch.utils.data import DataLoader
import torch.optim as optim
import matplotlib.pyplot as plt

batch_size = 64

mnist_transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize([0.1307, ], [0.3081, ])])
train_datasets = datasets.MNIST("../data/mnist", train=True, download=True, transform=mnist_transform)
test_datasets = datasets.MNIST("../data/mnist", train=False, download=True, transform=mnist_transform)
train_dataloader = DataLoader(dataset=train_datasets, batch_size=batch_size, shuffle=True)
test_dataloader = DataLoader(dataset=test_datasets, batch_size=batch_size, shuffle=False)


class Residual(torch.nn.Module):
    def __init__(self, channels):
        super(Residual, self).__init__()
        self.channels = channels
        self.conv1 = torch.nn.Conv2d(channels, channels, kernel_size=3, padding=1)
        self.conv2 = torch.nn.Conv2d(channels, channels, kernel_size=3, padding=1)

    def forward(self, x):
        y = F.relu(self.conv1(x))
        y = self.conv2(x)
        return F.relu(x + y)


class Net(torch.nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = torch.nn.Conv2d(1, 16, kernel_size=5)
        self.conv2 = torch.nn.Conv2d(16, 32, kernel_size=5)
        self.rBlock1 = Residual(16)
        self.rBlock2 = Residual(32)
        self.pooling = torch.nn.MaxPool2d(2)
        self.linear = torch.nn.Linear(512, 10)

    def forward(self, x):
        in_size = x.size(0)
        x = F.relu(self.pooling(self.conv1(x)))
        x = self.rBlock1(x)
        x = F.relu(self.pooling(self.conv2(x)))
        x = self.rBlock2(x)
        x = x.view(in_size, -1)  # 88 * 4 * 4
        x = self.linear(x)
        return x


model = Net()

criterion = torch.nn.CrossEntropyLoss()
optim = optim.SGD(model.parameters(), lr=0.01, momentum=0.5)
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model.to(device)


def train(epoch):
    running_loss = 0.0
    for index, data in enumerate(train_dataloader, 0):
        inputs, target = data
        inputs, target = inputs.to(device), target.to(device)  # 设置使用device机器
        outputs = model(inputs)
        loss = criterion(outputs, target)
        optim.zero_grad()
        loss.backward()
        optim.step()
        running_loss += loss.item()

        if index % 300 == 299:
            print("[%d %5d] loss :%3f " % (epoch + 1, index + 1, running_loss / 300))


def test():
    total = 0
    correct = 0
    with torch.no_grad():
        for data in test_dataloader:
            inputs, label = data
            inputs, label = inputs.to(device), label.to(device)  # 设置使用device机器
            outputs = model(inputs)
            _, predicted = torch.max(outputs, dim=1)
            total += label.size(0)
            correct += (predicted == label).sum().item()

        print("Accuracy: %d %%" % (100 * correct / total))
        return 100 * correct / total


if __name__ == '__main__':
    epoch_list = []
    acc_list = []
    for epoch in range(10):
        train(epoch)
        acc = test()
        epoch_list.append(epoch)
        acc_list.append(acc)

    plt.plot(epoch_list, acc_list)
    plt.xlabel("epoch")
    plt.ylabel("accuracy")
    plt.show()

使用Inception的代码如下,重点在Inception类

import torch
from torchvision import transforms
from torchvision import datasets
import torch.nn.functional as F
from torch.utils.data import DataLoader
import torch.optim as optim
import matplotlib.pyplot as plt

batch_size = 64

mnist_transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize([0.1307, ], [0.3081, ])])
train_datasets = datasets.MNIST("../data/mnist", train=True, download=True, transform=mnist_transform)
test_datasets = datasets.MNIST("../data/mnist", train=False, download=True, transform=mnist_transform)
train_dataloader = DataLoader(dataset=train_datasets, batch_size=batch_size, shuffle=True)
test_dataloader = DataLoader(dataset=test_datasets, batch_size=batch_size,shuffle=False)

# 构建Inception类,把重复的步骤定义成类,减少代码重复工作
class Inception(torch.nn.Module):
    def __init__(self, in_channels):
        super(Inception, self).__init__()
        self.branch_pool = torch.nn.Conv2d(in_channels, 24, kernel_size=1)
        self.branch1x1 = torch.nn.Conv2d(in_channels, 16, kernel_size=1)

        self.branch5x5_1 = torch.nn.Conv2d(in_channels, 16, kernel_size=1)
        self.branch5x5_2 = torch.nn.Conv2d(16, 24, kernel_size=5, padding=2)

        self.branch3x3_1 = torch.nn.Conv2d(in_channels, 16, kernel_size=1)
        self.branch3x3_2 = torch.nn.Conv2d(16, 24, kernel_size=3, padding=1)
        self.branch3x3_3 = torch.nn.Conv2d(24, 24, kernel_size=3, padding=1)

    def forward(self, x):
        branch_pool = F.avg_pool2d(x, kernel_size=3, stride=1, padding=1)
        branch_pool = self.branch_pool(branch_pool)

        branch1x1 = self.branch1x1(x)

        branch5x5 = self.branch5x5_1(x)
        branch5x5 = self.branch5x5_2(branch5x5)

        branch3x3 = self.branch3x3_1(x)
        branch3x3 = self.branch3x3_2(branch3x3)
        branch3x3 = self.branch3x3_3(branch3x3)

        outputs = [branch1x1, branch5x5, branch3x3, branch_pool]
        return torch.cat(outputs, dim=1)

# 模型类
class Net(torch.nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = torch.nn.Conv2d(1, 10, kernel_size=5)
        self.conv2 = torch.nn.Conv2d(88, 20, kernel_size=5)

        self.inceptionA = Inception(in_channels=10)  # 经过池化后还是10个通道
        self.inceptionB = Inception(in_channels=20)

        self.pooling = torch.nn.MaxPool2d(2)
        self.linear = torch.nn.Linear(1408, 10)

    def forward(self, x):
        in_size = x.size(0)
        x = F.relu(self.pooling(self.conv1(x)))
        x = self.inceptionA(x)
        x = F.relu(self.pooling(self.conv2(x)))
        x = self.inceptionB(x)
        x = x.view(in_size, -1)  # 88 * 4 * 4
        x = self.linear(x)
        return x


model = Net()

criterion = torch.nn.CrossEntropyLoss()
optim = optim.SGD(model.parameters(), lr=0.01, momentum=0.5)
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model.to(device)

# 训练过程函数
def train(epoch):
    running_loss = 0.0
    for index, data in enumerate(train_dataloader, 0):
        inputs, target = data
        inputs, target = inputs.to(device), target.to(device)  # 设置使用device机器
        outputs = model(inputs)
        loss = criterion(outputs, target)
        optim.zero_grad()
        loss.backward()
        optim.step()
        running_loss += loss.item()

        if index % 300 == 299:
            print("[%d %5d] loss :%3f " % (epoch + 1, index + 1, running_loss / 300))

# 测试函数
def test():
    total = 0
    correct = 0
    with torch.no_grad():
        for data in test_dataloader:
            inputs, label = data
            inputs, label = inputs.to(device), label.to(device)  # 设置使用device机器
            outputs = model(inputs)
            _, predicted = torch.max(outputs, dim=1)
            total += label.size(0)
            correct += (predicted == label).sum().item()

        print("Accuracy: %d %%" % (100 * correct / total))
        return 100 * correct / total


if __name__ == '__main__':
    epoch_list = []
    acc_list = []
    for epoch in range(10):
        train(epoch)
        acc = test()
        epoch_list.append(epoch)
        acc_list.append(acc)

    plt.plot(epoch_list, acc_list)
    plt.xlabel("epoch")
    plt.ylabel("accuracy")
    plt.show()

两份代码都和CNN训练MNIST数据集差不多,不同的是加了InceptionResidual类。对于程序看不懂,可进去这篇文章。Pytorch使用CNN实现基本的MNIST数据集学习 通俗理解CNN


二、疑问解答

这里声明,我使用了B站刘二大人的PPT作为讲解。

1. 为什么使用Inception

Inception也叫GoogLeNet,是一种基本的深度学习的结构,我们会在这种通过的结构上面做修改,以满足我们的模型需求。Inception还没提出来的时候,我们要增加网络的精确度,都是通过网络层数的加深的操作,去达到目的,但是随之而来的问题是,深度越深,可能会过拟合,可能会梯度消失。所以提出了Inception,我们先看看Inception的结构。
Inception结构我们在Inception中进行了多层次的卷积,但是我们是横向扩展的,并非纵向扩展,我们做了多层卷积后,会将多层的卷积合并成一个整体,注意进行合并的时候,需要确保图像的大小即高度和宽度是一样的才能合并,如上图所示。

为什么要进行多层卷积后合并呢?
用通俗的话来讲就是,模型通过不同的视角去观察数据的特征,能够更好地拟合我们的模型,加快了收敛的速度。而不同的视角就是不同的卷积核去卷积,在上图中有(池化+11卷积),(11卷积),(11卷积+55卷积),(11卷积+33卷积+33卷积)。而在其中的11卷积也有融合更多信息去卷积的作用,例如在RGB通道中,1*1卷积把3个通道的值卷积到一个通道中,融合了3个通道的信息去卷积。详细点本文章下文

这里也有一篇文章,比较深入解析了Inception Module,但是里面存在一些数学概念,初学者可以先通俗理解了,再进一步学习。
Inception Module-深度解析

2. 为什么使用Residual

Residual Network的意思是残差网络,目的是为了解决高层数模型学习过程中梯度消失,退化各种的问题。
训练错误率上图是经典论文 Deep Residual Learning for Image Recognition 中的插图,表示的是训练层数和错误率在训练集和测试集上的对比,我们可以看到我们想要我们的训练准确度高,并非单纯地增加我们的网络层数即可,这样子可能会适得其反。论文中也表示这个结果并非是过拟合导致的,或者并非仅仅是过拟合导致的。就像我们所说的,可能是梯度消失了。模型参数无法更新。

我们知道模型学习的时候,回归的时候,梯度的数值是由更浅层次的层的梯度数值相乘得到,这有点类似于高等数学的链式求导法则,但是当模型学习到一定程度的时候,梯度很小,比较深的层次得到的梯度值就会比较小,从而深层次的参数更新慢,从而训练效果一般。

下图是从数学本质去理解Residual
数学上的Residual
我们未使用Residual的时候是图片上方求梯度方式,g(x)和f(x)代表两个层,g(x)是前一层计算的结果,作为输入进入f(x),当前一层的梯度很小的时候,也就是序号2的数值很小,那么总的梯度值小,深层是得不到更新。而图片下方使用的是Residual的计算,通过增加上一层的输入值g(x),使得梯度不趋于0。可配合下方图片一起理解


下图是一个Residual架构设计案例。
Residual案例
它在内部使用了两个神经网络层,但是在结果输出的时候,我们会加一个输入值。上图的理解是这样,我们假设H(x)为我们想要拟合的函数曲线,x是已经在其他层数训练的数据,而我们F(x)要训练的是想要拟合的函数H(x)减去已经训练的x的部分,所以H(x)=F(x)+ x,将x移动到左边,就变成H(x)- x = F(x)。

但是Residual的理解方式很多
上图我们也可以理解为,将上一层训练的数据拿过来和新的数据一起去训练,也是就是那上一层残留的数据,所以Residual才有残留,残差等意思。


这里注意,上面的Residual只是一个很普通的设计,它并不是固定的,Residual可以有多种方式。
34层Residual
例如论文里给出了34层的Residual架构。对应下图的34-layer,除此之外,有更多其他层数的Residual架构,如下图,最后一行的FLOPs代表计算的大概复杂度。
多种Residual

这里注意!!!!!

  • Residual只是作加法而已,并不会增加我们的模型复杂度。加法并没有权重去赋予那些额外的参数,所以模型的复杂度是不会增加的。
  • 它的优点就是让模型收敛快,模型精确度高,如下图,对比了使用和未使用的情况。
    对比图在图中的第一段,可以看到使用的Residual,它的收敛速度比较快,而迭代到最后,使用了Residual的模型精确度也会更高。

3. 为什么使用1*1卷积核

减少卷积计算量!!!
这里我们用一个55的卷积层作为例子,我们去对比用了11卷积核和未使用时候的计算量差距。

卷积核不同
我们在上图下面的例子中使用了一个11的卷积核,它所进行的计算也类似33的卷积,通过卷积计算,我们把原来192维度的数据缩小至16个维度,进行了信息的融合,然后再进行55的卷积。下图是计算量的对比。
卷积对比通过对比,我们可以看到使用了1
1的卷积的计算量明显减少了!这就是它的作用!所以使用了1*1的卷积核的网络也叫做Network in Network(NIN)。

三、代码讲解

1. 这篇文章两份代码所做操作主要是添加了不同的类Inception和Residual

class Inception(torch.nn.Module):
    def __init__(self, in_channels):
        super(Inception, self).__init__()
        self.branch_pool = torch.nn.Conv2d(in_channels, 24, kernel_size=1)
        self.branch1x1 = torch.nn.Conv2d(in_channels, 16, kernel_size=1)

        self.branch5x5_1 = torch.nn.Conv2d(in_channels, 16, kernel_size=1)
        self.branch5x5_2 = torch.nn.Conv2d(16, 24, kernel_size=5, padding=2)

        self.branch3x3_1 = torch.nn.Conv2d(in_channels, 16, kernel_size=1)
        self.branch3x3_2 = torch.nn.Conv2d(16, 24, kernel_size=3, padding=1)
        self.branch3x3_3 = torch.nn.Conv2d(24, 24, kernel_size=3, padding=1)

    def forward(self, x):
        branch_pool = F.avg_pool2d(x, kernel_size=3, stride=1, padding=1)
        branch_pool = self.branch_pool(branch_pool)

        branch1x1 = self.branch1x1(x)

        branch5x5 = self.branch5x5_1(x)
        branch5x5 = self.branch5x5_2(branch5x5)

        branch3x3 = self.branch3x3_1(x)
        branch3x3 = self.branch3x3_2(branch3x3)
        branch3x3 = self.branch3x3_3(branch3x3)

        outputs = [branch1x1, branch5x5, branch3x3, branch_pool]
        return torch.cat(outputs, dim=1)

上面的Inception类,按照下图的架构进行构建的。
Inception架构


class Residual(torch.nn.Module):
    def __init__(self, channels):
        super(Residual, self).__init__()
        self.channels = channels
        self.conv1 = torch.nn.Conv2d(channels, channels, kernel_size=3, padding=1)  # 使用padding让图像的大小不变
        self.conv2 = torch.nn.Conv2d(channels, channels, kernel_size=3, padding=1)

    def forward(self, x):
        y = F.relu(self.conv1(x))
        y = self.conv2(x)
        return F.relu(x + y)

上面代码就是Inception的主要类,按照下图的架构构建
Residual架构

四、Reference

Deep Residual Learning for Image Recognition


总结

以上就是我个人对Inception和Residual的理解,希望配合上其他文章,能让初学者更容易理解。如果觉得有用,请大家点赞支持!!!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值