卷积神经网络和LeNet-5

概述

对于卷积运算,池化运算的细节本文就不过多介绍了,主要记录一些结论性的东西。

为什么引入卷积神经网络

如果用全连接神经网络处理大尺寸图像具有三个明显的缺点:

  • 图像展开为向量会丢失空间信息
  • 参数过多效率低下,训练困难
  • 大量的参数也很快会导致网络过拟合

使用卷积神经网络就可以完美解决上述问题。

假设输入的图片为 640x640 的三通道RGB图片。

  • 如果使用一个简单的全连接神经网络,隐藏层为1层 10 个单元。那么一层的参数数量为 640 * 640 * 3 * 10 = 12288000 个
  • 如果使用卷积神经网络,只设置一个卷积层,含有十个 大小为 11x11 的滤波器, 那么这个卷积层的参数数量为 11 * 11 * 10 = 1210 个, 直接缩小了一万倍。

卷积层输出的计算公式

假设卷积层输入为 inw×inhin_w \times in_h @ incin_c
滤波器的大小为 fw×fhf_w \times f_h , 数量为 fcf_c, padding 为 pp
那么输出层的大小 outw×outhout_w \times out_h @ outcout_c 满足

outw=inw+2pfw2s+1out_w = \left \lfloor \frac{in_w + 2p - f_w}{2s} + 1 \right \rfloor

outh=inh+2pfh2s+1out_h = \left \lfloor \frac{in_h + 2p - f_h}{2s} + 1 \right \rfloor

outc=fcout_c = f_c

因为当卷积操作的边界不足的时候,不进行卷积计算,所以outw, outhout_w ,\ out_h计算结果都是向下取整。

为什么引入池化层?

通常,一个卷积层后面都会紧跟着一个池化层,池化层可以有效的缩小参数矩阵的尺寸,从而减少最后连接层的中的参数数量。所以加入池化层可以加快计算速度和防止过拟合的作用。

关于池化层

  • 池化层的作用主要是下采样,由于池化层没有额外需要学习的参数,所以可以简化网络和一定程度上防止过拟合。
  • 以前一般使用平均池化,现在通常通常使用最大池化。
  • 池化操作的输出计算方式和卷积操作一样。

经典的卷积神经网络 LeNet-5

LeNet-5奠定了卷积神经网络的一个经典结构,就是一个或多个卷积层后面跟着一个池化层,然后又是若干个卷积层再接一个池化层,然后是全连接层,最后是输出,这种排列方式很常用。我们看一下 LeNet-5 的结构图。

image.png

  • 输入层是一个单通道 32x32 大小的图片
  • 首先网络的第一层使用6个步长为1的滤波器, padding为0,后面跟了一个 2x2 步长为 2 的平均池化层
  • 然后是16个 5x5,步长为 1 的 滤波器 和 一个 2x2 步长为 2 的平均池化层, 最终输出为16个通道,大小为 5x5
  • 最后跟着两层分别含有120和80个单元的全连接层。

使用 Pytorch 来实现LeNet-5完成FashionMNIST的图片分类任务。

Pytorch 实现 LeNet-5

import torch from torch import nn import torch.nn.functional as F class LeNet5(nn.Module): def __init__(self): super(LeNet5, self).__init__() self.conv1 = nn.Conv2d(1, 6, 5) self.pool1 = nn.AvgPool2d(2, stride=2) self.conv2 = nn.Conv2d(6, 16, 5) self.pool2 = nn.AvgPool2d(2, stride=2) self.fc1 = nn.Linear(4 * 4 * 16, 320) self.fc2 = nn.Linear(320, 80) self.fc3 = nn.Linear(80, 10) def forward(self, x): x = self.conv1(x) x = self.pool1(x) x = self.conv2(x) x = self.pool2(x) x = x.view(-1, 256) x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) x = self.fc3(x) return x

加载FashionMNIST数据集

from torchvision import datasets from torchvision.transforms import ToTensor from torch.utils.data import DataLoader train_data = datasets.FashionMNIST(root='data', train=True, download=True, transform = ToTensor()) test_data = datasets.FashionMNIST(root='data', train=False, download=True, transform = ToTensor()) train_loader = DataLoader(train_data, batch_size=256, shuffle=True) test_loader = DataLoader(test_data, batch_size=256, shuffle=True)

定义训练方法和验证方法

def train_loop(dataloader, model, loss_fn, optimizer): size = len(dataloader.dataset) model.train() for batch, (X, y) in enumerate(dataloader): X = X.cuda() y = y.cuda() optimizer.zero_grad() pred = model(X) loss = loss_fn(pred, y) loss.backward() optimizer.step() if batch % 100 == 0: loss, current = loss.item(), batch * len(X) print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]") def test_loop(dataloader, model, loss_fn): model.eval() size = len(dataloader.dataset) test_loss, correct = 0, 0 with torch.no_grad(): for X, y in dataloader: X = X.cuda() y = y.cuda() pred = model(X) test_loss += loss_fn(pred, y).item() correct += (pred.argmax(dim=1) == y).type(torch.float).sum().item() test_loss /= size correct /= size print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")

训练模型

model = LeNet5() model.cuda() loss_fn = nn.CrossEntropyLoss() optimizer = torch.optim.SGD(model.parameters(), lr=0.01) epochs = 20 for t in range(epochs): print(f"Epoch {t+1}\n-------------------------------") train_loop(train_loader, model, loss_fn, optimizer) test_loop(test_loader, model, loss_fn) print("Done!")

训练20个epochs后得到了 77.9% 的正确率

阅读(128)
评论(0)
updated@2021-05-19
评论区
目录