本章代碼:https://github.com/zhangxiann/PyTorch_Practice/blob/master/lesson3/module_containers.pypython
這篇文章來看下 PyTorch 中網絡模型的建立步驟。網絡模型的內容以下,包括模型建立和權值初始化,這些內容都在nn.Module
中有實現。git
建立模型有 2 個要素:構建子模塊和拼接子模塊。如 LeNet 裏包含不少卷積層、池化層、全鏈接層,當咱們構建好全部的子模塊以後,按照必定的順序拼接起來。github
class LeNet(nn.Module): # 子模塊建立 def __init__(self, classes): 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
當咱們調用net = LeNet(classes=2)
建立模型時,會調用__init__()
方法建立模型的子模塊。api
當咱們在訓練時調用outputs = net(inputs)
時,會進入module.py
的call()
函數中:網絡
def __call__(self, *input, **kwargs): for hook in self._forward_pre_hooks.values(): result = hook(self, input) if result is not None: if not isinstance(result, tuple): result = (result,) input = result if torch._C._get_tracing_state(): result = self._slow_forward(*input, **kwargs) else: result = self.forward(*input, **kwargs) ... ... ...
最終會調用result = self.forward(*input, **kwargs)
函數,該函數會進入模型的forward()
函數中,進行前向傳播。app
在 torch.nn
中包含 4 個模塊,以下圖所示。框架
nn.Module
有 8 個屬性,都是OrderDict
(有序字典)。在 LeNet 的__init__()
方法中會調用父類nn.Module
的__init__()
方法,建立這 8 個屬性。less
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()
其中比較重要的是parameters
和modules
屬性。機器學習
在 LeNet 的__init__()
中建立了 5 個子模塊,nn.Conv2d()
和nn.Linear()
都是 繼承於nn.module
,也就是說一個 module 都是包含多個子 module 的。ide
class LeNet(nn.Module): # 子模塊建立 def __init__(self, classes): 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) ... ... ...
當調用net = LeNet(classes=2)
建立模型後,net
對象的 modules 屬性就包含了這 5 個子網絡模塊。
def __setattr__(self, name, value): def remove_from(*dicts): for d in dicts: if name in d: del d[name] params = self.__dict__.get('_parameters') if isinstance(value, Parameter): if params is None: raise AttributeError( "cannot assign parameters before Module.__init__() call") remove_from(self.__dict__, self._buffers, self._modules) self.register_parameter(name, value) elif params is not None and name in params: if value is not None: raise TypeError("cannot assign '{}' as parameter '{}' " "(torch.nn.Parameter or None expected)" .format(torch.typename(value), name)) self.register_parameter(name, value) else: modules = self.__dict__.get('_modules') if isinstance(value, Module): if modules is None: raise AttributeError( "cannot assign module before Module.__init__() call") remove_from(self.__dict__, self._parameters, self._buffers) modules[name] = value elif modules is not None and name in modules: if value is not None: raise TypeError("cannot assign '{}' as child module '{}' " "(torch.nn.Module or None expected)" .format(torch.typename(value), name)) modules[name] = value ... ... ...
在這裏判斷 value 的類型是Parameter
仍是Module
,存儲到對應的有序字典中。
這裏nn.Conv2d(3, 6, 5)
的類型是Module
,所以會執行modules[name] = value
,key 是類屬性的名字conv1
,value 就是nn.Conv2d(3, 6, 5)
。
除了上述的模塊以外,還有一個重要的概念是模型容器 (Containers),經常使用的容器有 3 個,這些容器都是繼承自nn.Module
。
在傳統的機器學習中,有一個步驟是特徵工程,咱們須要從數據中認爲地提取特徵,而後把特徵輸入到分類器中預測。在深度學習的時代,特徵工程的概念被弱化了,特徵提取和分類器這兩步被融合到了一個神經網絡中。在卷積神經網絡中,前面的卷積層以及池化層能夠認爲是特徵提取部分,然後面的全鏈接層能夠認爲是分類器部分。好比 LeNet 就能夠分爲特徵提取和分類器兩部分,這 2 部分均可以分別使用 nn.Seuqtial
來包裝。
class LeNetSequetial(nn.Module): def __init__(self, classes): super(LeNet2, self).__init__() self.features = nn.Sequential( nn.Conv2d(3, 6, 5), nn.ReLU(), nn.MaxPool2d(2, 2), nn.Conv2d(6, 16, 5), nn.ReLU(), nn.MaxPool2d(2, 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
在初始化時,nn.Sequetial
會調用__init__()
方法,將每個子 module 添加到 自身的_modules
屬性中。這裏能夠看到,咱們傳入的參數能夠是一個 list,或者一個 OrderDict。若是是一個 OrderDict,那麼則使用 OrderDict 裏的 key,不然使用數字做爲 key (OrderDict 的狀況會在下面說起)。
def __init__(self, *args): super(Sequential, self).__init__() if len(args) == 1 and isinstance(args[0], OrderedDict): for key, module in args[0].items(): self.add_module(key, module) else: for idx, module in enumerate(args): self.add_module(str(idx), module)
網絡初始化完成後有兩個子 module
:features
和classifier
。
result = self.forward(*input, **kwargs)
,進入nn.Seuqetial
的forward()
函數,在這裏依次調用全部的 module。
def forward(self, input): for module in self: input = module(input) return input
在上面能夠看到在nn.Sequetial
中,裏面的每一個子網絡層 module 是使用序號來索引的,即便用數字來做爲 key。一旦網絡層增多,難以查找特定的網絡層,這種狀況可使用 OrderDict (有序字典)。代碼中使用
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), })) ... ... ...
nn.Sequetial
是nn.Module
的容器,用於按順序包裝一組網絡層,有如下兩個特性。
forward()
函數:在nn.Sequetial
的forward()
函數裏經過 for 循環依次讀取每一個網絡層,執行前向傳播運算。這使得咱們咱們構建的模型更加簡潔nn.ModuleList
是nn.Module
的容器,用於包裝一組網絡層,以迭代的方式調用網絡層,主要有如下 3 個方法:
下面的代碼經過列表生成式來循環迭代建立 20 個全鏈接層,很是方便,只是在 forward()
函數中須要手動調用每一個網絡層。
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 net = ModuleList() print(net) fake_data = torch.ones((10, 10)) output = net(fake_data) print(output)
nn.ModuleDict
是nn.Module
的容器,用於包裝一組網絡層,以索引的方式調用網絡層,主要有如下 5 個方法:
下面的模型建立了兩個ModuleDict
:self.choices
和self.activations
,在前向傳播時經過傳入對應的 key 來執行對應的網絡層。
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') # output = net(fake_img, 'conv', 'prelu') print(output)
AlexNet 是 Hinton 和他的學生等人在 2012 年提出的卷積神經網絡,以高出第二名 10 多個百分點的準確率得到 ImageNet 分類任務冠軍,今後卷積神經網絡開始在世界上流行,是劃時代的貢獻。
AlexNet 特色以下:
AlexNet 的網絡結構能夠分爲兩部分:features 和 classifier。
class AlexNet(nn.Module): def __init__(self, num_classes=1000): super(AlexNet, self).__init__() self.features = nn.Sequential( nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2), nn.ReLU(inplace=True), nn.MaxPool2d(kernel_size=3, stride=2), nn.Conv2d(64, 192, kernel_size=5, padding=2), nn.ReLU(inplace=True), nn.MaxPool2d(kernel_size=3, stride=2), nn.Conv2d(192, 384, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.Conv2d(384, 256, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.Conv2d(256, 256, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.MaxPool2d(kernel_size=3, stride=2), ) self.avgpool = nn.AdaptiveAvgPool2d((6, 6)) self.classifier = nn.Sequential( nn.Dropout(), nn.Linear(256 * 6 * 6, 4096), nn.ReLU(inplace=True), nn.Dropout(), nn.Linear(4096, 4096), nn.ReLU(inplace=True), nn.Linear(4096, num_classes), ) def forward(self, x): x = self.features(x) x = self.avgpool(x) x = torch.flatten(x, 1) x = self.classifier(x) return x
參考資料
若是你以爲這篇文章對你有幫助,不妨點個贊,讓我有更多動力寫出好文章。