(一)pytorch层与块的灵活性
通过继承nn.Module
这个类,可以对神经网络的构造进行自定义的设计。
这里面主要要写两个函数:构造函数和forward函数
下面直接开始代码吧!
import torch
from torch import nn
from torch.nn import functional as F
# 这里sequential是一个容器,用来装一些层,定义了一个特殊的module
# 在神经网络里面,任何一个层应该都是nn.module的子类
net = nn.Sequential(nn.Linear(20,256),nn.ReLU(),nn.Linear(256,10))
X = torch.rand((2,20))
print(net(X).shape)
# 我们来看一下如何来封装一个一样的多层感知机
class MLP(nn.Module):
def __init__(self):
# 这里必须调用super 的构造函数,初始化一些内部参数
super().__init__()
self.hidden = nn.Linear(20,256)
self.out = nn.Linear(256,10)
# 重写forward方法
def forward(self,X):
return self.out(F.relu(self.hidden(X)))
net1 = MLP()
print(net1(X))
# 现在来重新实现一下sequential
from turtle import forward
class my_sequential(nn.Module):
def __init__(self,*args) -> None:
super().__init__()
for block in args:
# 这是python里面的一个特殊的容器
self._modules[block] = block
def forward(self,X):
for block in self._modules.values():
X = block(X)
return X
net2 = my_sequential(nn.Linear(20,256),nn.ReLU(),nn.Linear(256,10))
net2(X)
# 以上我们定义的类似乎都是pytorch已经实现好的,那么我们为什么还要做这些工作呢?
# 原因是我们可以做一些更为复杂或者巧妙地神经网络结构的设计
class FixedHiddenMLP(nn.Module):
def __init__(self) -> None:
super().__init__()
# 这个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()
net3 = FixedHiddenMLP()
net3(X)
# 下面的代码也没有实际意义,展示一下pytorch的灵活
# 各种的混合搭配
class NestMLP(nn.Module):
def __init__(self) -> None:
super().__init__()
self.net = nn.Sequential(nn.Linear(20,256),nn.ReLU(),nn.Linear(256,10))
self.linear = nn.Linear(10,10)
def forward(self,X):
return self.linear(self.net(X))
net4 = NestMLP()
net4(X)
net5 = nn.Sequential(NestMLP(),nn.Linear(10,20),FixedHiddenMLP())
net5(X)
(二)参数管理
怎样定义和访问参数,首先我们关注单隐藏层的多层感知机里面的参数。
net = nn.Sequential(nn.Linear(4,8),nn.ReLU(),nn.Linear(8,1))
X = torch.rand((4,4))
net(X)
# 可以通过下标的方式来访问网络中的层,因为nn.Sequential就是一个网络数组
# state_dict()获取状态字典
print(net[2].state_dict())
print("-----------------------------------------------------------------------")
# 直接获取bias和weights
print(type(net[2].bias))
print(net[2].bias)
print(net[2].bias.data)
print("-----------------------------------------------------------------------")
print(type(net[2].weight))
print(net[2].weight)
print(net[2].weight.data)
# 由于现在还没有做方向传播,所以梯度 = None
net[2].weight.grad == None
# 一次性的访问所有层的参数
for name, param in net.named_parameters():
print(f"name = {name}, parameters.shape = {param.shape}")
print("-----------------------------------------------------------------------")
print(*[(name, param.shape) for name, param in net[0].named_parameters()])
# 还可以通过名字访问数据
net.state_dict()['2.bias'].data
# 从嵌套块收集参数
def block1():
return nn.Sequential(nn.Linear(4,8),nn.ReLU(),nn.Linear(8,4))
def block2():
net = nn.Sequential()
for i in range(4):
net.add_module(name=f"block_{i}",module=block1())
return net
rgnet = nn.Sequential(nn.Linear(4,4),block2())
rgnet(X)
# 打印网络结构
print(rgnet)
# 初始化参数,内置初始化,初始化为正态分布
def init_normal(m):
if type(m) == nn.Linear:
# normal_做的事情就是直接修改参数,没有返回值
nn.init.normal_(m.weight,mean=0,std=0.01)
nn.init.zeros_(m.bias)
# apply 遍历每个层,进行一些操作
rgnet.apply(init_normal)
print(rgnet[0].weight.data,rgnet[0].bias.data)
print("-----------------------------------------------------------------------")
# 初始化参数,内置初始化,初始化为一个常数
def init_constant(m):
if type(m) == nn.Linear:
# normal_做的事情就是直接修改参数,没有返回值
nn.init.constant_(m.weight,1)
nn.init.zeros_(m.bias)
# apply 遍历每个层,进行一些操作
rgnet.apply(init_constant)
print(rgnet[0].weight.data,rgnet[0].bias.data)
def 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(xavier)
net[2].apply(init_42)
print(net[0].weight.data)
print(net[2].weight.data)
# 最暴力的方法
net[0].weight.data[:] +=1
net[0].weight.data[0,0] = 42
print(net[0].weight.data)
# 怎么共享参数,有可能前后有两个层的参数是一样的
# 实际上就是一个变量用了两次
shared = nn.Linear(8,8)
net = nn.Sequential(nn.Linear(4,8),nn.ReLU(),shared,nn.ReLU(),shared,nn.ReLU(),nn.Linear(8,4))
net(X)
print(net[2].weight.data[0] == net[4].weight.data[0])
(三)自定义层
前面讲述了,怎么构造神经网络的结构,怎么访问参数和修改参数。
那么神经网络的层是不是也是可以自定义的呢?
下面将从带有参数和不带参数两个方面去考虑。
# 不带参数的层
class CenteredLayer(nn.Module):
def __init__(self) -> None:
super().__init__()
def forward(self,X):
return X-X.mean()
layer = CenteredLayer()
print(layer(torch.arange(12,dtype=torch.float32)))
net = nn.Sequential(nn.Linear(4,8),CenteredLayer())
net(X)
class MyLinear(nn.Module):
def __init__(self, in_uinits, unints) -> None:
super().__init__()
self.weight = nn.Parameter(torch.randn( in_uinits, unints))
self.bias = nn.Parameter(torch.zeros(unints,))
def forward(self,X):
linear = torch.matmul(X,self.weight.data) + self.bias.data
return F.relu(linear)
dense = MyLinear(4,8)
dense.weight
dense(torch.arange(12,dtype=torch.float32).reshape((3,4)))
(四)读写文件
加载张量,保存张量。为了保存模型和参数。但是pytorch对模型结构的保存的支持并不是很好,所以我们主要是学习保存参数。
import os
# create a directory
if os.path.isdir("./network_param"):
pass
else:
os.mkdir("./network_param")
# save a tensor attribution
x = torch.arange(12)
torch.save(x,r'./network_param/x-file')
x2 = torch.load(r'./network_param/x-file')
print(x2)
# 保存一个张量列表和字典
y = torch.zeros(12)
dic = {"x":x,"y":y}
torch.save([x,y],r'./network_param/[x,y]-file')
torch.save(dic,r'./network_param/dic-file')
print(torch.load(r'./network_param/[x,y]-file'))
print(torch.load(r'./network_param/dic-file'))
# 加载和保存模型的参数
class MLP(nn.Module):
def __init__(self):
# 这里必须调用super 的构造函数,初始化一些内部参数
super().__init__()
self.hidden = nn.Linear(20,256)
self.out = nn.Linear(256,10)
# 重写forward方法
def forward(self,X):
return self.out(F.relu(self.hidden(X)))
net = MLP()
y = net(torch.randn(2,20))
# 保存参数
torch.save(net.state_dict(),"./network_param/mlp_param")
# 加载参数
clone = MLP()
clone.load_state_dict(torch.load("./network_param/mlp_param"))
clone.eval()
print(net.state_dict()["hidden.weight"] == clone.state_dict()["hidden.weight"])
网友评论