在PyTorch中nn.Module类是用于定义网络中前向结构的父类,当要定义自己的网络结构时就要继承这个类。现有的那些类式接口(如nn.Linear、nn.BatchNorm2d、nn.Conv2d等)也是继承这个类的,nn.Module类可以嵌套若干nn.Module的对象,来形成网络结构的嵌套组合,下面记录nn.Module的功能。
1.继承nn.Module类的模块
使用其初始化函数创建对象,然后调用forward函数就能使用里面的前向计算过程。
包括:Linear、ReLU、Sigmoid、Conv2d、ConvTransposed2d、Dropout...
2.容器nn.Sequential()
nn.Sequential是一个Sequential容器,模块将按照构造函数中传递的顺序添加到模块中。通俗的说,就是根据自己的需求,把不同的函数组合成一个(小的)模块使用或者把组合的模块添加到自己的网络中。
1 conv_module = nn.Sequential( 2 nn.Conv2d(1,20,5), 3 nn.ReLU(), 4 nn.Conv2d(20,64,5), 5 nn.ReLU() 6 ) 7 8 # 具体的使用方法 9 class Net(nn.Module): 10 def __init__(self): 11 super(Net, self).__init__() 12 self.conv_module = nn.Sequential( 13 nn.Conv2d(1,20,5), 14 nn.ReLU(), 15 nn.Conv2d(20,64,5), 16 nn.ReLU() 17 ) 18 19 def forward(self, input): 20 out = self.conv_module(input) 21 return out
Tip:
- 使用nn.Module,我们可以根据自己的需求改变传播过程,如RNN等;
- 如果需要快速构建或者不需要过多的过程,直接使用nn.Sequential。
3.模块内部参数管理
可以用.parameters()或者.named_parameters()返回 其内的所有参数的迭代器:
1 from torch import nn 2 3 net=nn.Sequential( 4 nn.Linear(4,2), #输入维度4,输出维度2的线性层 5 nn.Linear(2,2) 6 ) 7 8 print(list(net.parameters())) 9 # [Parameter containing: 10 # tensor([[-0.0829, 0.3424, 0.4514, -0.3981], 11 # [-0.3401, 0.1429, -0.4525, 0.4991]], requires_grad=True), 12 # Parameter containing: 13 # tensor([-0.0321, 0.0872], requires_grad=True), 14 # Parameter containing: 15 # tensor([[ 0.0628, 0.3092], 16 # [ 0.5135, -0.4738]], requires_grad=True), 17 # Parameter containing: 18 # tensor([-0.4249, 0.3921], requires_grad=True)] 19 20 print(dict(net.named_parameters())) 21 # {'0.weight': Parameter containing: 22 # tensor([[-0.0829, 0.3424, 0.4514, -0.3981], 23 # [-0.3401, 0.1429, -0.4525, 0.4991]], requires_grad=True), 24 # '0.bias': Parameter containing: 25 # tensor([-0.0321, 0.0872], requires_grad=True), 26 # '1.weight': Parameter containing: 27 # tensor([[ 0.0628, 0.3092], 28 # [ 0.5135, -0.4738]], requires_grad=True), 29 # '1.bias': Parameter containing: 30 # tensor([-0.4249, 0.3921], requires_grad=True)}
Tip:
- 以第0层为例,weight.shape=[2,4],输出维度在前,输入维度在后,和Linear定义的时候相反;
- 相比.parameters(),.named_parameters()能看到参数名,默认情况下会使用所在的层数+参数类型的方式,从0层开始编号;
- 使用优化器时,可以直接调用nn.Module类定义的参数。
1 optimizer = optim.SGD(net.parameters(), lr=learning_rate)
4.模块树形结构
模块之间通过嵌套组合会形成树形结构,使用.children()可以获取其直接孩子结点,使用.modules()可以获取其所有子结点。
1 from torch import nn 2 3 class BaseNet(nn.Module): 4 5 def __init__(self): 6 super(BaseNet, self).__init__() 7 self.net = nn.Linear(4, 3) #输入4维输出3维的线性层 8 def forward(self, x): 9 return self.net(x) 10 11 12 class MyNet(nn.Module): 13 14 def __init__(self): 15 super(MyNet, self).__init__() 16 self.net = nn.Sequential( #使用Seq容器组合了三个模块 17 BaseNet(), 18 nn.ReLU(), 19 nn.Linear(3, 2) 20 ) 21 def forward(self, x): 22 return self.net(x) 23 24 25 my_net = MyNet() 26 27 print(list(my_net.children())) # 直接孩子 28 # [Sequential( 29 # (0): BaseNet((net): Linear(in_features=4, out_features=3, bias=True)) 30 # (1): ReLU() 31 # (2): Linear(in_features=3, out_features=2, bias=True) 32 # )] 33 34 print(list(my_net.modules())) # 所有孩子 35 # [MyNet( 36 # (net): Sequential( 37 # (0): BaseNet((net): Linear(in_features=4, out_features=3, bias=True)) 38 # (1): ReLU() 39 # (2): Linear(in_features=3, out_features=2, bias=True) 40 # )), 41 # Sequential( 42 # (0): BaseNet((net): Linear(in_features=4, out_features=3, bias=True)) 43 # (1): ReLU() 44 # (2): Linear(in_features=3, out_features=2, bias=True) 45 # ), 46 # BaseNet((net): Linear(in_features=4, out_features=3, bias=True)), 47 # Linear(in_features=4, out_features=3, bias=True), 48 # ReLU(), 49 # Linear(in_features=3, out_features=2, bias=True)]
.children()只返回自己的直系孩子列表,在这里也就是一个nn.Sequential容器。而使用.modules()获取的所有孩子是包括自己的。
5.设备
使用.to(device)可以在具体的CPU/GPU上切换,这会将其所有子模块也一起转移过去运行。
1 device = torch.device('cuda') 2 net = Net() 3 net.to(device)
Tip:模块的.to(device)是原地操作并返回自己的引用,而Tensor的.to(device)不会在当前Tensor上操作,返回的才是在目标设备上对应创建的Tensor,所以net = MLP().to(device)。
6.加载和保存
使用torch.load()载入检查点文件,然后传入net.load_state_dict()网络模型设置参数,把当前类所有状态net.state_dict()传入torch.save()保存到文件中去。在训练过程中,每隔一定的迭代次数可以保存一下检查点,将当前网络模型的状态传进去。
eg.ckpt.mdl是网络的一个中间状态
1 net.load_state_dict(torch.load('ckpt.mdl')) 2 #train... 3 torch.save(net.state_dict(), 'ckpt.mdl')
7.训练和测试模式
前面的学习中提到Dropout和Batch Normalization在训练和测试中的行为不同,需要对每一个nn.Module()模块单独设置训练状态和测试状态,可以直接为网络使用方法.train()切换到训练模式,使用.eval()方法切换到测试模式。
1 #train 2 net.train() 3 ... 4 #test 5 net.eval() 6 ...
8.实现自定义的类
8.1
例如一个将数据只保留第一维,其它维合并的Flatten层,可以用在卷积层和全连接层之间:
1 class Flatten(nn.Module): 2 3 def __init__(self): 4 super(Flatten, self).__init__() 5 def forward(self, input): 6 return input.view(input.size(0), -1) 7 8 9 class TestNet(nn.Module): 10 11 def __init__(self): 12 super(TestNet, self).__init__() 13 self.net = nn.Sequential(nn.Conv2d(1, 16, stride=1, padding=1), 14 nn.MaxPool2d(2, 2), 15 Flatten(), 16 nn.Linear(1*14*14, 10)) 17 def forward(self, x): 18 return self.net(x)
Tip:只有类才能写到Sequential里面,比如F.relu不可以,要重新定义nn.ReLU
8.2
如果在继承nn.Module类来实现模块时,出现需要操作Tensor的部分,那么应当使用nn.Parameters(注意这里P大写)将其包装起来。如果直接使用Tensor,那么就不能用.parameters()(注意这里p小写)获取到所有参数,也就不能直接传给优化器去记录要优化的这些参数了。
1 class MyLinear(nn.Module): 2 def __init__(self, inp, outp): 3 super(MyLinear, self).__init__() 4 # 线性层的参数w和b,对w而言输出维度放在前面 5 self.w = nn.Parameter(torch.randn(outp, inp)) 6 self.b = nn.Parameter(torch.randn(outp)) 7 8 def forward(self, x): 9 x = x @ self.w.t() + self.b 10 return x
Tip:使用nn.Parameter()包装Tensor时,自动设置了requires_grad=True,即默认情况下认为它是反向传播优化的参数。