本节课主要是从实践代码的角度看神经网络的各个结构,以及各个结构的实现方法。虽然没有太多理论,但是精华都在代码的注释中~

目录

模型构造(层和块)

参数管理

自定义层

读写文件


模型构造(层和块)

#层和块
#首先,我们回顾一下多层感知机
import torch
from torch import nn
from torch.nn import functional as F#一些函数
#单层神经网络=线性层+relu+线性层
net = nn.Sequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))
#2*20的随机矩阵作为输入,2是批量大小,20是输入维度
X = torch.rand(2, 20)
print(net(X))
print("###########################################################################")
#自定义块
#module是pytorch中很重要的概念
#nn.Sequential定义了一种特殊的Module,任何一个层和一个神经网络都是module的一个子类
#比如下面的MLP就是nn.module的一个子类
#module有两个比较重要的函数,一个是__init__,可以在里面定义我们所需要的类和参数;
class MLP(nn.Module):
    def __init__(self):
        super().__init__()#调用父类,以设好所需要的内部参数。方便初始化权重之类的参数
        self.hidden = nn.Linear(20, 256)#隐藏层,输入维度20,输出维度256.将其存入类的成员变量里。
        self.out = nn.Linear(256, 10)#输出层,输入维度100,输出10,也藏入了类成员变量里。
    def forward(self, X):#定义前向函数
        return self.out(F.relu(self.hidden(X)))#很清晰,先将输入放隐藏层里,再通过激活函数输出。
#实例化多层感知机的层,然后在每次调用正向传播函数时调用这些层
net = MLP()
print(net(X))#这个就是输出
print("###########################################################################")
#顺序块,与nn.sequential的效果一样
class MySequential(nn.Module):
    def __init__(self, *args):#*args是收集参数,相当于把若干个参数打包成一个来传入
        super().__init__()#调用父类的初始化函数
        for block in args:
            self._modules[block] = block#定义一个专门存放神经网络层的容器,并把层自己作为key,每一层是按先后顺序存入这个容器的。

    def forward(self, X):#前向函数
        #关于为啥是字典形式;因为self._moudles是父类的属性,这个属性类型是OrderedDict()
        #有序字典,这样添加层是将你的层嵌入到模型中,这也是为什么此处并没有重写forward函数
        for block in self._modules.values():#调用容器中的每一层
            X = block(X)#最后返回X
        return X
#下面使用的linear层、relu层还有最后的linear层的时候先放进init的args参数里面,再按顺序放入_modules里面
net = MySequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))
print(net(X))
print("###########################################################################")
#在正向传播函数中执行代码
#当sequential这个类不能满足需求时,自己创建的好处是在init和forward里面可以做大量的自定义的计算
#fixedhiddenmlp这个子类其实是个例子,并没什么特殊的意义,
#它表明了可以做较灵活的方法——继承nn.module这个父类去灵活调用参数的样子,以及前向计算的方法。
class FixedHiddenMLP(nn.Module):
    def __init__(self):
        super().__init__()
        #随机生成一个不参与训练的20*20的rand_weight,它不会计算梯度。
        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()
print(net(X))
print("###########################################################################")
#混合搭配各种组合块的方法
#可以嵌套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来说它的输入可以是任何nn.module的子类
chimera = nn.Sequential(NestMLP(), nn.Linear(16, 20), FixedHiddenMLP())
print(chimera(X))
print("###########################################################################")

参数管理

#参数管理
#我们首先关注具有单隐藏层的多层感知机
import torch
from torch import nn
##################### net【0】 ######## net【1】 ####### net【2】
net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 1))
X = torch.rand(size=(2, 4))
print(net(X))
print("###########################################################################")
#参数访问
#把每一层的权重拿出
print(net[2].state_dict())#把最后的线性层的权重和偏重(状态)拿出来
print("###########################################################################")
#目标参数
print(type(net[2].bias))#最后一层的偏移的类型是可以优化的参数parameter
print(net[2].bias)#最后一层的偏移
print(net[2].bias.data)#用.data真正访问它的值,而不访问梯度
print("###########################################################################")
print(net[2].weight.grad == None)#.grad是访问它的梯度,因为还没有进行反向计算,所有这个输出TRUE
print("###########################################################################")
#一次性访问所有参数
#使用named_parameters()函数访问第一层的所有参数,但这里我们要打印的是参数的名字和形状
print(*[(name, param.shape) for name, param in net[0].named_parameters()])
#使用named_parameters()函数访问所有参数,但这里我们要打印的是参数的名字和形状
print(*[(name, param.shape) for name, param in net.named_parameters()])
print("###########################################################################")
print(net.state_dict()['2.bias'].data)#通过名字获取参数,“2.bias”表示最后一个的偏移
print("###########################################################################")
#从嵌套块收集参数
#看看有嵌套的网络的情况
def block1():
    return nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 4),nn.ReLU())
def block2():#block2本身也是线性层,但是他插入了4个block1
    net = nn.Sequential()
    for i in range(4):
        # 前面的“f'block {i}'”可以把“2.bias”中的2替换成“block2”
        # 然后block2嵌套了4个block1
        net.add_module(f'block {i}', block1())
    return net
rgnet = nn.Sequential(block2(), nn.Linear(4, 1))#它应当包含4个block1和一个linear层
print(rgnet(X))#此处返回一个标量
print("###########################################################################")
#我们已经设计了网络,让我们看看它是如何组织的
print(rgnet)
print("###########################################################################")
#print(rgnet[0][1][0].bias.data)
#print("###########################################################################")
#内置初始化
def init_normal(m):
    if type(m) == nn.Linear:#如果传入的module是个线性类(全连接层的话)的话
        #下面的下划线表示这里的normal函数是个替换函数,它不会返回值,只是说把module的权重给替换掉
        nn.init.normal_(m.weight, mean=0, std=0.01)#就对它的权重做均值为0方差为0.01的初始化
        nn.init.zeros_(m.bias)#偏移置零
net.apply(init_normal)#对于所有net里的层进行遍历,然后传入到init_normal函数里
print(net[0].weight.data[0], net[0].bias.data[0])#输出正态分布后的参数
print("###########################################################################")
def init_constant(m):#和上面init_normal对比
    if type(m) == nn.Linear:
        nn.init.constant_(m.weight, 1)#这里和上面的区别就是权重被替换成1了
        nn.init.zeros_(m.bias)
net.apply(init_constant)
print(net[0].weight.data[0], net[0].bias.data[0])
print("###########################################################################")
#对某些块应用不同的初始化方法
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)#把权重置为42
net[0].apply(xavier)#第一个线性层用x初始化
net[2].apply(init_42)#最后一个用“宇宙的答案”初始化
print(net[0].weight.data[0])#data[0]表示权重的第0行
print(net[2].weight.data)
print("###########################################################################")
#自定义初始化
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)#把权重替换成-10到10之间的数
        m.weight.data *= m.weight.data.abs() >= 5#保留绝对值大于等于5的权重,小于5的权重设为0
net.apply(my_init)
print(net[0].weight[:2])
print("###########################################################################")
#更暴力的方法
net[0].weight.data[:] += 1#把第一层线性层的权重全部加1
net[0].weight.data[0, 0] = 42#把第一层权重的第一个值变为42
print(net[0].weight.data[0])#把替换后的第一行输出
print("###########################################################################")
#参数绑定(共享权重)
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#如果我把第二个层的权重改成100的话
print(net[2].weight.data[0] == net[4].weight.data[0])#输出后发现第三个层的权重也改成100了
print("###########################################################################")

自定义层

#自定义层
#构造一个没有任何参数的自定义层
import torch
import torch.nn.functional as F
from torch import nn
class CenteredLayer(nn.Module):
    def __init__(self):
        super().__init__()
    def forward(self, X):
        return X - X.mean()#输入减均值,把生成的张量均值变成0
layer = CenteredLayer()
print(layer(torch.FloatTensor([1, 2, 3, 4, 5])))
#将层作为组件合并到构建更复杂的模型中
net = nn.Sequential(nn.Linear(8, 128), CenteredLayer())#由一个线性层(输入八组拥有128个特征的输入)和一个centeredlayer组成
Y = net(torch.rand(4, 8))#生成4组形状和输入一样的矩阵。它是【0,1)之间的均匀分布。
                         #randn是返回一个包含从标准正态分布中抽取的随机数张量。
#print(Y)
print(Y.mean())#计算会有些误差
#带参数的层
class MyLinear(nn.Module):
    def __init__(self, in_units, units):
        super().__init__()
        #torch.randn(in_units, units)表示输出一个“输入乘输出大小”的0-1之间随机分布的矩阵,
        # 然后把它放入nn.parameter中后
        #会把梯度和名字加上
        self.weight = nn.Parameter(torch.randn(in_units, units))#调用parameter类就能弄参数。
        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)#输入是5组,输出是每组3个,也得输出5组
print(linear.weight)
#使用自定义层直接执行正向传播计算
print(linear(torch.rand(2, 5)))
#使用自定义层构建模型
net = nn.Sequential(MyLinear(64, 8), MyLinear(8, 1))
print(net(torch.rand(2, 64)))#rand的第二个参数要和第一层的输入一样,也就是和“MyLinear(64, 8)”的64一样

读写文件

#读写文件(训练好的东西如何存下来)
#加载和保存张量(矩阵)--------------------矩阵
import torch
from torch import nn
from torch.nn import functional as F
x = torch.arange(4)
torch.save(x, 'x-file')
x2 = torch.load('x-file')
print(x2)
print("###########################################################################")
#存储一个张量列表,然后把它们读回内存---------列表
y = torch.zeros(4)
torch.save([x, y], 'x-files')
x2, y2 = torch.load('x-files')
print((x2, y2))
print("###########################################################################")
#写入或读取从字符串映射到张量的字典-----------字典
mydict = {'x': x, 'y': y}
torch.save(mydict, 'mydict')
mydict2 = torch.load('mydict')
print(mydict2)
print("###########################################################################")
#加载和保存模型参数
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)
#将模型的参数存储为一个叫做“mlp.params”的文件
torch.save(net.state_dict(), 'mlp.params')#把mlp的所有参数存成一个字典
#实例化了原始多层感知机模型的一个备份。 直接读取文件中存储的参数
clone = MLP()#在进行load参数之前,需要先对网络进行定义,不然没有load的对象。
clone.load_state_dict(torch.load('mlp.params'))#定义之后把所有参数加载到mlp中
print(clone.eval())#看一下这个网络
Y_clone = clone(X)
print(Y_clone == Y)
print("###########################################################################")
11-17 08:47