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)
這是一個非常簡單的分類器,其編碼部分使用了兩層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)
你覺得好多了?
你注意到了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)
更乾淨!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)
我們為機器學習模型分離了邏輯,使其更易於閱讀和重用。我們的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)
我們創建了一個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)
我們可以為解碼器部分做同樣的事情
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)
我們遵循相同的模式,我們為解碼部分創建一個新的塊,線性+ 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)
請注意,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))
最終實施
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)
結論
- 使用Module時,當您有由多個小塊組成的大塊時
- Sequential想要從層創建小塊時使用
- 使用ModuleList,當您需要遍歷某些層或構建塊並執行某些操作時
- ModuleDict當您需要參數化機器學習模型的某些塊時使用,例如激活函數
閱讀更多 不靠譜的貓 的文章