1. 模型构造
在构造自定义块之前,我们先回顾一下多层感知机的代码。 下面的代码生成一个网络,其中包含一个具有256个单元和ReLU激活函数的全连接隐藏层, 然后是一个具有10个隐藏单元且不带激活函数的全连接输出层。
层和块
构造单层神经网咯:线性层+RELU+线性层
生成2x20(2是批量大小,20是批量维度)的随机矩阵
在这个例子中,我们通过实例化nn.Sequential来构建我们的模型, 层的执行顺序是作为参数传递的。 简而言之,nn.Sequential定义了一种特殊的Module, 即在PyTorch中表示一个块的类, 它维护了一个由Module组成的有序列表。 注意,两个全连接层都是Linear类的实例,Linear类本身就是Module的子类。 另外,到目前为止,我们一直在通过net(X)调用我们的模型来获得模型的输出。 这实际上是net.__call__(X)的简写。 这个前向传播函数非常简单: 它将列表中的每个块连接在一起,将每个块的输出作为下一个块的输入。
1.1 自定义
要想直观地了解块是如何工作的,最简单的方法就是自己实现一个。 在实现我们自定义块之前,我们简要总结一下每个块必须提供的基本功能。
-
将输入数据作为其前向传播函数的参数。
-
通过前向传播函数来生成输出。请注意,输出的形状可能与输入的形状不同。例如,我们上面模型中的第一个全连接的层接收一个20维的输入,但是返回一个维度为256的输出。
-
计算其输出关于输入的梯度,可通过其反向传播函数进行访问。通常这是自动发生的。
-
存储和访问前向传播计算所需的参数。
-
根据需要初始化模型参数
自定义快
MLP是nn.Module的子类,所以nn.Module有两个函数
实例化多层感知机的层
# 动手打一遍吧,加深一下印象嘞 class MLP(nn.Module): # 用模型参数声明层。这里,我们声明两个全连接的层 def __init__(self): # 调用MLP的父类Module的构造函数来执行必要的初始化。 # 这样,在类实例化时也可以指定其他函数参数,例如模型参数params(稍后将介绍) super().__init__() self.hidden = nn.Linear(20, 256) # 隐藏层 self.out = nn.Linear(256, 10) # 输出层 # 定义模型的前向传播,即如何根据输入X返回所需的模型输出 def forward(self, X): # 注意,这里我们使用ReLU的函数版本,其在nn.functional模块中定义。 return self.out(F.relu(self.hidden(X)))
上述代码的解析
# 测试上述代码 net = MLP() net(X) # 块的一个主要优点是它的多功能性。 # 我们可以子类化块以创建层(如全连接层的类)、 整个模型(如上面的MLP类)或具有中等复杂度的各种组件。 # 我们在接下来的章节中充分利用了这种多功能性, 比如在处理卷积神经网络时。
1.2 顺序块
现在我们可以更仔细地看看Sequential类是如何工作的, 回想一下Sequential的设计是为了把其他模块串起来。 为了构建我们自己的简化的MySequential, 我们只需要定义两个关键函数:
-
一种将块逐个追加到列表中的函数;
-
一种前向传播函数,用于将输入按追加块的顺序传递给块组成的“链条”。
下面的MySequential类提供了与默认Sequential类相同的功能。
顺序块
*args: lists of inputs of arguments
super( )._init_( ) 调用父类的初始化函数
self._modeules[block] : ordered dictionary. 放进去key. 【也就是说把传进去的每一层layer都按照顺序放在这个容器里,感觉相当于是数组的作用,只不过她存的是神经网络层】
class MySequential(nn.Module): def __init__(self, *args): super().__init__() for idx, module in enumerate(args): # 这里,module是Module子类的一个实例。我们把它保存在'Module'类的成员 # 变量_modules中。_module的类型是OrderedDict self._modules[str(idx)] = module def forward(self, X): # OrderedDict保证了按照成员添加的顺序遍历它们 for block in self._modules.values(): X = block(X) return X net = MySequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10)) net(X)
1.3 在前向传播函数中执行代码
Sequential类使模型构造变得简单, 允许我们组合新的架构,而不必定义自己的类。 然而,并不是所有的架构都是简单的顺序架构。 当需要更强的灵活性时,我们需要定义自己的块。 例如,我们可能希望在前向传播函数中执行Python的控制流。 此外,我们可能希望执行任意的数学运算, 而不是简单地依赖预定义的神经网络层。
添加图片注释,不超过 140 字(可选)
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() net = FixedHiddenMLP() net(X)
添加图片注释,不超过 140 字(可选)
我们可以混合搭配各种组合块的方法。 在下面的例子中,我们以一些想到的方法嵌套块。
class NestMLP(nn.Module): def __init__(self): super().__init__() self.net = nn.Sequential(nn.Linear(20, 64), nn.ReLU(), nn.Linear(64, 32), nn.ReLU()) 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)
不是很能完全理解....先这样,学到后面应该这里会好一些,迷茫抛在这里啦
2. 参数管理
我们首先看一下具有单隐藏层的多层感知机。
import torch from torch import nn net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 1)) X = torch.rand(size=(2, 4)) net(X)
2.1 参数访问
print(net[2].state_dict())
输出的结果告诉我们一些重要的事情: 首先,这个全连接层包含两个参数,分别是该层的权重和偏置。 两者都存储为单精度浮点数(float32)。 注意,参数名称允许唯一标识每个参数,即使在包含数百个层的网络中也是如此。
net[2] 拿到的是nn.Linear(8, 1)
state_dict() 就是权重
目标参数
一次性访问所有参数
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
3. 自定义层
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
4. 读写文件
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
看不懂的可以看看这本书的讲解:
http://www.feiguyunai.com/index.php/2019/09/11/pytorch-char03/
Pytorch神经网络工具箱
jupyter路径:pytorch/chapter_deep-learning-computation/model-construction.ipynb
https://github.com/Miraclelucy/dive-into-deep-learning 将李沐老师课堂中的jupyter notebook代码整理成了py格式的,欢迎关注,共同学习。