秃姐学AI系列之:PyTorch模型构造 | 参数管理 | 自定义层 | 读写文件

目录

模型构造

层和块

自定义块

 顺序块

在正向传播函数中执行代码

混合搭配各种组合块的方法

参数管理

参数访问

目标参数

一次性访问所有参数

从嵌套块收集参数

 初始化参数

内置初始化

对某些块应用不同的初始化方法

 自定义初始化

参数绑定

自定义层

将层作为组件合并构建更复杂的模型中

带参数的层

读写文件

加载和保存张量

存储一个张量列表,然后把他们读回内存

写入或读取从字符串映射到张量的字典

加载和保存模型参数


模型构造

层和块

首先,我们回顾一下多层感知机

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

# 一个简单的单层神经网络
net = nn.Sequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))

# 生成一个随机的input
X = torch.rand(2, 20)
net(x)  # 得到output

functional里面定义了一些没有包括参数的一些函数,之后会讲如何使用,刨个坑在这~ 

nn.Sequential定义了一种特殊的Module,在PyTorch中,Module是一个很重要的概念

自定义块

任何一个层,任何一个神经网络,都应该是Module的一个子类

下面自定义了一个MLP,实现了和刚刚一样的函数

class MLP(nn.Module):  # 是nn.Module的子类,继承之后会得到很多好用的函数
    def __init__(self):
        super().__init__()  # 调用父类,这样在初始化weight等的时候就可以直接全部弄好
        self.hidden = nn.Linear(20, 256)
        self.out == nn.Linear(256, 10)

    def forward(self, X):
        return self.out(F.relu(self.hidden(X))) # 直接通过nn.module里面实现的ReLU来激活

所有的Module都有两个比较重要的函数:

  • __init__:定义需要哪些类、哪些参数
  • forward:前向函数,用来写给定输入,如何操作来前向运算传输

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

net = MLP() # 实例化我们刚刚创建的类
net(X) # 把输入丢进去,最后得出一个2*10的矩阵

 顺序块

让我们来实现以下nn.Sequential,其实很简单

class MySequential(nn.Module):
    def __init__(self, *args): # 接收一个list of input arguments
        super().__init__() # 调用父类的初始化函数
        for block in args: # 对所有传入的层(称为block)放入一个特殊的容器里面
            # 放进_modules[],pytorch就知道里面都是我们需要的层,其实是一个按序排的字典,所以我们按序给它插入,将本身作为key
            self._modules[block] = block

    def forward(self, X):
        for block in self._modules.values(): # 因为是排过序的,所以会和我们调用的顺序是一样的
            X = block(X)
        return X

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

在正向传播函数中执行代码

当Sequential无法满足我们的时候,我们还可以做更多灵活的定义,比如如下代码所示

class FixedHiddenMLP(nn.Module):
    def __init__(self):
        super().__init__()
        # 这个随机的weight不参与训练,因为不计算梯度
        self.rand_weight = torch.rand((20, 20), requires_grad = false)
        self.linear = nn.Linear(20, 20)

    # 里面可以写任何你想做的函数! 任何!比Sequential更灵活!!
    def forward(self, X):
        X = self.linear(X)
        # mm:矩阵乘法
        X = F.relu(torch.mm(X, self.rand_weight) + 1)
        # 回头二次调用线性函数
        X = self.linear(X)
        while X.abs().sum() > 1:
            X /= 2    # 一直除2,除到你很小为止
        return X.sum()

net = FixedHiddenMLP()
net(X)

混合搭配各种组合块的方法

另一种灵活的方法就是:嵌套使用!因为无论是层、Sequential等都是nn.Module的子类,其实是可以很灵活的嵌套使用的

没任何意义,只是一个举例

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))  # 一个Sequential和Linear的嵌套

chimera = nn.Sequentisl(NestMLP(), nn.Linear(16, 20), FixedHiddenMLP())    # 再套一层!
chimera(X)

参数管理

当我们把模型定义好之后我们要如何访问我们的参数呢?

我们首先关注具有单隐藏层的多层感知机

import torch
from torch import nn

# 一个单隐藏层MLP
net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 1))
X = torch.rand(size = (2, 4))
net(X)

参数访问

# stare_dict():理论上他就是_Modules
print(net[2].statc_dict())

我们会看到这样的输出:

OrderedDict([('weight', tensor([[-0.1351, -0.0821, 0.2084, 0.2804, -0.2748, 0.0061, 
0.1729, -0.3424]])), ('bias', tensor([-0.3389]))])

可以简单把nn.Sequential当成python的一个list,那我们的net[2]就会拿到我们刚刚定义net时候的第三个层,也就是nn.Lunear(8, 1),最后的输出层

目标参数

我们也可以直接去访问一个具体的参数

print(type(net[2],bias))  # 打印最后一层偏移
print(net[2].bias)
print(net[2].bias.data)  # 只访问值,他还有个梯度,可以通过.grad来访问梯度值

输出: 

<class 'torch.nn.parameter.Parameter'>  # 类型是Parameter:是一个可以优化的参数
Parameter containing
tensor([-0.3389], required_grad = Trye)
tensot([-0.3389])

一次性访问所有参数

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

 因为1是ReLU,是没有参数的,所以拿不出来

当我们有名字之后,我们可以通过名字来获取参数

net.state_dict()['2.bias'].data

 输出:

tensor([-0.3389])

从嵌套块收集参数

当我们模型里面出现了嵌套我们要如何访问参数呢?

def block1():
    return nn.Sequential(nn.Linear(4,8), nn.ReLU(), nn.Linear(8, 4),
                        nn.ReLU())

# 嵌套了四个block1
def block2():
    net = nn.Sequential()
    for i in range(4):
        net.add_module(f'bock {i}', block1())    # 我自己传了个字符串给你,就不是0123了
    return net

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

输出: 

首先外层是一个Sequential,第0个元素也是Sequential(我们的block2),block2里面的block 0元素又是一个Sequential(block1),所以整个网络是一个三个Sequential的嵌套

所以可以通过print大概看出网络长什么样子 。当然这是个简单网络,如果是复杂网络的话,print看起来就没有那么方便

复杂模型可以一个block一个block看,这样对于模型结构也有好处

 初始化参数

如何修改默认的参数

内置初始化

def init_normal(m): # m:每次传入一个Module
    if type(m) == nn.Linear:
        nn.init.normal_(m.weight, mean = 0, std = 0.01) # 对weight初始化,下划线跟在后面表示这是一个替换函数,不是返回一个值
        nn.init.zeros_(m.bias)   # bias赋0

net.apply(init_normal) # apply:对net里面所有的Module去调用这个函数,遍历一遍

apply:给你一个方式让你去遍历整个神经网络去做一些事情

对某些块应用不同的初始化方法

def xavier(m):
    if type(m) == nn.Linear:
        nn.init.xavier_uniform_(m.weight) # 一个xavier初始化

def init_42(m):
    if type(m) == nn.Linear:
        nn.init.constant_(m.weight, 42)

net[0].apply(xavier)
ner[2].applu(init_42)

 自定义初始化

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.data[:] += 1
net[0].weight.data[0, 0] = 42

参数绑定

讲一个小应用,有时候我们会希望在不同层里面共享权重的时候,可以做一个参数绑定 

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])  # 比较shared
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])

自定义层

构造一个没有任何参数的自定义层

class CenteredLayer(nn.Module):
    def __init__(self):    # python3不写这个函数会自己加上
        super().__init__()

    def forward(self, X):
        return X - X.mean()

layer = CenteredLayer()
layer(torch.FloatTensor([1, 2, 3, 4, 5]))

# 输出
tensor({[-2., -1., 0., 1., 2.])

将层作为组件合并构建更复杂的模型中

net = nn.Sequential(nn.Linear(8, 128), CenteredLayer())

Y = net(torch.rand(4, 8))
Y.mean()

# 输出
tensor(-2.7940e-09, grad_fn = <MeanBackward0>)

带参数的层

通过nn.Parameter()把初始化的值包起来就行了

class MyLinear(nn.Module):
    def __init__(self, in_units, units):    # in_units:输入大小 units:输出大小
        super().__init__()
        # Parameter:给你梯度加上,再给你一个合适的名字,就干这点事情
        self.weight = nn.Parameter(torch.randn(in_units, units))
        self.bias = nn.Parameter(torch.randn(units,))

    def forward(self, X):
        # matmul:矩阵乘法
        linear = torch.matmul(X, self.weight.data) + self.bias.data
        return F.relu(linear)

dense = MyLinear(5, 3)
dense.weight

# 输出
Parameter containing:
tensor([[1.1537, -0.2809, 1.2778],
        [-0.7972, 0.8573, -0.6472],
        [0.6014, -2.0051, -0.3640],
        [1.3730, 0.5488, 1.1121],
        [-1.0814, 1.5180, -0.2057]], requires_grad = True)

读写文件

简而言之就是,你训练好的东西怎么存下来

加载和保存张量

x = torch.arange(4)
torch.save(x, 'x-file')  # 把这个向量x,存入当前目录下,文件名为x-file

x2 = torch.load("x-file")
x2

# 输出
tensor([0, 1, 2, 3])

存储一个张量列表,然后把他们读回内存

我们不单单可以只存一个向量,还可以存一个list,然后读取

y = torch.zeros(4)
torch.save([x, y], 'x-files')
x2, y2 = torch.load('x-files')
(x2, y2)

# 输出
(tensor([0, 1, 2, 3]), tensor([0., 0., 0., 0.]))

写入或读取从字符串映射到张量的字典

可以存list,那我们存一个字典也很正常叭!

mydict = {'x' : x, 'y' : y}
torch.save(mydict, 'mydict')
mydict2 = torch.load('mydict')
mydict2

# 输出
{'x': tensor([0, 1, 2, 3]), 'y': tensor([0., 0., 0., 0.])}

加载和保存模型参数

那我有一个MLP我要怎么办?

首先我们要明白,对于一个神经网络,我们应该存什么东西?

因为我们PyTorch是没有办法把整个网络模型存下来的,相较于Tensorflow有一定局限性

其实我们要存的是模型的权重参数!

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)

那我们要怎么存呢?

记得我们可以用state_dict()来得到我们所有的参数的字符串对吧!

那我们将模型的参数存储为一个叫做”mlp.params“的文件

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

那我们怎么load回来呢??

实例化了原始多层感知机模型的一个备份。直接读取文件中存储的参数

如果你真的要在别的地方load这个模型的话,你不光要把.params文件带走,你还得把网络的定义带走!!

因为你首先要声明生成一个网络出来

声明一个新的clone实例化MLP,这个时候网络参数已经被随机初始化过了,但是我们不用管

用load_state_dict来overwrite掉我们初始化的参数

clone = MLP()
# load():从本地文件读取参数;load_state_dict():把参数加载到模型
clone.load_state_dict(torch.load("mlp.params"))
clone.eval()

# 输出
MLP(
    (hidden): Linear(in_features = 20, out_features = 256, bias = True)
    (output): Linear(in_features = 256, out_features = 10, bias = True)
)
  • 9
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值