三大经典卷积神经网络架构:LeNet、AlexNet、VGG(代码实现及案例比较)
1.Lenet
1.1理论介绍
经过前面的介绍,我们已经了解了卷积神经网络的基本模块,接下来我们来讨论几个经典的神经网络结构,首先介绍LeNet-5 。LeNet是最早的卷积神经网络之一,其被提出用于识别手写数字和机器印刷字符。1998年,Yann LeCun第一次将LeNet卷积神经网络应用到图像分类上,在手写数字识别任务中取得了巨大成功。首先看看LeNet-5的网络结构,下图是原论文放出来的架构
假设你有一张32×32×1的图片,LeNet-5 可以识别图中的手写数字。由于LeNet-5是针对灰度图片训练的,所以图片的大小只有32×32×1。
LeNet的结构如下:
- **输入层:**接收输入图像的尺寸为32x32x1。
- 卷积层部分:
- 卷积层1:6×5x5的卷积核,步长为1,填充为0,使用Sigmoid激活函数。
- 平均池化层1:2x2的池化窗口,步长为2。
- 卷积层2:16×5x5的卷积核,步长为1,填充为0,使用Sigmoid激活函数。
- 平均池化层2:2x2的池化窗口,步长为2。
- 全连接层部分 :
- 全连接层1:120个神经元,使用Sigmoid激活函数。
- 全连接层2:84个神经元,使用Sigmoid激活函数。
- 全连接层3(输出层):10个神经元,对应10个手写数字类别,现在往往用softmax。
总的来说,如果我们从左往右看,随着网络越来越深,图像的高度和宽度在缩小,从最初的32×32缩小到28×28,再到14×14、10×10,最后只有5×5。与此同时,随着网络层次的加深,通道数量一直在增加,从1增加到6个,再到16个。
这个神经网络中还有一种模式至今仍然经常用到,就是一个或多个卷积层后面跟着一个池化层,然后又是若干个卷积层再接一个池化层,然后是全连接层,最后是输出,这种排列方式很常用。
Fashion---MNIST数据集
原始的LeNet是在MNIST数据集上实现的,但是MNIST数据集在今天来说实在太简单了,我们使用一个稍微复杂一点的数据集Fashion-MNIST,为了方便我们后续比较几个模型的性能。
Fashion-MNIST数据集由Zalando Research创建,并且与经典的MNIST数据集具有相似的结构。它包含了来自10个不同类别的共计70000张灰度图像,每个类别包含7000张图像。这些类别分别是:T恤、裤子、套头衫、连衣裙、外套、凉鞋、衬衫、运动鞋、包和短靴。
每张图像的尺寸为28x28像素,并以灰度形式表示,像素值范围在0到255之间。Fashion-MNIST数据集已经被标记,因此每个图像都与其对应的类别标签相关联。这使得Fashion-MNIST成为评估机器学习模型在图像分类任务上表现的理想数据集。
Fashion-MNIST的目标是提供一个更具挑战性的数据集,用于测试和比较不同算法的性能。与MNIST数据集相比,Fashion-MNIST涵盖更复杂、多样化的图像内容,更能反映现实世界中的图像分类问题。
我们来简单的看一下数据集,我们可以利用torchvision来下载
python
import torch
import torchvision
import matplotlib.pyplot as plt
# 加载Fashion-MNIST数据集
train_set = torchvision.datasets.FashionMNIST(root='./data', train=True, download=True)
test_set = torchvision.datasets.FashionMNIST(root='./data', train=False, download=True)
# 查看数据集大小
print(f"训练集大小: {len(train_set)}")
print(f"测试集大小: {len(test_set)}")
# 获取类别标签
labels = train_set.classes
print(f"类别标签: {labels}")
# 随机显示几个样本图像
fig, axes = plt.subplots(2, 5, figsize=(10, 4))
for i, ax in enumerate(axes.flat):
image, label = train_set[i]
ax.imshow(image, cmap='gray')
ax.set_title(labels[label])
ax.axis('off')
plt.show()
可以看到上面10张示例图,相对于手写数字识别(MNIST)数据集而言,更复杂一些,下面我们正式使用LeNet来对Fashion-MNIST数据集进行识别。
1.2代码实现
1.导入相关库:
python
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
2.定义LeNet框架
python
# 定义 LeNet 模型
class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
self.conv1 = nn.Conv2d(1, 6, kernel_size=5,padding=2)
self.avgpool = nn.AvgPool2d(kernel_size=2, stride=2)
self.conv2 = nn.Conv2d(6, 16, kernel_size=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):
out = self.avgpool(torch.relu(self.conv1(x)))
out = self.avgpool(torch.relu(self.conv2(out)))
out = out.view(out.size(0), -1)
out = torch.sigmoid(self.fc1(out))
out = torch.sigmoid(self.fc2(out))
out = self.fc3(out)
return out
请注意,在整个卷积块中,与上一层相比,每一层特征的高度和宽度都减小了,因此高度和宽度都减少了4个像素。 随着层叠的上升,通道的数量从输入时的1个,增加到第一个卷积层之后的6个,再到第二个卷积层之后的16个。 同时,每个池化层层的高度和宽度都减半。最后,每个全连接层减少维数,最终输出一个维数与结果分类数相匹配的输出。
设置gpu
python
# 设置gpu
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
3.导入Fashion-MINIST数据集
python
# 加载 Fashion-MNIST 数据集
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,))
])
trainset = torchvision.datasets.FashionMNIST(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.FashionMNIST(root='./data', train=False,
download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=64,
shuffle=False, num_workers=2)
4.初始化模型
python
# 初始化模型、损失函数和优化器
model = LeNet().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.9)
这里我们使用交叉熵损失函数和小批量梯度下降
5.模型训练和评估
python
num_epochs = 10
train_losses = []
test_losses = []
for epoch in range(num_epochs):
train_loss = 0.0
test_loss = 0.0
correct = 0
total = 0
# 训练模型
model.train()
for images, labels in trainloader:
images, labels = images.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(images)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
train_loss += loss.item()
# 测试模型
model.eval()
with torch.no_grad():
for images, labels in testloader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
loss = criterion(outputs, labels)
test_loss += loss.item()
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
avg_train_loss = train_loss / len(trainloader)
avg_test_loss = test_loss / len(testloader)
train_losses.append(avg_train_loss)
test_losses.append(avg_test_loss)
print(f"Epoch [{epoch+1}/{num_epochs}], Train Loss: {avg_train_loss:.4f}, Test Loss: {avg_test_loss:.4f}, Acc: {correct/total*100:.2f}%")
# 绘制测试误差和训练误差曲线
plt.plot(train_losses, label='Training Loss')
plt.plot(test_losses, label='Testing Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.show()
yaml
Epoch [1/10], Train Loss: 2.2963, Test Loss: 2.2134, Acc: 30.00%
Epoch [2/10], Train Loss: 0.9418, Test Loss: 0.6950, Acc: 75.43%
Epoch [3/10], Train Loss: 0.5754, Test Loss: 0.5239, Acc: 80.05%
Epoch [4/10], Train Loss: 0.4852, Test Loss: 0.4512, Acc: 83.23%
Epoch [5/10], Train Loss: 0.4302, Test Loss: 0.4255, Acc: 84.22%
Epoch [6/10], Train Loss: 0.3905, Test Loss: 0.3730, Acc: 85.98%
Epoch [7/10], Train Loss: 0.3644, Test Loss: 0.3640, Acc: 86.68%
Epoch [8/10], Train Loss: 0.3424, Test Loss: 0.3370, Acc: 87.41%
Epoch [9/10], Train Loss: 0.3253, Test Loss: 0.3261, Acc: 87.83%
Epoch [10/10], Train Loss: 0.3107, Test Loss: 0.3042, Acc: 88.74%
2. AlexNet
2.1 理论介绍
AlexNet ,是以论文的第一作者Alex Krizhevsky 的名字命名的,另外两位合著者是ilya Sutskever 和Geoffery Hinton 。AlexNet在2012年在ImageNet图像分类挑战赛上取得了突破性的成果,其本质上和LeNet没有区别,可以看做是一个更深的LeNet,拥有更多的参数。AlexNet首先用一张227×227×3图像,论文中实际用的是224×224×3,实践中往往227×227×3更有效,我们来看一下ALexNet的基本框架
- **输入层:**接收输入图像的尺寸为227x227x3。
- 卷积层部分:
- **卷积层1:**96个11x11的卷积核,步长为4,填充为0,ReLU激活函数。
- **最大池化层1:**3x3的池化窗口,步长为2。
- **卷积层2:**256个5x5的卷积核,步长为1,填充为2,ReLU激活函数。
- **最大池化层2:**3x3的池化窗口,步长为2。
- **卷积层3:**384个3x3的卷积核,步长为1,填充为1,ReLU激活函数。
- **卷积层4:**384个3x3的卷积核,步长为1,填充为1,ReLU激活函数。
- **卷积层5:**256个3x3的卷积核,步长为1,填充为1,ReLU激活函数。
- 最大池化层3:3x3的池化窗口,步长为2。
- 全连接层部分:
- **全连接层1:**4096个神经元,ReLU激活函数。
- **Dropout层1:**以0.5的概率随机将输入置为0。
- **全连接层2:**4096个神经元,ReLU激活函数。
- **Dropout层2:**以0.5的概率随机将输入置为0。
- **全连接层3(输出层):**1000个神经元,对应ImageNet的1000个类别。
实际上,AlexNet神经网络与LeNet 有很多相似之处,不过AlexNet 要大得多。正如前面讲到的LeNet 大约有6万个参数,而AlexNet 模型总共有5个卷积层,3个池化层和3个全连接层,参数量较大,约6000万个参数。同时,通过大输入图像尺寸和大尺寸的卷积核,使得网络能够更好地捕捉图像中的细节信息。此外,AlexNet 比LeNet 表现更为出色的另一个原因是它使用了ReLu 激活函数,以及在池化层中使用了maxpooling。于此同时,AlexNet引入了深度学习中的一些重要概念和技术,如使用ReLU激活函数、局部响应归一化(LRN)和Dropout正则化等。
2.2 代码实现
原文中AlexNet
是在ImageNet上进行训练的,但是这里为了方便比较,以及节约训练时间,我们依旧在Fashion---MNIST数据集上进行训练。
1.定义AlexNet模型
python
#定义AlexNet
class AlexNet(nn.Module):
def __init__(self, num_classes=10):
super(AlexNet, self).__init__()
self.features = nn.Sequential(
nn.Conv2d(1, 96, kernel_size=11, stride=4),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2),
nn.Conv2d(96, 256, kernel_size=5, padding=2),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2),
nn.Conv2d(256, 384, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(384, 384, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(384, 256, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2)
)
self.classifier = nn.Sequential(
nn.Dropout(p=0.5),
nn.Linear(256 * 6 * 6, 4096),
nn.ReLU(inplace=True),
nn.Dropout(p=0.5),
nn.Linear(4096, 4096),
nn.ReLU(inplace=True),
nn.Linear(4096, num_classes)
)
def forward(self, x):
x = self.features(x)
x = x.view(x.size(0), 256 * 6 * 6)
x = self.classifier(x)
return x
2.加载数据集
python
# 加载 Fashion-MNIST 数据集
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Resize((227,227)),#将原始图像扩宽到227×227
transforms.Normalize((0.5,), (0.5,))
])
trainset = torchvision.datasets.FashionMNIST(root='./data', train=True,
download=True, transform=transform)
train_loader = torch.utils.data.DataLoader(trainset, batch_size=64,
shuffle=True, num_workers=2)
testset = torchvision.datasets.FashionMNIST(root='./data', train=False,
download=True, transform=transform)
test_loader = torch.utils.data.DataLoader(testset, batch_size=64,
shuffle=False, num_workers=2)
3.初始化AlexNet模型
python
# 初始化AlexNet模型
model = AlexNet().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)
由于Fashion-MNINST图像默认是28×28的,我们需要将其增加到227×227,在实际中我们一般不会这样做。
4.模型训练和评估
python
# 训练AlexNet模型
num_epochs = 10
train_losses = []
test_losses = []
for epoch in range(num_epochs):
train_loss = 0.0
test_loss = 0.0
correct = 0
total = 0
# 训练模型
model.train()
for images, labels in train_loader:
images, labels = images.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(images)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
train_loss += loss.item()
# 测试模型
model.eval()
with torch.no_grad():
for images, labels in test_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
loss = criterion(outputs, labels)
test_loss += loss.item()
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
avg_train_loss = train_loss / len(train_loader)
avg_test_loss = test_loss / len(test_loader)
train_losses.append(avg_train_loss)
test_losses.append(avg_test_loss)
print(f"Epoch [{epoch+1}/{num_epochs}], Train Loss: {avg_train_loss:.4f}, Test Loss: {avg_test_loss:.4f}, Acc: {correct/total*100:.2f}%")
# 绘制测试误差和训练误差曲线
plt.plot(train_losses, label='Training Loss')
plt.plot(test_losses, label='Testing Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.show()
yaml
Epoch [1/10], Train Loss: 0.8024, Test Loss: 1.3865, Acc: 55.48%
Epoch [2/10], Train Loss: 0.4890, Test Loss: 0.4372, Acc: 83.66%
Epoch [3/10], Train Loss: 0.3692, Test Loss: 0.3832, Acc: 85.42%
Epoch [4/10], Train Loss: 0.3307, Test Loss: 0.3728, Acc: 86.03%
Epoch [5/10], Train Loss: 0.3030, Test Loss: 0.3281, Acc: 87.72%
Epoch [6/10], Train Loss: 0.2829, Test Loss: 0.3285, Acc: 87.78%
Epoch [7/10], Train Loss: 0.2697, Test Loss: 0.3515, Acc: 87.47%
Epoch [8/10], Train Loss: 0.2560, Test Loss: 0.3193, Acc: 88.24%
Epoch [9/10], Train Loss: 0.2466, Test Loss: 0.3005, Acc: 89.02%
Epoch [10/10], Train Loss: 0.2373, Test Loss: 0.3068, Acc: 89.00%
可以看出AlexNet在Fashion-MNIST数据集上测试精度有所提升,突破了89%,这是因为AlexNet使用了更深更大的网络。
3.VGG
3.1 理论介绍
经过Lenet和AlexNet的介绍,我们可以发现使用更深更大的神经网络,能够带来更好的效果,这也是目前深度学习领域一直在做的事情,包括现在热门gpt4
。但是AlexNet有一个问题是框架的设置太不规则,因此如何更好的设计更深更大的神经网络值得我们去思考。
为了实现这一目的,VGG模型(Visual Geometry Group)产生了,VGG由牛津大学的研究团队开发的深度卷积神经网络模型。VGG模型在2014年的ImageNet图像分类挑战赛中取得了很大的成功,并且在计算机视觉领域被广泛应用。
VGG 模型的主要特点是它采用了非常小的卷积核(3x3)和最大池化层(2x2) ,以及多个卷积和池化层的叠加。模型的深度可变 ,通过调整卷积和全连接层的数量来改变模型的深度。最常用的VGG模型有VGG16 和VGG19。
VGG 模型的主要优势是它具有非常好的表达能力和一致性,以及相对简单的结构。它通过多层卷积和池化层来逐渐提取图像的特征,并通过全连接层进行分类。这种结构使得模型能够捕获不同尺度下的图像特征,从而提高了模型的准确性,下面我们具体讲讲这种网络结构,如下图所示(VGG16)
VGG模型主要由VGG块和全连接层组成,通过多次叠加这些层来逐渐提取图像特征并进行分类。其中每一个VGG块都是由3×3的卷积层和2×2的池化层组成
下面是VGG16模型的详细结构:
-
输入层:接收输入图像的尺寸为224x224x3。
-
VGG块部分:
-
卷积层1-1
:64个3x3的卷积核,填充为1,ReLU激活函数。 -
卷积层1-2
:64个3x3的卷积核,填充为1,ReLU激活函数。 -
最大池化层
1:2x2的池化窗口,步长为2 -
卷积层2-1
:128个3x3的卷积核,填充为1,ReLU激活函数。 -
卷积层2-2
:128个3x3的卷积核,填充为1,ReLU激活函数。 -
最大池化层2
:2x2的池化窗口,步长为2。 -
卷积层3-1
:256个3x3的卷积核,填充为1,ReLU激活函数。 -
卷积层3-2
:256个3x3的卷积核,填充为1,ReLU激活函数。 -
卷积层3-3
:256个3x3的卷积核,填充为1,ReLU激活函数。 -
最大池化层3
:2x2的池化窗口,步长为2。 -
卷积层4-1
:512个3x3的卷积核,填充为1,ReLU激活函数。 -
卷积层4-2
:512个3x3的卷积核,填充为1,ReLU激活函数。 -
卷积层4-3
:512个3x3的卷积核,填充为1,ReLU激活函数。 -
最大池化层4
:2x2的池化窗口,步长为2。 -
卷积层5-1
:512个3x3的卷积核,填充为1,ReLU激活函数。 -
卷积层5-2
:512个3x3的卷积核,填充为1,ReLU激活函数。 -
卷积层5-3
:512个3x3的卷积核,填充为1,ReLU激活函数。 -
最大池化层5
:2x2的池化窗口,步长为2。
-
-
全连接层部分:
全连接层1
:4096个神经元,ReLU激活函数。Dropout层1
:以0.5的概率随机将输入置为0。全连接层2
:4096个神经元,ReLU激活函数。Dropout层2
:以0.5的概率随机将输入置为0。全连接层3
(输出层):1000个神经元,对应ImageNet的1000个类别。
VGG16模型总共有13个卷积层和3个全连接层,参数量较大。该模型的设计思想是通过多层的小卷积核和池化层来逐渐缩小宽度,并提取出更高级别的图像特征。同时,使用ReLU激活函数来增强网络的非线性表达能力。最后通过全连接层进行分类。
随着网络的加深,图像的高度和宽度都在以一定的规律不断缩小,每次池化后刚好缩小一半,而通道数量在不断增加,而且刚好也是在每组卷积操作后增加一倍。也就是说,图像缩小的比例和通道数增加的比例是有规律的。
-
VGG使用可重复使用的卷积块构建深度卷积神经网络
-
不同的卷积块个数和超参数可以得到不同系列的VGG(如:VGG16、VGG19)
3.2代码实现
1.VGG模型定义
原文VGG16模型定义如下
python
# 定义VGG16模型
class VGG16(nn.Module):
def __init__(self, num_classes=10):
super(VGG16, self).__init__()
self.features = nn.Sequential(
nn.Conv2d(1, 64, kernel_size=3, padding=1),nn.ReLU(inplace=True),
nn.Conv2d(64, 64, kernel_size=3, padding=1),nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(64, 128, kernel_size=3, padding=1),nn.ReLU(inplace=True),
nn.Conv2d(128, 128, kernel_size=3, padding=1),nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(128, 256, kernel_size=3, padding=1),nn.ReLU(inplace=True),
nn.Conv2d(256, 256, kernel_size=3, padding=1),nn.ReLU(inplace=True),
nn.Conv2d(256, 256, kernel_size=3, padding=1),nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(256, 512, kernel_size=3, padding=1),nn.ReLU(inplace=True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),nn.ReLU(inplace=True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(512, 512, kernel_size=3, padding=1),nn.ReLU(inplace=True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),nn.ReLU(inplace=True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2)
)
self.classifier = nn.Sequential(
nn.Linear(512 * 7 * 7, 4096),nn.ReLU(inplace=True),
nn.Dropout(),
nn.Linear(4096, 4096),nn.ReLU(inplace=True),
nn.Dropout(),
nn.Linear(4096, num_classes)
)
def forward(self, x):
x = self.features(x)
x = x.view(x.size(0), 512 * 7 * 7)
x = self.classifier(x)
return x
这样看上去有点冗余,为了方便更改架构,我们可以设置VGG块,然后根据VGG块来生成网络,后续的很多网络都用类似的想法。 VGG块定义如下
python
import torch
import torch.nn as nn
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)
由于这里使用的数据集数量较小,考虑到性能问题,这里我们使用VGG-11,共有8个卷积层和3个全连接层。
python
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)
2.加载Fashion-MNIST数据集
python
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
# 设置使用的设备为GPU,如果没有GPU则使用CPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 加载Fashion-MNIST数据集
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Resize((224,224)),#
transforms.Normalize((0.5,), (0.5,))
])
trainset = torchvision.datasets.FashionMNIST(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.FashionMNIST(root='./data', train=False,download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=64,shuffle=False, num_workers=2)
由于VGG输入图像要求为224×224,这里需要将Fashion---MNIST的图像大小更改,使用transforms.Resize
函数。
3.初始化模型
python
model = net.to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.05)
4.模型训练和评估
python
# 训练模型
num_epochs = 10
train_losses = []
test_losses = []
train_accs = []
test_accs = []
for epoch in range(num_epochs):
train_loss = 0.0
train_total = 0
train_correct = 0
model.train()
for images, labels in trainloader:
images, labels = images.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(images)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
train_loss += loss.item()
_, predicted = torch.max(outputs.data, 1)
train_total += labels.size(0)
train_correct += (predicted == labels).sum().item()
train_loss /= len(trainloader)
train_accuracy = 100.0 * train_correct / train_total
train_losses.append(train_loss)
train_accs.append(train_accuracy)
test_loss = 0.0
test_total = 0
test_correct = 0
model.eval()
with torch.no_grad():
for images, labels in testloader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
loss = criterion(outputs, labels)
test_loss += loss.item()
_, predicted = torch.max(outputs.data, 1)
test_total += labels.size(0)
test_correct += (predicted == labels).sum().item()
test_loss /= len(testloader)
test_accuracy = 100.0 * test_correct / test_total
test_losses.append(test_loss)
test_accs.append(test_accuracy)
print(f"Epoch {epoch+1}/{num_epochs}: Train Loss: {train_loss:.4f}, Train Acc: {train_accuracy:.2f}%, Test Loss: {test_loss:.4f}, Test Acc: {test_accuracy:.2f}%")
# 绘制训练误差和测试误差曲线
plt.figure(figsize=(10, 5))
plt.plot(range(1, num_epochs+1), train_losses, label='Train Loss')
plt.plot(range(1, num_epochs+1), test_losses, label='Test Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training and Testing Loss')
plt.legend()
plt.show()
# 绘制训练准确率和测试准确率曲线
plt.figure(figsize=(10, 5))
plt.plot(range(1, num_epochs+1), train_accs, label='Train Acc')
plt.plot(range(1, num_epochs+1), test_accs, label='Test Acc')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.title('Training and Testing Accuracy')
plt.legend()
plt.show()
python
Epoch 1/10: Train Loss: 2.2078, Train Acc: 15.18%, Test Loss: 0.9984, Test Acc: 65.97%
Epoch 2/10: Train Loss: 0.5435, Train Acc: 79.75%, Test Loss: 0.4182, Test Acc: 84.12%
Epoch 3/10: Train Loss: 0.3391, Train Acc: 87.61%, Test Loss: 0.3074, Test Acc: 88.30%
Epoch 4/10: Train Loss: 0.2872, Train Acc: 89.33%, Test Loss: 0.2830, Test Acc: 89.32%
Epoch 5/10: Train Loss: 0.2521, Train Acc: 90.65%, Test Loss: 0.2747, Test Acc: 90.11%
Epoch 6/10: Train Loss: 0.2228, Train Acc: 91.58%, Test Loss: 0.2585, Test Acc: 90.44%
Epoch 7/10: Train Loss: 0.1985, Train Acc: 92.61%, Test Loss: 0.2545, Test Acc: 91.10%
Epoch 8/10: Train Loss: 0.1767, Train Acc: 93.42%, Test Loss: 0.2654, Test Acc: 90.92%
Epoch 9/10: Train Loss: 0.1535, Train Acc: 94.28%, Test Loss: 0.2362, Test Acc: 91.81%
Epoch 10/10: Train Loss: 0.1324, Train Acc: 94.98%, Test Loss: 0.2662, Test Acc: 91.24%
我们对比三个架构在Fashion-MNIST数据集上的结果,发现测试集的Accuracy,VGG-11表现最好,突破了0.91,AlexNet次之,LeNet最低,这说明使用更深的网络是能够提升图像识别性能的。
总结
我们介绍了三种经典的卷积神经网络架构:LeNet,AlexNet,VGG。他们的共同思想都是使用卷积层来学习图片的空间信息,提取特征,最后使用全连接层转换到我们要的分类空间。
LeNet是首个成功应用在手写数字识别数据集上的深度卷积神经网络,只有2个卷积层、两个池化层和三个全连接层
AlexNet在LeNet基础上使用了更多更深的卷积层,在2012年的ImageNet比赛上一战成名,从此引领了深度学习的浪潮
VGG在AlexNet的基础上构建了一个非常深的卷积神经网络,通过堆叠多个小尺寸的卷积核和池化层来逐步提取图像特征。它的设计简单一致,具有较好的性能和可迁移性,成为了深度学习研究中的重要里程碑之一。
从LeNet到AlexNet再到VGG,网络在不断的变深变大,模型参数也在不断增加,包括现在很多模型都是上亿个参数,这对数据集和硬件都有很高的要求,后续我们再介绍一些能够减少模型参数的方法。