文章目录

  • 前言
  • 一、先上代码
  • 二、疑问解答
  • 1. 为什么使用Inception
  • 2. 为什么使用Residual
  • 3. 为什么使用1*1卷积核
  • 三、代码讲解
  • 1. 这篇文章两份代码所做操作主要是添加了不同的类Inception和Residual
  • 四、Reference
  • 总结



前言

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

    上图为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的结构。

pytorch 的criterion函数 pytorch inception_cnn

我们在Inception中进行了多层次的卷积,但是我们是横向扩展的,并非纵向扩展,我们做了多层卷积后,会将多层的卷积合并成一个整体,注意进行合并的时候,需要确保图像的大小即高度和宽度是一样的才能合并,如上图所示。

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

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

2. 为什么使用Residual

Residual Network的意思是残差网络,目的是为了解决高层数模型学习过程中梯度消失,退化各种的问题。

pytorch 的criterion函数 pytorch inception_pytorch_02

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

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

下图是从数学本质去理解Residual

pytorch 的criterion函数 pytorch inception_cnn_03


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


下图是一个Residual架构设计案例。

pytorch 的criterion函数 pytorch inception_pytorch_04


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

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


这里注意,上面的Residual只是一个很普通的设计,它并不是固定的,Residual可以有多种方式。

pytorch 的criterion函数 pytorch inception_cnn_05


例如论文里给出了34层的Residual架构。对应下图的34-layer,除此之外,有更多其他层数的Residual架构,如下图,最后一行的FLOPs代表计算的大概复杂度。

pytorch 的criterion函数 pytorch inception_cnn_06

这里注意!!!!!

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

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

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

pytorch 的criterion函数 pytorch inception_python_07


我们在上图下面的例子中使用了一个11的卷积核,它所进行的计算也类似33的卷积,通过卷积计算,我们把原来192维度的数据缩小至16个维度,进行了信息的融合,然后再进行55的卷积。下图是计算量的对比。

pytorch 的criterion函数 pytorch inception_深度学习_08

通过对比,我们可以看到使用了11的卷积的计算量明显减少了!这就是它的作用!所以使用了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类,按照下图的架构进行构建的。

pytorch 的criterion函数 pytorch inception_深度学习_09


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的主要类,按照下图的架构构建

pytorch 的criterion函数 pytorch inception_分类_10

四、Reference

Deep Residual Learning for Image Recognition


总结

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