「PyTorch 學習筆記」3.1 模型創建步驟與 nn.Module

2021-01-07 紙鶴視界

本章代碼:https://github.com/zhangxiann/PyTorch_Practice/blob/master/lesson3/module_containers.py

這篇文章來看下 PyTorch 中網絡模型的創建步驟。網絡模型的內容如下,包括模型創建和權值初始化,這些內容都在nn.Module中有實現。

網絡模型的創建步驟

創建模型有 2 個要素:構建子模塊和拼接子模塊。如 LeNet 裡包含很多卷積層、池化層、全連接層,當我們構建好所有的子模塊之後,按照一定的順序拼接起來。

這裡以上一篇文章中 `lenet.py`的 LeNet 為例,繼承`nn.Module`,必須實現`__init__()` 方法和`forward()`方法。其中`__init__()` 方法裡創建子模塊,在`forward()`方法裡拼接子模塊。 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__()方法創建模型的子模塊。

當我們在訓練時調用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()函數中,進行前向傳播。

在 torch.nn中包含 4 個模塊,如下圖所示。

其中所有網絡模型都是繼承於`nn.Module`的,下面重點分析`nn.Module`模塊。 nn.Module

nn.Module 有 8 個屬性,都是OrderDict(有序字典)。在 LeNet 的__init__()方法中會調用父類nn.Module的__init__()方法,創建這 8 個屬性。

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 屬性:存儲管理 nn.Parameter 類型的參數_modules 屬性:存儲管理 nn.Module 類型的參數_buffers 屬性:存儲管理緩衝屬性,如 BN 層中的 running_mean5 個 ***_hooks 屬性:存儲管理鉤子函數其中比較重要的是parameters和modules屬性。

在 LeNet 的__init__()中創建了 5 個子模塊,nn.Conv2d()和nn.Linear()都是 繼承於nn.module,也就是說一個 module 都是包含多個子 module 的。

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 個子網絡模塊。

下面看下每個子模塊是如何添加到 LeNet 的`_modules` 屬性中的。以`self.conv1 = nn.Conv2d(3, 6, 5)`為例,當我們運行到這一行時,首先 Step Into 進入 `Conv2d`的構造,然後 Step Out。右鍵`Evaluate Expression`查看`nn.Conv2d(3, 6, 5)`的屬性。

上面說了`Conv2d`也是一個 module,裡面的`_modules`屬性為空,`_parameters`屬性裡包含了該卷積層的可學習參數,這些參數的類型是 Parameter,繼承自 Tensor。

此時只是完成了`nn.Conv2d(3, 6, 5)` module 的創建。還沒有賦值給`self.conv1 `。在`nn.Module`裡有一個機制,會攔截所有的類屬性賦值操作(`self.conv1`是類屬性),進入到`__setattr__()`函數中。我們再次 Step Into 就可以進入`__setattr__()`。 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)。

總結

一個 module 裡可包含多個子 module。比如 LeNet 是一個 Module,裡面包括多個卷積層、池化層、全連接層等子 module一個 module 相當於一個運算,必須實現 forward() 函數每個 module 都有 8 個字典管理自己的屬性模型容器

除了上述的模塊之外,還有一個重要的概念是模型容器 (Containers),常用的容器有 3 個,這些容器都是繼承自nn.Module。

nn.Sequetial:按照順序包裝多個網絡層nn.ModuleList:像 python 的 list 一樣包裝多個網絡層,可以迭代nn.ModuleDict:像 python 的 dict 一樣包裝多個網絡層,通過 (key, value) 的方式為每個網絡層指定名稱。nn.Sequetial

在傳統的機器學習中,有一個步驟是特徵工程,我們需要從數據中認為地提取特徵,然後把特徵輸入到分類器中預測。在深度學習的時代,特徵工程的概念被弱化了,特徵提取和分類器這兩步被融合到了一個神經網絡中。在卷積神經網絡中,前面的卷積層以及池化層可以認為是特徵提取部分,而後面的全連接層可以認為是分類器部分。比如 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。

而`features`中的子 module 如下,每個網絡層以序號作為 key:

在進行前向傳播時,會進入 LeNet 的`forward()`函數,首先調用第一個`Sequetial`容器:`self.features`,由於`self.features`也是一個 module,因此會調用`__call__()`函數,裡面調用 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.ModuleList是nn.Module的容器,用於包裝一組網絡層,以迭代的方式調用網絡層,主要有以下 3 個方法:

append():在 ModuleList 後面添加網絡層extend():拼接兩個 ModuleListinsert():在 ModuleList 的指定位置中插入網絡層下面的代碼通過列表生成式來循環迭代創建 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 xnet = ModuleList()print(net)fake_data = torch.ones((10, 10))output = net(fake_data)print(output)nn.ModuleDict

nn.ModuleDict是nn.Module的容器,用於包裝一組網絡層,以索引的方式調用網絡層,主要有以下 5 個方法:

clear():清空 ModuleDictitems():返回可迭代的鍵值對 (key, value)keys():返回字典的所有 keyvalues():返回字典的所有 valuepop():返回一對鍵值,並從字典中刪除下面的模型創建了兩個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 xnet = ModuleDict()fake_img = torch.randn((4, 10, 32, 32))output = net(fake_img, 'conv', 'relu')# output = net(fake_img, 'conv', 'prelu')print(output)容器總結

nn.Sequetial:順序性,各網絡層之間嚴格按照順序執行,常用於 block 構建,在前向傳播時的代碼調用變得簡潔nn.ModuleList:迭代行,常用於大量重複網絡構建,通過 for 循環實現重複構建nn.ModuleDict:索引性,常用於可選擇的網絡層PyTorch 中的 AlexNet

AlexNet 是 Hinton 和他的學生等人在 2012 年提出的卷積神經網絡,以高出第二名 10 多個百分點的準確率獲得 ImageNet 分類任務冠軍,從此卷積神經網絡開始在世界上流行,是劃時代的貢獻。

AlexNet 特點如下:

採用 ReLU 替換飽和激活 函數,減輕梯度消失採用 LRN (Local Response Normalization) 對數據進行局部歸一化,減輕梯度消失採用 Dropout 提高網絡的魯棒性,增加泛化能力使用 Data Augmentation,包括 TenCrop 和一些色彩修改AlexNet 的網絡結構可以分為兩部分:features 和 classifier。

在`PyTorch`的計算機視覺庫`torchvision.models`中的 AlexNet 的代碼中,使用了`nn.Sequential`來封裝網絡層。 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參考資料

深度之眼 PyTorch 框架班如果你覺得這篇文章對你有幫助,不妨點個讚,讓我有更多動力寫出好文章。

相關焦點

  • 從零開始深度學習Pytorch筆記(12)—— nn.Module
    從零開始深度學習Pytorch筆記(1)——安裝Pytorch從零開始深度學習Pytorch
  • PyTorch  深度學習新手入門指南
    為了更好的理解這些知識,你需要確定自己滿足下面的幾點要求:1. 如果在領英上,你也許會說自己是一個深度學習的狂熱愛好者,但是你只會用 keras 搭建模型,那麼,這篇文章非常適合你。2. 你可能對理解 tensorflow 中的會話,變量和類等有困擾,並且計劃轉向 pytorch,很好,你來對地方了。3.
  • 【PyTorch】torch.nn.Module 源碼分析
    點擊上方「MLNLP」,選擇「星標」公眾號重磅乾貨,第一時間送達作者 | 藥師地址 | https://zhuanlan.zhihu.com/p/88712978專欄 | 我的機器學習筆記【PyTorch】torch.nn.Module 源碼分析torch.nn.Module 這個類的內部有多達 48 個函數,這個類是 PyTorch 中所有 neural network module 的基類,自己創建的網絡模型都是這個類的子類,下邊是一個示例。
  • 深度學習-Pytorch框架學習之模型定義與簡單操作
    __init__() # 這個super我一直沒能理解self.layer1 = nn.Sequential(nn.Conv2d(1, 16, kernel_size=5, stride=1, padding=2),nn.BatchNorm2d(16),nn.ReLU(),nn.MaxPool2d(kernel_size=2
  • PyTorch最佳實踐,教你寫出一手風格優美的代碼
    一個 nn.Module 網絡包含各種操作或其它構建模塊。損失函數也是包含在 nn.Module 內,因此它們可以被直接整合到網絡中。繼承 nn.Module 的類必須擁有一個「forward」方法,它實現了各個層或操作的前向傳導。一個 nn.module 可以通過「self.net(input)」處理輸入數據。
  • 如何使用 TensorFlow mobile 將 PyTorch 和 Keras 模型部署到行動裝置
    隨著深度學習的出現,我們的手機 app 將變得更加智能。下一代由深度學習驅動的手機 app 將可以學習並為你定製功能。一個很顯著的例子是「Microsoft Swiftkey」,這是一個鍵盤 app, 能通過學習你常用的單詞和詞組來幫助你快速打字。 計算機視覺,自然語言處理,語音識別和語音合成等技術能夠大大改善用戶在移動應用方面的體驗。
  • Facebook發布PyTorch 1.1,開源AI模型優化簡化工具BoTorch & Ax
    在今年的 Facebook F8 大會上,扎克伯格談到了過去一年最敏感的隱私問題,稱「過去我們常常是先發布,然後發現問題後再解決」,「未來我們會更加謹慎,諮詢專家做出評估,並採取更為主動積極的應對策略」。除此之外,Facebook 還發布了 PyTorch 的最新迭代版本——Pytorch 1.1 以及用於簡化模型優化流程的新工具 BoTorch 和 Ax。
  • 新版PyTorch 1.2 已發布:功能更多、兼容更全、操作更快!
    NN.TRANSFORMER在 PyTorch 1.2 中,我們現在包含一個標準的 nn.Transformer 模塊(https://pytorch.org/docs/stable/nn.html?highlight=transformer#torch.nn.Transformer),該模塊基於「Attention is All You Need(https://arxiv.org/abs/1706.03762)」這篇論文。nn.Transformer 模塊完全依賴注意力機制來構建輸入和輸出之間的全局依賴關係;該模塊的各個組件經過精心設計,可以獨立使用。
  • 【Pytorch 】筆記五:nn 模塊中的網絡層介紹
    今天是該系列的第五篇文章, 通過上一篇內容我們已經知道了如何搭建模型並且也學習了關於搭建模型非常重要的一個類 nn.Module 和模型容器 Containers。搭建模型我們提到兩個步驟,建立子模塊和拼接子模塊。而這次我們再細一點,具體學習幾個重要的子模塊,比如卷積層,池化層,激活函數,全連接層等。
  • Deep CARs:使用Pytorch學習框架實現遷移學習
    例如,如果想要構建一個網絡識別鳥類,與其從頭開始編寫一個複雜的模型,不如用一個現成的的模型,用於針對相同或相似任務的模型(在該實例中,可以使用一個識別其他動物的網絡系統來完成任務)。遷移學習法的優勢在於:學習過程更快、信息更準確,所需的訓練數據也更少。在遷移學習中,現有的這個模型稱為預訓練模型。大多數用於遷移學習的預訓練模型都是基於大型卷積神經網絡之上的。
  • (二)pytorch學習筆記
    (一)pytorch學習筆記(二)pytorch學習筆記關係擬合 (回歸)我會這次會來見證神經網絡是如何通過簡單的形式將一群數據用一條線條來表示. 或者說, 是如何在數據當中找到他們的關係, 然後用神經網絡模型來建立一個可以代表他們關係的線條.建立數據集我們創建一些假數據來模擬真實的情況.
  • pytorch學習筆記(2):在 MNIST 上實現一個 cnn
    在前面我要先說一下,這個系列是為了讓大家對 pytorch 從入門到熟悉,對於 deep learning 相關的知識我們不會花費過多的內容去介紹。如果大家對一些 DL 的基礎相關知識不懂的話,推薦幾個資源去學習:所以我們在筆記中對於一些相關的知識就不做深入介紹了。
  • PyTorch系列 | 如何加快你的模型訓練速度呢?
    譯者 | kbsc13("算法猿的成長"公眾號作者)原文 | https://towardsdatascience.com/speed-up-your-algorithms-part-1-pytorch-56d8a4ae7051聲明 | 翻譯是出於交流學習的目的,歡迎轉載,但請保留本文出於,請勿用作商業或者非法用途本文將主要介紹如何採用
  • 從零開始PyTorch項目:YOLO v3目標檢測實現
    但是,當出現類別「女性」(Women)和「人」(Person)時,該假設不可行。這就是作者選擇不使用 Softmax 激活函數的原因。在不同尺度上的預測YOLO v3 在 3 個不同尺度上進行預測。檢測層用於在三個不同大小的特徵圖上執行預測,特徵圖步幅分別是 32、16、8。
  • 深度學習大講堂之pytorch入門
    這其中,TensorFlow和Pytorch佔據了深度學習的半壁江山。今天小天就帶大家從數據操作、自動求梯度和神經網絡設計的pytorch版本三個方面來入門pytorch。3.1 定義網絡import torchimport torch.nn as nnimport torch.nn.functional
  • 使用PyTorch進行主動遷移學習:讓模型預測自身的錯誤
    模型預測標籤為「a」、「B」、「C」或「D」,單獨的數據集標籤為「W」、「X」、「Y」和「Z」。再訓練模型的最後一層模型現在能夠預測標籤「W」、「X」、「Y」和「Z」。遷移學習的最大優點是,與從頭開始訓練一個模型相比,你需要更少的人工標記的示例,這意味著你可以用更少的數據獲得更高精度的模型。
  • 【深度學習實戰】從基礎概念到實現,小白如何快速入門PyTorch
    即使完整的計算圖還沒有完成構建,我們也可以獨立地執行這些作為組件的小計算圖,這種動態計算圖被稱為「define-by-run」方法。更多介紹請查看:http://pytorch.org/about/安裝 PyTorch 非常簡單,我們可以按照自己的系統跟隨官方文檔的步驟輕鬆完成。
  • 教程 | 從頭開始了解PyTorch的簡單實現
    我們來舉幾個例子說明其中的區別:1 張量創建t = torch.rand(2, 4, 3, 5)a = np.random.rand(2, 4, 3, 5)2 張量分割t = torch.rand(2, 4, 3, 5)a = t.numpy()pytorch_slice
  • Pytorch中的分布式神經網絡訓練
    from torch import nnclass Network(nn.Module):def __init__(self, split_gpus=False): super().__init__() self.module1 = ... self.module2 = ...
  • [DL] PyTorch 折桂 6:torch.nn 總覽 & torch.nn.Module
    往期匯總:1 torch.nn 總覽PyTorch 把與深度學習模型搭建相關的全部類全部在 torch.nn 這個子模塊中。torch.nn.Sequential這是一個序列容器,既可以放在模型外面單獨構建一個模型,也可以放在模型裡面成為模型的一部分。