详解nn.Module类,children和modules方法区别
pytorch里面一切自定义操作基本上都是继承nn.Module类来实现的,所以此篇文章来了解下这个核心nn.Module类。
继承nn.Module后具体实现自定义模型类时有两种方式:
(1)高层API方法:使用torch.nn.****来实现;,这些接口都是类,类可以存储参数,比如全连接层的权值矩阵、偏置矩阵等都可以作为类的属性存储着,当对这些接口类创建对象后再作为自定义模型类的属性,就实现了存储参数
(2)低层API方法:使用低层函数方法,torch.nn.functional.****来实现;,从名称就看出是一些函数接口,实现函数的运算功能,没办法保存这些信息,若是用它创建有学习参数的层,需要自己再实现保存参数的部分。
在自定义网络模型时,需要继承nn.Module类,必须重新实现构造函数__init__构造函数和forward这两个方法。但有一些注意技巧:
(1)一般把网络中具有可学习参数的层(如全连接层、卷积层等)放在构造函数__init__()方法中,当然也可以把不具有参数的层也放在__init__方法里面;
(2)不具有可学习参数的层(如ReLU、dropout、BatchNormanation层)可放在构造函数__init__中,也可不放在构造函数__init__中,如果不放在构造函数__init__里面,则在forward方法里面可以使用nn.functional来代替, 因为搭建时将没有训练参数的层 没有放在构造函数里面了(当然就没有这些属性了),所以这些层就不会出现在model里面(打印或可视化model)
(3)forward方法是必须要重写的,它是实现模型的功能,实现各个层之间的连接关系的核心。
总结:更清楚的显示或者了解我们定义的模型结构,建议将网络层(可学习参数层和没有训练参数层)都放在构造函数内实现,使之成为模型的属性,forward方法实现各层之间的连接。
补充:一般情况下,我们定义的参数是可以求导的,但是自定义操作如不可导,还需要实现backward函数。
import torch
class MyNet(torch.nn.Module):
def __init__(self):
# 必须调用父类的构造函数,因为想要使用父类的方法,这也是继承Module的目的
super(MyNet, self).__init__()
self.conv1 = torch.nn.Conv2d(3, 32, 3, 1, 1)
self.relu1 = torch.nn.ReLU()
self.max_pooling1 = torch.nn.MaxPool2d(2, 1)
self.conv2 = torch.nn.Conv2d(3, 32, 3, 1, 1)
self.relu2 = torch.nn.ReLU()
self.max_pooling2 = torch.nn.MaxPool2d(2, 1)
self.dense1 = torch.nn.Linear(32 * 3 * 3, 128)
self.dense2 = torch.nn.Linear(128, 10)
def forward(self, x):
x = self.conv1(x)
x = self.relu1(x)
x = self.max_pooling1(x)
x = self.conv2(x)
x = self.relu2(x)
x = self.max_pooling2(x)
x = self.dense1(x)
x = self.dense2(x)
return x
model = MyNet()
print(model)
'''运行结果为:
MyNet(
(conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(relu1): ReLU()
(max_pooling1): MaxPool2d(kernel_size=2, stride=1, padding=0, dilation=1, ceil_mode=False)
(conv2): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(relu2): ReLU()
(max_pooling2): MaxPool2d(kernel_size=2, stride=1, padding=0, dilation=1, ceil_mode=False)
(dense1): Linear(in_features=288, out_features=128, bias=True)
(dense2): Linear(in_features=128, out_features=10, bias=True)
)
可以看出打印模型后,显示的就是自定义类的属性,且顺序是按照定义顺序,
各个层之间到底是什么连接关系并不能显示,这也是建议将所有层的实现放在构造函数实现的原因,需要模型可视化工具显示
'''
再来看一个例子
import torch
import torch.nn.functional as F
class MyNet(torch.nn.Module):
def __init__(self):
# 必须调用父类的构造函数,因为想要使用父类的方法,这也是继承Module的目的
super(MyNet, self).__init__()
self.conv1 = torch.nn.Conv2d(3, 32, 3, 1, 1)
self.conv2 = torch.nn.Conv2d(3, 32, 3, 1, 1)
self.dense1 = torch.nn.Linear(32 * 3 * 3, 128)
self.dense2 = torch.nn.Linear(128, 10)
def forward(self, x):
x = self.conv1(x)
x = F.relu(x)
x = F.max_pool2d(x)
x = self.conv2(x)
x = F.relu(x)
x = F.max_pool2d(x)
x = self.dense1(x)
x = self.dense2(x)
return x
model = MyNet()
print(model)
'''运行结果为:
MyNet(
(conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(conv2): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(dense1): Linear(in_features=288, out_features=128, bias=True)
(dense2): Linear(in_features=128, out_features=10, bias=True)
)
可看出此种方式与上面的区别了吧,打印模型后不知道模型使用激活层,池化层等的超参数配置,没有上面方式一目了然。
'''
这里总结下四种模型构建方式:
方法一:未使用torch.nn.Sequential容器,这种方式一般用来搭建比较简单的模型,对复杂模型不适用。
import torch
import torch.nn.functional as F
from collections import OrderedDict
class Net1(torch.nn.Module):
def __init__(self):
super(Net1, self).__init__()
self.conv1 = torch.nn.Conv2d(3, 32, 3, 1, 1)
self.dense1 = torch.nn.Linear(32 * 3 * 3, 128)
self.dense2 = torch.nn.Linear(128, 10)
def forward(self, x):
x = F.max_pool2d(F.relu(self.conv(x)), 2)
x = x.view(x.size(0), -1)
x = F.relu(self.dense1(x))
x = self.dense2(x)
return x
print("Method 1:")
model1 = Net1()
print(model1)
'''
Net1(
(conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(dense1): Linear(in_features=288, out_features=128, bias=True)
(dense2): Linear(in_features=128, out_features=10, bias=True)
)
可以看出,每层的名字默认是变量名
'''
方法二:利用torch.nn.Sequential()容器