寫在前面的話
前兩節我們進行了數據的預處理,介紹了賽題的相關背景:天池實戰-街景字符編碼識別-task1賽題理解,通過Pytorch 批量讀取圖像數據並進行圖像預處理:天池實戰-街景字符編碼識別-task2數據預處理。
這節我們通過CNN模型完成字符識別功能,並且介紹如何在Pytorch 下進行模型的建立
關於CNN模型
想必在使用Pytorch 之前你應該對CNN有一個基本的瞭解,最少得了解CNN一般都包括哪些層,每層都是用來幹嘛的這些,至於說建模和調參這些你可以先不用知道,在Pytorch 的使用中你會看到。
Pytorch 模型創建
在Pytorch 中建立模型只需要定義好模型的參數和正向傳播即可,Pytorch會根據正向傳播自動計算反向傳播。
那麼我們的問題就可以優化成建立CNN模型的正向傳播,在Pytorch 中包括這兩步:
- 構建子模塊(比如LeNet裏面的卷積層,池化層,全連接層)
- 拼接子模塊(有了子模塊,我們把子模塊按照一定的順序,邏輯進行拼接起來得到最終的模型)
構建子模塊
這一步主要是建立自己的模型,繼承nn.Module,並在 __init__()
方法中實現相關子模塊的構建
說到這不知道你有沒有想起上一節在讀取讀取的時候,同樣是繼承了Pytorch 的基類Dataset和DataLoader,不同的是這裏需要集成nn.Module這個基類。
拼接子模塊
基於上面創建的模型在模型的forward()
方法中進行模塊的拼接,相當於我們需要定義前向傳播的每個層
不同的是在module中會自己計算反向傳播,我們只需要定義前向傳播即可。
nn.Module 類
在nn.Module中,有8個以有序字典形式存在的屬性,用於管理整個模型
_parameters:
存儲管理屬於nn.Parameter類的屬性,例如權值,偏置等參數_modules:
存儲管理nn.Module類,比如模型中構建的子模塊、卷積層、池化層等會存儲在modules中_buffers:
存儲管理緩衝屬性*_hooks:
存儲管理鉤子函數(5個鉤子函數)
子模塊的構建機制如下:
- 創建一個大的Module 繼承nn.Module 這個基類,並且在這個大的基類中有很多的子模塊,子模塊同樣繼承於nn.Module,在Module 的
__init__
方法中,會先通過調用父類的初始化方法進行8個屬性的初始化。 - 然後在構建每個子模塊的時候,先初始化然後被
__setattr__
方法通過判斷value 的類型將其保存到相應的屬性字典中(例如module 類型、parameter類型),然後通過賦值的方式賦給相應的子模塊的成員。
通過上述兩步便完成了一個子模塊的構建,我們通過構建一個個這樣的子模塊,最終構成整個Module。
總結一下Module:
- 一個module可以包含多個子module(卷積層、池化層、全連接層等)
- 一個module相當於一個運算,必須實現forware()函數(前向傳播),module會自己計算反向傳播
- 每個module都有8個字典管理它的屬性(最常用的是
_parameters, _modules
)
上節在學習Dataset的時候瞭解到DataLoader,可以實現對圖像的批處理,那麼同樣的道理我們已經創建了一個個的子Module,也會存在一個模型容器Containers。
模型容器Containers
模型容器中包含3個子模塊,分別是nn.Sequential,nn.ModuleList和nn.ModuleDict
nn.Sequential
這是nn.module的容器,用於按順序包裝一組網絡層
在深度學習中,弱化了特徵工程部分,偏向於讓網絡自己提取特徵,然後進行分類或者回歸任務。
所以一般選擇以全連接層爲界限,將網絡模型劃分爲特徵提取模塊和分類模塊,在模型前面加上卷積層讓網絡自己學習提取特徵,在通過全連接層進行分類等任務。
那麼我們在創建模型的時候就可以這樣定義:
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
上面是創建了一個LeNet的模型,其中模型包括兩部分,分別是features進行特徵提取和classifier進行分類,然後通過Sequential包裝起來,相比而言forward函數只需要:features、形狀變換、classifier
我們可以發現其實Sequential是滿足以下規律的:
- 順序性:各網絡層之間嚴格按照順序構建,這時候一定要注意前後層數據的關係
- forward:自帶的forward裏,通過for循環依次執行前向傳播運算
nn.ModuleList
nn.ModuleList是nn.module的容器, 用於包裝一組網絡層, 以迭代方式調用網絡層。 主要方法:
- append(): 在ModuleList後面添加網絡層
- extend(): 拼接兩個ModuleList
- insert(): 指定在ModuleList中位置插入網絡層
這個方法的作用其實類似於我們的列表,只不過元素換成網絡層而已
nn.ModuleDict
nn.ModuleDict是nn.module的容器, 用於包裝一組網絡層, 以索引方式調用網絡層。主要方法:
- clear(): 清空ModuleDict
- items(): 返回可迭代的鍵值對(key-value pairs)
- keys(): 返回字典的鍵(key)
- values(): 返回字典的值(value)
- pop(): 返回一對鍵值對, 並從字典中刪除
總結一下Sequential
- nn.Sequential:順序性,各網絡層之間嚴格按順序執行
- nn.ModuleList:迭代性,常用於大量重複網構建,通過for循環即可實現
- nn.ModuleDict:索引性,常用於可選擇的網絡層
實操代碼
在本次項目的特徵提取模塊,通過resnet18模型作特徵提取模塊,也就相當於我們不需要再構建子模塊,直接使用resnet18的子模塊即可
代碼如下:
class SVHN_Model1(nn.Module):
def __init__(self):
super(SVHN_Model1, self).__init__()
model_conv = models.resnet18(pretrained=True)
model_conv.avgpool = nn.AdaptiveAvgPool2d(1)
model_conv = nn.Sequential(*list(model_conv.children())[:-1])
self.cnn = model_conv
self.fc1 = nn.Linear(512, 11)
self.fc2 = nn.Linear(512, 11)
self.fc3 = nn.Linear(512, 11)
self.fc4 = nn.Linear(512, 11)
self.fc5 = nn.Linear(512, 11)
def forward(self, img):
feat = self.cnn(img)
# print(feat.shape)
feat = feat.view(feat.shape[0], -1)
c1 = self.fc1(feat)
c2 = self.fc2(feat)
c3 = self.fc3(feat)
c4 = self.fc4(feat)
c5 = self.fc5(feat)
return c1, c2, c3, c4, c5
總結
本節通過對Pytorch下模型構建流程的分析,介紹了Pytorch下Module、Containers的用法和相關構建流程。在本次項目中通過resnet18對特徵進行提取,通過5個全連接層實現分類的目的