第2周学习:卷积神经网络基础

目录

Part1 视频学习 

一、绪论

卷积神经网络的应用

传统神经网络vs卷积神经网络

二、基本组成结构

卷积

池化 Polling:缩放

全连接:

三、卷积神经网络典型结构

AlexNet:2012年ImageNet图像分类竞赛中冠军

ZFNet:2013年ImageNet图像分类竞赛中冠军

VGG:一个更深的网络(原来只有8层),2014年第二名,先训练前11层,把参数固定住,再训练后面的

GoogleNet:2014年第一名

ResNet:残差学习网络,2015年ILSVRC竞赛冠军 ,深度有152层

Part2代码练习

MNIST 数据集分类

1. 加载数据 (MNIST)

2. 创建网络

3. 在小型全连接网络上训练(Fully-connected network)

4. 在卷积神经网络上训练

5. 打乱像素顺序再次在两个网络上训练与测试

CIFAR10 数据集分类

定义网络,损失函数和优化器: 

训练网络:

使用 VGG16 对 CIFAR10 分类

1. 定义 dataloader

2. VGG 网络定义

3. 网络训练

4. 测试验证准确率:

Part3 思考题:

1、dataloader 里面 shuffle 取不同值有什么区别?

2、transform 里,取了不同值,这个有什么区别?

3、epoch 和 batch 的区别?

4、1x1的卷积和 FC 有什么区别?主要起什么作用?

5、residual leanring 为什么能够提升准确率?

6、代码练习二里,网络和1989年 Lecun 提出的 LeNet 有什么区别?

7、代码练习二里,卷积以后feature map 尺寸会变小,如何应用 Residual Learning?

8、有什么方法可以进一步提升准确率?

已解决的一些小问题:


Part1 视频学习 

一、绪论

  1. 卷积神经网络的应用

    1. 分类
    2. 检索
    3. 人脸识别
    4. 表情识别
    5. 图像生成——基于对抗生产网络
    6. 图像风格转换
    7. 自动驾驶
  2. 传统神经网络vs卷积神经网络

    1. 深度学习三部曲
      1. 搭建神经网络结构(提特征)
      2. 找到合适的损失函数(交叉熵损失,均方误差MSE)评估表示与真实值之间的差异
      3. 找到合适的优化函数(BP,随机梯度下降SGD)更新参数
    2. 损失函数:用来衡量预测结果与真是结果之间的吻合度

第2周学习:卷积神经网络基础_第1张图片

               3.传统神经网络:层与层之间都是全连接的网络

                        参数太多:权重矩阵的参数太多—>过拟合

第2周学习:卷积神经网络基础_第2张图片

                4.卷积神经网络:由卷积层,激活层,池化层,全连接层构成

                                           局部关联,参数共享

二、基本组成结构

  1. 卷积

    1. 一维卷积:
      1. 用在信号处理中,用于计算信号的延迟累积
      2. 假设一个信号发生器在时刻t发出一个信号xt,其信息的衰减率为fx,即在k-1个时间步长后,信息衰减为原来的fk倍
      3. 在时刻t收到的信号yt为当前时刻产生的信息和以前时刻延迟信息的叠加
      4. 此处fx被称为滤波器或卷积核
      5. 设滤波器f的长度为m,它和一个信息序列x1x2x3x4……的卷积记为
    2. 卷积:是对两个实变函数的一种数学操作
    3. 二维卷积:卷积核(F*F*x)是一个矩阵,以一定步长移动
      1. 卷积核:一个卷积核有x个权重矩阵,x=原图像通道数
      2. 步长:卷积核每次移动几个格子
      3. 感受野:卷积核进行一次卷积时输入图像(N*N*x)所对应的区域
      4. 特征图:卷积后输出的结果
        1. 特征图大小(N-F)/步长+1
        2. 特征图大小(N+pedding*2-F)/步长+1
      5. Depth/channel:特征图的深度/个数(和卷积核的个数保持一致)
      6. pedding:如果原图像在卷积的过程中出现一部分不够一次卷积,则在图像四周都补0
      7. 参数量=(F*F+1)*channel
    4. 卷积可视化理解:浅层更关注整体信息,和输入图片比较像;深层更加抽象
  2. 池化 Polling:缩放

    1. 保留主要特征的同时减少参数和计算量,防止过拟合,提高模型泛化能力
    2. 它一般处于卷积层与卷积层之间,全连接层与全连接层之间
    3. 池化核,步长
    4. 类型:
      1. Max polling:最大值池化——分类任务中更倾向
      2. Average polling:平均池化
  3. 全连接:

    1. 两层之间的所有神经元都有权重链接
    2. 通常全连接层在卷积神经网络尾部
    3. 全连接层参数量通常最大

三、卷积神经网络典型结构

  1. AlexNet:2012年ImageNet图像分类竞赛中冠军

    1. ReLU函数
      1. 解决了梯度消失的问题
      2. 计算速度特别快,只需要判断输入是否大于0
      3. 收敛速度远快于sigmoid
    2. DropOut(随即失活):训练时随即关闭部分神经元,测试时整合所有神经元
    3. 数据增强:增大数据量
      1. 平移、翻转、对称:
        1. 随机crop,在256*256的图片中可以随机裁出来很多224*224的图片
        2. 水平翻转,相当于将样本倍增
      2. 改变RGB通道强度:对RGB加入高斯扰动第2周学习:卷积神经网络基础_第3张图片

      第2周学习:卷积神经网络基础_第4张图片

       

  2. ZFNet:2013年ImageNet图像分类竞赛中冠军

    1. 网络结构与AlexNet相同
    2. 将卷积层1中的感受野大小由11x11改为7x7,步长由4改为2(可以提取到更详细的信息)
    3. 卷积层3,4,5中的滤波器个数由384,384,256改为512,512,1024
  3. VGG:一个更深的网络(原来只有8层),2014年第二名,先训练前11层,把参数固定住,再训练后面的

    1. VGG16
    2. VGG19
  4. GoogleNet:2014年第一名

    1. 网络包含22个带参数的层(如果考虑pollig是27层),独立成块的层总共有约100个
    2. 参数量大概是Alexnet的1/12(因为它全连接层少,只有用来分类的)
    3. 没有FC层
    4. inception模块,在同一层用了大小不同的3个卷积核(1x1,3x3,5x5)增加特征多样性——特征图层数太多了,可以通过加入1*1的卷积核降维(inceptionV2)
    5. Inception V3:将5*5的卷积核裂变成两个3*3的卷积核,降低了参数量,增加了非线性激活函数,表征能力增强
    6. Inception V4
  5. ResNet:残差学习网络,2015年ILSVRC竞赛冠军 ,深度有152层

    1. 残差的思想:去掉相同的主体部分,从而突出微小的变化
    2. 是一个更灵活的网络,可以根据任务需求学习一个需要的网络深度

第2周学习:卷积神经网络基础_第5张图片

Part2代码练习

MNIST 数据集分类

构建简单的CNN对 mnist 数据集进行分类

深度卷积神经网络中,有如下特性

  • 很多层: compositionality
  • 卷积: locality + stationarity of images
  • 池化: Invariance of object class to translations

1. 加载数据 (MNIST)

PyTorch里包含了 MNIST, CIFAR10 等常用数据集,调用 torchvision.datasets 即可把这些数据由远程下载到本地,下面给出MNIST的使用方法:

torchvision.datasets.MNIST(root, train=True, transform=None, target_transform=None, download=False)

  • root 为数据集下载到本地后的根目录,包括 training.pt 和 test.pt 文件
  • train,如果设置为True,从training.pt创建数据集,否则从test.pt创建。
  • download,如果设置为True, 从互联网下载数据并放到root文件夹下
  • transform, 一种函数或变换,输入PIL图片,返回变换之后的数据。
  • target_transform 一种函数或变换,输入目标,进行变换。

另外值得注意的是,DataLoader是一个比较重要的类,提供的常用操作有:batch_size(每个batch的大小), shuffle(是否进行随机打乱顺序的操作), num_workers(加载数据的时候使用几个子进程)

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
import matplotlib.pyplot as plt
import numpy

# 一个函数,用来计算模型中有多少参数
def get_n_params(model):
    np=0
    for p in list(model.parameters()):
        np += p.nelement()
    return np

# 使用GPU训练,可以在菜单 "代码执行工具" -> "更改运行时类型" 里进行设置
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
datasets.MNIST(root='F:\pytorch', train=True, transform=None, target_transform=None, download=True)
input_size  = 28*28   # MNIST上的图像尺寸是 28x28
output_size = 10      # 类别为 0 到 9 的数字,因此为十类

train_loader = torch.utils.data.DataLoader(
    datasets.MNIST('./data', train=True, download=True,
        transform=transforms.Compose(
            [transforms.ToTensor(),
             transforms.Normalize((0.1307,), (0.3081,))])),
    batch_size=64, shuffle=True)

test_loader = torch.utils.data.DataLoader(
    datasets.MNIST('./data', train=False, transform=transforms.Compose([
             transforms.ToTensor(),
             transforms.Normalize((0.1307,), (0.3081,))])),
    batch_size=1000, shuffle=True)
plt.figure(figsize=(8, 5))
for i in range(20):
    plt.subplot(4, 5, i + 1)
    image, _ = train_loader.dataset.__getitem__(i)
    plt.imshow(image.squeeze().numpy(),'gray')
    plt.axis('off');

输出结果:

第2周学习:卷积神经网络基础_第6张图片

显示数据集中的部分图像:

plt.figure(figsize=(8, 5))
for i in range(20):
    plt.subplot(4, 5, i + 1)
    image, _ = train_loader.dataset.__getitem__(i)
    plt.imshow(image.squeeze().numpy(),'gray')
    plt.axis('off');
plt.show()  # 展示图象

输出结果:第2周学习:卷积神经网络基础_第7张图片

 

2. 创建网络

定义网络时,需要继承nn.Module,并实现它的forward方法,把网络中具有可学习参数的层放在构造函数init中。

只要在nn.Module的子类中定义了forward函数,backward函数就会自动被实现(利用autograd)。

class FC2Layer(nn.Module):
    def __init__(self, input_size, n_hidden, output_size):
        # nn.Module子类的函数必须在构造函数中执行父类的构造函数
        # 下式等价于nn.Module.__init__(self)        
        super(FC2Layer, self).__init__()
        self.input_size = input_size
        # 这里直接用 Sequential 就定义了网络,注意要和下面 CNN 的代码区分开
        self.network = nn.Sequential(
            nn.Linear(input_size, n_hidden), 
            nn.ReLU(), 
            nn.Linear(n_hidden, n_hidden), 
            nn.ReLU(), 
            nn.Linear(n_hidden, output_size), 
            nn.LogSoftmax(dim=1)
        )
    def forward(self, x):
        # view一般出现在model类的forward函数中,用于改变输入或输出的形状
        # x.view(-1, self.input_size) 的意思是多维的数据展成二维
        # 代码指定二维数据的列数为 input_size=784,行数 -1 表示我们不想算,电脑会自己计算对应的数字
        # 在 DataLoader 部分,我们可以看到 batch_size 是64,所以得到 x 的行数是64
        # 大家可以加一行代码:print(x.cpu().numpy().shape)
        # 训练过程中,就会看到 (64, 784) 的输出,和我们的预期是一致的

        # forward 函数的作用是,指定网络的运行过程,这个全连接网络可能看不啥意义,
        # 下面的CNN网络可以看出 forward 的作用。
        x = x.view(-1, self.input_size)
        return self.network(x)
    


class CNN(nn.Module):
    def __init__(self, input_size, n_feature, output_size):
        # 执行父类的构造函数,所有的网络都要这么写
        super(CNN, self).__init__()
        # 下面是网络里典型结构的一些定义,一般就是卷积和全连接
        # 池化、ReLU一类的不用在这里定义
        self.n_feature = n_feature
        self.conv1 = nn.Conv2d(in_channels=1, out_channels=n_feature, kernel_size=5)
        self.conv2 = nn.Conv2d(n_feature, n_feature, kernel_size=5)
        self.fc1 = nn.Linear(n_feature*4*4, 50)
        self.fc2 = nn.Linear(50, 10)    
    
    # 下面的 forward 函数,定义了网络的结构,按照一定顺序,把上面构建的一些结构组织起来
    # 意思就是,conv1, conv2 等等的,可以多次重用
    def forward(self, x, verbose=False):
        x = self.conv1(x)
        x = F.relu(x)
        x = F.max_pool2d(x, kernel_size=2)
        x = self.conv2(x)
        x = F.relu(x)
        x = F.max_pool2d(x, kernel_size=2)
        x = x.view(-1, self.n_feature*4*4)
        x = self.fc1(x)
        x = F.relu(x)
        x = self.fc2(x)
        x = F.log_softmax(x, dim=1)
        return x

定义训练和测试函数

# 训练函数
def train(model):
    model.train()
    # 主里从train_loader里,64个样本一个batch为单位提取样本进行训练
    for batch_idx, (data, target) in enumerate(train_loader):
        # 把数据送到GPU中
        data, target = data.to(device), target.to(device)

        optimizer.zero_grad()
        output = model(data)
        loss = F.nll_loss(output, target)
        loss.backward()
        optimizer.step()
        if batch_idx % 100 == 0:
            print('Train: [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
                batch_idx * len(data), len(train_loader.dataset),
                100. * batch_idx / len(train_loader), loss.item()))


def test(model):
    model.eval()
    test_loss = 0
    correct = 0
    for data, target in test_loader:
        # 把数据送到GPU中
        data, target = data.to(device), target.to(device)
        # 把数据送入模型,得到预测结果
        output = model(data)
        # 计算本次batch的损失,并加到 test_loss 中
        test_loss += F.nll_loss(output, target, reduction='sum').item()
        # get the index of the max log-probability,最后一层输出10个数,
        # 值最大的那个即对应着分类结果,然后把分类结果保存在 pred 里
        pred = output.data.max(1, keepdim=True)[1]
        # 将 pred 与 target 相比,得到正确预测结果的数量,并加到 correct 中
        # 这里需要注意一下 view_as ,意思是把 target 变成维度和 pred 一样的意思                                                
        correct += pred.eq(target.data.view_as(pred)).cpu().sum().item()

    test_loss /= len(test_loader.dataset)
    accuracy = 100. * correct / len(test_loader.dataset)
    print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
        test_loss, correct, len(test_loader.dataset),
        accuracy))

3. 在小型全连接网络上训练(Fully-connected network)

n_hidden = 8 # number of hidden units

model_fnn = FC2Layer(input_size, n_hidden, output_size)
model_fnn.to(device)
optimizer = optim.SGD(model_fnn.parameters(), lr=0.01, momentum=0.5)
print('Number of parameters: {}'.format(get_n_params(model_fnn)))

train(model_fnn)
test(model_fnn)

结果输出:

第2周学习:卷积神经网络基础_第8张图片

 

4. 在卷积神经网络上训练

需要注意的是,上在定义的CNN和全连接网络,拥有相同数量的模型参数

# Training settings 
n_features = 6 # number of feature maps

model_cnn = CNN(input_size, n_features, output_size)
model_cnn.to(device)
optimizer = optim.SGD(model_cnn.parameters(), lr=0.01, momentum=0.5)
print('Number of parameters: {}'.format(get_n_params(model_cnn)))

train(model_cnn)
test(model_cnn)

结果输出: 

第2周学习:卷积神经网络基础_第9张图片

通过上面的测试结果,可以发现,含有相同参数的 CNN 效果要明显优于 简单的全连接网络,是因为 CNN 能够更好的挖掘图像中的信息,主要通过两个手段:

  • 卷积:Locality and stationarity in images
  • 池化:Builds in some translation invariance

5. 打乱像素顺序再次在两个网络上训练与测试

考虑到CNN在卷积与池化上的优良特性,如果我们把图像中的像素打乱顺序,这样 卷积 和 池化 就难以发挥作用了,为了验证这个想法,我们把图像中的像素打乱顺序再试试。

首先下面代码展示随机打乱像素顺序后,图像的形态:

# 这里解释一下 torch.randperm 函数,给定参数n,返回一个从0到n-1的随机整数排列
perm = torch.randperm(784)
plt.figure(figsize=(8, 4))
for i in range(10):
    image, _ = train_loader.dataset.__getitem__(i)
    # permute pixels
    image_perm = image.view(-1, 28*28).clone()
    image_perm = image_perm[:, perm]
    image_perm = image_perm.view(-1, 1, 28, 28)
    plt.subplot(4, 5, i + 1)
    plt.imshow(image.squeeze().numpy(), 'gray')
    plt.axis('off')
    plt.subplot(4, 5, i + 11)
    plt.imshow(image_perm.squeeze().numpy(), 'gray')
    plt.axis('off')

第2周学习:卷积神经网络基础_第10张图片

重新定义训练与测试函数,我们写了两个函数 train_perm 和 test_perm,分别对应着加入像素打乱顺序的训练函数与测试函数。

与之前的训练与测试函数基本上完全相同,只是对 data 加入了打乱顺序操作。

# 对每个 batch 里的数据,打乱像素顺序的函数
def perm_pixel(data, perm):
    # 转化为二维矩阵
    data_new = data.view(-1, 28*28)
    # 打乱像素顺序
    data_new = data_new[:, perm]
    # 恢复为原来4维的 tensor
    data_new = data_new.view(-1, 1, 28, 28)
    return data_new

# 训练函数
def train_perm(model, perm):
    model.train()
    for batch_idx, (data, target) in enumerate(train_loader):
        data, target = data.to(device), target.to(device)
        # 像素打乱顺序
        data = perm_pixel(data, perm)

        optimizer.zero_grad()
        output = model(data)
        loss = F.nll_loss(output, target)
        loss.backward()
        optimizer.step()
        if batch_idx % 100 == 0:
            print('Train: [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
                batch_idx * len(data), len(train_loader.dataset),
                100. * batch_idx / len(train_loader), loss.item()))

# 测试函数
def test_perm(model, perm):
    model.eval()
    test_loss = 0
    correct = 0
    for data, target in test_loader:
        data, target = data.to(device), target.to(device)

        # 像素打乱顺序
        data = perm_pixel(data, perm)

        output = model(data)
        test_loss += F.nll_loss(output, target, reduction='sum').item()
        pred = output.data.max(1, keepdim=True)[1]                                            
        correct += pred.eq(target.data.view_as(pred)).cpu().sum().item()

    test_loss /= len(test_loader.dataset)
    accuracy = 100. * correct / len(test_loader.dataset)
    print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
        test_loss, correct, len(test_loader.dataset),
        accuracy))

 在全连接网络上训练与测试:

perm = torch.randperm(784)
n_hidden = 8 # number of hidden units

model_fnn = FC2Layer(input_size, n_hidden, output_size)
model_fnn.to(device)
optimizer = optim.SGD(model_fnn.parameters(), lr=0.01, momentum=0.5)
print('Number of parameters: {}'.format(get_n_params(model_fnn)))

train_perm(model_fnn, perm)
test_perm(model_fnn, perm)

第2周学习:卷积神经网络基础_第11张图片

 在卷积神经网络上训练与测试:

perm = torch.randperm(784)
n_features = 6 # number of feature maps

model_cnn = CNN(input_size, n_features, output_size)
model_cnn.to(device)
optimizer = optim.SGD(model_cnn.parameters(), lr=0.01, momentum=0.5)
print('Number of parameters: {}'.format(get_n_params(model_cnn)))

train_perm(model_cnn, perm)
test_perm(model_cnn, perm)

第2周学习:卷积神经网络基础_第12张图片

从打乱像素顺序的实验结果来看,全连接网络的性能基本上没有发生变化,但是 卷积神经网络的性能明显下降。

这是因为对于卷积神经网络,会利用像素的局部关系,但是打乱顺序以后,这些像素间的关系将无法得到利用。

CIFAR10 数据集分类

对于视觉数据,PyTorch 创建了一个叫做 totchvision 的包,该包含有支持加载类似Imagenet,CIFAR10,MNIST 等公共数据集的数据加载模块 torchvision.datasets 和支持加载图像数据数据转换模块 torch.utils.data.DataLoader。

下面将使用CIFAR10数据集,它包含十个类别:‘airplane’, ‘automobile’, ‘bird’, ‘cat’, ‘deer’, ‘dog’, ‘frog’, ‘horse’, ‘ship’, ‘truck’。CIFAR-10 中的图像尺寸为3x32x32,也就是RGB的3层颜色通道,每层通道内的尺寸为32*32。

首先,加载并归一化 CIFAR10 使用 torchvision 。torchvision 数据集的输出是范围在[0,1]之间的 PILImage,我们将他们转换成归一化范围为[-1,1]之间的张量 Tensors。

使用 CNN 对 CIFAR10 数据集进行分类

import torch
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import numpy as np
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

# 使用GPU训练,可以在菜单 "代码执行工具" -> "更改运行时类型" 里进行设置
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

transform = transforms.Compose(
    [transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

# 注意下面代码中:训练的 shuffle 是 True,测试的 shuffle 是 false
# 训练时可以打乱顺序增加多样性,测试是没有必要
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
                                        download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=64,
                                          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=8,
                                         shuffle=False, num_workers=2)

classes = ('plane', 'car', 'bird', 'cat',
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

下面展示 CIFAR10 里面的一些图片:

def imshow(img):
    plt.figure(figsize=(8,8))
    img = img / 2 + 0.5     # 转换到 [0,1] 之间
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0)))
    plt.show()

# 得到一组图像
images, labels = iter(trainloader).next()
# 展示图像
imshow(torchvision.utils.make_grid(images))
# 展示第一行图像的标签
for j in range(8):
    print(classes[labels[j]])

 结果输出:

第2周学习:卷积神经网络基础_第13张图片

第2周学习:卷积神经网络基础_第14张图片 

定义网络,损失函数和优化器: 

 

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 6, 5)
        self.pool = nn.MaxPool2d(2, 2)
        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)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = x.view(-1, 16 * 5 * 5)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

# 网络放到GPU上
net = Net().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(net.parameters(), lr=0.001)

训练网络:

for epoch in range(10):  # 重复多轮训练
    for i, (inputs, labels) in enumerate(trainloader):
        inputs = inputs.to(device)
        labels = labels.to(device)
        # 优化器梯度归零
        optimizer.zero_grad()
        # 正向传播 + 反向传播 + 优化 
        outputs = net(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        # 输出统计信息
        if i % 100 == 0:   
            print('Epoch: %d Minibatch: %5d loss: %.3f' %(epoch + 1, i + 1, loss.item()))

print('Finished Training')

第2周学习:卷积神经网络基础_第15张图片 

 

 现在我们从测试集中取出8张图片:

# 得到一组图像
images, labels = iter(testloader).next()
# 展示图像
imshow(torchvision.utils.make_grid(images))
# 展示图像的标签
for j in range(8):
    print(classes[labels[j]])

第2周学习:卷积神经网络基础_第16张图片 第2周学习:卷积神经网络基础_第17张图片

我们把图片输入模型,看看CNN把这些图片识别成什么:

outputs = net(images.to(device))
_, predicted = torch.max(outputs, 1)

# 展示预测的结果
for j in range(8):
    print(classes[predicted[j]])

第2周学习:卷积神经网络基础_第18张图片  

 可以看到,有几个都识别错了~~~ 让我们看看网络在整个数据集上的表现:

correct = 0
total = 0

for data in testloader:
    images, labels = data
    images, labels = images.to(device), labels.to(device)
    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))

 

准确率还可以,通过改进网络结构,性能还可以进一步提升。在 Kaggle 的LeaderBoard上,准确率高的达到95%以上。

使用 VGG16 对 CIFAR10 分类

VGG是由Simonyan 和Zisserman在文献《Very Deep Convolutional Networks for Large Scale Image Recognition》中提出卷积神经网络模型,其名称来源于作者所在的牛津大学视觉几何组(Visual Geometry Group)的缩写。

该模型参加2014年的 ImageNet图像分类与定位挑战赛,取得了优异成绩:在分类任务上排名第二,在定位任务上排名第一。

VGG16的网络结构如下图所示:

 第2周学习:卷积神经网络基础_第19张图片

16层网络的结节信息如下:

  • 01:Convolution using 64 filters
  • 02: Convolution using 64 filters + Max pooling
  • 03: Convolution using 128 filters
  • 04: Convolution using 128 filters + Max pooling
  • 05: Convolution using 256 filters
  • 06: Convolution using 256 filters
  • 07: Convolution using 256 filters + Max pooling
  • 08: Convolution using 512 filters
  • 09: Convolution using 512 filters
  • 10: Convolution using 512 filters + Max pooling
  • 11: Convolution using 512 filters
  • 12: Convolution using 512 filters
  • 13: Convolution using 512 filters + Max pooling
  • 14: Fully connected with 4096 nodes
  • 15: Fully connected with 4096 nodes
  • 16: Softmax

1. 定义 dataloader

 

import torch
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import numpy as np
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

# 使用GPU训练,可以在菜单 "代码执行工具" -> "更改运行时类型" 里进行设置
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

transform_train = transforms.Compose([
    transforms.RandomCrop(32, padding=4),
    transforms.RandomHorizontalFlip(),
    transforms.ToTensor(),
    transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))])

transform_test = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))])

trainset = torchvision.datasets.CIFAR10(root='./data', train=True,  download=True, transform=transform_train)
testset  = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform_test)

trainloader = torch.utils.data.DataLoader(trainset, batch_size=128, shuffle=True, num_workers=2)
testloader = torch.utils.data.DataLoader(testset, batch_size=128, shuffle=False, num_workers=2)

classes = ('plane', 'car', 'bird', 'cat',
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

2. VGG 网络定义

下面定义VGG网络,参数太多,随便改简单了些~~~

现在的结构基本上是:

64 conv, maxpooling,

128 conv, maxpooling,

256 conv, 256 conv, maxpooling,

512 conv, 512 conv, maxpooling,

512 conv, 512 conv, maxpooling,

softmax

下面是模型的实现代码:

class VGG(nn.Module):
    def __init__(self):
        super(VGG, self).__init__()
        self.cfg = [64, 'M', 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M']
        self.features = self._make_layers(cfg)
        self.classifier = nn.Linear(512, 10)

    def forward(self, x):
        out = self.features(x)
        out = out.view(out.size(0), -1)
        out = self.classifier(out)
        return out

    def _make_layers(self, self.cfg):
        layers = []
        in_channels = 3
        for x in cfg:
            if x == 'M':
                layers += [nn.MaxPool2d(kernel_size=2, stride=2)]
            else:
                layers += [nn.Conv2d(in_channels, x, kernel_size=3, padding=1),
                           nn.BatchNorm2d(x),
                           nn.ReLU(inplace=True)]
                in_channels = x
        layers += [nn.AvgPool2d(kernel_size=1, stride=1)]
        return nn.Sequential(*layers)

初始化网络,根据实际需要,修改分类层。因为 tiny-imagenet 是对200类图像分类,这里把输出修改为200。

# 网络放到GPU上
net = VGG().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(net.parameters(), lr=0.001)

3. 网络训练

训练的代码和以前是完全一样的:

for epoch in range(10):  # 重复多轮训练
    for i, (inputs, labels) in enumerate(trainloader):
        inputs = inputs.to(device)
        labels = labels.to(device)
        # 优化器梯度归零
        optimizer.zero_grad()
        # 正向传播 + 反向传播 + 优化 
        outputs = net(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        # 输出统计信息
        if i % 100 == 0:   
            print('Epoch: %d Minibatch: %5d loss: %.3f' %(epoch + 1, i + 1, loss.item()))

print('Finished Training')

4. 测试验证准确率:

测试的代码和之前也是完全一样的。

correct = 0
total = 0

for data in testloader:
    images, labels = data
    images, labels = images.to(device), labels.to(device)
    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: %.2f %%' % (
    100 * correct / total))

第2周学习:卷积神经网络基础_第20张图片 

 

可以看到,使用一个简化版的 VGG 网络,就能够显著地将准确率由 64%,提升到 83.85%

Part3 思考题:

1、dataloader 里面 shuffle 取不同值有什么区别?

DataLoader用于加载数据到模型中
在pytorch 中的数据加载到模型的操作顺序是这样的:

① 创建一个 Dataset 对象
② 创建一个 DataLoader 对象
③ 循环这个 DataLoader 对象,将img, label加载到模型中进行训练

shuffer=False表示不打乱数据的顺序,然后以batch为单位从头到尾按顺序取用数据。shuffer=Ture表示在每一次epoch中都打乱所有数据的顺序,然后以batch为单位从头到尾按顺序取用数据。这样的结果就是不同epoch中的数据都是乱序的。
 

2、transform 里,取了不同值,这个有什么区别?

torchvision.transforms是pytorch中的图像预处理包,包含了很多种对图像数据进行变换的函数,这些都是在我们进行图像数据读入步骤中必不可少的。

 torchvision.datasets 中的数据集都有两个参数:

  • transform:可以对数据进行的变换;
  • target_transform:可以对标签进行的变换。

而 torchvision.transforms.functional 模块提供了一些常用的转换,这些转换都能够接受以下三种输入:

  • PIL Image:对于 RGB 图像,size 为 (W, H),将其转换为 NumPy array 后 size 为 (H, W, C);
  • Tensor Image:指具有 shape 为 (C, H, W) 的一个 tensor,C 为通道数,H、W 分别是图像的高和宽;
  • batch of Tensor Images:指具有 shape 为 (B, C, H, W) 的一个 tensor,B 为 batchsize,也就是一个批次中的图像数量。

这里需要注意的一个地方是:

Pytorch 中存储的 Tensor Image 的存储格式为 (C, H, W);而转换为 NumPy array 的 PIL Image 的 存储格式 为 (H, W, C);
相关函数具体使用可见Pytorch(三):数据变换 Transforms_犬冢紬希的博客-CSDN博客_target_transform

3、epoch 和 batch 的区别?

Batch大小是一个超参数,用于定义在更新内部模型参数之前要处理的样本数。

Epoch数是一个超参数,它定义了学习算法在整个训练数据集中的工作次数。Epoch由一个或多个Batch组成。

Batch大小是在更新模型之前处理的多个样本。Epoch数是通过训练数据集的完整传递次数。批处理的大小必须大于或等于1且小于或等于训练数据集中的样本数。

工作实例

假设您有一个包含200个样本(数据行)的数据集,并且您选择的Batch大小为5和1,000个Epoch。

这意味着数据集将分为40个Batch,每个Batch有5个样本。每批五个样品后,模型权重将更新。

这也意味着一个epoch将涉及40个Batch或40个模型更新。

有1000个Epoch,模型将暴露或传递整个数据集1,000次。在整个培训过程中,总共有40,000Batch。

神经网络中Batch和Epoch之间的区别是什么?_喜欢打酱油的老鸟的博客-CSDN博客_batch

4、1x1的卷积和 FC 有什么区别?主要起什么作用?

卷积跟全连接都是一个点乘的操作,区别在于卷积是作用在一个局部的区域,而全连接是对于整个输入而言,那么只要把卷积作用的区域扩大为整个输入,那就变成全连接了。全连接的输入是特征图所有元素乘以权重再求和,但是这个权重向量是在设计网络的时候就需要固定的,所以全连接没办法适应输入尺寸的变化只能固定。但是1*1卷积的输出与输入尺寸是一样大的,输出尺寸可以随着输入尺寸的变化而变化,所以1*1卷积无需固定输出尺寸。

1x1的卷积核可以降低特征图的维度同时加入非线性,实现跨通道信息交互(channal 的变换)

全连接层可以对输入的数据进行线性变换,还可以完成最后的分类任务


5、residual leanring 为什么能够提升准确率?

残差网络在每次输出前都在结果上+输入,从而去掉相同的主体部分,从而突出微小的变化,同时它还是一个更灵活的网络,可以根据任务需求学习一个需要的网络深度


6、代码练习二里,网络和1989年 Lecun 提出的 LeNet 有什么区别?

1989年 Lecun 提出的 LeNet 是CNN的开山之作:

在Lenet-1中, 28x28 的输入图像 --> 4个24×24 feature maps卷积层(5×5 size) -->平均池化层(2×2大小) -->8个12×12 feature maps 卷积层(5×5 size)--> 平均池化层(2×2大小)--> 直接全连接后输出

引入卷积和下采样/池化层后,LeNet-1对测试数据的错误率为1.7%.值得注意的是,在作者发明LeNet时,他们使用平均池化层,输出2×2特征图的平均值。目前很多LeNet实现使用max pooling只输出2×2 feature map的最大值,这有助于加快训练速度。当选择最强的特征时,反向传播可以得到较大的梯度。
在LeNet系列中LeNet-5最受欢迎:

32x32的输入图像 => 6个28×28 feature maps 卷积层(5×5 size) => 平均池化层(2×2大小)=>16个10×10 feature maps 卷积层(5×5 size) => 平均池化层(2×2大小)=>全连接到120个神经元 => 全连接84个神经元 => 全连接到10个输出

有了更多的feature map和全连接层,测试数据的错误率为0.95%。

代码练习二中所构建的网络是LeNet-5的变形,比LeNet-5少了一个池化层,同时将LeNet-5中的平均池化优化为最大池化,将激活函数由softmax变换为relu
 


7、代码练习二里,卷积以后feature map 尺寸会变小,如何应用 Residual Learning?

通过线性变换将原图像缩小为和feature map大小相同的图像

当输入输出维度上升时有两种处理方式:第一种是仍使用恒等映射,多出来的通道使用零矩阵填充,这样做的好处是不会带来额外的参数;第二种是添加变换方程,通常来说会使用 1*1 卷积来完成升维。


8、有什么方法可以进一步提升准确率?

  • 改进网络结构
  • 选择合适的激活函数
  • 选择合适的损失函数
  • 使用更大的训练集:使用数据增强技术(data augmentation),主要是在训练数据上增加微小的扰动或者变化,一方面可以增加训练数据,从而提升模型的泛化能力,另一方面可以增加噪声数据,从而增强模型的鲁棒性。主要的数据增强方法有:翻转变换 flip、随机修剪(random crop)、色彩抖动(color jittering)、平移变换(shift)、尺度变换(scale)、对比度变换(contrast)、噪声扰动(noise)、旋转变换/反射变换 (rotation/reflection)等
  • 使用更深的网络
  • 增加训练轮次

已解决的一些小问题:

1、VGG网络定义时报错name ‘cfg’ is not defined,需要做以下更改:

改为:

2、VGG网络训练时报错RuntimeError: mat1 and mat2 shapes cannot be multiplied (128x512 and 2048x10),图片大小不同,需要做以下更改

改为:

 

 

你可能感兴趣的:(学习)