跳转到主要内容
Chal1ce blog

AlexNet和LeNet详细介绍和实现

这是一篇深度学习入门文章,给大家介绍AlexNet和LeNet这两个经典网络

AlexNet和LeNet详细介绍和实现

说到深度学习,大概很多人都会想到那些能够“看图识物”的神经网络,比如帮手机识别照片里的猫咪,或者让汽车知道眼前是红灯还是绿灯。今天,我想跟大家聊聊两位“大功臣”——LeNet和AlexNet。这两位神经网络“大咖”不仅改变了计算机如何看世界,也推动了人工智能的飞速发展。

LeNet是个“老前辈”,上世纪90年代就已经登场,堪称神经网络家族的开山鼻祖之一。而AlexNet则是后起之秀,它的横空出世,直接点燃了深度学习的热潮。虽然它们“出道”时间相隔了将近20年,但从LeNet到AlexNet的发展过程,正好展现了神经网络从“小打小闹”到“大展拳脚”的成长故事。

接下来,我们就一起走进LeNet和AlexNet的世界,看看它们有什么不同,又是如何一步步让“机器看懂图片”变得如此神奇的。别担心,我会尽量用简单易懂的语言,让你轻松看明白!

LeNet

LeNet 简介及详细解读

1. 什么是 LeNet?它为什么经典?

LeNet 是卷积神经网络(CNN)中的鼻祖级架构,由 Yann LeCun 等人在 1990 年提出,最初是为了手写数字识别任务设计的,比如 MNIST 数据集。虽然它的结构现在看起来比较简单,但当时已经相当创新了,尤其是它的卷积和池化思想,奠定了现代深度学习图像处理的基础。

你可以把 LeNet 想象成一个特征提取加分类器的组合机器:前面几层(卷积+池化)负责找到图片的特征,比如边缘和纹理;后面几层(全连接)则把这些特征“翻译”成分类结果。

2. LeNet 的整体结构

Diagnostics | Free Full-Text | A Modified LeNet CNN for Breast Cancer ...

LeNet 的核心分成两部分:卷积层块全连接层块

  1. 卷积层块

    • 它主要由 卷积层池化层 组成。这部分的作用就是“缩小图片,提炼信息”。
      • 卷积层:通过小窗口(卷积核)扫描图片,找到局部特征,比如边缘、角点等。LeNet 的卷积核大小固定是 (5 \times 5),卷积后通过 sigmoid 激活函数 加入非线性。
      • 池化层:使用 (2 \times 2) 的窗口做 最大池化,这可以减少每层输出的大小,同时保留关键特征。它还增加了对位置变化的“免疫力”,比如图片稍微移动,不会影响结果太多。
    • LeNet 的卷积块有两次这样的组合,第一个卷积层输出 6 个通道,第二个卷积层输出 16 个通道。
  2. 全连接层块

    • 这一块就像传统的神经网络,负责“最后一公里”,也就是把卷积层提取的特征和最终分类结果联系起来。
      • 卷积层的输出先展平为一维向量,类似“摊平”处理。
      • 接着经过三层全连接网络,单元数分别是 120、84 和类别数(比如 10 类)。
      • 最后一层没有激活函数,直接输出分类概率。

3. 按层拆解:从输入到输出的过程

假设我们输入的图片大小是 (28 \times 28) 的灰度图,LeNet 每层的输出形状如下:

  • 输入图像:(1 \times 28 \times 28)(1 表示单通道的灰度图像)
  • 第 1 卷积层:6 个 (5 \times 5) 卷积核后,输出大小是 (6 \times 24 \times 24)。这一步提取了图片中的局部特征,比如边缘。
  • 第 1 池化层:(2 \times 2) 的最大池化,输出大小变为 (6 \times 12 \times 12)。池化减少了尺寸,但保留了关键特征。
  • 第 2 卷积层:16 个 (5 \times 5) 卷积核,输出大小是 (16 \times 8 \times 8)。这层进一步提取更复杂的特征,比如局部形状。
  • 第 2 池化层:再次做 (2 \times 2) 的最大池化,输出大小变为 (16 \times 4 \times 4)。
  • 展平:将 (16 \times 4 \times 4) 展平为一维向量,大小为 (256)。
  • 全连接层 1:120 个神经元,输出大小为 (1 \times 120)。
  • 全连接层 2:84 个神经元,输出大小为 (1 \times 84)。
  • 输出层:根据分类任务(比如 10 个类别),输出大小为 (1 \times 10)。

4. 代码实现 LeNet

接下来,我们用 MXNet 框架来实现 LeNet,看看它的每一层是如何工作的:

from mxnet.gluon import nn
from mxnet import nd

# 定义 LeNet 网络
net = nn.Sequential()
net.add(
    nn.Conv2D(channels=6, kernel_size=5, activation='sigmoid'),
    nn.MaxPool2D(pool_size=2, strides=2),
    nn.Conv2D(channels=16, kernel_size=5, activation='sigmoid'),
    nn.MaxPool2D(pool_size=2, strides=2),
    nn.Dense(120, activation='sigmoid'),
    nn.Dense(84, activation='sigmoid'),
    nn.Dense(10)
)

# 初始化网络
net.initialize()

# 测试网络
X = nd.random.uniform(shape=(1, 1, 28, 28))  # 输入一张随机图片
for layer in net:
    X = layer(X)
    print(f"{layer.name} output shape: {X.shape}")

运行结果类似这样:

conv0 output shape: (1, 6, 24, 24)
pool0 output shape: (1, 6, 12, 12)
conv1 output shape: (1, 16, 8, 8)
pool1 output shape: (1, 16, 4, 4)
dense0 output shape: (1, 120)
dense1 output shape: (1, 84)
dense2 output shape: (1, 10)

5. 实战:训练 LeNet

我们用 Fashion-MNIST 数据集(类似 MNIST,但更复杂)来训练 LeNet。

from mxnet import gluon, autograd, init
from mxnet.gluon import loss as gloss, Trainer
import time

# 加载数据
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)

# 定义训练函数
def train_ch5(net, train_iter, test_iter, batch_size, trainer, ctx, num_epochs):
    loss = gloss.SoftmaxCrossEntropyLoss()
    for epoch in range(num_epochs):
        train_l_sum, train_acc_sum, n, start = 0.0, 0.0, 0, time.time()
        for X, y in train_iter:
            X, y = X.as_in_context(ctx), y.as_in_context(ctx)
            with autograd.record():
                y_hat = net(X)
                l = loss(y_hat, y).sum()
            l.backward()
            trainer.step(batch_size)
            train_l_sum += l.asscalar()
            train_acc_sum += (y_hat.argmax(axis=1) == y).sum().asscalar()
            n += y.size
        test_acc = evaluate_accuracy(test_iter, net, ctx)
        print(f"epoch {epoch+1}, loss {train_l_sum/n:.4f}, train acc {train_acc_sum/n:.3f}, test acc {test_acc:.3f}, time {time.time()-start:.1f} sec")

# 初始化并训练
ctx = d2l.try_gpu()
net.initialize(force_reinit=True, ctx=ctx, init=init.Xavier())
trainer = Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.9})
train_ch5(net, train_iter, test_iter, batch_size, trainer, ctx, 5)

6. LeNet 表现如何?能改进吗?

在 Fashion-MNIST 上,LeNet 的初始准确率可能不太高,但随着训练,最终可以达到 70%-75% 的测试准确率。这对于一个早期设计的网络来说已经不错了!

如果要进一步提升:

  • 换激活函数:用 ReLU 代替 Sigmoid,计算效率和性能更高。
  • 增加层数或通道数:比如多加几层卷积或全连接层。
  • 优化算法:尝试 Adam、RMSProp 等更先进的优化器。
  • 数据增强:对输入图片做旋转、裁剪等增强操作,提高模型的泛化能力。

简单点说,LeNet 是一个很棒的起点,但我们可以在它的基础上玩出很多花样,构建更强大的模型!

接下来再来讲讲深度学习的里程碑模型AlexNet。

在 2012 年之前,计算机视觉主要依赖于手工设计的特征,如 SIFT、HOG 等。

然而,这种方法需要大量的经验和试验,且在面对复杂数据时难以扩展。AlexNet 的出现彻底改变了这一局面,它证明了端到端学习的深度神经网络能够在复杂任务上超越传统方法,成为深度学习历史上的重要里程碑。

早期的神经网络,如 LeNet,虽然在小数据集(如 MNIST)上表现出色,但受限于计算资源和数据规模,在更复杂的任务中难以超越其他机器学习方法(如支持向量机)。下面就是两个比较关键的问题:

  1. 数据规模限制:早期数据集往往规模较小,模型难以学习到丰富的特征。
  2. 计算能力不足:复杂网络需要强大的硬件支持,而 90 年代的计算能力不足以支持深层模型。

随着ImageNet 数据集GPU 的广泛应用,这两个限制逐渐被突破,为 AlexNet 的成功奠定了基础。

AlexNet

AlexNet 的核心特点

AlexNet 的设计与 LeNet 类似,但在以下方面进行了显著改进:

  1. 更多的层数和参数

    • AlexNet 包含 8 层网络(5 个卷积层和 3 个全连接层),相比 LeNet 更加深层。
    • 大幅增加了卷积通道数和全连接层节点数,用以处理更大的 ImageNet 数据集。
  2. ReLU 激活函数

    • AlexNet 使用 ReLU 激活函数取代了 sigmoid。
    • 优点:计算简单,避免梯度消失问题,加速了模型的训练。
  3. 丢弃法(Dropout)

    • 在全连接层引入丢弃法,随机屏蔽部分神经元以减少过拟合。
  4. 数据增强

    • 通过翻转、裁剪和颜色变化扩展数据规模,提高模型的泛化能力。
  5. 使用 GPU 加速

    • 采用双 GPU 训练,解决了显存不足的问题。

AlexNet 的代码实现

模型结构

以下代码展示了 AlexNet 的网络结构。

from mxnet.gluon import nn

net = nn.Sequential()

# 第一层:卷积 + 最大池化
net.add(nn.Conv2D(96, kernel_size=11, strides=4, activation='relu'),
        nn.MaxPool2D(pool_size=3, strides=2))

# 第二层:卷积 + 最大池化
net.add(nn.Conv2D(256, kernel_size=5, padding=2, activation='relu'),
        nn.MaxPool2D(pool_size=3, strides=2))

# 第三到第五层:连续卷积
net.add(nn.Conv2D(384, kernel_size=3, padding=1, activation='relu'),
        nn.Conv2D(384, kernel_size=3, padding=1, activation='relu'),
        nn.Conv2D(256, kernel_size=3, padding=1, activation='relu'),
        nn.MaxPool2D(pool_size=3, strides=2))

# 全连接层:带丢弃法
net.add(nn.Dense(4096, activation="relu"), nn.Dropout(0.5),
        nn.Dense(4096, activation="relu"), nn.Dropout(0.5),
        nn.Dense(10))  # 输出层,10 个类别(用于 Fashion-MNIST 数据集)

输出形状分析

通过给定一个高和宽为 224 的输入图像,我们可以观察每一层的输出形状。

from mxnet import nd

X = nd.random.uniform(shape=(1, 1, 224, 224))
net.initialize()

for layer in net:
    X = layer(X)
    print(layer.name, 'output shape:\t', X.shape)

输出示例:

conv0 output shape: (1, 96, 54, 54)
pool0 output shape: (1, 96, 26, 26)
conv1 output shape: (1, 256, 26, 26)
pool1 output shape: (1, 256, 12, 12)
...
dense2 output shape: (1, 10)

数据准备

AlexNet 原本使用 ImageNet 数据集,但由于训练时间较长,我们使用 Fashion-MNIST 数据集来演示。以下代码将图像的大小调整为 (224 \times 224)。

from mxnet.gluon.data.vision import transforms
from mxnet.gluon.data import DataLoader
from mxnet.gluon.data.vision import FashionMNIST

def load_data_fashion_mnist(batch_size, resize=None):
    transformer = []
    if resize:
        transformer.append(transforms.Resize(resize))
    transformer.append(transforms.ToTensor())
    transformer = transforms.Compose(transformer)
    
    train_set = FashionMNIST(train=True).transform_first(transformer)
    test_set = FashionMNIST(train=False).transform_first(transformer)
    
    train_loader = DataLoader(train_set, batch_size, shuffle=True, num_workers=4)
    test_loader = DataLoader(test_set, batch_size, shuffle=False, num_workers=4)
    
    return train_loader, test_loader

batch_size = 128
train_iter, test_iter = load_data_fashion_mnist(batch_size, resize=224)

模型训练

以下代码展示了如何训练 AlexNet,并输出训练和测试的准确率。

from mxnet import gluon, init
from mxnet.gluon import Trainer
from mxnet.gluon.loss import SoftmaxCrossEntropyLoss
from d2l import mxnet as d2l

# 定义训练函数
def train_ch5(net, train_iter, test_iter, batch_size, trainer, num_epochs):
    loss = SoftmaxCrossEntropyLoss()
    for epoch in range(num_epochs):
        train_loss, train_acc, n = 0, 0, 0
        for X, y in train_iter:
            X, y = X.as_in_context(ctx), y.as_in_context(ctx)
            with autograd.record():
                y_hat = net(X)
                l = loss(y_hat, y)
            l.backward()
            trainer.step(batch_size)
            train_loss += l.sum().asscalar()
            train_acc += (y_hat.argmax(axis=1) == y.astype('float32')).sum().asscalar()
            n += y.size
        test_acc = evaluate_accuracy(test_iter, net, ctx)
        print(f"epoch {epoch + 1}, loss {train_loss/n:.4f}, train acc {train_acc/n:.3f}, test acc {test_acc:.3f}")

# 初始化模型和训练参数
ctx, lr, num_epochs = d2l.try_gpu(), 0.01, 5
net.initialize(init=init.Xavier(), ctx=ctx)
trainer = Trainer(net.collect_params(), 'sgd', {'learning_rate': lr})

# 开始训练
train_ch5(net, train_iter, test_iter, batch_size, trainer, num_epochs)

总结

  1. AlexNet 的创新:通过增加深度、引入 ReLU 和 Dropout,并利用 GPU 和大数据,AlexNet 将深度学习推向了实用化。
  2. 模型的关键改进:在特征提取和训练方法上的提升,使深度学习真正超越了传统手工设计的特征。
  3. 代码实现简洁:尽管 AlexNet 的代码相比 LeNet 更复杂,但其核心思想简单且高效。

AlexNet 的成功不仅让深度学习成为研究热点,还为后续模型(如 VGG、ResNet)奠定了基础。希望通过本文,您对 AlexNet 有了清晰的理解!