【从零开始学习深度学习】25.卷积神经网络之LeNet模型介绍及其Pytorch实现【含完整代码】

本文涉及的产品
函数计算FC,每月15万CU 3个月
简介: 【从零开始学习深度学习】25.卷积神经网络之LeNet模型介绍及其Pytorch实现【含完整代码】

之前我们对Fashion-MNIST数据集中的图像进行分类时,是将28*28图像中的像素逐行展开,得到长度为784的向量,并输入进全连接层中进行计算,这种分类方法有一定的局限性。

  1. 图像在同一列邻近的像素在这个向量中可能相距较远。它们构成的模式可能难以被模型识别。
  2. 对于大尺寸的输入图像,使用全连接层容易造成模型过大。假设输入是高和宽均为1000像素的彩色照片(含3个通道)。即使全连接层输出个数仍是256,该层权重参数的形状是3 , 000 , 000 × 256 3,000,000\times 2563,000,000×256:它占用了大约3 GB的内存或显存。这带来过复杂的模型和过高的存储开销。

卷积层尝试解决这两个问题:

一方面,卷积层保留输入形状,使图像的像素在高和宽两个方向上的相关性均可能被有效识别;

另一方面,卷积层通过滑动窗口将同一卷积核与不同位置的输入重复计算,从而避免参数尺寸过大。

卷积神经网络就是含卷积层的网络。本文我们将介绍一个早期用来识别手写数字图像的卷积神经网络:LeNet 。

Lenet 是一系列网络的合称,包括 Lenet1 - Lenet5,由 Yann LeCun 等人在 1990 年《Handwritten Digit Recognition with a Back-Propagation Network》中提出,是卷积神经网络的 HelloWorld。LeNet展示了通过梯度下降训练卷积神经网络可以达到手写数字识别在当时最先进的结果。这个奠基性的工作第一次将卷积神经网络推上舞台,为世人所知。LeNet5的网络结构如下图所示。

1. LeNet模型介绍与实现

LeNet分为卷积层块全连接层块两个部分。下面我们分别介绍这两个模块。

卷积层块里的基本单位是卷积层后接最大池化层:卷积层用来识别图像里的空间模式,如线条和物体局部,之后的最大池化层则用来降低卷积层对位置的敏感性。卷积层块由两个这样的基本单位重复堆叠构成。在卷积层块中,每个卷积层都使用5 × 5 5\times 55×5的窗口,并在输出上使用sigmoid激活函数。第一个卷积层输出通道数为6,第二个卷积层输出通道数则增加到16。这是因为第二个卷积层比第一个卷积层的输入的高和宽要小,所以增加输出通道使两个卷积层的参数尺寸类似。卷积层块的两个最大池化层的窗口形状均为2 × 2 2\times 22×2,且步幅为2。由于池化窗口与步幅形状相同,池化窗口在输入上每次滑动所覆盖的区域互不重叠。

卷积层块的输出形状为(批量大小, 通道, 高, 宽)。当卷积层块的输出传入全连接层块时,全连接层块会将小批量中每个样本变平(flatten)。也就是说,全连接层的输入形状将变成二维,其中第一维是小批量中的样本,第二维是每个样本变平后的向量表示,且向量长度为通道、高和宽的乘积。全连接层块含3个全连接层。它们的输出个数分别是120、84和10,其中10为输出的类别个数。

下面我们通过Sequential类来实现LeNet模型。

import time
import torch
from torch import nn, optim
import sys
import d2lzh_pytorch as d2l
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
class LeNet(nn.Module):
    def __init__(self):
        super(LeNet, self).__init__()
        # 卷积神经网络
        self.conv = nn.Sequential(
            nn.Conv2d(1, 6, 5), # in_channels, out_channels, kernel_size
            nn.Sigmoid(),
            nn.MaxPool2d(2, 2), # kernel_size, stride
            nn.Conv2d(6, 16, 5),
            nn.Sigmoid(),
            nn.MaxPool2d(2, 2)
        )
        # 分类器
        self.fc = nn.Sequential(
            nn.Linear(16*4*4, 120),
            nn.Sigmoid(),
            nn.Linear(120, 84),
            nn.Sigmoid(),
            nn.Linear(84, 10)
        )
    def forward(self, img):
        feature = self.conv(img)
        # 将feature展平,传入分类器fc
        output = self.fc(feature.view(img.shape[0], -1))   
        return output

接下来查看每个层的形状。

net = LeNet()
print(net)

输出:

LeNet(
  (conv): Sequential(
    (0): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
    (1): Sigmoid()
    (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (3): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
    (4): Sigmoid()
    (5): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (fc): Sequential(
    (0): Linear(in_features=256, out_features=120, bias=True)
    (1): Sigmoid()
    (2): Linear(in_features=120, out_features=84, bias=True)
    (3): Sigmoid()
    (4): Linear(in_features=84, out_features=10, bias=True)
  )
)

可以看到,在卷积层块中输入的高和宽在逐层减小。卷积层由于使用高和宽均为5的卷积核,从而将高和宽分别减小4,而池化层则将高和宽减半,但通道数则从1增加到16。全连接层则逐层减少输出个数,直到变成图像的类别数10。

2. 输入为Fashion-MNIST时各层输出形状

如果输入为Fashion-MNIST数据集,那么各层的形状的变化过程如下:

self.conv = nn.Sequential(
      # 输入:1*28*28
            nn.Conv2d(1, 6, 5), # in_channels, out_channels, kernel_size
            # 输出:6 * 24 * 24    【24=28-5+1】
            nn.Sigmoid(),
        # 输出:6 * 24 * 24
            nn.MaxPool2d(2, 2), # kernel_size, stride
        # 输出:6 * 12 * 12    【12=(24-2+2)/2】
            nn.Conv2d(6, 16, 5),
        # 输出:16 * 8 * 8     【8=12-5+1】
            nn.Sigmoid(),
        # 输出:16 * 8 * 8
            nn.MaxPool2d(2, 2)
        # 输出:16 * 4 * 4     【4=(8-2+2)/2】
        )
        # 分类器
        self.fc = nn.Sequential(
            # 输入:16*4*4
            nn.Linear(16*4*4, 120),
            # 输出:120
            nn.Sigmoid(),
            nn.Linear(120, 84),
            # 输出:84
            nn.Sigmoid(),
            nn.Linear(84, 10)
            # 输出:10
        )

3. 获取Fashion-MNIST数据和并使用LeNet模型进行训练

下面我们运用LeNet模型对Fashion-MNIST数据集进行训练。

batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)

因为卷积神经网络计算比多层感知机要复杂,建议使用GPU来加速计算。定义评价函数evaluate_accuracy,能同时支持GPU与CPU计算。

def evaluate_accuracy(data_iter, net, device=None):
    if device is None and isinstance(net, torch.nn.Module):
        # 如果没指定device就使用net的device
        device = list(net.parameters())[0].device
    acc_sum, n = 0.0, 0
    with torch.no_grad():
        for X, y in data_iter:
            if isinstance(net, torch.nn.Module):
                net.eval() # 评估模式, 这会关闭dropout
                acc_sum += (net(X.to(device)).argmax(dim=1) == y.to(device)).float().sum().cpu().item()
                net.train() # 改回训练模式
            else: 
                if('is_training' in net.__code__.co_varnames): # 如果有is_training这个参数
                    # 将is_training设置成False
                    acc_sum += (net(X, is_training=False).argmax(dim=1) == y).float().sum().item() 
                else:
                    acc_sum += (net(X).argmax(dim=1) == y).float().sum().item() 
            n += y.shape[0]
    return acc_sum / n

定义train_ch3训练函数,确保计算使用的数据和模型同在内存或显存上。

def train_ch5(net, train_iter, test_iter, batch_size, optimizer, device, num_epochs):
    net = net.to(device)
    print("training on ", device)
    loss = torch.nn.CrossEntropyLoss()
    for epoch in range(num_epochs):
        train_l_sum, train_acc_sum, n, batch_count, start = 0.0, 0.0, 0, 0, time.time()
        for X, y in train_iter:
            X = X.to(device)
            y = y.to(device)
            y_hat = net(X)
            l = loss(y_hat, y)
            optimizer.zero_grad()
            l.backward()
            optimizer.step()
            train_l_sum += l.cpu().item()
            train_acc_sum += (y_hat.argmax(dim=1) == y).sum().cpu().item()
            n += y.shape[0]
            batch_count += 1
        test_acc = evaluate_accuracy(test_iter, net)
        print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f, time %.1f sec'
              % (epoch + 1, train_l_sum / batch_count, train_acc_sum / n, test_acc, time.time() - start))

学习率采用0.001,训练算法使用Adam算法,损失函数使用交叉熵损失函数。

lr, num_epochs = 0.001, 5
optimizer = torch.optim.Adam(net.parameters(), lr=lr)
train_ch5(net, train_iter, test_iter, batch_size, optimizer, device, num_epochs)

输出:

training on  cpu
epoch 1, loss 1.7832, train acc 0.341, test acc 0.595, time 15.3 sec
epoch 2, loss 0.9300, train acc 0.649, test acc 0.705, time 15.5 sec
epoch 3, loss 0.7574, train acc 0.722, test acc 0.731, time 15.6 sec
epoch 4, loss 0.6708, train acc 0.745, test acc 0.743, time 15.6 sec
epoch 5, loss 0.6165, train acc 0.762, test acc 0.764, time 15.8 sec

4.完整代码

import time
import torch
from torch import nn, optim
import sys
import d2lzh_pytorch as d2l
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# 定义模型
class LeNet(nn.Module):
    def __init__(self):
        super(LeNet, self).__init__()
        # 卷积神经网络
        self.conv = nn.Sequential(
            nn.Conv2d(1, 6, 5), # in_channels, out_channels, kernel_size
            nn.Sigmoid(),
            nn.MaxPool2d(2, 2), # kernel_size, stride
            nn.Conv2d(6, 16, 5),
            nn.Sigmoid(),
            nn.MaxPool2d(2, 2)
        )
        # 分类器
        self.fc = nn.Sequential(
            nn.Linear(16*4*4, 120),
            nn.Sigmoid(),
            nn.Linear(120, 84),
            nn.Sigmoid(),
            nn.Linear(84, 10)
        )
    def forward(self, img):
        feature = self.conv(img)
        # 将feature展平,传入分类器fc
        output = self.fc(feature.view(img.shape[0], -1))   
        return output
# 定义评价函数
def evaluate_accuracy(data_iter, net, device=None):
    if device is None and isinstance(net, torch.nn.Module):
        # 如果没指定device就使用net的device
        device = list(net.parameters())[0].device
    acc_sum, n = 0.0, 0
    with torch.no_grad():
        for X, y in data_iter:
            if isinstance(net, torch.nn.Module):
                net.eval() # 评估模式, 这会关闭dropout
                acc_sum += (net(X.to(device)).argmax(dim=1) == y.to(device)).float().sum().cpu().item()
                net.train() # 改回训练模式
            else: 
                if('is_training' in net.__code__.co_varnames): # 如果有is_training这个参数
                    # 将is_training设置成False
                    acc_sum += (net(X, is_training=False).argmax(dim=1) == y).float().sum().item() 
                else:
                    acc_sum += (net(X).argmax(dim=1) == y).float().sum().item() 
            n += y.shape[0]
    return acc_sum / n
# 定义训练函数
def train_ch5(net, train_iter, test_iter, batch_size, optimizer, device, num_epochs):
    net = net.to(device)
    print("training on ", device)
    loss = torch.nn.CrossEntropyLoss()
    for epoch in range(num_epochs):
        train_l_sum, train_acc_sum, n, batch_count, start = 0.0, 0.0, 0, 0, time.time()
        for X, y in train_iter:
            X = X.to(device)
            y = y.to(device)
            y_hat = net(X)
            l = loss(y_hat, y)
            optimizer.zero_grad()
            l.backward()
            optimizer.step()
            train_l_sum += l.cpu().item()
            train_acc_sum += (y_hat.argmax(dim=1) == y).sum().cpu().item()
            n += y.shape[0]
            batch_count += 1
        test_acc = evaluate_accuracy(test_iter, net)
        print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f, time %.1f sec'
              % (epoch + 1, train_l_sum / batch_count, train_acc_sum / n, test_acc, time.time() - start))
# 使用模型进行训练
net = LeNet()
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)
lr, num_epochs = 0.001, 5
optimizer = torch.optim.Adam(net.parameters(), lr=lr)
train_ch5(net, train_iter, test_iter, batch_size, optimizer, device, num_epochs)


相关实践学习
【文生图】一键部署Stable Diffusion基于函数计算
本实验教你如何在函数计算FC上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。函数计算提供一定的免费额度供用户使用。本实验答疑钉钉群:29290019867
建立 Serverless 思维
本课程包括: Serverless 应用引擎的概念, 为开发者带来的实际价值, 以及让您了解常见的 Serverless 架构模式
相关文章
|
1月前
|
机器学习/深度学习 数据采集 人工智能
AI赋能教育:深度学习在个性化学习系统中的应用
【10月更文挑战第26天】随着人工智能的发展,深度学习技术正逐步应用于教育领域,特别是个性化学习系统中。通过分析学生的学习数据,深度学习模型能够精准预测学生的学习表现,并为其推荐合适的学习资源和规划学习路径,从而提供更加高效、有趣和个性化的学习体验。
134 9
|
2月前
|
PyTorch Linux 算法框架/工具
pytorch学习一:Anaconda下载、安装、配置环境变量。anaconda创建多版本python环境。安装 pytorch。
这篇文章是关于如何使用Anaconda进行Python环境管理,包括下载、安装、配置环境变量、创建多版本Python环境、安装PyTorch以及使用Jupyter Notebook的详细指南。
325 1
pytorch学习一:Anaconda下载、安装、配置环境变量。anaconda创建多版本python环境。安装 pytorch。
|
19天前
|
机器学习/深度学习 人工智能 PyTorch
Transformer模型变长序列优化:解析PyTorch上的FlashAttention2与xFormers
本文探讨了Transformer模型中变长输入序列的优化策略,旨在解决深度学习中常见的计算效率问题。文章首先介绍了批处理变长输入的技术挑战,特别是填充方法导致的资源浪费。随后,提出了多种优化技术,包括动态填充、PyTorch NestedTensors、FlashAttention2和XFormers的memory_efficient_attention。这些技术通过减少冗余计算、优化内存管理和改进计算模式,显著提升了模型的性能。实验结果显示,使用FlashAttention2和无填充策略的组合可以将步骤时间减少至323毫秒,相比未优化版本提升了约2.5倍。
35 3
Transformer模型变长序列优化:解析PyTorch上的FlashAttention2与xFormers
|
2月前
|
机器学习/深度学习
深度学习笔记(十二):普通卷积、深度可分离卷积、空间可分离卷积代码
本文探讨了深度可分离卷积和空间可分离卷积,通过代码示例展示了它们在降低计算复杂性和提高效率方面的优势。
197 2
深度学习笔记(十二):普通卷积、深度可分离卷积、空间可分离卷积代码
|
26天前
|
机器学习/深度学习 算法框架/工具 Python
深度学习的奥秘与实践:从理论到代码
本文将探索深度学习的世界,揭示其背后的原理,并分享如何将这些理论应用到实际编程中。我们将一起踏上一段旅程,从神经网络的基础概念出发,逐步深入到复杂的模型训练和优化技术。你将看到,即使是初学者,也可以实现自己的深度学习项目。
|
2月前
|
机器学习/深度学习 存储 自然语言处理
深度学习之少样本学习
少样本学习(Few-Shot Learning, FSL)是深度学习中的一个重要研究领域,其目标是在只有少量标注样本的情况下,训练出能够很好地泛化到新类别或新任务的模型。
46 2
|
2月前
|
机器学习/深度学习 自然语言处理 计算机视觉
深度学习中的迁移学习技术
【10月更文挑战第11天】 本文探讨了深度学习中的迁移学习技术,并深入分析了其原理、应用场景及实现方法。通过实例解析,展示了迁移学习如何有效提升模型性能和开发效率。同时,文章也讨论了迁移学习面临的挑战及其未来发展方向。
|
2月前
|
机器学习/深度学习 人工智能 算法
揭开深度学习与传统机器学习的神秘面纱:从理论差异到实战代码详解两者间的选择与应用策略全面解析
【10月更文挑战第10天】本文探讨了深度学习与传统机器学习的区别,通过图像识别和语音处理等领域的应用案例,展示了深度学习在自动特征学习和处理大规模数据方面的优势。文中还提供了一个Python代码示例,使用TensorFlow构建多层感知器(MLP)并与Scikit-learn中的逻辑回归模型进行对比,进一步说明了两者的不同特点。
95 2
|
2月前
|
机器学习/深度学习 缓存 PyTorch
pytorch学习一(扩展篇):miniconda下载、安装、配置环境变量。miniconda创建多版本python环境。整理常用命令(亲测ok)
这篇文章是关于如何下载、安装和配置Miniconda,以及如何使用Miniconda创建和管理Python环境的详细指南。
513 0
pytorch学习一(扩展篇):miniconda下载、安装、配置环境变量。miniconda创建多版本python环境。整理常用命令(亲测ok)
|
1月前
|
安全 搜索推荐 机器学习/深度学习
AI赋能教育:深度学习在个性化学习系统中的应用
【10月更文挑战第26天】在人工智能的推动下,个性化学习系统逐渐成为教育领域的重要趋势。深度学习作为AI的核心技术,在构建个性化学习系统中发挥关键作用。本文探讨了深度学习在个性化推荐系统、智能辅导系统和学习行为分析中的应用,并提供了代码示例,展示了如何使用Keras构建模型预测学生对课程的兴趣。尽管面临数据隐私和模型可解释性等挑战,深度学习仍有望为教育带来更个性化和高效的学习体验。
143 0
下一篇
DataWorks