李沐d2lDL部分-深度学习代码


李沐D2L-DL基础代码

colab d2l

  • 解决colab没有d2l的问题,使用这个比从github下载更快
!pip install d2l==0.14.

注意

避免一些无意义的复制粘贴,只记录一些体会和关键的点。

深度学习计算

(这段稍微有点记多了)

  • ”当我们使用softmax回归时,一个单层本身就是模型。事实证明,研究讨论“比单个层大”但“比整个模型小”的组件更有价值。 例如,在计算机视觉中广泛流行的ResNet-152架构就有数百层, 这些层是由层组(groups of layers)的重复模式组成。 “

  • 使用nn.sequential(torch.nn.modules.container.Sequential,)构造网络,更方便。d2l:nn.Sequential定义了一种特殊的Module), 即在PyTorch中表示一个块的类, 它维护了一个由Module组成的有序列表。

  • 自己声明nn.Module子类的块(sequential的实现其实也是一样的),同样是自动计算梯度,只需要管前向传播。

from torch.nn import functional as F

class MLP(nn.Module): # 多层感知机
    # 用模型参数声明层。这里,我们声明两个全连接的层
    # 在init里面声明结构
    def __init__(self):
        # 调用MLP的父类Module的构造函数来执行必要的初始化。
        # 这样,在类实例化时也可以指定其他函数参数,例如模型参数params(稍后将介绍)
        super().__init__() # 调用父类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))) # 使用nn.functional的relu
  • 关于为什么使用nn.functional中的relu,gpt:因为它只是一个函数调用,而不是一个层对象(如nn.ReLU,这样就跟Linear一样要在init中声明)。这使得在模型前向传播中可以直接使用函数调用,而不需要创建额外的层实例。
  • d2l中写出了nn.sequential的实现逻辑,即将里面的层作为参数传入,顺便补充一下,type(nn.Linear) = torch.nn.modules.linear.Linear
class MySequential(nn.Module): # 定义顺序块
    def __init__(self, *args): # 通过args传入顺序块中的层
        super().__init__()
        for idx, module in enumerate(args):
            # 这里,module是Module子类的一个实例。我们把它保存在'Module'类的成员
            # _module的类型是collections.OrderedDict
            self._modules[str(idx)] = module

    def forward(self, X):
        # OrderedDict保证了按照成员添加的顺序遍历它们
        for block in self._modules.values():
            X = block(X) # 顺序执行_module中的层
        return X
  • 自己定义块的好处在于可以在自由地进行一些运算,比如d2l中举例的,定义了一组随机的权重参数,并且通过requires_grad=False使这组参数不更新。
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)
        # 控制流
        # 这段定义了一个循环来展示块中在L1范数大于1时一直/2,只是为了演示可以将运算集成到块中
        while X.abs().sum() > 1: 
            X /= 2
        return X.sum()
  • 块也是可以嵌套的,同样,sequential也可以连接nn中封装的层和我们自定义的块
class NestMLP(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(nn.Linear(20, 64), nn.ReLU(), # 块中嵌套sequential
                                 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()) # sequential再嵌套一层
chimera(X)

查看参数

(感觉这几节涉及代码的还是多记点好了)

# 一开始看不懂这句
print(*[(name, param.shape) for name, param in net.named_parameters()])
# *是解压用list用,作为多个元素传给print
# [表达式 for 变量 in 列表] 
# 如将i作为列表返回
tmp = [i for i in range(10)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  • nn.container的add_module,以及d2l没介绍的第二种调用方法
def block1():
    return nn.Sequential(nn.Linear(4, 8), nn.ReLU(),
                         nn.Linear(8, 4), nn.ReLU())

def block2():
    net = nn.Sequential()
    for i in range(4):
        # 在这里嵌套
        net.add_module(f'block{i}', block1()) ## nn.container的add_module(name, module)
    return net

rgnet = nn.Sequential(block2(), nn.Linear(4, 1))
rgnet(X)

# (1)一样可以下标调用嵌套内的
print(rgnet[0][1])
print(rgnet[0][1][0].bias.data)

# (2)通过name调用的方法
print(rgnet[0].block1)
  • 参数初始化方法
def init_normal(m):
    if type(m) == nn.Linear:
      	# 正态分布初始化
        nn.init.normal_(m.weight, mean=0, std=0.01)
        # 初始化为常数
        # nn.init.constant_(m.weight, 1)
        # xavier初始化
        # nn.init.xavier_uniform_(m.weight)
        nn.init.zeros_(m.bias)
net.apply(init_normal)
net[0].weight.data[0], net[0].bias.data[0]

# 这种怎么自己传入参数呢?gpt给的方法
def init_constant_x(m, w, b):
    if type(m) == nn.Linear:
        nn.init.constant_(m.weight, w)
        nn.init.constant_(m.bias, b)
net.apply(lambda x: init_constant_x(x, 1, 2))

自定义参数

  • d2l用linear距离
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,))
        # 加个,使randn传入的参数是个tuple,不然units会成一个标量,在units=1的时候会报错
        # 这种情况下传入的参数不是张量而是标量了
    def forward(self, X):
        linear = torch.matmul(X, self.weight.data) + self.bias.data # wx+b
        return F.relu(linear)
# 我自己写错的地方
				# self.data_weight = torch.randn(input_size,output_size)# 这里错了
        # self.data_bias = torch.randn(output_size)
        self.data_weight = nn.Parameter(torch.randn(input_size,output_size))
        self.data_bias = nn.Parameter(torch.randn(output_size))
        # 如果不用nn.parameter,导致后续参数不能优化

保存模型

# save
torch.save(net.state_dict(), 'mlp.params') # 保存到同目录下mlp.params

# load
clone = MLP()
clone.load_state_dict(torch.load('mlp.params'))
# 这里load_state_dict不需要额外定义,是nn.Module父类的函数,在MLP中依旧只需要定义init和forward即可

GPU

  • 张量默认创建在cpu上。(用x.device查看tensor x所在的位置)
  • 多张gpu时,torch.device(f'cuda:{i}')
  • 指定设备
# 模型位置
net = nn.Sequential(nn.Linear(3, 1))
net = net.to(device=try_gpu())

# 张量位置
X = torch.ones(2, 3, device=try_gpu())

# 移动
z = x.cuda(i)

LeNet

  • lenet一章中提供了一种输出在sequential中,不同层间输入输出大小的方法

6.6. 卷积神经网络(LeNet) — 动手学深度学习 2.0.0 documentation (d2l.ai)

X = torch.rand(size=(1, 1, 28, 28), dtype=torch.float32)
for layer in net:
    X = layer(X)
    print(layer.__class__.__name__,'output shape: \t',X.shape)

文章作者: REXWind
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 REXWind !
评论
  目录