3--深度学习计算

本文围绕PyTorch展开,介绍了层和块的概念,包括自定义块、顺序块类及在向前传播函数中执行代码;阐述了参数管理,如访问参数、参数初始化和不同模型组件间共享参数;还涉及自定义层(不带参数和带参数)、读写文件(加载和保存张量及模型参数)以及GPU使用注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

3.1 层和块

        (block)可以描述单个层、由多个层组成的组件或整个模型本身。以下代码通过实例化nn.Sequential来构建模型, 层的执行顺序是作为参数传递的。 简而言之,nn.Sequential定义了一种特殊的Module, 即在PyTorch中表示一个块的类, 它维护了一个由Module组成的有序列表。 注意,两个全连接层都是Linear类的实例, Linear类本身就是Module的子类。 

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

    3.1.1 自定义块

         每个块必须提供的功能:

  1. 将输入数据作为其前向传播函数的参数。

  2. 通过前向传播函数来生成输出。请注意,输出的形状可能与输入的形状不同。例如,我们上面模型中的第一个全连接的层接收一个20维的输入,但是返回一个维度为256的输出。

  3. 计算其输出关于输入的梯度,可通过其反向传播函数进行访问。通常这是自动发生的。

  4. 存储和访问前向传播计算所需的参数。

  5. 根据需要初始化模型参数。

import torch
from torch import nn
from torch.nn import functional as F

X = torch.rand(2,20)

class MLP(nn.Module):
  def __init__(self):
    super().__init__()#调用MLP的父类Module的构造函数进行必要的初始化 例如线性的w和b
    self.hidden = nn.Linear(20,256)#隐藏层
    self.out = nn.Linear(256,10)#输出层
  def forward(self,X):#定义模型的前向传播
    return self.out(F.relu(self.hidden(X)))# ReLU的函数版本,其在nn.functional模块中定义

net = MLP()
net(X)

#块的一个主要优点是它的多功能性。 我们可以子类化块以创建层(如全连接层的类)、 整个模型(如上面的MLP类)或具有中等复杂度的各种组件。

输出结果:

tensor([[ 0.1207, -0.1397, -0.0507, 0.0215, 0.1688, 0.3081, 0.1183, -0.3262, -0.2948, 0.0578], [ 0.1575, -0.1467, -0.0656, 0.1134, 0.0938, 0.1988, 0.0573, -0.3333, -0.2152, 0.1373]], grad_fn=<AddmmBackward0>) 

3.1.2 顺序块-Sequential

     Sequential的设计是为了把其他模块顺序的串起来,自定义Sequential类只需要定义两个关键函数:

  1. 一种将块逐个追加到列表中的函数。

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

class MySequential(nn.Module):
  def __init__(self,*args):
    super().__init__()
    for idx, module in enumerate(args):#enumerate()在遍历中可以获得索引和元素值。”
      self._modules[str(idx)] = module#把每个模块添加到字典里面
      #这里不使用自己定义列表是因为,在模块的参数初始化过程中系统知道在_modules字典中查找需要初始化参数的子块。
  def forward(self,X):
    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)

 输出结果:

tensor([[-0.0415, -0.1059, -0.2242, 0.2065, -0.2658, 0.2432, 0.0123, 0.0771, 0.2188, -0.0420], [ 0.0145, -0.0870, -0.1768, 0.2148, -0.1436, 0.1497, 0.0112, 0.0389, 0.0588, -0.0525]], grad_fn=<AddmmBackward0>)

 3.1.3 在向前传播函数中执行代码

         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)
    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)

 输出结果:

tensor(-0.1532, grad_fn=<SumBackward0>)

总结: 

  • 一个块可以由许多层组成;一个块可以由许多块组成。

  • 块可以包含代码。

  • 块负责大量的内部处理,包括参数初始化和反向传播。

  • 层和块的顺序连接由Sequential块处理 

3.2 参数管理

3.2.1 访问参数

        当通过Sequential类定义模型时, 我们可以通过索引来访问模型的任意层。 这就像模型是一个列表一样,每层的参数都在其属性中。 

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)

print(net[2].state_dict())#访问第2层的参数字典 即nn.Linear(8.1)的参数
print(net[2].weight)#访问weight
print(net[2].bias)#访问bias
print(net[2].weight.data)#直接输出值

输出结果:

OrderedDict([('weight', tensor([[-0.3429, 0.2159, -0.3050, 0.3406, -0.1746, 0.0702, 0.2869, -0.1781]])), ('bias', tensor([0.0261]))])

Parameter containing: tensor([[-0.3429, 0.2159, -0.3050, 0.3406, -0.1746, 0.0702, 0.2869, -0.1781]], requires_grad=True)

Parameter containing: tensor([0.0261], requires_grad=True)

tensor([[-0.3429, 0.2159, -0.3050, 0.3406, -0.1746, 0.0702, 0.2869, -0.1781]])

直接访问所有层的所有参数:

print(*[(name, param.shape) for name, param in net[0].named_parameters()])
print(*[(name, param.shape) for name, param in net.named_parameters()])
#加*的目的是取出list中的每个值
#例:print(*[1,2,3]) --> 1 2 3

输出结果:

('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])) 

3.2.2 参数初始化

        自定义参数的初始化规则

net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 1))
def init_normal(m):
  if type(m) == nn.Linear:
    nn.init.normal_(m.weight, mean=0, std=0.01)#将所有权重参数初始化为标准差为0.01的高斯随机变量
    #nn.init.constant_(m.weight, 1) #将wight参数初始化为给定的常数1
    nn.init.zeros_(m.bias)#将偏置参数设置为0
net.apply(init_normal)
net[0].weight.data[0],net[0].bias.data#nn.Linear(4, 8)初始化后的权重和偏置

输出结果: 

(tensor([-0.0001, -0.0070, 0.0078, 0.0049]), tensor([0., 0., 0., 0., 0., 0., 0., 0.]))

#(tensor([1., 1., 1., 1.]), tensor([0., 0., 0., 0., 0., 0., 0., 0.]))

        对不同层网络使用不同的初始化参数方法,例如使用Xavier初始化方法初始化第一个神经网络层, 然后将第三个神经网络层初始化为常量值42。 

def init_xavier(m):
  if type(m)==nn.Linear:
    nn.init.xavier_uniform_(m.weight)
def init_42(m):
  if type(m)==nn.Linear:
    nn.init.constant_(m.weight,42)
net[0].apply(init_xavier)
net[2].apply(init_42)
print(net[0].weight.data[0])
print(net[2].weight.data)

输出结果:

 tensor([-0.1781, -0.4115, 0.3015, 0.1715]) tensor([[42., 42., 42., 42., 42., 42., 42., 42.]])

        实现w满足该函数的初始化代码(思想跟前面一致,这里函数的表达自己写不出来 所以记录下来):

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)

3.2.3 不同模型组件间共享参数 

        在多个层间共享参数以定义一个稠密层,然后使用它的参数来设置另一个层的参数 。

# 需要给共享层一个名称,以便可以引用它的参数
shared = nn.Linear(8, 8)
net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(),
                    shared, nn.ReLU(),
                    shared, nn.ReLU(),
                    nn.Linear(8, 1))
net(X)
# 检查参数是否相同
print(net[2].weight.data[0] == net[4].weight.data[0])
net[2].weight.data[0, 0] = 100
# 确保它们实际上是同一个对象,而不只是有相同的值
print(net[2].weight.data[0] == net[4].weight.data[0])


输出:
tensor([True, True, True, True, True, True, True, True])
tensor([True, True, True, True, True, True, True, True])

3.3 自定义层

3.3.1 不带参数的层 

        这里不需要参数,就直接使用父类的初始化,并设置一个向前传播forward函数(这里设置为从输入中减去均值)。

class Mylayer(nn.Module):
  def __init__(self):
    super().__init__()
  def forward(self,X):
    return X-X.mean()

net = nn.Sequential(nn.Linear(8,128),Mylayer())#可直接传入Sequential进行使用
X = torch.rand(4,8)
Y = net(X)
Y.mean()

输出结果:(由于处理的是浮点数,因为存储精度的原因,我们仍然可能会看到一个非常小的非零数。)

tensor(9.3132e-09, grad_fn=<MeanBackward0>) 

3.3.2 带参数的层

        该层需要两个参数,一个用于表示权重,另一个用于表示偏置项。 使用修正线性单元作为激活函数。 该层需要输入参数:in_unitsunits,分别表示输入数和输出数。

class MyLinear(nn.Module):
    def __init__(self, in_units, units):
        super().__init__()
        self.weight = nn.Parameter(torch.randn(in_units, units))
        self.bias = nn.Parameter(torch.randn(units,))
    def forward(self, X):
        linear = torch.matmul(X, self.weight.data) + self.bias.data
        return F.relu(linear)

linear = MyLinear(5, 3)
linear.weight

输出结果:

Parameter containing:
tensor([[-1.4779, -0.6027, -0.2225],
        [ 1.1270, -0.6127, -0.2008],
        [-2.1864, -1.0548,  0.2558],
        [ 0.0225,  0.0553,  0.4876],
        [ 0.3558,  1.1427,  1.0245]], requires_grad=True)

3.4 读写文件

3.4.1 加载和保存张量

        可以直接调用loadsave函数分别读写它们。 这两个函数都要求我们提供一个名称,save要求将要保存的变量作为输入。

x = torch.arange(4)
torch.save(x, 'x-file')#写到名为“x-file”的文件中

y = torch.load('x-file')
x==y 

输出:

tensor([True, True, True, True]) 

3.4.2 加载和保存模型参数

         深度学习框架提供了内置函数来保存和加载整个网络。 需要注意的一个重要细节是,这将保存模型的参数而不是保存整个模型。

class MLP(nn.Module):
    def __init__(self):
        super().__init__()
        self.hidden = nn.Linear(20, 256)
        self.output = nn.Linear(256, 10)

    def forward(self, x):
        return self.output(F.relu(self.hidden(x)))

net = MLP()
X = torch.randn(size=(2, 20))
Y = net(X)

torch.save(net.state_dict(), 'mlp.params')

#实例化了原始多层感知机模型的一个备份,这里不需要随机初始化模型参数,而是直接读取文件中存储的参数。
clone = MLP()
clone.load_state_dict(torch.load('mlp.params'))

Y_clone = clone(X)
Y_clone == Y#检查是否一致

输出结果:

tensor([[True, True, True, True, True, True, True, True, True, True],
        [True, True, True, True, True, True, True, True, True, True]])

3.5 GPU

  • 我们可以指定用于存储和计算的设备,例如CPU或GPU。默认情况下,数据在主内存中创建,然后使用CPU进行计算。

  • 深度学习框架要求计算的所有输入数据都在同一设备上,无论是CPU还是GPU。

  • 不经意地移动数据可能会显著降低性能。一个典型的错误如下:计算GPU上每个小批量的损失,并在命令行中将其报告给用户(或将其记录在NumPy ndarray中)时,将触发全局解释器锁,从而使所有GPU阻塞。最好是为GPU内部的日志分配内存,并且只移动较大的日志。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值