卷积神经网络之手撕代码

2021-09-26
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()