概述
对于卷积运算,池化运算的细节本文就不过多介绍了,主要记录一些结论性的东西。
为什么引入卷积神经网络
如果用全连接神经网络处理大尺寸图像具有三个明显的缺点:
- 图像展开为向量会丢失空间信息
- 参数过多效率低下,训练困难
- 大量的参数也很快会导致网络过拟合
使用卷积神经网络就可以完美解决上述问题。
假设输入的图片为 640x640 的三通道RGB图片。
- 如果使用一个简单的全连接神经网络,隐藏层为1层 10 个单元。那么一层的参数数量为 640 * 640 * 3 * 10 = 12288000 个
- 如果使用卷积神经网络,只设置一个卷积层,含有十个 大小为 11x11 的滤波器, 那么这个卷积层的参数数量为 11 * 11 * 10 = 1210 个, 直接缩小了一万倍。
卷积层输出的计算公式
假设卷积层输入为 @
滤波器的大小为 , 数量为 , padding 为
那么输出层的大小 @ 满足
因为当卷积操作的边界不足的时候,不进行卷积计算,所以计算结果都是向下取整。
为什么引入池化层?
通常,一个卷积层后面都会紧跟着一个池化层,池化层可以有效的缩小参数矩阵的尺寸,从而减少最后连接层的中的参数数量。所以加入池化层可以加快计算速度和防止过拟合的作用。
关于池化层
- 池化层的作用主要是下采样,由于池化层没有额外需要学习的参数,所以可以简化网络和一定程度上防止过拟合。
- 以前一般使用平均池化,现在通常通常使用最大池化。
- 池化操作的输出计算方式和卷积操作一样。
经典的卷积神经网络 LeNet-5
LeNet-5奠定了卷积神经网络的一个经典结构,就是一个或多个卷积层后面跟着一个池化层,然后又是若干个卷积层再接一个池化层,然后是全连接层,最后是输出,这种排列方式很常用。我们看一下 LeNet-5 的结构图。
- 输入层是一个单通道 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% 的正确率