Module 类是一个通用的模型构造类,是所有神经网络模块的基类。可以基于该类构建神经网络的层(layer, 如Linear层)或者直接构建模型。继承该函数一般需要重载__init__函数和forward函数,分别用于创建模型参数和定义前向计算。 除了采用直接继承定义模型以外,pytorch 还提供了更加还实现了继承自Module的,可以方便构建模型的类: 如Sequential、ModuleList 和ModuleList等。
import torch
from torch import nn
# 法1:直接使用多个子模块作为参数
net = nn.Sequential(
nn.Linear(784, 256),
nn.ReLU(),
nn.Linear(256, 10),
)
# 法2:采用add_module来添加子模块
net = nn.Sequential()
net.add_module(nn.Linear(784, 256))
net.add_module(nn.ReLU())
net.add_module(nn.Linear(256, 10))
# 法3:采用添加有序字典的方式
from collections import OrderedDict
net = nn.Sequential(OrderedDict([
('linear', nn.Linear(784, 256))
('ReLU', nn.ReLU())
('linear', nn.Linear(256, 10))
]))
class MyModule(nn.Module):
def __init__(self):
super(MyModule, self).__init__()
self.linears = nn.ModuleList([nn.Linear(10, 10) for i in range(10)])
#也可以用append来添加子模块,如
# self.linears.append(nn.Linear(256, 10))
# 需要自行定义ModuleList 中模型的forward 函数
def forward(self, x):
# ModuleList can act as an iterable, or be indexed using ints
for i, l in enumerate(self.linears):
x = self.linears[i // 2](x) + l(x)
return x
net = nn.ModuleDict({
'linear': nn.Linear(784, 256),
'act': nn.ReLU(),
})
net['output'] = nn.Linear(256, 10) # 添加
nn中的内置模型,如Linear、ReLU、Linear是会默认采用一定得策略对模型的参数做初始化,所以当使用这些子模块时可以不用自行定义参数初值,采用pytorch内置的初始化方式。
import torch
from torch import nn
from torch.nn import init
# pytorch会对Sequential中的内置子模块作默认的模型参数初始化
net = nn.Sequential(nn.Linear(4, 3), nn.ReLU(), nn.Linear(3, 1))
# 访问模型参数
for name, param in net.named_parameters():
print(name, param.size())
'''输出
0.weight torch.Size([3, 4]) #0表示第0层
0.bias torch.Size([3])
2.weight torch.Size([1, 3])
2.bias torch.Size([1]) #2表示第2层
'''
# 可以使用net[i].named_parameters() 访问第i层的参数
for name, param in net[0].named_parameters():
print(name, param.size(), type(param))
'''输出
weight torch.Size([3, 4])
bias torch.Size([3])
'''
torch.nn.parameter.Parameter是Tensor的子类,所以如果一个Tensor被定义为Parameter,那么会被自动加到模型参数中。因为Parameter是Tensor,即用于Tensor的所有属性,比如可以根据Data来访问参数数值,用Grad来访问参数梯度。
weight_0 = list(net[0].parameters())[0]
print(weight_0.data)
print(weight_0.grad) # 反向传播前梯度为None
Y.backward() # backward()计算梯度的值
print(weight_0.grad)
'''输出
tensor([[ 0.2719, -0.0898, -0.2462, 0.0655],
[-0.4669, -0.2703, 0.3230, 0.2067],
[-0.2708, 0.1171, -0.0995, 0.3913]])
None
tensor([[-0.2281, -0.0653, -0.1646, -0.2569],
[-0.1916, -0.0549, -0.1382, -0.2158],
[ 0.0000, 0.0000, 0.0000, 0.0000]])
'''
除了nn.Module模块内置的初始化,还可以使用PyTorch的init模块进行初始化,init模块中提供了多种预设模型初始化方法。
from torch.nn import init
for name, param in net.named_parameters():
if 'weight' in name:
init.normal_(param, mean=0, std=0.01) # 正态初始化
print(name, param.data)
''' # 常数初始化
if 'bias' in name:
init.constant_(param, val=0)
print(name, param.data)
'''
如果init模块中提供常用初始化方法不够用的话,还可以自行定义初始化函数
'''#下面的例子里,令权重有一半概率初始化为0,
#有另一半概率初始化为[−10,−5][−10,−5]和[5,10][5,10]两个区间里均匀分布的随机数 '''
def init_weight_(tensor):
with torch.no_grad(): #表示以下代码中的参数改变中,禁止梯度计算
tensor.uniform_(-10, 10)
tensor *= (tensor.abs() >= 5).float()
for name, param in net.named_parameters():
if 'weight' in name:
init_weight_(param)
print(name, param.data)
'''输出
0.weight tensor([[ 7.0403, 0.0000, -9.4569, 7.0111],
[-0.0000, -0.0000, 0.0000, 0.0000],
[ 9.8063, -0.0000, 0.0000, -9.7993]])
2.weight tensor([[-5.8198, 7.7558, -5.0293]])
'''
共享模型参数有两种方式:1)在Module类的forward函数里多次调用同一个层;2)传入Sequential 的模块是同一个Module实例,参数也是共享。
(1)在Module类的forward函数里多次调用同一个层
class FancyMLP(nn.Module):
def __init__(self, **kwargs):
super(FancyMLP, self).__init__(**kwargs)
self.rand_weight = torch.rand((20, 20), requires_grad=False) # 不可训练参数(常数参数)
self.linear = nn.Linear(20, 20)
def forward(self, x):
x = self.linear(x)
# 使用创建的常数参数,以及nn.functional中的relu函数和mm函数
x = nn.functional.relu(torch.mm(x, self.rand_weight.data) + 1)
# 复用全连接层。等价于两个全连接层共享参数
x = self.linear(x)
# 控制流,这里我们需要调用item函数来返回标量进行比较
while x.norm().item() > 1:
x /= 2
if x.norm().item() < 0.8:
x *= 10
return x.sum()
(2) 传入Sequential 的模块是同一个Module实例,那么这两个实例参数共享
linear = nn.Linear(1, 1, bias=False)
net = nn.Sequential(linear, linear) # 两次使用同一个实例,共享参数
print(net)
for name, param in net.named_parameters():
init.constant_(param, val=3)
print(name, param.data)
'''输出
Sequential(
(0): Linear(in_features=1, out_features=1, bias=False)
(1): Linear(in_features=1, out_features=1, bias=False)
)
0.weight tensor([[3.]])
'''
'''------注意------'''
# 在内存中,这两个线性层其实一个对象
# 在反向传播计算时,这些共享参数的梯度是累加的