卷積神經網路之手撕程式碼

1、計算卷積神經網路的輸出尺寸

\[n = \dfrac{W-F+2P}{S}+1
\]

其中:\(N\) 代表輸出尺寸,\(W\) 代表輸入尺寸,\(F\) 代表卷積核大小,\(P\) 代表填充尺寸,\(S\) 代表步長

2、網路參數量的計算

對於CNN而言,每個卷積層的參數量如下:

\[params = C_{o} \times(C_{i}\times k_{w} \times k_{h}+1)
\]

其中, \(C_{i}\) 代表輸入特徵的通道數,也即是每個卷積核的通道數,\(k_{w}, k_{h}\) 分別代表卷積核的寬和高,\(C_{o}\) 代表輸出特徵的通道數,也即是卷積核的個數。\(+1\) 代表偏置項。

對於全連接層而言,每個全連接層的參數量如下:

\[parames = I\times O+O
\]

其中,\(I\) 代表輸入神經元的個數,\(O\) 代表輸出神經元的個數,\(+O\) 代表偏置項

3、網路運算量(Flops)

FLOPs 是英文 floating point operations 的縮寫,表示浮點運算量,中括弧內的值表示卷積操作計算出 feature map 中一個點所需要的運算量(乘法和加法)。

對於CNN而言,每個卷積層的運算量如下:

\[FLOPs = [(C_{i} \times k_{w} \times k_{h}) + (C_{i} \times k_{w} \times k_{h} -1)+1]\times C_{o} \times W \times H
\]

其中,\(C_{i}\) 表示輸入特徵圖的通道,\(k_{w},k_{h}\) 分別代表卷積核的寬和高,\(C_{o}\) 代表輸出特徵的通道數,\(H,W\) 分別代表輸出特徵向量的寬和高。由於輸出特徵的每個像素都是由卷積核元素與輸入特徵向量對應位置一一相乘然後再相加得到的,因此乘法計算量為:\((C_{i} \times k_{w} \times k_{h})\times C_{o} \times W \times H\),加法計算量為:\((C_{i} \times k_{w} \times k_{h} -1)\times C_{o} \times W \times H\),因為如果乘了9次,只做了8次加法運算,偏置的計算量為:\(C_{o} \times W \times H\),所以總的 FLOPs 如上所示。

對於全連接網路而言,每個全連接層的FLOPs如下:

\[FLOPs = [I + (I-1) + 1] \times O
\]

其中,\(I\) 代表每個輸出神經元的乘法運算量,\(I-1\) 代表每個輸出神經元的加法運算量,\(+1\) 代表偏置。

4、CNN中感受野的計算

在卷積神經網路中, 感受野(Receptive Field)是指特徵圖上的某個點能看到的輸入影像的區域,即特徵圖上的點是由輸入影像中感受野大小區域的計算得到的,計算如下:

\[RF_{i+1} = RF_{i}+(k-1) \times S_{i}
\]

其中,\(RF_{i+1}\)\(RF_{i}\) 分別表示第 \(i+1\) 和第 \(i\) 層的感受野大小,\(k\) 表示卷積核的大小,\(S_{i}\) 表示之前所有層的步長的乘積(不包括本層)。

5、CNN中上取樣的方法

雙三次插值:其根據離待插值最近的4*4=16個已知值來計算待插值,每個已知值的權重由距離待插值距離決定,距離越近權重越大。


k = nn.Upsample(scale_factor=2, mode='bicubic', align_corners=True)	# 創建雙三次插值實例
Output = k(Input)

反卷積:該方式將引入許多『0』的行和『0』的列,導致實現上非常的低效。並且,反卷積只能恢復尺寸,並不能恢複數值,因此經常用在神經網路中作為提供恢復的尺寸,具體的數值往往通過訓練得到。

Input = torch.arange(1,10,dtype=torch.float32).view(1,1,3,3)
Transposed = nn.ConvTranspose2d(1,1,3,stride=2, padding = 1)
Output = Transposed(Input)

亞像素上取樣:假設原始輸入為 \([C,H,W]\),需要變成 \([C,sH,sW]\) 的大小,那麼亞像素上取樣分兩步走:

  1. \([C,H,W]\) ==> \([s^{2}C, H, W]\),通過卷積實現通道的擴充
  2. \([s^{2}C, H, W] ==> [C,sH,sW]\),通過 PixelShuffle 的方式實現長寬的增加
class Net(nn.Module):
    def __init__(self, upscale_factor):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 64, (5, 5), (1, 1), (2, 2))
        self.conv2 = nn.Conv2d(64, 32, (3, 3), (1, 1), (1, 1))
        self.conv3 = nn.Conv2d(32, 1 * (upscale_factor ** 2), (3, 3), (1, 1), (1, 1))	# 最終將輸入轉換成 [32, 9, H, W]
        self.pixel_shuffle = nn.PixelShuffle(upscale_factor)	# 通過 Pixel Shuffle 來將 [32, 9, H, W] 重組為 [32, 1, 3H, 3W]
    def forward(self, x):
        x = torch.tanh(self.conv1(x))
        x = torch.tanh(self.conv2(x))
        x = torch.sigmoid(self.pixel_shuffle(self.conv3(x)))
        return x

6、Softmax函數

Softmax 函數:將激活值與所有神經元的輸出值聯繫在一起,所有神經元的激活值加起來為1。第L層(最後一層)的第j個神經元的激活輸出為:

\[a_{j}^{L}=\frac{e^{Z_{j}^{L}}}{\sum_{k} e^{Z_{t}^{L}}}
\]

def softmax(x):
    shift_x = x - np.max(x)#防止輸入增大時輸出為nan
    exp_x = np.exp(shift_x)
    return exp_x / np.sum(exp_x)

7、手寫一個卷積神經網路的訓練模板

import torch
import torch.nn as nn
class Network(nn.Module):
    def __init__(self):
        super().__init__()     
        self.head = nn.Senquential(
		nn.Conv2d(1,20,5),
		nn.ReLU(),
		nn.Conv2d(20,64,5),
		nn.ReLU())
        self.tail = nn.Senquential(
		nn.Conv2d(64,20,5),
		nn.ReLU(),
		nn.Conv2d(20,3,5),
		nn.ReLU())   
    def forward(self, x):
        x = self.head(x)
        x = self.tail(x)
        return x

import torch.optimal
optimal = torch.optimal.Adam()
MSE = nn.MSELoss()

model = Network()
Input = torch.floatTensor(1,3,32,32)
for i in range(epochs):
    out = model(Input)
    loss = MSE(out, label)
    loss.backward()
    loss.step()