Lenet-5服装分类
卷积神经网络详细指南
SGD+动量法
反向传播公式推导
vgg原文
虽然AlexNet证明深层神经网络卓有成效,但它没有提供一个通用的模板来指导后续的研究人员设计新的网络。
与芯片设计中工程师从放置晶体管到逻辑元件再到逻辑块的过程类似,神经网络架构的设计也逐渐变得更加抽象。研究人员开始从单个神经元的角度思考问题,发展到整个层,现在又转向块,重复层的模式。
使用块的想法首先出现在牛津大学的视觉几何组(visualgeometry
group)的VGG网络中。通过使用循环和子程序,可以很容易地在任何现代深度学习框架的代码中实现这些重复的架构。
VGG耗费更多计算资源,并且使用了更多的参数(这里不是3x3卷积的锅),导致更多的内存占用(140M)。其中绝大多数的参数都是来自于第一个全连接层。VGG可是有3个全连接层。
在此之前,为了更好的理解论文,我们要先学会如何计算感受野。我们把图片输入进卷积层,会输出关于这张图的feature map,这张feature map(假设channel=1)中的每一个点都对应了原图的一个区域(换句话说就是这个点是卷积核看过原图的多少个点得到的),而这个区域我们就叫做感受野。
计算公式如下:
R F l + 1 = R F l + ( K e r n e l S i z e l − 1 ) ∗ S r i d e l RF_{l+1} =RF_l+(KernelSize_{l}-1)*Sride_{l} RFl+1=RFl+(KernelSizel−1)∗Sridel
假设我们有一个 7 * 7的图像,经过一次2 * 2卷积,padding=0,stride=1,输出为6 * 6,在经过3 * 3卷积,padding=0,stride=1,输出为4*4.
其中原图的感受野为1.
R F 1 = R F 0 + ( K e r n e l S i z e 0 − 1 ) ∗ S t r i d e 0 = 1 + ( 2 − 1 ) ∗ 1 = 2 RF_1=RF_0+(KernelSize_0-1)*Stride_0=1+(2-1)*1=2 RF1=RF0+(KernelSize0−1)∗Stride0=1+(2−1)∗1=2
R F 2 = R F 1 + ( k e r n e l S i z e 1 − 1 ) ∗ S t r i d e 1 = 2 + ( 3 − 1 ) ∗ 1 = 4 RF_2=RF_1+(kernelSize_1-1)*Stride_1=2+(3-1)*1=4 RF2=RF1+(kernelSize1−1)∗Stride1=2+(3−1)∗1=4
我们知道一个图片经过了一个7 * 7卷积的feature map的感受野是7 * 7,而图片经历了三个3 * 3后的frature map也是7 * 7,在VGG的论文中,就用到了这一个想法。
后来经过实验证明,发现用三个3 * 3卷积的效果确实比7*7好。
为了方便对照,VGG作者设立了6个模型:
激活函数采用ReLU,优化方法采用带动量的SGD,采用了L2正则,在前两个全连接层加入DropOut层。
作者还运用了随机裁剪的数据增强方法,其中我们把图像的最小边设置成S(裁剪大小),其中S不小于224,如果S=224,那么送入网络的就是完整的图像,如果S大于224,模型就会拿到图片中的一个小物体,或者物体的一部分。
作者提到了两种设置S的方法:
当我们的模型训练好,在测试的时候,我们把测试图片的最小边设置为Q(测试尺度),其中Q不一定等于S。如果训练阶段采取多个S,那么测试阶段的Q采用多个S的均值。
在作者后来的实验中,作者发现:S固定没有S不固定的效果要好。
作者把网络结构的后三层全连接网络转换成全卷积网络,把第一层转化成4096个77的卷积核,后面两层则换成了11的记得卷积核,数量为之前神经元的数量。这样的话,如果图像尺寸改变,送入网络后,他不会因为尺寸不对而报错,只会增加feature map的大小,最后我们可以采用均值池化的方式来降维,于是他就不需要裁剪。
通过将测试图片缩放到不同大小Q,Q可以不等于S(训练时图片大小)。在QQ图片上裁剪出多个SS的图像块,将这些图像块进行测试,得到多个1*n维的向量。通过对这些向量每一纬求平均,得到在某一类上的概率。这种方法叫做multi-crop。
测试结果如下:
我们可以通过框架中的内置函数将Fashion-MNIST数据集下载并读取到内存中。
# 通过ToTensor实例将图像数据从PIL类型变换成32位浮点数格式,
# 并除以255使得所有像素的数值均在0到1之间
def load_data_fashion_mnist(batch_size, resize=None): #@save
"""下载Fashion-MNIST数据集,然后将其加载到内存中"""
trans = [transforms.ToTensor()]
if resize:
trans.insert(0, transforms.Resize(resize))
trans = transforms.Compose(trans)#通过compose组合多个操作
mnist_train = torchvision.datasets.FashionMNIST(
root="../data", train=True, transform=trans, download=True)
mnist_test = torchvision.datasets.FashionMNIST(
root="../data", train=False, transform=trans, download=True)
return (data.DataLoader(mnist_train, batch_size, shuffle=True,
num_workers=4),
data.DataLoader(mnist_test, batch_size, shuffle=False,
num_workers=4))
#num workers 为线程数
import torch
from torch import nn
from d2l import torch as d2l
conv_arch = ((1, 64), (1, 128), (2, 256), (2, 512), (2, 512))
def vgg_block(num_convs, in_channels, out_channels):
layers = []
for _ in range(num_convs):
layers.append(nn.Conv2d(in_channels, out_channels,
kernel_size=3, padding=1))
layers.append(nn.ReLU())
in_channels = out_channels
layers.append(nn.MaxPool2d(kernel_size=2,stride=2))
return nn.Sequential(*layers)
conv_arch = ((1, 64), (1, 128), (2, 256), (2, 512), (2, 512))
def vgg(conv_arch):
conv_blks = []
in_channels = 1
# 卷积层部分
for (num_convs, out_channels) in conv_arch:
conv_blks.append(vgg_block(num_convs, in_channels, out_channels))
in_channels = out_channels
return nn.Sequential(
*conv_blks, nn.Flatten(),
# 全连接层部分
nn.Linear(out_channels * 7 * 7, 4096), nn.ReLU(), nn.Dropout(0.5),
nn.Linear(4096, 4096), nn.ReLU(), nn.Dropout(0.5),
nn.Linear(4096, 10))
net = vgg(conv_arch)
# ratio = 4
# small_conv_arch = [(pair[0], pair[1] // ratio) for pair in conv_arch]
# net = vgg(small_conv_arch)
def evaluate_accuracy_gpu(net, data_iter, device=None): #@save
"""使用GPU计算模型在数据集上的精度"""
if isinstance(net, nn.Module):
net.eval() # 设置为评估模式
if not device:
device = next(iter(net.parameters())).device
# 正确预测的数量,总预测的数量
metric = d2l.Accumulator(2)#累加器
with torch.no_grad():#禁止计算梯度
for X, y in data_iter:
if isinstance(X, list):
# BERT微调所需的(之后将介绍)
X = [x.to(device) for x in X]
else:
X = X.to(device)
y = y.to(device)
metric.add(d2l.accuracy(net(X), y), y.numel())
return metric[0] / metric[1]
#@save
def train_ch6(net, train_iter, test_iter, num_epochs, lr, device):
"""用GPU训练模型(在第六章定义)"""
def init_weights(m):
if type(m) == nn.Linear or type(m) == nn.Conv2d:
nn.init.xavier_uniform_(m.weight)
net.apply(init_weights)
print('training on', device)
net.to(device)
optimizer = torch.optim.SGD(net.parameters(), lr=lr)
loss = nn.CrossEntropyLoss()
animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs],
legend=['train loss', 'train acc', 'test acc'])
timer, num_batches = d2l.Timer(), len(train_iter)
for epoch in range(num_epochs):
# 训练损失之和,训练准确率之和,样本数
metric = d2l.Accumulator(3)
net.train()
for i, (X, y) in enumerate(train_iter):
timer.start()
optimizer.zero_grad()
X, y = X.to(device), y.to(device)
y_hat = net(X)
l = loss(y_hat, y)
l.backward()
optimizer.step()#更新参数
with torch.no_grad():
metric.add(l * X.shape[0], d2l.accuracy(y_hat, y), X.shape[0])
timer.stop()
train_l = metric[0] / metric[2]
train_acc = metric[1] / metric[2]
if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1:
animator.add(epoch + (i + 1) / num_batches,
(train_l, train_acc, None))
test_acc = evaluate_accuracy_gpu(net, test_iter)
animator.add(epoch + 1, (None, None, test_acc))
print(f'loss {train_l:.3f}, train acc {train_acc:.3f}, '
f'test acc {test_acc:.3f}')
print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec '
f'on {str(device)}')
train_iter, test_iter = load_data_fashion_mnist(256, resize=224)
train_ch6(net, train_iter, test_iter, 10, 0.01, d2l.try_gpu())