如何搭建一個神經網絡模型
再看看 Pytorch API 的 torch.nn
部分
模型構建有兩要素,一是構建子模塊,二是拼接子模塊。
構建子模塊就是從torch.nn
中找到想要的layer
,最終通過前向傳播(即forward()
函數) 把這些layer
拼接在一起。
有了 Pytorch,搭建神經網絡模型就跟搭積木一樣。
nn.Module
每個網絡層(無論是用戶自定義的,還是 Pytorch 內置的)都要繼承nn.Module
類,這樣是爲了規範化每個網絡層,更方便管理。
上官方文檔:
class Module(object):
r"""Base class for all neural network modules.
Your models should also subclass this class.
Modules can also contain other Modules, allowing to nest them in
a tree structure.
"""
dump_patches = False
_version = 1
def __init__(self):
"""
Initializes internal Module state, shared by both nn.Module and ScriptModule.
"""
torch._C._log_api_usage_once("python.nn_module")
self.training = True
self._parameters = OrderedDict()
self._buffers = OrderedDict()
self._backward_hooks = OrderedDict()
self._forward_hooks = OrderedDict()
self._forward_pre_hooks = OrderedDict()
self._state_dict_hooks = OrderedDict()
self._load_state_dict_pre_hooks = OrderedDict()
self._modules = OrderedDict()
def forward(self, *input):
r"""Defines the computation performed at every call.
Should be overridden by all subclasses.
note:
Although the recipe for forward pass needs to be defined within
this function, one should call the :class:`Module` instance afterwards
instead of this since the former takes care of running the
registered hooks while the latter silently ignores them.
"""
raise NotImplementedError
可以看到,nn.Module 共有 8 個參數(8 個有序字典)
- parameters:存儲管理 nn.Parameter 類(包括權值、偏置等)
- modules:管理各種子模塊
- buffers:存儲管理緩衝屬性,如 BN 層中的running_mean
- hooks(5個):存儲管理鉤子函數
一個構建實例:
class LeNet(nn.Module):
# 在這裏構建子模塊
def __init__(self, classes):
# 初始化 nn.Module的 8個參數
super(LeNet, self).__init__()
self.conv1 = nn.Conv2d(3, 6, 5)
self.conv2 = nn.Conv2d(6, 16, 5)
self.fc1 = nn.Linear(16*5*5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, classes)
def forward(self, x):
out = F.relu(self.conv1(x))
out = F.max_pool2d(out, 2)
out = F.relu(self.conv2(out))
out = F.max_pool2d(out, 2)
out = out.view(out.size(0), -1)
out = F.relu(self.fc1(out))
out = F.relu(self.fc2(out))
out = self.fc3(out)
return out
在實例化 LeNet 類時,在初始化方法中,在初始化父類 nn.Module 的 8 個參數後,每一個賦值操作,如self.conv1 = nn.Conv2d(3, 6, 5)
,都會被攔截下來,進行類型判斷,如果是 parameter 則添加進self._parameters
,如果是 module 則添加進self._modules
。
上述幾點可通過 Debug 調式驗證。
nn.Module 總結
- 一個 module 可以包含多個子 module
- 一個 module 相當於一個運算,必須實現forward()
- 函數每個module都有8個字典管理它的屬性
Module Container
參考:https://blog.csdn.net/oldmao_2001/article/details/102787546
- nn.Sequetial:按順序包裝多個網絡層
- nn.ModuleList:像 Python 中的 list 一樣包裝多個網絡層
- nn.ModuleDict:像 Python 中的 dict 一樣包裝多個網絡層
注:這裏講的網絡層,全稱是神經網絡層。
發文截止,Containers 最新所有 API
Sequential
nn.Sequential 是 nn.module 的容器,用於按順序包裝一組網絡層,如下圖的LeNet,可以用 2 個 Sequential 把 LeNet 包裝起來。(見白色虛線框)
當然也可以只用一個 Sequential,但不直觀。
代碼如下
class LeNetSequential(nn.Module):
def __init__(self, classes):
super(LeNetSequential, self).__init__()
self.features = nn.Sequential(
nn.Conv2d(3, 6, 5),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(6, 16, 5),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2),)
self.classifier = nn.Sequential(
nn.Linear(16*5*5, 120),
nn.ReLU(),
nn.Linear(120, 84),
nn.ReLU(),
nn.Linear(84, classes),)
def forward(self, x):
x = self.features(x)
x = x.view(x.size()[0], -1)
x = self.classifier(x)
return x
上面定義的各個層的命名都是序號,在多個層的時候不是很方便調試或者閱讀,可以在創建 Sequential 的時候用 OrderedDict 指定層的名字,具體做法如下:
# 寫法二(更直觀)
class LeNetSequentialOrderDict(nn.Module):
def __init__(self, classes):
super(LeNetSequentialOrderDict, self).__init__()
self.features = nn.Sequential(OrderedDict({
'conv1': nn.Conv2d(3, 6, 5),
'relu1': nn.ReLU(inplace=True),
'pool1': nn.MaxPool2d(kernel_size=2, stride=2),
'conv2': nn.Conv2d(6, 16, 5),
'relu2': nn.ReLU(inplace=True),
'pool2': nn.MaxPool2d(kernel_size=2, stride=2),
}))
self.classifier = nn.Sequential(OrderedDict({
'fc1': nn.Linear(16*5*5, 120),
'relu3': nn.ReLU(),
'fc2': nn.Linear(120, 84),
'relu4': nn.ReLU(inplace=True),
'fc3': nn.Linear(84, classes),
}))
def forward(self, x):
x = self.features(x)
x = x.view(x.size()[0], -1)
x = self.classifier(x)
return x
小結:
- 順序性:各網絡層之間嚴格按照順序構建
- 自帶 forward():自帶的 forward() 裏,通過for循環依次執行前向傳播運算
ModuleList
nn.ModuleList 是 nn.module 的容器,用於包裝一組網絡層,以迭代方式調用網絡層
主要方法:
- append():在ModuleList後面添加網絡層
- extend():拼接兩個ModuleList
- insert():指定在ModuleList中位置插入網絡層
下面例子是創建一個20層的 FC 網絡模型
class ModuleList(nn.Module):
def __init__(self):
super(ModuleList, self).__init__()
self.linears = nn.ModuleList([nn.Linear(10, 10) for i in range(20)])
def forward(self, x):
for i, linear in enumerate(self.linears):
x = linear(x)
return x
ModuleLDict
nn.ModuleDict 是 nn.module 的容器,用於包裝一組網絡層,以索引方式調用網絡層主要方法:
- clear():清空 ModuleDict
- items():返回可迭代的鍵值對(key-value pairs)
- keys():返回字典的鍵(key)
- values():回字典的值(value)
- pop():返回一對鍵值,並從字典中刪除
class ModuleDict(nn.Module):
def __init__(self):
super(ModuleDict, self).__init__()
self.choices = nn.ModuleDict({
'conv': nn.Conv2d(10, 10, 3),
'pool': nn.MaxPool2d(3)
})
self.activations = nn.ModuleDict({
'relu': nn.ReLU(),
'prelu': nn.PReLU()
})
def forward(self, x, choice, act):
x = self.choices[choice](x)
x = self.activations[act](x)
return x
net = ModuleDict()
fake_img = torch.randn((4, 10, 32, 32))
output = net(fake_img, 'conv', 'relu')
print(output)
模型容器總結
- nn.Sequential:順序性,各網絡層之間嚴格按順序執行,常用於 block 構建
- nn.ModuleList:迭代性,常用於大量重複網絡層構建,通過 for 循環實現重複構建
- nn.ModuleDict:索引性,常用於可選擇的網絡層