在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,即默認情況下認為它是反向傳播優化的參數。