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

本文涉及的产品
函数计算FC,每月免费额度15元,12个月
简介: 【从零开始学习深度学习】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)


相关实践学习
基于函数计算一键部署掌上游戏机
本场景介绍如何使用阿里云计算服务命令快速搭建一个掌上游戏机。
建立 Serverless 思维
本课程包括: Serverless 应用引擎的概念, 为开发者带来的实际价值, 以及让您了解常见的 Serverless 架构模式
相关文章
|
1天前
|
网络架构
OSI网络七层模型
OSI网络七层模型
|
2天前
|
网络协议 程序员 定位技术
学习网络的第一步:全面解析OSI与TCP/IP模型
**网络基础知识概览:** 探索网络通信的关键模型——OSI七层模型和TCP/IP五层模型。OSI模型(物理、数据链路、网络、传输、会话、表示、应用层)提供理论框架,而TCP/IP模型(物理、数据链路、网络、传输、应用层)更为实际,合并了会话、表示和应用层。两者帮助理解数据在网络中的传输过程,为网络设计和管理提供理论支持。了解这些模型,如同在复杂的网络世界中持有了地图。
8 2
|
17小时前
|
机器学习/深度学习 PyTorch 算法框架/工具
图神经网络是一类用于处理图结构数据的神经网络。与传统的深度学习模型(如卷积神经网络CNN和循环神经网络RNN)不同,
图神经网络是一类用于处理图结构数据的神经网络。与传统的深度学习模型(如卷积神经网络CNN和循环神经网络RNN)不同,
14 9
|
1天前
|
机器学习/深度学习 人工智能 算法
探索深度学习在图像识别中的应用及其挑战
本文深入探讨了深度学习技术在图像识别领域的应用,分析了其背后的原理、当前的研究进展以及面临的主要挑战。通过对比传统图像处理方法,我们展示了深度学习如何提高识别准确率和效率。同时,本文还讨论了数据偏差、模型泛化能力等关键问题,并提出了未来研究的可能方向。
|
2天前
|
机器学习/深度学习 人工智能 自然语言处理
深度学习在自然语言处理中的应用与挑战
【7月更文挑战第12天】随着人工智能技术的飞速发展,深度学习已成为推动自然语言处理(NLP)领域革新的核心动力。本文将深入探讨深度学习技术如何赋能NLP,实现从文本分类到机器翻译的多样化应用,并分析当前面临的主要挑战,如数据偏差、模型可解释性及多语言处理问题,最后展望深度学习在NLP领域的未来发展方向。
13 5
|
1天前
|
机器学习/深度学习 传感器 自动驾驶
深度学习在图像识别中的应用与挑战
随着人工智能技术的飞速发展,深度学习已成为推动图像识别领域进步的关键力量。通过模拟人脑处理信息的方式,深度学习模型能够自动提取高维数据特征,实现对复杂图像的高效识别。然而,尽管取得了显著成就,深度学习在图像识别中仍面临数据偏差、模型泛化能力不足以及对抗性攻击等挑战。本文将探讨深度学习在图像识别领域的应用现状,分析其面临的主要技术挑战,并提出未来研究的可能方向。
|
1天前
|
机器学习/深度学习 自然语言处理 监控
深度学习在自然语言处理中的应用与挑战
本文探讨了深度学习在自然语言处理(NLP)领域的应用现状及面临的挑战。通过分析深度学习模型在文本分类、情感分析、机器翻译等任务中的成功案例和技术原理,深入剖析了语言数据的复杂性对模型训练和性能的影响。此外,文章还讨论了数据获取与质量、模型解释性、多语言处理等方面的挑战,并展望了未来深度学习在NLP中的发展方向。 【7月更文挑战第13天】
|
2天前
|
机器学习/深度学习 监控 自动驾驶
深度学习在图像识别中的应用与挑战
【7月更文挑战第12天】本文将探讨深度学习技术在图像识别领域的应用及其面临的挑战。我们将首先介绍深度学习的基本原理和关键技术,然后详细讨论其在图像识别中的具体应用,包括面部识别、物体检测和场景理解等。最后,我们将分析当前深度学习在图像识别领域所面临的主要挑战,如数据偏见、模型泛化能力和计算资源需求等。
12 4
|
2天前
|
机器学习/深度学习 人工智能 监控
深度学习在图像识别中的应用与挑战
【7月更文挑战第12天】随着人工智能技术的飞速发展,深度学习已经成为图像识别领域的核心技术。本文将探讨深度学习如何改变图像识别的面貌,包括其在特征提取、目标检测和分类方面的应用。同时,我们也将分析深度学习面临的主要挑战,如过拟合、数据集偏差和模型解释性问题,并提出相应的解决策略。通过深入讨论,旨在为深度学习在图像识别领域的未来发展提供洞见。
|
2天前
|
机器学习/深度学习 自然语言处理 监控
深度学习在自然语言处理中的应用与挑战
【7月更文挑战第12天】本文深入探讨了深度学习技术在自然语言处理(NLP)领域的应用,并分析了当前面临的主要挑战。文章首先概述了深度学习对NLP的革新性影响,随后详细讨论了在语言模型、机器翻译和情感分析等方面的具体应用。最后,本文指出了数据偏差、模型可解释性和资源消耗等关键挑战,并提出了未来研究的可能方向。
10 3