神经网络基础篇-3-PyTorch构建及训练模型

TORCH.NN API

PyTorch的主要功能:

  • 可以在GPU上运行n维张量(Tensor)
  • 自动区分以构建和训练神经网络
    • 网络正向传递将定义一个动态计算图;图中的节点为张量,图中的边为从输入张量产生输出张量的函数。通过该图进行反向传播,可以轻松计算梯度。
      • 如果 x是一个张量,并且有 x.requires_grad=True,那么x.grad就是另一个张量,代表着x相对于某个标量值的梯度。

1.模型定义及训练

在PyTorch中,可以使用torch.nn模块快速定义神经网络模型,通过继承nn.Module(属于torch.nn的Containers之一)实现。需要定义模型的初始化init函数(构造函数),前向传播forward函数(将输入数据传入模型并输出),以及反向传播backward函数(用于计算损失函数并反向传播误差)等。

  • 只需要定义__init__forward 函数,backward函数会在使用autograd时自动定义。
  • 也可以通过定义torch.autograd.Function的子类并实现forwardbackward函数,来定义自己的自动求导运算。

一个神经网络的典型训练过程:

  • 定义包含一些可学习参数(或者叫权重)的神经网络
  • 在输入数据集上迭代
  • 通过网络处理输入
  • 计算损失(输出和正确答案的距离)
  • 将梯度反向传播给网络的参数
  • 更新网络的权重,一般使用一个简单的规则:weight = weight - learning_rate * gradient

使用PyTorch定义神经网络模型的示例:

import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms

class Net(nn.Module):
    # __init__函数定义了所有层及其参数
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 6, 5) # 输入通道数为3,输出通道数为6,卷积核大小为5的卷积层
        self.pool = nn.MaxPool2d(2, 2)  # kernel_size, stride
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    # forward函数则定义了具体的前向传播过程
    def forward(self, x):
        x = self.pool(nn.functional.relu(self.conv1(x)))
        x = self.pool(nn.functional.relu(self.conv2(x)))
        x = x.view(-1, 16 * 5 * 5)
        x = nn.functional.relu(self.fc1(x))
        x = nn.functional.relu(self.fc2(x))
        x = self.fc3(x)
        return x
    
# 加载数据集
transform = transforms.Compose(
    [transforms.ToTensor(),     # numpy.ndarray数组转换为tensor,HWC to CHW
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])   # 归一化
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
                                        download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4,   # 每个mini-batch大小为4
                                          shuffle=True, num_workers=2)
testset = torchvision.datasets.CIFAR10(root='./data', train=False,
                                       download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=4,
                                         shuffle=False, num_workers=2)
classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

# 定义网络、损失函数和优化器
net = Net()
criterion = nn.CrossEntropyLoss() # 使用交叉熵损失函数
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9) # 使用随机梯度下降(SGD)优化器,lr学习率,momentum动量

# 训练
for epoch in range(10):  # 多轮训练
    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        # 获取输入
        inputs, labels = data
        # 清空梯度
        optimizer.zero_grad()

        # 前向传播,计算预测输出
        outputs = net(inputs)
        # 计算损失
        loss = criterion(outputs, labels)

        # 反向传播,使用autograd计算反向传播,这个调用将计算loss对所有requires_grad=True的tensor的梯度。
        loss.backward()
        # 更新模型参数
        optimizer.step()

        # 计算损失
        running_loss += loss.item()
        if i % 2000 == 1999:    # 训练每2000个mini-batch时,打印当前的平均损失
            print('[%d, %5d] loss: %.3f' %
                  (epoch + 1, i + 1, running_loss / 2000))
            running_loss = 0.0

print('Finished Training')

# 测试
correct = 0
total = 0
with torch.no_grad():   # 关闭自动求导
    for data in testloader:
        images, labels = data
        # 对于每个测试样本,使用网络模型进行前向传播,并计算预测输出和实际标签的差别。
        outputs = net(images)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

print('Accuracy of the network on the 10000 test images: %d %%' % (
    100 * correct / total))

1)使用其他torch.nn container构建网络

Sequential、ModuleList以及ModuleDict都是PyTorch中神经网络模块化的类,用于方便地构建复杂的神经网络模型。

  • Sequential类

    • 一个按照顺序执行的模块容器,可以将多个模块按照顺序依次堆叠在一起,组成一个大的神经网络模型。
    • 接收一个子模块的有序字典(OrderedDict)或者一系列子模块作为参数来逐一添加Module的实例,而模型的前向计算就是将这些实例按添加的顺序逐一计算。
    import torch.nn as nn
    
    model = nn.Sequential(
              nn.Linear(784, 256),
              nn.ReLU(),
              nn.Linear(256, 10),
              nn.LogSoftmax(dim=1)
            )
    
  • ModuleList类

    • 一个模块列表容器,可以将模块按照列表的形式存储在其中,访问模块也可以通过索引来进行。同时,ModuleList会自动监测其中的模块,并将其注册到当前模块中。
    import torch.nn as nn
    
    class MyModule(nn.Module):
        def __init__(self):
            super(MyModule, self).__init__()
            self.layers = nn.ModuleList([
                nn.Linear(10, 20),
                nn.Linear(20, 30),
                nn.Linear(30, 40)
            ])
    
        def forward(self, x):
            for layer in self.layers:
                x = layer(x)
            return x
    
  • ModuleDict类

    • 一个模块字典容器,可以将模块按照键值对的形式存储在其中,访问模块也可以通过键值对的形式来进行。
    import torch.nn as nn
    
    class MyModule(nn.Module):
        def __init__(self):
            super(MyModule, self).__init__()
            self.layers = nn.ModuleDict({
                'layer1': nn.Linear(10, 20),
                'layer2': nn.Linear(20, 30),
                'layer3': nn.Linear(30, 40)
            })
    
        def forward(self, x):
            x = self.layers['layer1'](x)
            x = self.layers['layer2'](x)
            x = self.layers['layer3'](x)
            return x
    

2.模型保存与加载

1)保存与加载state_dict【推荐】

在PyTorch中,torch.nn.Module模型的可学习参数(即权重和偏差)包含在模型的参数中(使用model.parameters()可以进行访问)。

state_dict是PyTorch中的一个字典对象,它保存了模型每一层权重及偏差参数的信息。

  • 优化器对象也有一个state_dict,它包含了优化器的状态以及被使用的超参数(如lr, momentum,weight_decay等)
state_dict = {
    'conv1.weight': tensor([[[[ 0.0386, -0.0148, -0.0626],
                              ...
                              [-0.0583, -0.0112, -0.0550]]]]),
    'conv1.bias': tensor([ 0.1119, -0.1208, -0.1754, -0.0994, -0.0624, -0.0749,  0.0975,  0.1172]),
    'fc1.weight': tensor([[ 0.0359,  0.0196, -0.0268,  ..., -0.0507, -0.0568, -0.0642],
                         ...
                         [ 0.0318,  0.0371, -0.0123,  ...,  0.0539, -0.0556, -0.0420]]),
    'fc1.bias': tensor([-0.0263,  0.0609, -0.0096, -0.0608, -0.0191, -0.0188, -0.0452, -0.0507, 
                        ...
                        -0.0462,  0.0511,  0.0128, -0.0529, -0.0495, -0.0010, -0.0294, -0.0026])
}

保存与加载方法

torch.save(model.state_dict(), PATH)

model = TheModelClass(*args, **kwargs)
model.load_state_dict(torch.load(PATH))
model.eval()
  • torch.save:将序列化的对象保存到磁盘。此函数使用Python的 pickle实用程序进行序列化。使用此功能可以保存各种对象的模型,张量和字典。
  • [torch.load](https://pytorch.org/docs/stable/torch.html?highlight=torch load#torch.load):使用pickle的将目标文件反序列化到内存中。
  • torch.nn.Module.load_state_dict:使用反序列化的state_dict加载模型的参数字典 。
    • 可以加入strict参数并设置为False,在迁移学习时以忽略不匹配的键。
  • 在运行推理之前,必须先调用eval()以将退出和批处理规范化层设置为评估模式。不这样做将产生不一致的推断结果。

2)保存和加载整个模型

torch.save(model, PATH)

model = torch.load(PATH)
model.eval()

3)保存与加载checkpoint

将未训练完成的模型参数及优化器参数保存下来。

torch.save({
    'epoch': epoch,
    'model_state_dict': model.state_dict(),
    'optimizer_state_dict': optimizer.state_dict(),
    'loss': loss,
    ...
    }, PATH)


model = TheModelClass(*args, **kwargs)
optimizer = TheOptimizerClass(*args, **kwargs)
checkpoint = torch.load(PATH)
model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
epoch = checkpoint['epoch']
loss = checkpoint['loss']
model.eval()
# - or -
model.train()

4)保存在GPU上,在GPU上加载

torch.save(model.state_dict(), PATH)

device = torch.device("cuda")
model = TheModelClass(*args, **kwargs)
model.load_state_dict(torch.load(PATH))
model.to(device)
  • 只需使用model.to(torch.device('cuda')将初始化后的模型转换为CUDA优化模型即可。
  • 要确保在所有模型输入上使用.to(torch.device('cuda'))函数。

5)保存在CPU上,在GPU上加载

torch.save(model.state_dict(), PATH)

device = torch.device("cuda")
model = TheModelClass(*args, **kwargs)
model.load_state_dict(torch.load(PATH, map_location="cuda:0"))  # Choose whatever GPU device number you want
model.to(device)
  • 要将torch.load()函数中的map_location参数设置为cuda:device_id, 这会将模型加载到给定的GPU设备。
  • 调用model.to(torch.device('cuda'))将模型的参数张量转换为CUDA张量。

6)保存与加载torch.nn.DataParallel模型

torch.nn.DataParallel是支持并行GPU利用率的模型包装器,可以灵活地将所需的模型加载到所需的任何设备。

  • 单卡保存+多卡加载

    os.environ['CUDA_VISIBLE_DEVICES'] = '0'   #这里替换成希望使用的GPU编号
    model = models.resnet152(pretrained=True)
    model.cuda()
    torch.save(model.state_dict(), PATH)
    
    
    # 加载模型时,需先实例化模型,再使用DataParallel将其包装,在加载参数时也需指定module属性
    os.environ['CUDA_VISIBLE_DEVICES'] = '0,1,2'   #这里替换成希望使用的GPU编号
    model = models.resnet152(pretrained=True)
    model.load_state_dict(torch.load('model.pth'))
    model = torch.nn.DataParallel(model)  # 进行分布式训练设置
    model.cuda()
    
  • 多卡保存+单卡加载

    加载模型时需要去掉权重字典键名中的"module",以保证模型的统一性。

    os.environ['CUDA_VISIBLE_DEVICES'] = '1,2'   #这里替换成希望使用的GPU编号
    model = models.resnet152(pretrained=True)
    model = nn.DataParallel(model).cuda() # 进行分布式训练设置
    torch.save(model.module.state_dict(), PATH)
    
    # 加载方法1
    os.environ['CUDA_VISIBLE_DEVICES'] = '0'   #这里替换成希望使用的GPU编号
    model = models.resnet152(pretrained=True)
    model = torch.nn.DataParallel(model)  # 进行分布式训练设置
    model.cuda()
    model.load_state_dict(torch.load('model.pth'))
    
    # 加载方法2
    # 遍历字典去除module
    from collections import OrderedDict
    os.environ['CUDA_VISIBLE_DEVICES'] = '0'   #这里替换成希望使用的GPU编号
    loaded_dict = torch.load('model.pth')
    
    new_state_dict = OrderedDict()
    for k, v in loaded_dict.items():
        name = k[7:] # module字段在最前面,从第7个字符开始就可以去掉module
        new_state_dict[name] = v #新字典的key值对应的value一一对应
    
    model = models.resnet152(pretrained=True)
    model.state_dict = new_state_dict
    model.cuda()
    

3.常用的训练数据集

机器学习和计算机视觉领域中最常用的数据集如下,这些数据集可以训练和测试各种不同类型的深度学习模型,例如卷积神经网络、递归神经网络、循环神经网络等。

  • MNIST: 手写数字数据集,包含60,000个训练图像和10,000个测试图像。每个图像大小为28x28像素。

  • CIFAR-10/CIFAR-100: 图像数据集,分别包含10类和100类物体种类的颜色图像。每个图像大小为32x32像素。CIFAR-10包含50,000个训练图像和10,000个测试图像,CIFAR-100包含50,000个训练图像和10,000个测试图像。

  • ImageNet: 非常大的图像数据集,包含超过10万个种类的物体。这是在分类竞赛中最受欢迎的数据集之一。

  • COCO: 一个广泛用于目标检测、图像分割和场景解析等任务的大型数据集。包含超过33万个图像和250万个标注。

  • Pascal VOC: 包含20个不同物体类别的图像数据集。数据集包含超过10,000个图像,其中大约有5,000个用于训练,其余用于测试。

  • Cityscapes: 专门用于视觉场景理解的大型数据集,用于评估目标检测、语义分割和实例分割等任务。

  • LFW: 一个面部识别数据集,包含超过5,000对不同人的面部图像。

4.常用的损失函数

torch.nn Loss Functions

损失函数的作用就是计算神经网络每次迭代的前向计算结果与真实值的差距,从而指导下一步的训练向正确的方向进行。

1)交叉熵损失函数

用于分类问题,特别是多标签分类问题。

在信息论中,交叉熵是表示两个概率分布 p,q 的差异,其中 p 表示真实分布,q 表示预测分布,那么 H(p,q) 就称为交叉熵。主要用于度量两个概率分布间的差异性信息。
H(p,q)=\sum_i p_i \cdot \ln {1 \over q_i} = - \sum_i p_i \ln q_i \tag{1}

  • 交叉熵函数可以保证区间内单调,分类问题的最后一层网络,需要分类函数(Sigmoid或者Softmax),如果再接均方差函数的话,其求导结果复杂,运算量比较大。用交叉熵函数的话,可以得到比较简单的计算结果,一个简单的减法就可以得到反向误差。

2)均方误差损失函数

用于回归问题,特别是连续输出的回归问题。

计算预测值和真实值之间的欧式距离。回归问题使用均方差函数可以保证损失函数是个凸函数,即可以得到最优解。
loss = {1 \over 2}(z-y)^2 \tag{单样本}

3)边界框损失函数

用于目标检测任务中的边界框回归。

4)对抗损失函数

用于生成对抗网络 (GAN) 中的生成器和判别器之间的博弈。

5)感知损失函数

用于视觉任务,如图像风格转换和图像超分辨率。

5.模型优化器(更新权重)

详解梯度下降法

优化器是深度学习模型中的一种重要算法,用于自动调整模型中的参数以最小化损失函数。

作用:

  1. 加快模型训练:优化器可以帮助快速更新模型参数,从而加快模型训练速度。
  2. 改进模型性能:优化器可以通过调整学习率、动量和参数更新策略等,不断迭代地更新模型参数,从而帮助模型更好地适应训练数据,提高模型的性能。
  3. 防止模型陷入局部最优解:优化器可以帮助模型跳出局部最优解,从而增加模型在整个参数空间上的搜索。
  4. 处理高维参数:优化器可以处理高维参数,如矩阵或张量,从而在计算梯度时不必手动计算。优化器会自动计算每个参数的梯度并进行相应调整。

1)优化在深度学习中的常见问题

  • 局部最小值

    深度学习模型的目标函数可能有若干局部最优值。当一个优化问题的数值解在局部最优解附近时,由于目标函数有关解的梯度接近或变成零,最终迭代求得的数值解可能只令目标函数局部最小化而非全局最小化。

  • 鞍点

    梯度接近或变成零可能是由于当前解在局部最优解附近造成的。事实上,另一种可能性是当前解在鞍点(saddle point)附近。

2)学习率与动量

  • 学习率(Learning rate)属于超参数,是优化算法中的一个可调参数,它决定了每次迭代的步长,使得优化向损失函数的最小值前进。
    • 过高的学习率会使迈一大步,超过最小值;但过低的学习率会导致收敛速度变慢,或收敛于局部最小值。
    • 自适应梯度下降算法(如Adagrad、Adadelta、RMSprop和Adam)可以自适应更新学习率。
  • 动量(momentum)属于超参数,它累计之前的梯度,如果当前梯度和累计的梯度方向一致,则增大学习率,如果不一致就减小学习率。

3)常用优化器

  • 随机梯度下降 (SGD)

    最基本的优化器,通过计算损失函数的梯度来更新模型参数。

    weight = weight - learning_rate * gradient

  • Adam

    将梯度的平方和平均值的平方根与梯度的一阶矩的指数移动平均数结合起来,调整学习率和动量参数。

  • Adagrad

    通过学习率自适应方法来调整每个参数的学习率,使得具有较大梯度的参数具有更小的学习率。

  • RMSprop

    与Adagrad类似,但是将梯度的平方和平均值不断衰减的移动平均数使用指数衰减。

  • AdaDelta

    与RMSprop类似,但是使用历史梯度平方的指数移动平均。

6.模型的欠拟合和过拟合

模型训练中经常出现的两类典型问题:

  1. 模型无法得到较低的训练误差,将这一现象称作欠拟合(underfitting)。
    • 训练误差:模型在训练数据集上表现出的误差。
  2. 模型的训练误差远小于它在测试数据集上的误差,我们称该现象为过拟合(overfitting)。
    • 模型在任意一个测试数据样本上表现出的误差的期望。

影响因素

  • 模型复杂度

    给定训练数据集,如果模型的复杂度过低,很容易出现欠拟合;如果模型复杂度过高,很容易出现过拟合。

  • 训练数据集大小

    如果训练数据集中样本数过少,过拟合更容易发生。此外,泛化误差不会随训练数据集里样本数量增加而增大。

防止欠拟合的方法

  1. 增加模型复杂度:可以增加模型的参数数量、层数、神经元数量等,使其能够更好地拟合数据。但是要注意不要过度拟合。
  2. 增加更多的特征:通过增加更多的特征来提高模型的复杂度,从而更好地拟合数据。
  3. 减少正则化:如果使用了正则化来防止过度拟合,但是发现模型欠拟合了,可以考虑减少正则化的强度。
  4. 使用更好的优化算法:在训练过程中使用更好的优化算法可以提高模型的收敛速度和精度。
  5. 增加训练数据:增加训练数据可以更好地帮助模型学习特征,从而减少欠拟合现象。
  6. 缩小batch size:缩小batch size可以增加模型随机性,提高其学习能力。
    • batch取太大会陷入局部最小值,batch取太小会抖动厉害。
  7. 使用预训练模型:如果存在相似的任务或数据,可以使用预训练模型来提高模型的泛化能力,防止欠拟合。
  8. Ensemble方法:使用多个模型集成来提高模型的泛化能力,防止欠拟合。

防止过拟合的方法

  1. 增加数据集:增加训练数据可以有效地减轻过拟合现象,使模型更好地泛化到未知数据。

  2. 数据增强:通过对原始数据进行随机变换,生成更多的训练样本,可以提高模型的鲁棒性和泛化能力。

  3. Dropout:随机将一部分神经元置零,可以防止神经元之间的过度相互适应,减少过拟合。

  4. Early stopping:在训练过程中监测验证集误差,当验证集误差不再降低时停止训练,防止模型过度拟合训练集。

  5. 权重衰减(L1和L2正则化):通过对模型参数添加L1或L2正则化项,可以惩罚模型复杂度,避免模型对噪声过度敏感,从而减少过拟合。

  6. Batch normalization:通过将每个batch的输入数据进行标准化,可以减少内部协变量转移,帮助模型更快地收敛,同时减少过拟合。

  7. 模型压缩:通过裁剪模型参数、量化模型参数、剪枝模型权重等方法,可以降低模型复杂度,达到防止过拟合的目的。

7.基础神经网络

1)LeNet

LeNet是一个早期用来识别手写数字图像的卷积神经网络,展示了通过梯度下降训练卷积神经网络可以达到手写数字识别在当时最先进的结果。这个奠基性的工作第一次将卷积神经网络推上舞台,为世人所知。

LeNet网络结构

网络实现

import time
import torch
from torch import nn, optim
class LeNet(nn.Module):
    def __init__(self):
        super(LeNet, self).__init__()
        self.conv = nn.Sequential(
            nn.Conv2d(1, 6, 5), # in_channels, out_channels, kernel_size
            nn.Sigmoid(),
            nn.MaxPool2d(2, 2), # kernel_size, stride
            nn.Conv2d(6, 16, 5),
            nn.Sigmoid(),
            nn.MaxPool2d(2, 2)
        )
        self.fc = nn.Sequential(
            nn.Linear(16*4*4, 120),
            nn.Sigmoid(),
            nn.Linear(120, 84),
            nn.Sigmoid(),
            nn.Linear(84, 10)
        )
    def forward(self, img):
        feature = self.conv(img)
        output = self.fc(feature.view(img.shape[0], -1))
        return output

2)AlexNet

AlexNet使用了8层卷积神经网络,并以很大的优势赢得了ImageNet 2012图像识别挑战赛。它首次证明了学习到的特征可以超越手工设计的特征,从而一举打破计算机视觉研究的前状。

网络结构

  • AlexNet第一层中的卷积窗口形状是11x11,因为ImageNet图像的物体占用更多的像素,所以需要更大的卷积窗口来捕获物体。
  • 第二层中的卷积窗口形状减小到5x5,之后全采用3x3。
  • 此外,第一、第二和第五个卷积层之后都使用了窗口形状为3x3 、步幅为2的最大池化层。而且,AlexNet使用的卷积通道数也大于LeNet中的卷积通道数数十倍。
  • 最后一个卷积层的是两个输出个数为4096的全连接层。这两个巨大的全连接层带来将近1 GB的模型参数。
  • AlexNet将sigmoid激活函数改成了更加简单的ReLU激活函数,也避免梯度消失。

网络实现

import time
import torch
from torch import nn, optim
import torchvision
class AlexNet(nn.Module):
    def __init__(self):
        super(AlexNet, self).__init__()
        self.conv = nn.Sequential(
            nn.Conv2d(1, 96, 11, 4), # in_channels, out_channels, kernel_size, stride, padding
            nn.ReLU(),
            nn.MaxPool2d(3, 2), # kernel_size, stride
            # 减小卷积窗口,使用填充为2来使得输入与输出的高和宽一致,且增大输出通道数
            nn.Conv2d(96, 256, 5, 1, 2),
            nn.ReLU(),
            nn.MaxPool2d(3, 2),
            # 连续3个卷积层,且使用更小的卷积窗口。除了最后的卷积层外,进一步增大了输出通道数。
            # 前两个卷积层后不使用池化层来减小输入的高和宽
            nn.Conv2d(256, 384, 3, 1, 1),
            nn.ReLU(),
            nn.Conv2d(384, 384, 3, 1, 1),
            nn.ReLU(),
            nn.Conv2d(384, 256, 3, 1, 1),
            nn.ReLU(),
            nn.MaxPool2d(3, 2)
        )
         # 这里全连接层的输出个数比LeNet中的大数倍。使用丢弃层来缓解过拟合
        self.fc = nn.Sequential(
            nn.Linear(256*5*5, 4096),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(4096, 4096),
            nn.ReLU(),
            nn.Dropout(0.5),
            # 输出层。由于这里使用Fashion-MNIST,所以用类别数为10,而非论文中的1000
            nn.Linear(4096, 10),
        )
    def forward(self, img):
        feature = self.conv(img)
        output = self.fc(feature.view(img.shape[0], -1))
        return output

3)GoogLeNet

GoogleNet系列论文学习

GoogLeNet在2014年的ImageNet图像识别挑战赛中大放异彩。

GoogLeNet提出了Inception结构,对输入数据进行1 * 1,3 * 3,5 * 5的卷积,将各自的卷积结构连接构成下一层的输入。同时由于polling操作的必要性,进行了并行的polling操作,结果也合并到3个卷积的输出中构成整体的输出。

  • 前3条线路使用窗口大小分别是1x1、3x3、5x5 卷积层来抽取不同空间尺寸下的信息,其中中间2个线路会对输入先做1x1卷积来减少输入通道数,以降低模型复杂度。
  • 第四条线路则使用3x3最大池化层,后接1x1卷积层来改变通道数。
  • 4条线路都使用了合适的填充来使输入与输出的高和宽一致。
  • 最后将每条线路的输出在通道维上连结,并输入接下来的层中去。
import time
import torch
from torch import nn, optim
import torch.nn.functional as F
class Inception(nn.Module):
    # c1 - c4为每条线路里的层的输出通道数
    def __init__(self, in_c, c1, c2, c3, c4):
        super(Inception, self).__init__()
        # 线路1,单1 x 1卷积层
        self.p1_1 = nn.Conv2d(in_c, c1, kernel_size=1)
        # 线路2,1 x 1卷积层后接3 x 3卷积层
        self.p2_1 = nn.Conv2d(in_c, c2[0], kernel_size=1)
        self.p2_2 = nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1)
        # 线路3,1 x 1卷积层后接5 x 5卷积层
        self.p3_1 = nn.Conv2d(in_c, c3[0], kernel_size=1)
        self.p3_2 = nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2)
        # 线路4,3 x 3最大池化层后接1 x 1卷积层
        self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)
        self.p4_2 = nn.Conv2d(in_c, 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)  # 在通道维上连结输出

4)ResNet

ResNet在2015年被提出,在ImageNet比赛classification任务上获得第一名,因为它 “简单与实用” 并存,之后很多方法都建立在ResNet50或者ResNet101的基础上完成的,具有很强的适应性。

随着网络的加深,出现了训练集准确率下降的现象,而加入残差连接设计的ResNet解决了该问题。

残差块

import time
import torch
from torch import nn, optim
import torch.nn.functional as F
class Residual(nn.Module):  # 本类已保存在d2lzh_pytorch包中方便以后使用
    def __init__(self, in_channels, out_channels, use_1x1conv=False, stride=1):
        super(Residual, self).__init__()
        self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1, stride=stride)
        self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1)
        if use_1x1conv:
            self.conv3 = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride)
        else:
            self.conv3 = None
        self.bn1 = nn.BatchNorm2d(out_channels)
        self.bn2 = nn.BatchNorm2d(out_channels)
    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)
        return F.relu(Y + X)

5)R-CNN

区域卷积神经网络R-CNN是将深度模型应用于目标检测的开创性工作之一。首先对图像选取若干提议区域并标注它们的类别和边界框(如偏移量)。然后用卷积神经网络对每个提议区域做前向计算抽取特征。之后用每个提议区域的特征预测类别和边界框。

R-CNN主要由以下4步构成。

  • 对输入图像使用选择性搜索来选取多个高质量的提议区域 。这些提议区域通常是在多个尺度下选取的,并具有不同的形状和大小。每个提议区域将被标注类别和真实边界框。
  • 选取一个预训练的卷积神经网络,并将其在输出层之前截断。将每个提议区域变形为网络需要的输入尺寸,并通过前向计算输出抽取的提议区域特征。
  • 将每个提议区域的特征连同其标注的类别作为一个样本,训练多个支持向量机对目标分类。其中每个支持向量机用来判断样本是否属于某一个类别。
  • 将每个提议区域的特征连同其标注的边界框作为一个样本,训练线性回归模型来预测真实边界框。

6)MobileNet

CNN模型之MobileNet

在移动或者嵌入式设备,大而复杂的模型难以被应用。MobileNet是Google团队针对移动端提出的高效图像识别网络。

MobileNet的基本单元是深度级可分离卷积(depthwise separable convolution),其可以分解为两个更小的操作(depthwise convolution和pointwise convolution)。

  • 首先采用depthwise convolution对不同输入通道分别进行卷积,然后采用pointwise convolution将上面的输出再进行结合,这样其实整体效果和一个标准卷积是差不多的,但是会大大减少计算量和模型参数量。

网络结构

7)U-NET

论文地址

u-net是一种用于用于生物医学图像分割的卷积网络。2015年MICCAI会议上提出,在图像分割任务特别是医学图像分割中,U-Net是最成功的方法之一。

u-net主要思想是通过连续的层来补充通常的收缩网络,其中汇聚操作员被上采样操作员替换。因此,这些层增加了输出的分辨率。为了定位,从收缩路径的高分辨率特征与上采样输出相结合。

网络结构

  • 由一个收缩路径(左侧)和一个扩张路径(右侧)组成。
  • 收缩路径遵循卷积神经网络的典型架构,由两个3x3卷积(无填充),每个卷积后跟一个修正线性单元(ReLU)和一个2x2的最大池化操作(步长为2)重复应用。在每个下采样步骤中,将特征通道数量加倍。
  • 扩张路径的每个步骤包括特征映射的上采样,然后是一个减半特征通道的2x2卷积(“上卷积”),与从收缩路径对应裁剪的特征映射连接,以及两个3x3卷积,每个卷积后跟一个ReLU。由于每次卷积都会丢失边界像素,因此裁剪是必要的。在最后一层中,使用一个1x1卷积将每个64个分量的特征向量映射到所需的类别数量。
  • 总体而言,网络有23个卷积层。 为了允许平滑拼接输出的分割地图,选择输入图块大小非常重要,以使所有2x2最大池化操作都应用于一个具有偶数x和y尺寸的层。

8.迁移学习

https://github.com/jindongwang/transferlearning

在非常大的数据集(例如 ImageNet)上预先训练一个卷积神经网络,然后使用这个卷积神经网络对目标任务进行初始化或用作固定特征提取器。

1)微调卷积神经网络

使用预训练网络来初始化网络,而不是随机初始化,比如一个已经在imagenet 1000数据集上训练好的网络一样。其余训练和往常一样。

model_ft = models.resnet18(pretrained=True)
num_ftrs = model_ft.fc.in_features
# Here the size of each output sample is set to 2.
# Alternatively, it can be generalized to nn.Linear(num_ftrs, len(class_names)).
model_ft.fc = nn.Linear(num_ftrs, 2)
model_ft = model_ft.to(device)

criterion = nn.CrossEntropyLoss()

# Observe that all parameters are being optimized
optimizer_ft = optim.SGD(model_ft.parameters(), lr=0.001, momentum=0.9)

# Decay LR by a factor of 0.1 every 7 epochs
exp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft, step_size=7, gamma=0.1)

2)将卷积神经网络作为固定特征提取器

将冻结除最终全连接层之外的整个网络的权重。最后一个全连接层被替换为具有随机权重的新层,并且仅训练该层。

  • 设置requires_grad == False来冻结参数,以便在backward()中不会计算梯度。
  • 训练速度比上一个场景要快,因为不需要为绝大多数网络计算梯度。
model_conv = torchvision.models.resnet18(pretrained=True)
for param in model_conv.parameters():
    param.requires_grad = False

# Parameters of newly constructed modules have requires_grad=True by default
num_ftrs = model_conv.fc.in_features
model_conv.fc = nn.Linear(num_ftrs, 2)
model_conv = model_conv.to(device)

criterion = nn.CrossEntropyLoss()

# Observe that only parameters of final layer are being optimized as opposed to before.
optimizer_conv = optim.SGD(model_conv.fc.parameters(), lr=0.001, momentum=0.9)

# Decay LR by a factor of 0.1 every 7 epochs
exp_lr_scheduler = lr_scheduler.StepLR(optimizer_conv, step_size=7, gamma=0.1)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容