Pytorch如何使用Module,Sequential、ModuleList和ModuleDict

Pytorch如何使用Module,Sequential、ModuleList和ModuleDict

Pytorch是一個開源的深度學習框架,提供了創建機器學習(ML)模型的智能方法。本文示例適用於Pytorch 4.1。

今天,我們將看到如何使用PyTorch的三個主要構建塊:Module, Sequential and ModuleList。

所有這四個類都包含在內 torch.nn

import torch.nn as nn

# nn.Module
# nn.Sequential
# nn.Module

Module:主要構建塊

Module是主要構建塊,它定義了所有神經網絡的基類。

讓我們創建一個經典的CNN分類器作為示例,Python代碼如下:

import torch.nn.functional as F

class MyCNNClassifier(nn.Module):
def __init__(self, in_c, n_classes):
super().__init__()
self.conv1 = nn.Conv2d(in_c, 32, kernel_size=3, stride=1, padding=1)
self.bn1 = nn.BatchNorm2d(32)

self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1)
self.bn2 = nn.BatchNorm2d(32)

self.fc1 = nn.Linear(32 * 28 * 28, 1024)
self.fc2 = nn.Linear(1024, n_classes)

def forward(self, x):
x = self.conv1(x)
x = self.bn1(x)

x = F.relu(x)

x = self.conv2(x)
x = self.bn2(x)
x = F.relu(x)

x = x.view(x.size(0), -1) # flat

x = self.fc1(x)
x = F.sigmoid(x)
x = self.fc2(x)

return x

model = MyCNNClassifier(1, 10)
print(model)
Pytorch如何使用Module,Sequential、ModuleList和ModuleDict

這是一個非常簡單的分類器,其編碼部分使用了兩層3x3的convs + batchnorm + relu,解碼部分使用了兩個線性層。如果您不是PyTorch的新手,您可能以前見過這種類型的代碼,但是有兩個問題。

如果我們想添加一個層,我們必須再次在__init__和forward函數中編寫大量代碼。另外,如果我們有一些常見的塊,我們想在另一個機器學習模型中使用,例如3x3 conv + batchnorm + relu,我們必須再寫一遍。

Sequential: stack和erge層

Sequential是一個模塊的容器,可以stacked在一起並同時運行。

您可以注意到我們必須存儲到self所有內容中。我們可以Sequential用來改進我們的代碼。Python代碼如下:

class MyCNNClassifier(nn.Module):
def __init__(self, in_c, n_classes):
super().__init__()
self.conv_block1 = nn.Sequential(
nn.Conv2d(in_c, 32, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(32),
nn.ReLU()
)

self.conv_block2 = nn.Sequential(
nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(64),
nn.ReLU()
)

self.decoder = nn.Sequential(
nn.Linear(32 * 28 * 28, 1024),
nn.Sigmoid(),

nn.Linear(1024, n_classes)
)


def forward(self, x):
x = self.conv_block1(x)
x = self.conv_block2(x)

x = x.view(x.size(0), -1) # flat

x = self.decoder(x)

return x
model = MyCNNClassifier(1, 10)
print(model)
Pytorch如何使用Module,Sequential、ModuleList和ModuleDict

你覺得好多了?

你注意到了conv_block1,conv_block2看起來幾乎一樣嗎?我們可以創建一個函數來重新生成nn.Sequential甚至簡化代碼!

def conv_block(in_f, out_f, *args, **kwargs):
return nn.Sequential(
nn.Conv2d(in_f, out_f, *args, **kwargs),
nn.BatchNorm2d(out_f),
nn.ReLU()
)

然後我們可以在我們的模塊中調用此Python函數

class MyCNNClassifier(nn.Module):
def __init__(self, in_c, n_classes):
super().__init__()
self.conv_block1 = conv_block(in_c, 32, kernel_size=3, padding=1)

self.conv_block2 = conv_block(32, 64, kernel_size=3, padding=1)


self.decoder = nn.Sequential(
nn.Linear(32 * 28 * 28, 1024),
nn.Sigmoid(),
nn.Linear(1024, n_classes)
)


def forward(self, x):
x = self.conv_block1(x)
x = self.conv_block2(x)

x = x.view(x.size(0), -1) # flat

x = self.decoder(x)

return x
model = MyCNNClassifier(1, 10)
print(model)
Pytorch如何使用Module,Sequential、ModuleList和ModuleDict

更乾淨!conv_block1和conv_block2幾乎一樣!我們可以使用nn.Sequential來merge他們

class MyCNNClassifier(nn.Module):
def __init__(self, in_c, n_classes):
super().__init__()
self.encoder = nn.Sequential(
conv_block(in_c, 32, kernel_size=3, padding=1),
conv_block(32, 64, kernel_size=3, padding=1)
)


self.decoder = nn.Sequential(
nn.Linear(32 * 28 * 28, 1024),
nn.Sigmoid(),
nn.Linear(1024, n_classes)
)


def forward(self, x):
x = self.encoder(x)

x = x.view(x.size(0), -1) # flat

x = self.decoder(x)

return x
model = MyCNNClassifier(1, 10)
print(model)
Pytorch如何使用Module,Sequential、ModuleList和ModuleDict

我們為機器學習模型分離了邏輯,使其更易於閱讀和重用。我們的conv_block函數可以導入並用於其他機器學習模型。

動態Sequential:一次創建多個層

如果我們可以在self.encoder中添加新的層:

self.encoder = nn.Sequential(
conv_block(in_c, 32, kernel_size=3, padding=1),
conv_block(32, 64, kernel_size=3, padding=1),
conv_block(64, 128, kernel_size=3, padding=1),
conv_block(128, 256, kernel_size=3, padding=1),

)

如果我們可以將尺寸定義為數組並自動創建所有層而不編寫每個層,那會不會很好?幸運的是,我們可以創建一個數組並將其傳遞給Sequential

class MyCNNClassifier(nn.Module):
def __init__(self, in_c, n_classes):
super().__init__()
self.enc_sizes = [in_c, 32, 64]

conv_blocks = [conv_block(in_f, out_f, kernel_size=3, padding=1)
for in_f, out_f in zip(self.enc_sizes, self.enc_sizes[1:])]

self.encoder = nn.Sequential(*conv_blocks)


self.decoder = nn.Sequential(
nn.Linear(32 * 28 * 28, 1024),
nn.Sigmoid(),
nn.Linear(1024, n_classes)
)


def forward(self, x):

x = self.encoder(x)

x = x.view(x.size(0), -1) # flat

x = self.decoder(x)

return x
model = MyCNNClassifier(1, 10)
print(model)
Pytorch如何使用Module,Sequential、ModuleList和ModuleDict

我們創建了一個self.enc_sizes包含編碼器大小的數組。然後我們conv_blocks通過迭代大小來創建一個數組。因為我們必須每層給booth一個in size和一個outsize。

為了清楚起見,請看下面的例子:

sizes = [1, 32, 64]

for in_f,out_f in zip(sizes, sizes[1:]):
print(in_f,out_f)

1 32 32 64

然後,由於Sequential不接受列表,我們使用*運算符對其進行分解。

現在,如果我們只想添加大小,我們可以輕鬆地在列表中添加新數字。將大小作為參數是常見的做法。

class MyCNNClassifier(nn.Module):
def __init__(self, in_c, enc_sizes, n_classes):
super().__init__()
self.enc_sizes = [in_c, *enc_sizes]

conv_blokcs = [conv_block(in_f, out_f, kernel_size=3, padding=1)
for in_f, out_f in zip(self.enc_sizes, self.enc_sizes[1:])]

self.encoder = nn.Sequential(*conv_blokcs)


self.decoder = nn.Sequential(
nn.Linear(32 * 28 * 28, 1024),
nn.Sigmoid(),
nn.Linear(1024, n_classes)
)


def forward(self, x):
x = self.encoder(x)

x = x.view(x.size(0), -1) # flat

x = self.decoder(x)

return x
model = MyCNNClassifier(1, [32,64, 128], 10)
print(model)

Pytorch如何使用Module,Sequential、ModuleList和ModuleDict

我們可以為解碼器部分做同樣的事情

def dec_block(in_f, out_f):
return nn.Sequential(
nn.Linear(in_f, out_f),
nn.Sigmoid()
)

class MyCNNClassifier(nn.Module):
def __init__(self, in_c, enc_sizes, dec_sizes, n_classes):
super().__init__()
self.enc_sizes = [in_c, *enc_sizes]
self.dec_sizes = [32 * 28 * 28, *dec_sizes]

conv_blokcs = [conv_block(in_f, out_f, kernel_size=3, padding=1)
for in_f, out_f in zip(self.enc_sizes, self.enc_sizes[1:])]

self.encoder = nn.Sequential(*conv_blokcs)


dec_blocks = [dec_block(in_f, out_f)
for in_f, out_f in zip(self.dec_sizes, self.dec_sizes[1:])]

self.decoder = nn.Sequential(*dec_blocks)

self.last = nn.Linear(self.dec_sizes[-1], n_classes)


def forward(self, x):
x = self.encoder(x)

x = x.view(x.size(0), -1) # flat

x = self.decoder(x)

return x
model = MyCNNClassifier(1, [32,64], [1024, 512], 10)
print(model)
Pytorch如何使用Module,Sequential、ModuleList和ModuleDict

我們遵循相同的模式,我們為解碼部分創建一個新的塊,線性+ sigmoid,然後我們傳遞一個大小的數組。我們不得不添加一個,self.last因為我們不想激活輸出

現在,我們甚至可以將我們的機器學習模型分解為兩個!編碼器+解碼器

class MyEncoder(nn.Module):
def __init__(self, enc_sizes):
super().__init__()
self.conv_blokcs = nn.Sequential(*[conv_block(in_f, out_f, kernel_size=3, padding=1)
for in_f, out_f in zip(enc_sizes, enc_sizes[1:])])

def forward(self, x):
return self.conv_blokcs(x)

class MyDecoder(nn.Module):
def __init__(self, dec_sizes, n_classes):
super().__init__()
self.dec_blocks = nn.Sequential(*[dec_block(in_f, out_f)
for in_f, out_f in zip(dec_sizes, dec_sizes[1:])])
self.last = nn.Linear(dec_sizes[-1], n_classes)

def forward(self, x):
return self.dec_blocks()


class MyCNNClassifier(nn.Module):
def __init__(self, in_c, enc_sizes, dec_sizes, n_classes):
super().__init__()
self.enc_sizes = [in_c, *enc_sizes]

self.dec_sizes = [32 * 28 * 28, *dec_sizes]

self.encoder = MyEncoder(self.enc_sizes)

self.decoder = MyDecoder(dec_sizes, n_classes)

def forward(self, x):
x = self.encoder(x)

x = x.flatten(1) # flat


x = self.decoder(x)

return x
model = MyCNNClassifier(1, [32,64], [1024, 512], 10)
print(model)
Pytorch如何使用Module,Sequential、ModuleList和ModuleDict

請注意,MyEncoder和MyDecoder也有可能是返回nn.Sequential函數。我更喜歡使用第一個模型用於模型,第二個模式用於構建塊。

通過將我們的模塊擴展到子模塊中,可以更輕鬆地共享代碼,對其進行調試和測試。

ModuleList:我們需要迭代的時候

ModuleList允許您存儲Module為列表。當您需要遍歷層並存儲/使用某些信息(如U-net)時,它非常有用。

Sequential的主要區別在於ModuleList沒有forward 方法,因此內部層沒有連接。假設我們需要解碼器中每一層的每個輸出,我們可以將其存儲為:

class MyModule(nn.Module):
def __init__(self, sizes):
super().__init__()
self.layers = nn.ModuleList([nn.Linear(in_f, out_f) for in_f, out_f in zip(sizes, sizes[1:])])
self.trace = []

def forward(self,x):
for layer in self.layers:
x = layer(x)
self.trace.append(x)
return x
model = MyModule([1, 16, 32])
import torch

model(torch.rand((4,1)))

[print(trace.shape) for trace in model.trace]

torch.Size([4, 16]) torch.Size([4, 32]) [None, None]

ModuleDict:我們需要選擇的時候

如果我們想在conv_block中切換到LearkyRelu呢?我們可以使用ModuleDict創建一個Module字典,並在需要時動態切換

def conv_block(in_f, out_f, activation='relu', *args, **kwargs):

activations = nn.ModuleDict([
['lrelu', nn.LeakyReLU()],
['relu', nn.ReLU()]
])

return nn.Sequential(

nn.Conv2d(in_f, out_f, *args, **kwargs),
nn.BatchNorm2d(out_f),
activations[activation]
)
print(conv_block(1, 32,'lrelu', kernel_size=3, padding=1))
print(conv_block(1, 32,'relu', kernel_size=3, padding=1))
Pytorch如何使用Module,Sequential、ModuleList和ModuleDict

最終實施

Python代碼如下:

def conv_block(in_f, out_f, activation='relu', *args, **kwargs):
activations = nn.ModuleDict([
['lrelu', nn.LeakyReLU()],
['relu', nn.ReLU()]
])

return nn.Sequential(
nn.Conv2d(in_f, out_f, *args, **kwargs),

nn.BatchNorm2d(out_f),
activations[activation]
)

def dec_block(in_f, out_f):
return nn.Sequential(
nn.Linear(in_f, out_f),
nn.Sigmoid()
)

class MyEncoder(nn.Module):
def __init__(self, enc_sizes, *args, **kwargs):
super().__init__()
self.conv_blokcs = nn.Sequential(*[conv_block(in_f, out_f, kernel_size=3, padding=1, *args, **kwargs)
for in_f, out_f in zip(enc_sizes, enc_sizes[1:])])

def forward(self, x):
return self.conv_blokcs(x)

class MyDecoder(nn.Module):
def __init__(self, dec_sizes, n_classes):
super().__init__()
self.dec_blocks = nn.Sequential(*[dec_block(in_f, out_f)
for in_f, out_f in zip(dec_sizes, dec_sizes[1:])])
self.last = nn.Linear(dec_sizes[-1], n_classes)

def forward(self, x):
return self.dec_blocks()


class MyCNNClassifier(nn.Module):
def __init__(self, in_c, enc_sizes, dec_sizes, n_classes, activation='relu'):
super().__init__()
self.enc_sizes = [in_c, *enc_sizes]
self.dec_sizes = [32 * 28 * 28, *dec_sizes]

self.encoder = MyEncoder(self.enc_sizes, activation=activation)

self.decoder = MyDecoder(dec_sizes, n_classes)

def forward(self, x):
x = self.encoder(x)

x = x.flatten(1) # flat

x = self.decoder(x)

return x
model = MyCNNClassifier(1, [32,64], [1024, 512], 10, activation='lrelu')
print(model)

Pytorch如何使用Module,Sequential、ModuleList和ModuleDict

結論

  • 使用Module時,當您有由多個小塊組成的大塊時
  • Sequential想要從層創建小塊時使用
  • 使用ModuleList,當您需要遍歷某些層或構建塊並執行某些操作時
  • ModuleDict當您需要參數化機器學習模型的某些塊時使用,例如激活函數


分享到:


相關文章: