AlexNet是第一个现代深度卷积网络模型,其首次使用了很多现代深度卷积网络的一些方法,如使用GPU并行训练,采用ReLU函数作为非线性函数,使用Dropout技术防止过拟合,使用数据增强来提高模型准确率等等。AlexNet赢得了2012年ImageNet大赛冠军,并且是以领先第二名10%的准确率夺得冠军,自此,深度学习的威力才正式被人们所认识到。所以,这次我们来学习一下AlexNet网络模型。
AlexNet网络结构
AlexNet的网络结构如下图所示:
可以看到,训练分别在两个GPU上并行训练,最终合并到一起的。其实,是由于当时GPU计算能力不强,现在已经完全可以用一个GPU来代替了。下图直观的展示了LeNet-5和AlexNet的网络结构。
这里有一个问题要注意一下,如果按照网络结构说明的编写,采用3224224图像作为输入,会发现在全连接层会报错,这是因为AlexNet论文中,原图输入224 × 224,实际上进行了随机裁剪,实际大小为227 × 227。所以我们如果输入3224224的图像的话,最终卷积层输出是25655。下面我们具体说明一下每一层的计算过程(卷积运算 o = ⌊(i + 2p - k) / s⌋ + 1,向下取整):
- 输入图像3x224x224
- 卷积层,输入通道数3,输出通道数96,采用11x11的卷积核,步幅为4,输出结果为96x54x54,再做非线性激活
- 池化层,通道数不变,卷积核3x3,步幅为2,输出结果为96x26x26
- 卷积层,输入通道数96,输出通道数256,卷积核5x5,填充为2,输出结果为256x26x26,再做非线性激活
- 池化层,通道数不变,卷积核3x3,步幅为2,输出结果为256x12x12
- 卷积层,输入通道数256,输出通道数384,卷积核3x3,填充为1,输出结果为384x12x12,再做非线性激活
- 卷积层,输入通道数384,输出通道数384,卷积核3x3,填充为1,输出结果为384x12x12,再做非线性激活
- 卷积层,输入通道数384,输出通道数256,卷积核3x3,填充为1,输出结果为256x12x12,再做非线性激活
- 池化层,通道数不变,卷积核3x3,步幅为2,输出结果为256x5x5
- 全连接层,输入大小256x5x5=6400,输出结果大小4096
- 全连接层,输入大小4096,输出结果大小4096
- 全连接层,输入大小4096,输出结果1000(imagenet分类比赛数量,我们可以根据具体问题修改)
代码实现
下面,我们用pytorch实现一下AlexNet,并用cifar数据集进行训练,查看结果。
首先定义AlexNet网络结构:
import torch.nn as nn
import torch
import torch.optim as optim
import torchvision.transforms as transforms
class AlexNet(nn.Module):
def __init__(self, num_classes) -> None:
super(AlexNet, self).__init__()
self.num_classes = num_classes
self.features = nn.Sequential(
# 卷积运算 o = ⌊(i + 2p - k) / s⌋ + 1,向下取整
nn.Conv2d(in_channels=3, out_channels=96, kernel_size=11, stride=4), # 输出54*54*96,如果要像论文中一样55*55*96,输入需要是227*227
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2), # 输出26*26*96
nn.Conv2d(in_channels=96, out_channels=256, kernel_size=5, padding=2), # 输出26*26*256
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2), # 输出12*12*256
nn.Conv2d(in_channels=256, out_channels=384, kernel_size=3, padding=1), # 输出12*12*384
nn.ReLU(),
nn.Conv2d(in_channels=384, out_channels=384, kernel_size=3, padding=1), # 输出12*12*384
nn.ReLU(),
nn.Conv2d(in_channels=384, out_channels=256, kernel_size=3, padding=1), # 输出12*12*256
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2), # 输出5*5*256
)
self.classifier = nn.Sequential(
nn.Dropout(), # 随机丢弃一部分参数,防止过拟合
nn.Linear(5*5*256, 4096),
nn.ReLU(),
nn.Dropout(),
nn.Linear(4096, 4096),
nn.ReLU(),
nn.Dropout(),
nn.Linear(4096, self.num_classes),
)
def forward(self, x):
x = self.features(x)
x = x.view(x.size(0), 5*5*256) # x.size(0)就是batch_size
x = self.classifier(x)
return x
然后,定义一个训练函数
def train(net, data_loader, test_loader, epochs=300):
net.train()
criterion = nn.CrossEntropyLoss()
lr = 0.01
optimizer = optim.SGD(net.parameters(), lr=lr, momentum=0.9)
for epoch in range(epochs):
total_loss = 0.0
for i, (image, label) in enumerate(data_loader):
if torch.cuda.is_available():
image = Variable(image).cuda()
label = Variable(label).cuda()
net.cuda()
else:
image = Variable(image)
label = Variable(label)
outputs = net(image)
loss = criterion(outputs, label)
total_loss += loss.item()
optimizer.zero_grad()
loss.backward()
optimizer.step()
print('Epoch {}, Loss: {:.4f}'.format(epoch+1, total_loss/len(data_loader)))
if epoch==80:
lr = lr/10
optimizer = torch.optim.SGD(net.parameters(), lr=lr, momentum=0.9)
torch.save(net, 'checkpoints/alexnet.pt')
print('saveing checkpoints/alexnet.pt')
# 测试模型
net.eval()
with torch.no_grad():
correct = 0
total = 0
for i, (image, label) in enumerate(test_loader):
if torch.cuda.is_available():
image = Variable(image).cuda()
label = Variable(label).cuda()
outputs = net(image)
predicted = torch.max(outputs, 1)[1] # 获取到预测的结果,[1]代表的是预测出来的索引值,也就是标签的值
total += label.size(0)
correct += (predicted == label).sum().item()
print('Test Accuracy: {:.2f}%'.format(100 * correct / total))
pass
注意,因为cifar-10数据集原始大小是3232的,所以需要把cifar-10图像大小变换为224224才可以输入到AlexNet网络中。
这里简单介绍一下cifar-10数据集:
cifar-10数据集共有60000个样本,每个样本都是一张32*32像素的RGB图像(彩色图像),也就是我们之前所说的3通道图像。这60000个样本被分成了50000个训练样本和10000个测试样本。
cifar-10中有10类物体,标签值分别按照0~9来区分,他们分别是飞机( airplane )、汽车( automobile )、鸟( bird )、猫( cat )、鹿( deer )、狗( dog )、青蛙( frog )、马( horse )、船( ship )和卡车( truck )。
cifar-10数据集的内容,如下图所示:
该数据集非常适合用来验证模型的分类效果,之后的网络模型我们也会经常用到该数据集。下面我们在cifar-10数据集上训练一下AlexNet:
# 加载cifar-10数据集
import torchvision.datasets as datasets
from torchvision.transforms.functional import InterpolationMode
net = AlexNet(num_classes=10)
transform = transforms.Compose([transforms.Resize((224, 224), interpolation=InterpolationMode.BICUBIC),
transforms.ToTensor(),
# 此为训练集上的均值与方差
transforms.Normalize(mean=[0.4914, 0.4822, 0.4465], std=[0.247, 0.2435, 0.2616])
])
train_dataset_cifar = datasets.CIFAR10('./data', train=True, transform=transform, download=True)
test_dataset_cifar = datasets.CIFAR10('./data', train=False, transform=transform, download=True)
train_loader_cifar = torch.utils.data.DataLoader(dataset=train_dataset_cifar, batch_size=64, shuffle=True)
test_loader_cifar = torch.utils.data.DataLoader(dataset=test_dataset_cifar, batch_size=64, shuffle=False)
train(net, train_loader_cifar, test_loader_cifar, epochs=10)
# 输出:
Epoch 1, Loss: 1.8302
Epoch 2, Loss: 1.2409
Epoch 3, Loss: 0.9477
Epoch 4, Loss: 0.7935
Epoch 5, Loss: 0.6797
Epoch 6, Loss: 0.5851
Epoch 7, Loss: 0.5176
Epoch 8, Loss: 0.4554
Epoch 9, Loss: 0.4121
Epoch 10, Loss: 0.3587
saveing checkpoints/alexnet.pt
Test Accuracy: 81.46%
可以看到,经过10个epoch的训练,测试精度已经达到了81.46%。
预训练
这里考虑一个问题,如果数据集不是cifar-10这样规整的,并且大小不一的数据集,比如我们之前在《神经网络Pytorch实现》一文中提到的猫狗数据集。
如果用这个数据集直接训练我们的AlexNet网络,你会发现损失值几乎不会下降或者下降很慢,这是由于我们的网络模型是从头构建的,很多参数都需要重新学习。这个时候可以采用公开的预训练模型。所谓的预训练模型就是已经用数据集(如Imagenet,coco等数据集)训练好了的模型,比如AlexNet,vgg等等。这些预训练模型中已经保存了之前在大数据集上训练过之后的参数。在预训练模型上对我们自己的数据集进行训练的过程,叫作“微调”。所以,我们只要在预训练模型上进行微调,即可将模型重新在我们的数据集上进行训练,效果会比我们从头开始训练好得多。
这里,我从pytorch获取到预训练的模型,并把分类输出修改为2类,猫和狗,在此基础上再训练(微调),同时我们比较一下使用预训练模型和未预训练模型进行训练的效果:
# 猫狗分类
from dogcat import myDataset
trainDataset = myDataset(r'D:\zj\自己项目\Python机器学习实践\样本库\dogcat\zip\train',transform=transform)
train_loader = DataLoader(dataset=trainDataset, batch_size=32, shuffle=True)
valDataset = myDataset(r'D:\zj\自己项目\Python机器学习实践\样本库\dogcat\zip\val',transform=transform)
val_loader = DataLoader(dataset=valDataset, batch_size=32, shuffle=False)
net = models.alexnet(pretrained=False) # 未预训练
#net = models.alexnet(pretrained=True) # 预训练
dim_in = net.classifier[-1].in_features # 最后一个分类层的输入维度
net.classifier[-1] = nn.Linear(dim_in, 2) # 网络最后一层改为分类到两个类别
train(net4, train_loader, val_loader, epochs=10)
当我们选择未预训练的模型时,输出结果如下:
Epoch 1, Loss: 0.6933
Epoch 2, Loss: 0.6932
Epoch 3, Loss: 0.6933
Epoch 4, Loss: 0.6933
Epoch 5, Loss: 0.6929
Epoch 6, Loss: 0.6931
Epoch 7, Loss: 0.6932
Epoch 8, Loss: 0.6927
Epoch 9, Loss: 0.6932
Epoch 10, Loss: 0.6931
saveing checkpoints/alexnet.pt
Test Accuracy: 50.00%
可以看到,损失值几乎没有下降,并且测试结果相当于随机猜测,只有50%的正确率。而当我们选择预训练的模型时,输出结果如下:
Epoch 1, Loss: 0.7668
Epoch 2, Loss: 0.7152
Epoch 3, Loss: 0.6424
Epoch 4, Loss: 0.6262
Epoch 5, Loss: 0.6390
Epoch 6, Loss: 0.5899
Epoch 7, Loss: 0.5850
Epoch 8, Loss: 0.5673
Epoch 9, Loss: 0.5370
Epoch 10, Loss: 0.5071
saveing checkpoints/alexnet.pt
Test Accuracy: 74.50%
可以看到,差距是非常大的,损失值有了明显的下降,并且仅仅训练了10个epoch,测试精度达到了74.5%。我做了一下实验,当训练到100个epoch时,测试精度达到了87%,由此可知,预训练模型对于实际生产环境中的数据具有很好的效果。下一篇我们将聚焦另一个重要的卷积神经网络Vgg。