【动手学深度学习】LeNet:卷积神经网络的开山之作

【动手学深度学习】LeNet:卷积神经网络的开山之作


1,LeNet卷积神经网络简介

LeNet 是一种经典的卷积神经网络,是现代卷积神经网络的起源之一。它是早期成功的神经网络;LeNet先使用卷积层来学习图片空间信息,使用池化层降低图片敏感度,然后使用全连接层来转换到类别空间。 其思想被广泛应用于图像分类、目标检测、图像分割等多个计算机视觉领域,为这些领域的研究和发展提供了新的思路和方法。例如,在安防领域用于面部识别和监控系统,在自动驾驶领域用于实时视频分析和对象跟踪等。

1989年,Yann LeCun等人在贝尔实验室工作期间提出了LeNet-1。这个网络主要用于手写数字识别,引入了卷积操作和权值共享的概念,简化了网络结构,减少了参数数量,提高了模型的泛化能力和训练速度。此后经过多年的迭代改进,1998年,LeCun等人正式发表了LeNet-5。LeNet-5在LeNet-1的基础上进一步优化了网络结构,增加了网络的深度和复杂度,使其在手写数字识别任务上取得了更好的性能。LeNet-5的成功应用证明了CNN在图像识别领域的巨大潜力,为后续CNN的发展奠定了坚实的基础。


2,Fashion-MNIST图像分类数据集

Fashion-MNIST数据集是一个广泛使用的图像分类数据集。Fashion-MNIST中包含的10个类别,分别为t-shirt(T恤)、trouser(裤子)、pullover(套衫)、dress(连衣裙)、coat(外套)、sandal(凉鞋)、shirt(衬衫)、sneaker(运动鞋)、bag(包)和ankle boot(短靴)。

之前,已经学习过Fashion-MNIST数据集。 【动手学深度学习】Fashion-MNIST图片分类数据集,其基本情况如下:

  • 训练集:包含60,000张图像,用于模型训练;
  • 测试集:包含10,000张图像,用于评估模型性能;
  • 数据集由灰度图像组成,其通道数为1;
  • 每个图像的高度和宽度均为28像素;
  • 调用load_data_fashion_mnist()函数加载数据集;

具体定义如下:

python 复制代码
"""
下载Fashion-MNIST数据集,然后将其加载到内存中
参数resize表示调整图片大小
"""
def load_data_fashion_mnist(batch_size, resize=None): 
    
    # trans是一个用于转换的 *列表*
    trans = [transforms.ToTensor()]
    
    if resize:    # resize不为空,表示需要调整图片大小
        trans.insert(0, transforms.Resize(resize))
        
    trans = transforms.Compose(trans)
    
    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=get_dataloader_workers()),
            data.DataLoader(mnist_test, batch_size, shuffle=False,
                            num_workers=get_dataloader_workers()))

3,LeNet总体架构

总体来看,LeNet(LeNet-5)由两个部分组成:

  • 卷积编码器:由两个卷积层组成;
  • 全连接层密集块:由三个全连接层组成;

每个卷积块中的基本单元是一个卷积层、一个sigmoid激活函数和平均汇聚层。(实际上使用ReLU激活函数和最大汇聚层更有效,但当时还没有发现):

  • Fashion-MNIST数据集的图像通道为1,大小为28×28,内部经过卷积层填充之后得到的实际输入数据是32×32的图像数据

  • 第一卷积层有6个输出通道,而第二个卷积层有16个输出通道;

  • 对应输出通道的数量,第一个卷积层有6个5×5的卷积核,第二个卷积层有16个5×5的卷积核;

  • 每个卷积核应用于输入数据时会产生一个特征图(feature map),也就是一个输出通道;

  • 每个卷积层都使用不同数量的5×5的卷积核和一个sigmoid激活函数。这些层将输入映射到多个二维特征输出,通常同时增加通道的数量;

  • 卷积操作后,通过2×2的池化操作默认步幅为2和池化窗口大小保持一致)将原特征图的各维度减半。比如原来是28×28,池化后变为14×14;


4,LeNet代码实现

接下来使用深度学习框架实现LeNet模型,并进行训练和测试。


4.1,定义LeNet模型

LeNet模型总共七层: 两层卷积层、两层池化层、三层全连接层; 其中每层都使用sigmod作为激活函数,它将卷积层的输出压缩到0和1之间,有助于非线性变换。

python 复制代码
import torch
from torch import nn
from d2l import torch as d2l
""" 默认情况下,深度学习框架中的步幅与汇聚窗口的大小相同(窗口没有重叠)"""

# nn.Sequential 是一个容器,可按顺序包装一系列子模块(如层、激活函数)。使得模型的构建变得更加简洁
net = nn.Sequential(
    # 第一个二维卷积层,输入通道是1(灰度图像),输出通道是6,卷积核大小5×5,图像周围加入两层0填充
    # 使用sigmod激活函数
    nn.Conv2d(1, 6, kernel_size=5, padding=2), nn.Sigmoid(),

    # 第一个平均池化层:用2x2的池化窗口,步长为2。经此池化操作后得6个14×14的特征图
    nn.AvgPool2d(kernel_size=2, stride=2),
    
    # 这是第二个二维卷积层,输入通道数为6(与第一个卷积层的输出通道数相匹配),输出通道数为16。卷积核的大小为5x5,没有使用padding填充
    # 使用sigmod激活函数
    nn.Conv2d(6, 16, kernel_size=5), nn.Sigmoid(),
    
    # 第二个平均池化层:配置与第一层平均池化层相同。
    nn.AvgPool2d(kernel_size=2, stride=2),
    
    # 在将数据传递给全连接层之前,需要将多维的卷积和池化输出展平为一维向量。以便传给全连接层
    nn.Flatten(),
    
    # 经过前面的卷积和池化操作后,输出16个5×5的特征图
    # 全连接层,输入特征的数量是16 * 5 * 5。输出特征的数量是120。
    nn.Linear(16 * 5 * 5, 120), nn.Sigmoid(),
    
    # 全连接层,输入特征数量120,输出84
    nn.Linear(120, 84), nn.Sigmoid(),
    
	# 全连接层,输入特征数量84,输出10,对应Fashion-MNIST数据集的10个类别
    nn.Linear(84, 10))

下面,我们将一个大小为 28 × 28 28 \times 28 28×28的单通道(黑白)图像通过LeNet。通过在每一层打印输出的形状,我们可以检查模型,以确保其操作与我们期望的一致。

python 复制代码
# 打印调试信息,检查模型
# size=(1, 1, 28, 28):批次大小1,通道数1,形状28*28 
X = torch.rand(size=(1, 1, 28, 28), dtype=torch.float32)

# 遍历了神经网络 net 中的每一层
for layer in net:
    X = layer(X)
    # 打印该层的类型(Conv2d、AvgPool2d、Flatten、Linear)以及输出张量的形状
    print(layer.__class__.__name__,'output shape: \t',X.shape)
    
# torch.Size([1, 6, 28, 28])中的1代表批次大小,6表示通道数

运行结果如下:


4.2,定义模型评估函数

我们已经实现了LeNet,接下来让我们看看LeNet在Fashion-MNIST数据集上的表现。

加载Fashion-MNIST图片分类数据集

python 复制代码
batch_size = 256  # 批量大小
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)

定义评估函数计算预测准确率

python 复制代码
def evaluate_accuracy_gpu(net, data_iter, device=None):
    """使用GPU计算模型在数据集上的精度"""
    if isinstance(net, nn.Module):
        net.eval()  # 设置为评估模式
        if not device: 
            # 若没有指定device,则通过获取模型参数的第一个元素的设备来确定应该使用的设备
            # net.parameters()返回模型的所有可学习参数(如权重和偏置)
            # next() 函数从迭代器中获取第一个元素。通常是第一个层的权重或偏置
            # .device 是 PyTorch 张量(torch.Tensor)的一个属性,表示该张量所在的备(如 GPU 或 CPU)
            # 例如,模型在 GPU 上运行,.device 的值可能是 device(type='cuda', index=0)
            device = next(iter(net.parameters())).device
            
    # 累加器记录正确预测的数量和总预测的数量
    metric = d2l.Accumulator(2)
    with torch.no_grad():  # 评估模型时,不需要计算梯度
        for X, y in data_iter: # 每次迭代获取一个数据批次X和对应的标签y
            if isinstance(X, list):  # x为list,每个元素都挪到对应的设备
                X = [x.to(device) for x in X]
            else:   # x是tensor,只需要挪一次
                X = X.to(device)
            y = y.to(device)
            
            # accuracy可以计算出预测正确的样本数量
            # y.numel()计算出样本总数
            metric.add(d2l.accuracy(net(X), y), y.numel())
    return metric[0] / metric[1]

4.3,定义训练函数进行训练

定义可以使用GPU训练的训练函数。

python 复制代码
def train_ch6(net, train_iter, test_iter, num_epochs, lr, device):
    """用GPU训练模型"""
    def init_weights(m): # 初始化权重
    	# 如果是全连接层或卷积层使用Xavier均匀初始化方法
        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)
    # 使用随机梯度下降(SGD)优化器
    optimizer = torch.optim.SGD(net.parameters(), lr=lr)
    # 使用交叉熵损失函数(nn.CrossEntropyLoss),适用于分类任务
    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)
        
        # 将模型设置为训练模式,这会启用Dropout等训练时特有的操作
        net.train()
        
        for i, (X, y) in enumerate(train_iter): # 遍历训练数据集
            timer.start()
            optimizer.zero_grad() # 梯度清零
            X, y = X.to(device), y.to(device) # 将输入数据X和标签y移动到指定的设备
			
			# 前向传播,得到预测结果 y_hat
            y_hat = net(X) 
            """
            在 PyTorch 中,nn.CrossEntropyLoss 默认会对每个样本的损失值进行平均,返回的是批次中所有样本损失的平均值。
            """
            l = loss(y_hat, y) # 计算损失
            # 进行反向传播,计算梯度。
            l.backward()
            # 使用优化器更新模型参数。
            optimizer.step()
            
           
            with torch.no_grad():  # 禁用梯度计算
            	# l * X.shape[0]是当前批次的总损失。样本平均损失乘当前批次样本数
            	# d2l.accuracy(y_hat, y) 计算当前批次正确预测的样本数
            	# X.shape[0]代表当前批次的样本数
            	# 最终累加器累积了整个训练集的总损失,预测正确的样本总数和总样本数
                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))
                             
        # 在每个epoch结束时,计算测试集上的准确率
        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)}')

调用函数进行训练

python 复制代码
lr, num_epochs = 0.9, 10
train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())

运行结果如下:

相关推荐
vsropy33 分钟前
matlab安装python API 出现Invalid version: ‘R2022a‘,
开发语言·python
atec20002 小时前
使用uv管理python项目环境
开发语言·python·uv
清岚_lxn2 小时前
原生SSE实现AI智能问答+Vue3前端打字机流效果
前端·javascript·人工智能·vue·ai问答
zybishe4 小时前
免费送源码:Java+ssm+MySQL 酒店预订管理系统的设计与实现 计算机毕业设计原创定制
java·大数据·python·mysql·微信小程序·php·课程设计
_一条咸鱼_4 小时前
大厂AI 大模型面试:注意力机制原理深度剖析
人工智能·深度学习·机器学习
FIT2CLOUD飞致云4 小时前
四月月报丨MaxKB正在被能源、交通、金属矿产等行业企业广泛采纳
人工智能·开源
_一条咸鱼_4 小时前
大厂AI大模型面试:泛化能力原理
人工智能·深度学习·机器学习
Amor风信子4 小时前
【大模型微调】如何解决llamaFactory微调效果与vllm部署效果不一致如何解决
人工智能·学习·vllm
Jamence5 小时前
多模态大语言模型arxiv论文略读(十五)
人工智能·语言模型·自然语言处理
农民小飞侠5 小时前
ubuntu 安装pyllama教程
linux·python·ubuntu