《动手学深度学习》笔记2.1——神经网络从基础→进阶 (层和块 - 自定义块)

news2024/11/17 9:39:37

目录

0. 前言

原书正文(第五章)

第五章 - 第一节 - 层和块 - 自定义块

1. Sequential() PyTorch高级API

2. MLP() 无传入参数

3. MySequential() 传入任意层(块)

4. FixedHiddenMLP() 无传入参数-固定隐藏层

5. NestMLP() 传入嵌套块-多次嵌套

6. 效率问题

7. 小结


0. 前言

  • 课程全部代码(pytorch版)已上传到附件
  • 本章节为原书第5章,共分为6节,本篇是第1节:层和块(自定义块)
    • 第2-3节:《动手学深度学习》笔记2.2——神经网络从基础→进阶 (参数管理-每层的权重/偏置)-CSDN博客
    • 第4节:《动手学深度学习》笔记2.3——神经网络从基础→进阶 (自定义层)-CSDN博客
    • 第5节:《动手学深度学习》笔记2.4——神经网络从基础→进阶 (文件读写-保存参数和模型)-CSDN博客
    • 第6节:《动手学深度学习》笔记2.5——神经网络从基础→使用GPU (CUDA-单卡-多卡-张量操作)-CSDN博客
  • 本章的代码位置:chapter_deep-learning-computation/index.ipynb
  • 本章的视频链接:
    • 16 PyTorch 神经网络基础【动手学深度学习v2】_哔哩哔哩_bilibili
    • 17 使用和购买 GPU【动手学深度学习v2】_哔哩哔哩_bilibili
  • 本章核心内容:深入探索深度学习计算的关键组件, 即模型构建、参数访问与初始化、设计自定义层和块、将模型读写到磁盘, 以及利用GPU实现显著的加速
  • 从基础到进阶:本章知识将使读者从深度学习“基础用户”变为“高级用户”
  • 与后面高级模型的关系:虽然本章不介绍任何新的模型或数据集, 但后面的高级模型章节在很大程度上依赖于本章的知识
  • 同学反馈:本章的学习解开了前后章节的很多困惑,对打牢基础非常有帮助
  • 想完全读懂本章节,需要有线性回归、softmax回归、多层感知机等相关基础,主要的前置知识可参考
    • https://blog.csdn.net/weixin_57972634/category_12776752.html
    • 还非常需要python基础,得学到面向对象:https://download.csdn.net/download/weixin_57972634/89738887?spm=1001.2014.3001.5503

原书正文(第五章)

除了庞大的数据集和强大的硬件, 优秀的软件工具在深度学习的快速发展中发挥了不可或缺的作用。 从2007年发布的开创性的Theano库开始, 灵活的开源工具使研究人员能够快速开发模型原型, 避免了我们使用标准组件时的重复工作, 同时仍然保持了我们进行底层修改的能力。 随着时间的推移,深度学习库已经演变成提供越来越粗糙的抽象。 就像半导体设计师从指定晶体管到逻辑电路再到编写代码一样, 神经网络研究人员已经从考虑单个人工神经元的行为转变为从层的角度构思网络, 通常在设计架构时考虑的是更粗糙的块(block)。

之前我们已经介绍了一些基本的机器学习概念, 并慢慢介绍了功能齐全的深度学习模型。 在上一章中,我们从零开始实现了多层感知机的每个组件, 然后展示了如何利用高级API轻松地实现相同的模型。 为了易于学习,我们调用了深度学习库,但是跳过了它们工作的细节。 在本章中,我们将深入探索深度学习计算的关键组件, 即模型构建、参数访问与初始化、设计自定义层和块、将模型读写到磁盘, 以及利用GPU实现显著的加速。 这些知识将使读者从深度学习“基础用户”变为“高级用户”。 虽然本章不介绍任何新的模型或数据集, 但后面的高级模型章节在很大程度上依赖于本章的知识。

第五章 - 第一节 - 层和块 - 自定义块

之前首次介绍神经网络时,我们关注的是具有单一输出的线性模型。 在这里,整个模型只有一个输出。

注意,单个神经网络:

(1)接受一些输入;

(2)生成相应的标量输出;

(3)具有一组相关 参数(parameters),更新这些参数可以优化某目标函数。

然后,当考虑具有多个输出的网络时, 我们利用矢量化算法来描述整层神经元。

像单个神经元一样,层:

(1)接受一组输入,

(2)生成相应的输出,

(3)由一组可调整参数描述。

当我们使用softmax回归时,一个单层本身就是模型。 然而,即使我们随后引入了多层感知机,我们仍然可以认为该模型保留了上面所说的基本架构。

对于多层感知机而言,整个模型及其组成层都是这种架构。 整个模型接受原始输入(特征),生成输出(预测), 并包含一些参数(所有组成层的参数集合)。 同样,每个单独的层接收输入(由前一层提供), 生成输出(到下一层的输入),并且具有一组可调参数, 这些参数根据从下一层反向传播的信号进行更新。

事实证明,研究讨论 “比单个层大” 但 “比整个模型小” 的组件更有价值。 例如,在计算机视觉中广泛流行的ResNet-152架构就有数百层, 这些层是由层组(groups of layers)的重复模式组成。 这个ResNet架构赢得了2015年ImageNet和COCO计算机视觉比赛 的识别和检测任务 :cite:He.Zhang.Ren.ea.2016。 目前ResNet架构仍然是许多视觉任务的首选架构。 在其他的领域,如自然语言处理和语音, 层组以各种重复模式排列的类似架构现在也是普遍存在。

为了实现这些复杂的网络,我们引入了神经网络的概念。 (block)可以描述单个层、由多个层组成的组件或整个模型本身。 使用块进行抽象的一个好处是可以将一些块组合成更大的组件, 这一过程通常是递归的,如 :numref:fig_blocks所示。 通过定义代码来按需生成任意复杂度的块, 我们可以通过简洁的代码实现复杂的神经网络。

从编程的角度来看,块由(class)表示。 它的任何子类都必须定义一个将其输入转换为输出的前向传播函数, 并且必须存储任何必需的参数。 注意,有些块不需要任何参数。 最后,为了计算梯度,块必须具有反向传播函数。 在定义我们自己的块时,由于自动微分(在 《动手学深度学习》笔记1.3——矩阵求导_方向导数与梯度的关系-CSDN博客 中引入自动求导) 提供了一些后端实现,我们只需要考虑前向传播函数和必需的参数。

在构造自定义块之前,(我们先回顾一下多层感知机) (《动手学深度学习》笔记1.6——多层感知机→代码实现_两层感知机代码函数实现-CSDN博客)的代码。 下面的代码生成一个网络,其中包含一个具有256个单元和ReLU激活函数的全连接隐藏层, 然后是一个具有10个隐藏单元且不带激活函数的全连接输出层。

1. Sequential() PyTorch高级API

import torch
from torch import nn
from torch.nn import functional as F  # functional Module包括了一些没有参数的函数,下面会看到具体用法

net = nn.Sequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))  # 单隐藏层(256个神经元), 输入20→输出10

X = torch.rand(2, 20)  # 权重X,初始化成2行20列的、均值为0方差为1的随机矩阵
# 2是批量大小,20是输入的维度,10是输出的大小
net(X)

在这个例子中,我们通过实例化nn.Sequential来构建我们的模型, 层的执行顺序是作为参数传递的。 简而言之,(nn.Sequential定义了一种特殊的 Module), 即在PyTorch中表示一个块的类, 它维护了一个由Module组成的有序列表。 注意,两个全连接层都是Linear类的实例, Linear类本身就是Module的子类。 另外,到目前为止,我们一直在通过net(X)调用我们的模型来获得模型的输出。 这实际上是net.__call__(X)的简写

这个前向传播函数非常简单: 它将列表中的每个块连接在一起,将每个块的输出作为下一个块的输入

2. MLP() 无传入参数

要想直观地了解块是如何工作的,最简单的方法就是自己实现一个块 ,进而从零定义和执行块。 在实现我们自定义块之前,我们简要总结一下每个块必须提供的基本功能。

  1. 将输入数据作为其前向传播函数的参数。
  2. 通过前向传播函数来生成输出。请注意,输出的形状可能与输入的形状不同。例如,我们上面模型中的第一个全连接的层接收一个20维的输入,但是返回一个维度为256的输出。
  3. 计算其输出关于输入的梯度,可通过其反向传播函数进行访问。通常这是自动发生的。
  4. 存储和访问前向传播计算所需的参数。
  5. 根据需要初始化模型参数。

在下面的代码片段中,我们从零开始编写一个块。 它包含一个多层感知机,其具有256个隐藏单元的隐藏层和一个10维输出层。 注意,下面的MLP类继承了表示块的类。 我们的实现只需要提供我们自己的构造函数(Python中的__init__函数)和前向传播函数。

class MLP(nn.Module):  # 定义MLP是nn.Module的一个子类(涉及到python面向对象编程的相关知识)
    # 用模型参数声明层。这里,我们声明两个全连接的层
    def __init__(self):  # 创建对象(实例)时,__init__会自动执行
        # 调用MLP的父类Module的构造函数来执行必要的初始化。
        # 这样,在类实例化时也可以指定其他函数参数,例如模型参数params(稍后将介绍)
        super().__init__()  # super() 调用父类nn.Module的构造函数__init__(),初始化模型(必要)的参数和子模块
        self.hidden = nn.Linear(20, 256)  # 隐藏层:存在类的成员变量hidden里面
        self.out = nn.Linear(256, 10)  # 输出层:存在类的成员变量hidden里面

    # 定义模型的前向传播,即如何根据输入X返回所需的模型输出
    def forward(self, X):  # 前向传播:逐层运算;更新参数在反向传播过程中
        # 注意,这里我们使用ReLU的函数版本,其在nn.functional模块中定义。
        return self.out(F.relu(self.hidden(X))) # 先把输入X存到hidden层里面,得到隐藏层的输出
                                                # 用ReLU激活后放入out层里

我们首先看一下前向传播函数,它以X作为输入, 计算带有激活函数的隐藏表示,并输出其未规范化的输出值。 在这个MLP实现中,两个层都是实例变量。 要了解这为什么是合理的,可以想象实例化两个多层感知机(net1net2), 并根据不同的数据对它们进行训练。 当然,我们希望它们学到两种不同的模型。

实例化多层感知机的层,然后在每次调用前向传播函数时调用这些层

注意一些关键细节: 首先,我们定制的__init__函数通过super().__init__() 调用父类的__init__函数, 省去了重复编写模版代码的痛苦。 然后,我们实例化两个全连接层, 分别为self.hiddenself.out。 注意,除非我们用到一个新的运算符, 否则我们不必担心反向传播基础的函数或参数初始化, 系统将调用父类nn.Module的__init__()函数自动初始化。

我们来试一下这个MLP()函数:

net = MLP()
net(X)

的一个主要优点是它的多功能性。 我们可以子类化块以创建层(如全连接层的类)、 整个模型(如上面的MLP类)或具有中等复杂度的各种组件。 我们在接下来的章节中充分利用了这种多功能性, 比如在处理卷积神经网络时。

3. MySequential() 传入任意层(块)

现在我们可以更仔细地看看Sequential类是如何工作的, 回想一下Sequential的设计是为了把其他模块串起来。 为了构建我们自己的简化的MySequential, 我们只需要定义两个关键函数:

  1. 一种将块逐个追加到列表中的函数;
  2. 一种前向传播函数,用于将输入按追加块的顺序传递给块组成的“链条”。

下面的MySequential类提供了与默认Sequential类相同的功能。

class MySequential(nn.Module):
    def __init__(self, *args):  # *args是可变参数列表, 在实例化 MySequential 类时传入任意数量的参数
        # *args 是一个元组(tuple),可以传入多个 nn.Module 子类实例(层,块),它们会被打包成一个元组
        super().__init__()  # 和上面MLP()一样,调用父类nn.Module中的__init__()来初始化基本的模型和参数
        for idx, module in enumerate(args):  # enumerate()是内置函数,可同时获取可迭代对象的索引和对应的元素
            # 可以这样理解: 下方MySequential()传入的3个成员nn.Linear(20, 256),nn.ReLU(),nn.Linear(256, 10)
            # 放到了*args元组里,为每个成员(层,块)生成一个顺序idx,按顺序放进成员变量_module里
            self._modules[str(idx)] = module  # 成员变量_module的类型是OrderedDict(顺序字典)

    def forward(self, X):
        # OrderedDict保证了按照成员(层,块)添加的顺序遍历(调用)它们
        for block in self._modules.values():  # 按顺序每次用values()拿一个成员(层,块),放到block里,传入X
            X = block(X)  # 这里的X可看作中间变量,不断按顺序在成员(层,块)间塑性非线性变换
        return X  # 返回最后输出的X

__init__函数将每个模块逐个添加到有序字典_modules中。 读者可能会好奇为什么每个Module都有一个_modules属性? 以及为什么我们使用它而不是自己定义一个Python列表? 简而言之,_modules的主要优点是: 在模块的参数初始化过程中, 系统知道在_modules字典中查找需要初始化参数的子块。

  • MySequential的前向传播函数被调用时, 每个添加的块都按照它们被添加的顺序执行。

现在可以使用我们的 MySequential类 重新实现多层感知机

net = MySequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))
net(X)

请注意,MySequential的用法与之前为Sequential类编写的代码相同(咱的MySequential算是基础版的Sequential

拓展:可参考/回顾 《动手学深度学习》笔记1.6——多层感知机→代码实现_两层感知机代码函数实现-CSDN博客 )

4. FixedHiddenMLP() 无传入参数-固定隐藏层

Sequential类使模型构造变得简单, 允许我们组合新的架构,而不必定义自己的类。 然而,并不是所有的架构都是简单的顺序架构。 当需要更强的灵活性时,我们需要定义自己的块。 例如,我们可能希望在前向传播函数中执行Python的控制流。 此外,我们可能希望执行任意的数学运算, 而不是简单地依赖预定义的神经网络层。

class FixedHiddenMLP(nn.Module):
    def __init__(self):
        super().__init__()
        # 不计算梯度的随机权重参数。因此其在训练期间保持不变
        self.rand_weight = torch.rand((20, 20), requires_grad=False)
        self.linear = nn.Linear(20, 20)

    def forward(self, X):
        X = self.linear(X)
        # 使用创建的常量参数以及relu和mm函数
        X = F.relu(torch.mm(X, self.rand_weight) + 1)
        # 复用全连接层。这相当于两个全连接层共享参数
        X = self.linear(X)
        # 控制流
        while X.abs().sum() > 1:
            X /= 2
        return X.sum()

在这个FixedHiddenMLP模型中,我们实现了一个隐藏层, 其权重(self.rand_weight)在实例化时被随机初始化,之后为常量。 这个权重不是一个模型参数,因此它永远不会被反向传播更新。 然后,神经网络将这个固定层的输出通过一个全连接层。

注意,在返回输出之前,模型做了一些不寻常的事情: 它运行了一个while循环,在L1范数大于1的条件下, 将输出向量除以2,直到它满足条件为止。 最后,模型返回了 X 中所有项的和。 注意,此操作可能不会常用于在任何实际任务中, 我们只展示如何将任意代码集成到神经网络计算的流程中。

net = FixedHiddenMLP()
net(X)

输出:

5. NestMLP() 传入嵌套块-多次嵌套

我们可以[混合搭配各种组合块的方法]。 在下面的例子中,我们以一些想到的方法嵌套块,嵌套了用 nn.Sequential() 合并好的块。

class NestMLP(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(nn.Linear(20, 64), nn.ReLU(),  # 隐藏层1
                                 nn.Linear(64, 32), nn.ReLU())  # 隐藏层2
        self.linear = nn.Linear(32, 16)  # 输出层

    def forward(self, X):
        return self.linear(self.net(X))  # 一次嵌套

chimera = nn.Sequential(NestMLP(), nn.Linear(16, 20), FixedHiddenMLP())  # 二次嵌套,两个块夹一层拼在一起
chimera(X)

输出:

6. 效率问题

读者可能会开始担心操作效率的问题。 毕竟,我们在一个高性能的深度学习库中进行了大量的字典查找、 代码执行和许多其他的Python代码。 Python的问题全局解释器锁 是众所周知的。 在深度学习环境中,我们担心速度极快的GPU可能要等到CPU运行Python代码后才能运行另一个作业。

7. 小结

  • 一个块可以由许多层组成;一个块可以由许多块组成。
  • 块可以包含代码。
  • 块负责大量的内部处理,包括参数初始化和反向传播。
  • 层和块的顺序连接由Sequential块处理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2170641.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

GPU共享技术深度剖析与总结

在人工智能和深度学习领域,GPU(图形处理器)已成为不可或缺的计算工具。随着深度学习模型的规模和复杂性的增加,单个GPU已经难以满足所有训练需求,GPU共享技术应运而生,成为提高训练效率的重要手段。本文将深…

【十八】MySQL 8.0 新特性

MySQL 8.0 新特性 目录 MySQL 8.0 新特性 概述 简述 1、数据字典 2、原子数据定义语句 3、升级过程 4、会话重用 5、安全和账户管理 6、资源管理 7、表加密管理 8、InnoDB增强功能 9、字符集支持 10、增强JSON功能 11、数据类型的支持 12、查询的优化 13、公用…

SQL进阶技巧:如何计算块熵?

目录 0 信息量定义 信息熵 1 块熵定义 2 问题描述 ​3 数据准备 4 问题分析 5 小结 想要进一步了解SQL这门艺术语言的,可以订阅我的专栏数字化建设通关指南,将在该专栏进行详细解析。专栏 原价99,现在活动价39.9,按照阶梯式…

240927-各种卷积最清晰易懂blender动画展示

240927-一些常用卷积清晰易懂的blender动画展示(Conv、GConv、DWConv、1*1Conv、Shuffle) 在几个月前,写过一篇关于卷积过程中输入图像维度变化的博客240627_关于CNN中图像维度变化问题_图像的尺寸为什么又四个维度-CSDN博客,但是…

新手教学系列——在MySQL分表中批量调整表结构的实践与优化

在当今的互联网业务中,随着数据量的不断增长,单个数据库的处理能力往往难以满足高并发、高性能的要求。因此,分库分表已经成为解决数据库扩展性问题的主流方案之一。然而,分表虽然能有效提升数据库的读写性能,但也带来了一个新的挑战:当业务需求变化时,需要对大量分表进…

【DAY20240927】经典深度学习模型对比:LeNet5、CNN、ResNet20、AlexNet、TextCNN 与 VGG-11

文章目录 前言一、LeNet5二、CNN三、AlexNet四、ResNet20五、TextCNN六、VGG-11 前言 We leverage 6 models to deal with the data, i.e., LeNet5 (LeNet) (LeCun et al. 1989), a synthetic CNN network (CNN), ResNet20 (ResNet) (He et al.2016), AlexNet (Krizhevsky, Su…

矿山、石场重型机械设备数据集-挖掘机-自卸卡车-轮式装载机

描述 本项目旨在创建一个高效的计算机或机器视觉模型,用于在建筑工地检测不同种类的施工设备,我们从三个类别开始:挖掘机、卡车和轮式装载机。 数据集的理学硕士提供。 原始图像(v1)包含: 1,532个标注…

Git的安装 + 基本操作

一、Git初识 1. 提出问题 ​ 不知道你在工作或学习中,有没有遇到这样的情况:我们在创建并编写各种文档时,为了防止文档丢失,更改失误,失误后能恢复到原来的版本,不得不复制出一个一个的副本,保…

中东电商:下一个蓝海,Google Cloud和Google Maps助力企业乘风破浪

随着“一带一路”倡议的深入推进,中东地区已成为全球瞩目的新兴市场。庞大的年轻消费群体、丰富的石油资源以及不断完善的数字基础设施,为中国企业提供了前所未有的发展机遇。中东电商市场,无疑是下一个蓝海! 中东电商市场&#x…

学日语必备神器!这4款翻译APP你用过吗?

小伙伴们,你们有没有在日常生活或工作中遇到过需要翻译日语的场景呢?无论是阅读日本原著、工作文档还是和日本小伙伴交流,一个好的翻译工具绝对能成为你的贴心小助手;今天,我就来跟大家分享几款我个人非常喜欢的日语翻…

开放原子超级链内核XuperCore可搭建区块链

区块链是一种分布式数据库技术,它以块的形式存储数据,并使用密码学方法保证数据的安全性和完整性。 每个块包含一定数量的交易信息,并通过加密链接到前一个块,形成一个不断增长的链条。 这种设计使得数据在网络中无法被篡改,因为任何尝试修改一个块的数据都会破坏整个链的…

鼎阳加油-IOC关键技术问题的解决记

鼎阳SDS6204示波器EPICS IOC的搭建-CSDN博客 这款示波器在labview下工作的很好,以前搭建逐束团3D系统时连续几个月不间断的工作连接从没断过线,并做过速率测试,单通道时10Hz的波形更新速率都可以达到: 鼎阳SDS6204示波器波形读取…

hive分区详细教程

为什么要分区? 为了提高sql的查询效率 比如: select * from orders where create_date20230826; 假如数据量比较大,这个sql就是全表扫描,速度肯定慢。 可以将数据按照天进行分区,一个分区就是一个文件夹,当…

C++之STL—常用算术生成算法

#include <numeric> accumulate(iterator beg, iterator end, value); // 计算容器元素累计总和 // beg 开始迭代器 // end 结束迭代器 // value 起始值 fill (iterator beg, iterator end, value); // 向容器中填充元素 // beg 开始迭代器 // end 结束迭代器 // val…

AI驱动的Java开发框架:Spring AI Alibaba实战部署教程

前言 随着生成式 AI 的快速发展&#xff0c;基于 AI 开发框架构建 AI 应用的诉求迅速增长&#xff0c;涌现出了包括 LangChain、LlamaIndex 等开发框架&#xff0c;但大部分框架只提供了 Python 语言的实现。但这些开发框架对于国内习惯了 Spring 开发范式的 Java 开发者而言&a…

32. Java栈和队列

1. 前言 栈和队列相关的题目是校招中出现频率一般,但是是属于相对基础的题型。我们要关注两类问题,栈和队列的添加和删除操作,以及栈和队列之间的区别和联系。 2. 栈和队列 2.1 数据结构 首先我们给出栈和队列的数据结构定义: (1)栈(Stack):允许在某一端插入元素(…

【PyTorch入门】编程杂谈·1(文件、字典结构、字符串统计、结果保存)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;PyTorch入门宝典_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前…

搭建基于H.265编码的RTSP推流云服务器

一、前言 网上能够找到的RTSP流地址&#xff0c;均是基于H.264编码的RTSP流地址&#xff0c;无法测试应用是否可以播放H265实时流为此&#xff0c;搭建本地的把H.264转码成H.265的RTSP服务器&#xff0c;不管是通过VLC搭建本地RTSP服务器&#xff0c;还是通过FFmpeg搭建本地RT…

创作一个JavaScript案例,提示工程师请了解一下。

预期 提问1&#xff1a; web开发im的聊天记录展示的实现思路和代码实战。用户数据结构{nickName&#xff0c;uerID&#xff0c;msg,picture}。当nickName"主人"的时候聊天内容底色绿色&#xff0c;主人的聊天记录是靠左显示&#xff0c;其他人靠右显示。数据内容有长…

如何在 Three.js 场景中创建可点击展开的标签

在复杂的可视化场景中&#xff0c;经常需要为 3D 对象添加可交互的标签&#xff0c;以便用户点击时可以查看详细信息。这篇文章将通过一个简单的案例展示&#xff0c;如何在 Three.js 中为对象创建可点击的标签&#xff0c;点击标签可以展开详细信息&#xff0c;再次点击可以关…