深度学习模型构建、参数访问与初始化、设计自定义层和块、将模型读写到磁盘, 以及利用GPU实现显著的加速

释放双眼,带上耳机,听听看~!
本篇内容探讨了深度学习计算的关键组件,包括模型构建、参数访问与初始化、设计自定义层和块、将模型读写到磁盘,以及利用GPU实现显著的加速。

前言:

本篇内容记录笔者学习深度学习的学习过程,如果你有任何想询问的问题,欢迎在以下任何平台提问!

在本章中,我们将深入探索深度学习计算的关键组件, 即模型构建、参数访问与初始化、设计自定义层和块、将模型读写到磁盘, 以及利用GPU实现显著的加速

这些知识将使读者从深度学习”基础用户”变为”高级用户”。

虽然本章不介绍任何新的模型或数据集, 但后面的高级模型章节在很大程度上依赖于本章的知识

参考书:《动手学深度学习》

个人博客:conqueror712.github.io/

知乎:www.zhihu.com/people/soeu…

Bilibili:space.bilibili.com/57089326

掘金:juejin.cn/user/129787…

层和块:

深度学习模型构建、参数访问与初始化、设计自定义层和块、将模型读写到磁盘, 以及利用GPU实现显著的加速

从这里也可以看出,层和块的关系:块里面包含单个或多个层

我们简要说明一下每个块必须提供的基本功能。

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

深度学习模型构建、参数访问与初始化、设计自定义层和块、将模型读写到磁盘, 以及利用GPU实现显著的加速

如图:多个层被组合成块,形成更大的模型。

简要总结。

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

参数管理:

在选择了架构并设置了超参数后,我们就进入了训练阶段

此时,我们的目标是找到使损失函数最小化的模型参数值

经过训练后,我们将需要使用这些参数来做出未来的预测。

此外,有时我们希望提取参数,以便在其他环境中复用它们;

将模型保存下来,以便它可以在其他软件中执行, 或者为了获得科学的理解而进行检查。

这是成为调参工程师的第一步!

首先,我们来明确一点,超参数和模型参数有什么区别?如何区分它们?

(已经了解的读者可以跳过)

两种不同的参数:

(该部分引用自zhuanlan.zhihu.com/p/37476536,…

模型参数是模型内部的配置变量,其值可以根据数据进行估计。

  • 模型在进行预测时需要它们。
  • 它们的,定义了可使用的模型。
  • 他们是从数据估计或获悉的。
  • 它们通常由编程者手动设置
  • 他们通常被保存学习模型的一部分

模型超参数是模型外部的配置,其值无法从数据中估计。

  • 它们通常用于帮助估计模型参数
  • 它们通常由人工指定
  • 他们通常可以使用启发式设置
  • 他们经常被调整为给定的预测建模问题。

我们虽然无法知道给定问题的模型超参数的最佳值,但是我们可以使用经验法则,在其他问题上使用复制值,或通过反复试验来搜索最佳值。

如何区分?

如果必须手动指定模型参数,那么它可能是一个模型超参数。

接下来,我们进入正题。

访问参数:

我们可以通过一些方式来访问参数(也就是查看):

print(net[2].state_dict())
# 输出:
# OrderedDict([('weight', tensor([[ 0.3016, -0.1901, -0.1991, -0.1220,  0.1121, -0.1424, -0.3060,  0.3400]])), ('bias', tensor([-0.0291]))])

注意,state_dict()并不是一定要这么写,具体要看情况。

不过我们也可以采用一种更加简便的方式访问所有的参数,而不用每一层都问一次:

print(*[(name, param.shape) for name, param in net[0].named_parameters()])
print(*[(name, param.shape) for name, param in net.named_parameters()])
# 输出:
# ('weight', torch.Size([8, 4])) ('bias', torch.Size([8]))
# ('0.weight', torch.Size([8, 4])) ('0.bias', torch.Size([8])) ('2.weight', torch.Size([1, 8])) ('2.bias', torch.Size([1]))

当然我们也可以从嵌套块收集参数,但因为我们的终点不是讲某一个细节,而是作为一个初学者,对于每一部分的知识形成一个整体的印象和框架,所以我们并不深入地去看这到底是怎么实现的。

可能会有较真的严谨一些的读者对此表示嗤之以鼻,的确,如果能第一次学习就将所有的细节都掌握那自然是更好的,不过笔者并不能做到这一点,相信也有一部分读者赞同这个观点,总之大家可以各取所需就是了。

参数初始化:

良好的初始化是很有必要的,有两种参数初始化的办法:

  • 深度学习框架提供的默认随机初始化;
  • 自定义初始化方法, 满足我们通过其他规则实现初始化权重。

下面直接看两种办法在Pytorch里面的代码对比:

# 默认
def init_normal(m):
    if type(m) == nn.Linear:
        nn.init.normal_(m.weight, mean=0, std=0.01)
        nn.init.zeros_(m.bias)
net.apply(init_normal)
net[0].weight.data[0], net[0].bias.data[0]
# 自定义
def my_init(m):
    if type(m) == nn.Linear:
        print("Init", *[(name, param.shape)
                        for name, param in m.named_parameters()][0])
        nn.init.uniform_(m.weight, -10, 10)
        m.weight.data *= m.weight.data.abs() >= 5

net.apply(my_init)
net[0].weight[:2]

参数绑定:

有时我们希望在多个层间共享参数: 我们可以定义一个稠密层,然后使用它的参数来设置另一个层的参数。

特别地,当参数绑定时,梯度会发生什么情况?

答案是由于模型参数包含梯度,因此在反向传播期间,绑定层的梯度会加在一起

延后初始化与自定义层:

延后初始化:

深度学习框架无法判断网络的输入维度是什么,不过有延后初始化(defers initialization):

  • 直到数据第一次通过模型传递时,框架才会动态地推断出每个层的大小。

延后初始化使框架能够自动推断参数形状,使修改模型架构变得容易,避免了一些常见的错误。

我们可以通过模型传递数据,使框架最终初始化参数。

自定义层:

深度学习成功背后的一个因素是神经网络的灵活性

  • 我们可以用创造性的方式组合不同的层,从而设计出适用于各种任务的架构。

关于文件读写和GPU的介绍就不在本文给出了,读者可以自行查找相关内容。

本文正在参加 人工智能创作者扶持计划

本网站的内容主要来自互联网上的各种资源,仅供参考和信息分享之用,不代表本网站拥有相关版权或知识产权。如您认为内容侵犯您的权益,请联系我们,我们将尽快采取行动,包括删除或更正。
AI教程

Groq介绍:软件定义硬件的TSP架构和设计理念

2023-12-1 2:43:14

AI教程

池化层和步长为2的卷积层的对比实验

2023-12-1 4:23:14

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索