作者:北京交通大學計算機學院 秦梓鑫
學号:21120390
代碼僅供參考,歡迎交流;請勿用于任何形式的課程作業。
- Pytorch實驗一:從零實作Logistic回歸和Softmax回歸
- Pytorch實驗2:手動實作前饋神經網絡、Dropout、正則化、K折交叉驗證,解決多分類、二分類、回歸任務
目錄:實作卷積神經網絡、空洞卷積、殘差網絡
- 實驗内容
-
- 二維卷積實驗
- 空洞卷積實驗
- 殘差網絡實驗
- 實驗環境及實驗資料集
- 實驗過程
-
- 二維卷積實驗
-
- 手動實作二維卷積
- 使用torch實作二維卷積
- 對不同超參數的實驗結果對比分析
- 複現AlexNet模型
- 與前饋神經網絡的效果進行對比
- 空洞卷積實驗
- 殘差網絡實驗
- 實驗結果
-
- 二維卷積實驗
-
- 手動實作二維卷積
- 使用torch實作二維卷積
- 對不同超參數的實驗結果對比分析
-
- 卷積核大小的影響
- 卷積層數的影響
- 複現AlexNet模型
- 與前饋神經網絡的效果進行對比
- 空洞卷積實驗
-
- 空洞卷積實驗結果
- 空洞卷積與普通卷積的效果對比
- 超參數的對比
-
- 卷積核大小的影響
- 卷積層數的影響
- 殘差網絡實驗
-
- 殘差網絡實驗結果
- 殘差空洞卷積實驗結果
- 實驗心得體會
- 參考文獻
實驗内容
二維卷積實驗
卷積神經網絡是一種具有局部連接配接、權重共享特性的深層前饋神經網絡。局部連接配接是指通過引入卷積核,使得每次運算學習的特征隻和局部輸入相關,極大地減少了計算量和連接配接層數;權值共享是指學習的特征具有局部不變性,使得對此類特征提取更加充分。
在深度學習中,卷積的定義和分析數學、信号進行中的定義略有不同。一般地,卷積通過互相關(cross-correlation)計算。給定輸入 X ∈ R M × N \textbf{X}\in R^{M\times N} X∈RM×N 和卷積核 W ∈ R U × V \textbf{W}\in R^{U\times V} W∈RU×V,它們的互相關為
y i , j = ∑ u = 1 U ∑ v = 1 V w u , v x i + u − 1 , j + v + 1 y_{i,j}= \sum_{u=1}^{U}{ \sum_{v=1}^{V}{w_{u,v}x_{i+u-1,j+v+1}}} yi,j=u=1∑Uv=1∑Vwu,vxi+u−1,j+v+1
也記作:
Y = W ⊗ X \textbf{Y}=\textbf{W}⊗\textbf{X} Y=W⊗X
在考慮特征抽取問題時,互相關和卷積的運算是等價的。在卷積的基礎上,可以引入卷積核的步長(stride)和填充(padding)來控制提取的特征圖的尺寸。
一般地,卷積的輸出大小由參數:卷積核數目( F F F)、卷積核大小( K K K)、步長( S S S)、填充( P P P),和輸入圖檔的通道數( D D D)、寬度( W W W)、高度( H H H)共同決定;其中輸出的通道數、寬度、高度分别是:
D o u t = F D_{out}=F Dout=F
W o u t = W + 2 P − K S + 1 W_{out}=\frac{W+2P-K}{S}+1 Wout=SW+2P−K+1
H o u t = H + 2 P − K S + 1 H_{out}=\frac{H+2P-K}{S}+1 Hout=SH+2P−K+1
對于每個卷積核,共引入 K × K × D + 1 K\times K\ \times D\ +1 K×K ×D +1個參數;由于有F個卷積核,是以共有 F × D × K 2 + F F\times D\times K^2+F F×D×K2+F個參數。
卷積層(Convolution Layer)通常與彙聚層(Pooling Layer)混合使用。彙聚層是一種下采樣(down sample)操作,作用是減少參數和特征的數量、加快運算速度和增加模型的魯棒性。常用的彙聚包括:最大彙聚和平均彙聚。
二維卷積實驗的目的是:(1)手動實作二維卷積;(2)使用torch實作二維卷積;(3)進行實驗結果的分析、超參數的對比分析;(4)複現AlexNet模型;(5)與前饋神經網絡的效果進行對比。
空洞卷積實驗
空洞卷積是指通過在卷積核的元素間插入空隙來增加感受野的方法。對于大小為 K × K K\times K K×K的卷積核,在每兩個元素中插入 D − 1 D-1 D−1個元素,則空洞卷積核的有效大小是:
K ′ = K + ( K − 1 ) × ( D − 1 ) K'=K+(K-1)×(D-1) K′=K+(K−1)×(D−1)
其中:D是空洞卷積的膨脹率。例如:原卷積核大小為 3 × 3 3\times3 3×3,則空洞卷積核的單邊大小為 3 + ( 3 − 1 ) × ( 2 − 1 ) = 5 3+\left(3-1\right)\times\left(2-1\right)=5 3+(3−1)×(2−1)=5。普通卷積可以視為 D = 1 D=1 D=1時的空洞卷積。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL51ERPFTUU1EeRpHW4Z0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL5QTOwQGMlZTM0kDNxUWY5EDZ0QDZlNWZmZmZiNTY2M2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
由于空洞卷積引入了空隙,有一部分特征沒有參與計算,且捕獲具有一定間隔的特征可能是不相關的。Panqu Wang等人在2018年提出混合空洞卷積條件(Hybrid Dilated Convolution, HDC),對網絡中每一層的膨脹率變化規律做出規定。滿足此條件的網絡可以規避特征空隙、同時捕獲遠端和近距離資訊。
對于卷積核大小均為K\times K的N層卷積神經網絡,其每一層膨脹率組成的序列分别為: [ r 1 , … , r i , … , r n ] \left[r_1,\ldots,r_i,\ldots,r_n\right] [r1,…,ri,…,rn],需滿足:
∀ 1 < i < n , M i ≤ K ∀1<i<n,M_i≤K ∀1<i<n,Mi≤K
其中:
M i = m a x [ r i + 1 − 2 r i , 2 r i − r i + 1 , r i ] Mi=max[r_{i+1}-2r_i,2r_i-r_{i+1},r_i] Mi=max[ri+1−2ri,2ri−ri+1,ri]
本實驗的目的是:(1)利用torch.nn實作空洞卷積,其中:膨脹率序列(dilation)滿足HDC條件,在車輛分類資料集上實驗并輸出結果;(2)比較空洞卷積和普通卷積的結果;(3)選取1-2個超參數進行對比分析。
殘差網絡實驗
在深度神經網絡中,如果期望非線性單元 f ( x ; θ ) f\left(\textbf{x};\theta\right) f(x;θ)去逼近目标函數 h ( x ) h(\textbf{x}) h(x);可以考慮将目标函數分解為:
h ( x ) = x + h ( x ) − x h(\textbf{x})=\textbf{x}+h(\textbf{x})-\textbf{x} h(x)=x+h(x)−x
其中, x \textbf{x} x稱為恒等函數(identity function), ( h ( x ) − x ) (h(\textbf{x})-\textbf{x}) (h(x)−x)稱為殘差函數(residue function)。
根據通用近似定理,非線性單元可以在理論上逼近任意函數。是以,原問題可以進行如下轉化:期望 f ( x ) f\left(\textbf{x}\right) f(x)逼近殘差函數 ( h ( x ) − x ) (h(\textbf{x})-\textbf{x}) (h(x)−x),使得 f ( x ) + x f\left(\textbf{x}\right)+\textbf{x} f(x)+x逼近目标函數 h ( x ) h(\textbf{x}) h(x)。
一般地,殘差網絡可以通過跳層連接配接(shortcut connection)實作。過往的實驗表明:殘差網絡可以解決深層神經網絡的梯度爆炸、梯度消失和網絡退化問題。
本實驗的目的是:(1)複現給定的殘差網絡架構,分析結果;(2)結合空洞卷積,對比分析實驗結果。
實驗環境及實驗資料集
資料集采用車輛分類資料集,共1358張車輛圖檔,三個類别。其中:随機抽取70%作訓練集,30%作測試集。
實驗環境為Linux 3.10.0-1062.el7.x86_64;運算器為NVIDIA GeForce RTX 2080;架構為:Pytorch 1.6.0;采用Pycharm内置的SSH連接配接進行互動。
實驗過程
二維卷積實驗
手動實作二維卷積
要實作卷積,需要依次實作:單通道互相關運算、多通道互相關運算、多卷積核互相關運算。需要說明的是,在Open CV讀入的圖檔格式中,通道(channel)是最後一個次元,是以需要對讀取的資料進行通道變換。代碼段分别如下。
1. #單通道互相關運算
2. def corr2d(X,K):
3. batch_size,H,W = X.shape
4. h,w = K.shape[0],K.shape[1]
5. Y = torch.zeros(batch_size,H-h+1,W-w+1).to(device)
6. for i in range(Y.shape[1]):
7. for j in range(Y.shape[2]):
8. area = X[:,i:i+h, j:j+w]
9. Y[:,i,j] = (area* K).sum()
10. return Y
11.
12. #多通道互相關運算
13. def corr2d_multi_in(X, K):
14. res = corr2d(X[:,:,:,0],K[:,:,0])
15. for i in range(0,X.shape[3]):
16. res += corr2d(X[:,:,:,i],K[:,:,i])
17. return res
18.
19. #多卷積核互相關運算
20. def corr2d_multi_in_out(X, K):
21. return torch.stack([corr2d_multi_in(X,k) for k in K],dim=1)
定義了互相關運算、二維池化運算後,我們可以把卷積封裝成卷積層子產品。
1. #定義池化層
2. def pool2d(X, pool_size, mode='max'):
3. p_h, p_w = pool_size,pool_size
4. Y = torch.zeros((X.shape[0],X.shape[1],X.shape[2] - p_h + 1, X.shape[3] - p_w + 1))
5. for i in range(Y.shape[2]):
6. for j in range(Y.shape[3]):
7. if mode == 'max':
8. Y[:,:,i, j] = X[:,:,i: i + p_h, j: j + p_w].max()
9. elif mode == 'avg':
10. Y[:,:,i, j] = X[:,:,i: i + p_h, j: j + p_w].mean()
11. return Y
12.
13. #定義卷積子產品
14. class MyConv2D(nn.Module):
15. def __init__(self,in_channels,out_channels,kernel_size):
16. super(MyConv2D,self).__init__()
17. if isinstance(kernel_size,int):
18. kernel_size = (kernel_size,kernel_size)
19. self.weight = nn.Parameter(torch.randn((out_channels,in_channels)+kernel_size))
20. self.bias = nn.Parameter(torch.randn(out_channels,1,1))
21. def forward(self,x):
22. y = corr2d_multi_in_out(x,self.weight) +self.bias
23. return y
使用torch實作二維卷積
使用Pytorch可以直接定義模型完成訓練。值得注意的是,由于卷積層的參數和輸入圖檔大小會影響全連接配接層的參數設定,每一次調整網絡架構時,全連接配接層的次元都需要重新計算。
此次實驗中,我們把資料集中的圖檔壓縮為200*100的尺寸,根據公式(3)~(5),我們可以得出全連接配接層的次元是15532.
1. class ConvModule(nn.Module):
2. def __init__(self):
3. super(ConvModule,self).__init__()
4. self.conv = nn.Sequential(
5. nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, stride=1, padding=0),
6. nn.BatchNorm2d(32),
7. nn.ReLU(inplace=True)
8. )
9. self.pool = nn.MaxPool2d(2,2)
10. self.fc = nn.Linear(155232, num_classes)
11. def forward(self,X):
12. out = self.conv(X.float())
13. out = self.pool(out)
14. out = out.view(out.size(0), -1) # flatten each instance
15. out = self.fc(out)
16. return out
對不同超參數的實驗結果對比分析
此部分,我們試驗了不同的卷積層層數(在ConvModule中定義)和卷積核大小。
複現AlexNet模型
AlexNet中引入了Dropout、ReLU激活和局部相應歸一化技巧,以提升模型的泛化能力。AlexNet中的局部響應歸一化(Local Response Normalization, LRN)是一種對于同層的部分神經元的歸一化方法。它受到生物學中側抑制現象的啟發,即:活躍神經元對鄰近神經元由平衡和限制作用。數學上,可視作在激活函數的基礎上,再做一次簡單線性回歸。此方法現已被池化層取代,很少應用。此處,我們直接采用[1]中的LRN實作.
AlexNet是第一個現代深度網絡模型,由Krizhevsky等人在2012年參與ImageNet競賽中提出。由于當時GPU的運算性能限制,當時被拆分為兩個子網絡進行分别訓練。它包含5個卷積層、3個彙聚層和3個全連接配接層。
在本實驗中,我們主要參照[1]中的AlexNet類進行實驗。需要注意的是,多層網絡、LRN、ReLU激活可能會帶來大規模的梯度爆炸問題,造成損失為NaN。是以,我們在每一層卷積的末尾額外添加了一個BatchNorm層進行歸一化處理。
與前饋神經網絡的效果進行對比
我們将卷積網絡與實驗2中的網絡(代碼如下)進行對比。
1. layers = collections.OrderedDict([
2. ('L1',nn.Linear(154587,192)),
3. ('A1',nn.Hardswish()),
4. ('drop1', nn.Dropout(p=0.1)),
5. ('L2', nn.Linear(192, 96)),
6. ('A2', nn.LeakyReLU()),
7. ('drop2', nn.Dropout(p=0.1)),
8. ('FC', nn.Linear(96,3))
9. ])
10. AnnNet = nn.Sequential(layers)
空洞卷積實驗
在torch中,定義空洞卷積較為簡便,隻需要修改卷積層的dilation參數即可。
殘差網絡實驗
由于複現的殘差網絡具有一定的子結構相似性,此處,我們進行了子產品化處理。首先,我們定義兩層卷積的殘差網絡:
1. class Residual(nn.Module):
2. def __init__(self, input_channels,num_channels, use_1x1conv=False, strides=1, **kwargs):
3. super(Residual,self).__init__()
4. self.conv1 = nn.Conv2d(input_channels,num_channels, kernel_size=3, padding=1,stride=strides,dilation=2)
5. self.conv2 = nn.Conv2d(num_channels,num_channels, kernel_size=3, padding=1,dilation=5)
6. if use_1x1conv:
7. self.conv3 = nn.Conv2d(num_channels,num_channels, kernel_size=1,stride=strides)
8. else:
9. self.conv3 = None
10. self.bn1 = nn.BatchNorm2d(num_channels)
11. self.bn2 = nn.BatchNorm2d(num_channels)
12.
13. def forward(self, X):
14. Y = self.bn1(self.conv1(X))
15. Y = F.relu(Y)
16. Y = self.bn2(self.conv2(Y))
17. if self.conv3:
18. X = self.conv3(X)
19. return F.relu(Y + X)
20. return F.relu(Y)
之後,我們定義一層上采樣、一層卷積的殘差網絡:
1. class UpsampleResidual(nn.Module):
2. def __init__(self, in_channel,out_channel, kernel_size=3,strides=1, **kwargs):
3. super(UpsampleResidual, self).__init__()
4. self.l1 = nn.Conv2d(in_channels=in_channel, out_channels=out_channel, kernel_size=kernel_size, stride=strides,padding=1)
5. self.l2 = nn.Conv2d(in_channels=out_channel, out_channels=out_channel, kernel_size=kernel_size,stride=1,padding=1)
6. self.shortcut = nn.Conv2d(in_channels=in_channel,out_channels=out_channel,kernel_size=1,stride=strides)
7. def forward(self,X):
8. Y = self.l1(X)
9. Y = F.relu(Y)
10. Y = self.l2(Y)
11. shortcut = self.shortcut(X)
12. return F.relu(Y+ shortcut)
最終,我們将以上兩個子產品進行多次複合,得到需要複現的網絡:
class ResidualModule(nn.Module):
def __init__(self):
super(ResidualModule,self).__init__()
self.conv1 = nn.Conv2d(3,64,3)
self.conv2 = Residual(64,64,True)
self.conv3 = UpsampleResidual(64,128,3)
self.conv4 = Residual(128,128, True)
self.conv5 = UpsampleResidual(128,256,3)
self.conv6 = Residual(256,256, True)
self.conv7 = UpsampleResidual(256,512,3)
self.conv8 = Residual(512,512,True)
self.pool = torch.nn.AvgPool2d(kernel_size=3)
self.fc = nn.Linear(460800, num_classes)
self.process = nn.Sequential(self.conv1,self.conv2,self.conv3,
self.conv4,self.conv5,self.conv6,self.conv7,self.conv8)
def forward(self,X):
out = self.process(X)
out = out.view(out.size(0), -1) # flatten each instance
out = self.fc(out)
return out
實驗結果
二維卷積實驗
手動實作二維卷積
手動定義的卷積沒有進行矩陣優化,時間複雜度為 O ( n 4 ) \mathcal{O}\left(n^4\right) O(n4),對運算時間要求較高。經過手動驗證,反向傳播過程耗費時間過長,無法預計完成模型訓練的時間;是以,這裡不能提供具體的手動實作的卷積的結果圖表,但是可以觀察到loss的計算過程。
使用torch實作二維卷積
實驗疊代次數為50次時,在測試集、訓練集上的loss和準确率變化如上圖所示。可以觀察到loss的震蕩變化和準确率的逐漸上升;但訓練集上的表現始終優于測試集,說明模型存在一定的過拟合現象。在GPU環境下,訓練兩層卷積神經網絡模型訓練50次epoch的時間為7分鐘左右。
對不同超參數的實驗結果對比分析
卷積核大小的影響
可以觀察到:尺寸相對較大的卷積核在訓練時收斂更快、總體震蕩較小,收斂時精度更高、loss更小。尺寸較小的卷積核在訓練過程種震蕩趨勢明顯。總體而言,不同尺寸卷積核的模型在此次訓練時都存在着過拟合的趨勢;可見,是否過拟合和卷積核大小無關。
卷積層數的影響
可以觀察到:随着卷積層數的增加:在訓練階段,訓練集上的震蕩更小;在收斂階段,loss更低,在訓練集上的準确率更高。在測試集上,三種網絡的精确度表現近似,但卷積層數更多的網絡相對收斂更快。
複現AlexNet模型
在AlexNet上疊代50次的結果如上。可以觀察到,随着層數的加深,網絡收斂所需的epoch數明顯增加。
與前饋神經網絡的效果進行對比
實驗結果表明,在本實驗所用的車輛分類資料集上,疊代20次時,前饋神經網絡的效果适中,效果最優的是三層卷積神經網絡。AlexNet在預測準确度的效果不佳,可能是原因是:(1)資料集過小,模型無法充分學習特征;(2)訓練次數不夠,模型沒有收斂。但值得注意的是,AlexNet在測試集和訓練集上的表現都非常接近,說明沒有出現過拟合現象。
空洞卷積實驗
空洞卷積實驗結果
空洞卷積的實驗結果如上。可以觀察到:相比于振蕩現象明顯的普通卷積,空洞卷積模型沒有出現過拟合現象,可能是由于空洞卷積能很好地同時提取遠近距離特征。
空洞卷積與普通卷積的效果對比
從圖像可以看出:空洞卷積的準确度變化更平穩、在測試集上的精度和普通卷積類似;在同樣訓練次數下,空洞卷積模型訓練集上的精度略低于普通卷積。可能原因是:空洞卷積提取了更多的層次特征,需要更多的疊代次數才能收斂。
空洞卷積模型相比于普通卷積在時間上略有增加,每次訓練平均實際在8分鐘左右;但空洞卷積在不同超參數設定下,運作時間變化較大。經過實驗驗證,可以發現主要耗時的子產品在于反向傳播部分。
超參數的對比
卷積核大小的影響
實驗結果表明:在空洞卷積模型訓練前期,卷積核大小分别設定為3、3、2時在測試集上的表現較優、且變化趨勢較穩定;在訓練後期,模型逐漸收斂,卷積核大小對實驗結果的影響減少。可能原因是:卷積核的尺寸增加等價于模型參數量增加,是以在訓練前期可能會有更好的表達力。在空洞卷積中,尺寸大的卷積核并沒有表現出普通卷積中出現的巨大性能提升。
卷積層數的影響
可以觀察到:在收斂階段,深層的網絡在訓練集上準确度和loss上表現更好,但在測試集上沒有突出的優勢。
殘差網絡實驗
殘差網絡實驗結果
觀察實驗結果可知:殘差網絡在訓練集上疊代20次即可達到接近99%的準确率;此外,模型收斂速度非常快。可見,轉化後的函數逼近問題更容易學習。
殘差空洞卷積實驗結果
實驗結果顯示,在訓練集上,空洞殘差卷積具有更高的精度;在測試集上,空洞殘差卷積和普通殘差卷積表現相似,兩者均具有較高的精度。
實驗心得體會
本次實驗遇到的最大的問題是計算效率的問題;此外,在實驗中,還有一些細節可以優化::
- 進入卷積神經網絡部分後,定義的網絡在CPU上的訓練時間已遠遠超出預期;是以,模型遷移到顯存、使用GPU進行訓練是必須的。在擷取伺服器使用權限後、進行模型訓練後,我直覺地體會到GPU帶來的效率提升。
- 手動實作的卷積時間複雜度太高,目前仍然無法使用。本實驗主要使用torch的實作,沒有采用手動卷積。如果後續有可能對此部分進行優化,需要對矩陣乘法部分進行Image to Column的并行化處理。
- 資料集的生成。實驗中采用的是OpenCV架構進行讀取,但其實OpenCV的通道和RGB顔色的定義和torch中有較大的差別。是以,後續可以考慮使用其它的圖檔讀取架構。
- 超參數的導入。實驗中每一層的超參數(卷積層數、卷積核通道數等)是固定的,但這不友善對超參數進行網格搜尋。後續可以針對此問題,設定層數的傳參。
參考文獻
[1] Pytorch手撕經典網絡之AlexNet. https://zhuanlan.zhihu.com/p/29786939
[2] 邱錫鵬,神經網絡與深度學習,機械工業出版社,https://nndl.github.io/ , 2020.
[3] Krizhevsky A, Sutskever I, Hinton G E. Wang P, Chen P, Yuan Y, et al. Understanding convolution for semantic segmentation[C]//2018 IEEE Winter Conference on Applications of Computer Vision (WACV). IEEE, 2018: 1451-1460.
[4] Zhang, Aston and Lipton, Zachary C. and Li, Mu and Smola, Alexander J. Dive into Deep Learning