在深度学习的历史上,AlexNet 可谓是一座里程碑。2012 年,这款由 Alex Krizhevsky 等人提出的卷积神经网络在 ImageNet 图像识别大赛中以绝对优势震撼了整个计算机视觉界,开启了深度学习的黄金时代。尽管 AlexNet 这篇论文今天被很多大佬拉出来“鞭尸”,但对于我这样的初学者小白来说,还是一篇值得阅读的论文。
在 2012 年之前,图像识别大多依赖传统方法和浅层网络,数据量和计算资源的限制使得这些方法在处理大规模图像数据时捉襟见肘。就在那时,AlexNet 的出现大幅提升了分类准确率,也用深层卷积网络证明了计算机完全可以像人类一样“看”懂图像。从论文来看 AlexNet 的成功归功于以下内容:
在传统的图像识别方法中,很多早期的神经网络只有 2-3 层,这种浅层网络只能捕捉到图像中最基本、最简单的特征,比如边缘或简单的纹理。想象一下,你在看一幅画时,如果只看最外层的轮廓,往往无法体会到画中细腻的情感和复杂的细节。类似地,浅层网络无法充分表达图像中的复杂模式。
AlexNet 则采用了深层结构,包含 8 层(其中 5 层为卷积层,3 层为全连接层),这使得网络可以层层递进地提取特征:
公式上,我们可以将每一层的卷积操作描述为:
$x^{(l-1)}$ 表示上一层的输入,$w^{(l)}$ 表示当前层的卷积核权重,$b^{(l)}$ 为偏置,$f$ 为激活函数(例如 ReLU)。这种局部运算保证了每个神经元只关注输入的一小部分区域(局部感受野),同时,通过多个卷积层的堆叠,整个网络能够捕捉到从局部细节到整体结构的多级特征。
在神经网络中,激活函数的作用是引入非线性,使得网络可以学习到复杂的映射关系。过去,大多数人使用 Sigmoid 或 Tanh 激活函数,但这些函数在深层网络中存在梯度消失的问题,即在反向传播过程中,梯度值不断变小,导致网络无法有效更新参数。
AlexNet 在其网络中采用了 ReLU(Rectified Linear Unit)激活函数,其数学表达式非常简单:
可以将 ReLU 想象成一部“电源开关”,当电流(输入值)足够强时开关“打开”,反之则关闭。不过论文中灭有说明 ReLU 比 Sigmoid 训练速度快的原因,现在看也没有快多少,纯粹是因为它简单。
在训练神经网络时,过拟合是一个常见问题。过拟合指的是模型在训练数据上表现很好,但在新数据(测试数据)上效果很差。原因在于模型可能学到了训练数据中的噪声和偶然性,而不是普遍适用的规律。
为了防止过拟合,AlexNet 在全连接层中引入了 Dropout 技术。Dropout 的基本思想是在每次训练时,随机“丢弃”一部分神经元(即暂时将它们的输出设为 0),从而使网络不依赖于某些特定的神经元,而是学习到多个神经元之间的冗余和互补关系。
假设一个全连接层有 100 个神经元,在每一次前向传播过程中,Dropout 会以一定的概率(例如 0.5)随机关闭一半的神经元,使其在训练过程中不能过分依赖局部信息,而是必须从全局角度考虑问题。从而提高模型在测试集上的泛化能力。
在卷积操作后,不同神经元的响应值可能相差悬殊。局部响应归一化(LRN)技术就是为了让神经元之间的响应值更加平衡,模拟人类视觉系统中神经元间的相互抑制机制。
LRN 的基本思想是在同一局部区域内,对神经元的响应值进行归一化处理。简单来说,假设某个神经元的响应值较高,那么它所在区域的其他神经元响应值就会被适当地抑制。这种方式有助于突出最显著的特征,并减少噪声干扰,使得网络输出更稳定。
公式上,局部响应归一化可以写成:
其中,a_{x,y}^{(i)} 是位置 (x,y) 上第 i 个神经元的响应值, k, \alpha, \beta 和 n 是超参数。
深层神经网络的训练涉及大量的矩阵运算和并行计算,这些任务传统 CPU 处理起来速度非常慢。GPU(图形处理单元)则专为大规模并行运算设计,能够同时处理数千个甚至上万个运算单元。
AlexNet 是利用多 GPU 加速训练的深层网络之一。通过使用两块 NVIDIA GTX 580 3GB GPU,AlexNet 的训练速度得到了提升(虽然在当时训练速度仍不及 CPU)。至于这个结构图,B站李沐老师讲的很易懂,大家可以去康康。
在理解了 AlexNet 论文中的核心思想之后,接下来我们将通过代码实践,使用 PyTorch 实现一个基于 AlexNet 的图像分类器。我们选用 CIFAR-10 数据集,它包含 10 个类别的彩色图片,虽然规模和难度与 ImageNet 有差异,但对于初学者来说已经足够进行实验。
首先,在 Cloud Studio 上创建一个新的 Pytorch 项目,并确保环境中已安装 PyTorch 和 torchvision。接着,我们使用 torchvision 加载 CIFAR-10 数据集,并通过 transforms 对数据进行预处理,统一图片尺寸和归一化。
import torch
import torchvision
import torchvision.transforms as transforms
# 定义数据预处理:调整图片大小为 224x224(AlexNet 的输入尺寸),转换为 Tensor,并归一化
transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])
# 加载 CIFAR-10 数据集
train_dataset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
test_dataset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
# 定义 DataLoader,批量加载数据,设置 batch_size 为 32
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=32, shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=32, shuffle=False)
数据集总大小 170MB 左右,下载完成后 DataLoader 就会调整图片大小、转换为张量和归一化。不过国内下载太慢,大家可以从网盘找一个下载完,上传到 Cloud Studio 上去。
接下来,我们构建 AlexNet 模型。由于原始 AlexNet 结构较为庞大,为了便于理解和实验,我们实现一个简化版。模型主要包括五个卷积层和三个全连接层,同时使用 ReLU、Dropout 和最大池化层来增强模型性能。
import torch.nn as nn
import torch.nn.functional as F
class AlexNet(nn.Module):
def __init__(self, num_classes=10):
super(AlexNet, self).__init__()
self.features = nn.Sequential(
# 第一层卷积:输入 3 通道,输出 64 个特征图,卷积核 11x11,步长 4,填充 2
nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2),
# 第二层卷积:输入 64,输出 192 个特征图,卷积核 5x5,填充 2
nn.Conv2d(64, 192, kernel_size=5, padding=2),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2),
# 第三层卷积:输入 192,输出 384 个特征图,卷积核 3x3,填充 1
nn.Conv2d(192, 384, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
# 第四层卷积:输入 384,输出 256 个特征图,卷积核 3x3,填充 1
nn.Conv2d(384, 256, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
# 第五层卷积:输入 256,输出 256 个特征图,卷积核 3x3,填充 1
nn.Conv2d(256, 256, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2)
)
self.classifier = nn.Sequential(
nn.Dropout(),
# 全连接层:输入由卷积层输出的特征图展平后,大小为 256*6*6
nn.Linear(256 * 6 * 6, 4096),
nn.ReLU(inplace=True),
nn.Dropout(),
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), -1)
# 通过分类器
x = self.classifier(x)
return x
# 创建模型实例并打印结构
model = AlexNet(num_classes=10)
print("模型结构:\n", model)
有了数据和模型后,下一步就是训练模型。训练的目标是让模型在大量图片上不断调整参数,从而使得预测结果逐步接近真实标签。我们采用交叉熵损失函数和 Adam 优化器来实现这一过程。
import torch.optim as optim
# 定义损失函数(交叉熵损失)和优化器(Adam)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 设定训练轮数
num_epochs = 20
for epoch in range(num_epochs):
running_loss = 0.0
for i, (images, labels) in enumerate(train_loader):
# 前向传播:将图片输入模型,获得预测结果
outputs = model(images)
loss = criterion(outputs, labels)
# 反向传播前先清空梯度
optimizer.zero_grad()
# 反向传播:自动计算梯度
loss.backward()
# 更新模型参数
optimizer.step()
running_loss += loss.item()
if (i+1) % 100 == 0:
print(f'Epoch [{epoch+1}/{num_epochs}], Step [{i+1}/{len(train_loader)}], Loss: {running_loss/100:.4f}')
running_loss = 0.0
print("训练完成!")
训练过程中,模型不断调整参数,损失逐步降低,说明模型在不断学习如何更好地对图片进行分类。
每一次的参数更新都相当于模型在朝着正确答案迈进一步。
训练了四个多小时后,训练完成。
训练完成后,我们需要评估模型在测试集上的表现,并展示部分预测结果,以直观感受模型的分类效果。
model.eval() # 将模型设置为评估模式,关闭 Dropout 等训练特性
correct = 0
total = 0
with torch.no_grad():
for images, labels in test_loader:
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print(f'测试集准确率:{100 * correct / total:.2f}%')
# 展示部分测试图片和预测结果
import matplotlib.pyplot as plt
import numpy as np
dataiter = iter(test_loader)
images, labels = next(dataiter)
def imshow(img):
img = img / 2 + 0.5 # 反归一化
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0)))
plt.show()
imshow(torchvision.utils.make_grid(images[:8]))
outputs = model(images[:8])
_, predicted = torch.max(outputs, 1)
print("预测结果:", predicted.numpy())
计算了模型在整个测试集上的准确率,并展示了部分测试图片及其预测结果,让你直观感受到模型的实际表现。
为了方便以后使用,我们可以将训练好的模型保存到文件中,以便随时加载,无需重新训练。
# 保存模型参数
torch.save(model.state_dict(), "alexnet_model.pth")
print("模型保存成功!")
# 加载模型参数
loaded_model = AlexNet(num_classes=10)
loaded_model.load_state_dict(torch.load("alexnet_model.pth"))
loaded_model.eval()
print("模型加载成功,开始推理...")
with torch.no_grad():
outputs = loaded_model(images[:8])
_, predicted = torch.max(outputs, 1)
print("加载后预测结果:", predicted.numpy())
精读 AlexNet 论文,我们简单了解了其核心设计思想——深层卷积结构、ReLU 激活、Dropout 正则化、数据增强、局部响应归一化和 GPU 加速等创新技术,这些技术共同推动了当时深度学习的发展。
AlexNet 的成功不仅证明了深层卷积神经网络在大规模图像识别任务中的巨大潜力,也为后续的网络设计提供了宝贵的经验和思路。
AlexNet 源码似乎今天也在 GitHub 上开源了,大家可以去康康!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。