神经网络组件

  下面介绍的网络组件都是 torch.nn.Module 的派生类,所以它们有一些共同的成员函数:

  1. named_parameters:返回该网络层参数的迭代器。通过这个迭代器可以输出该层的参数名(如 weight 和 bias)和参数值。
  2. parameters: 返回该网络层参数的迭代器。通过这个迭代器可以输出该层的参数值。
  3. state_dict: 返回该网络层状态字典的迭代器。状态字典包含网络参数和持久化缓存(persistent buffers),比如 BN 层的状态字典不仅包含参数 weight 和 bias,还包含持久化缓存 running_mean、running_var 和 num_batches_tracked。

1. 卷积层


立体卷积核

  卷积层的输入必须是 4 维特征,参数有 weight、bias,即权重、偏置。使用 PyTorch 定义卷积层:

torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros')

1. kernel_size: int 或 tuple 类型,指定卷积核的宽高。
2. int 或 tuple 类型,指定卷积核的在横竖方向上的步长。
3. dilation: int 或 tuple 类型,指定卷积核元素间的距离。默认为 1。用于空洞卷积。
4. groups: int 类型。用于可分离卷积。输出通道必须是该参数的整数倍。
5. padding: int、tuple 或 str 类型,在输入特征四周填充的行数,默认填充 0 行。
 5.1 int x: 在上下左右都填充 x 行。
 5.2 tuple (x, y): 上下填充 x 行,左右填充 y 行。
 5.3 str valid 或 same: 低版本不支持。不填充或填充到使输入输出宽高相同,这两个参数只有在 stride = 1 时有效。
6. padding_mode: str 类型,可以是 zeros、reflect、replicate 或 circular。默认是 zeros。
 6.1 zeros: 填充 0。
 6.2 reflect: 以边界为对称轴,用边界里面的像素值填充。
 6.3 replicate: 用边界像素值填充。
 6.4 circular: 把下边界里面的像素值填充到上面,其它三边填充类似。

  卷积核的尺寸:分类网络一般使用边长为 5、7 的大卷积核,用于降低特征间的冗余性;重建任务一般使用边长为 3 的小卷积核,用于保留更多的局部特征。卷积核的边长 n 一般为单数,这样的情况下,输入特征四周各有 (n-1)/2 行填充,卷积步长为 1 就可以保证输入输出特征尺寸相等。

1.1 常规卷积


  卷积核的通道等于输入特征的通道 in_channels,宽高等于 kernel_size,个数等于输出特征的通道个数 out_channels。
  

常规卷积

1.2 空洞卷积


  感受野内像素不连续的卷积。一个感受野内相邻像素间的距离(单位是像素个数)称为空洞率,常规卷积是空洞率为 1 的空洞卷积。

空洞卷积
  下面我们根据 DeepLab v2 中的 ASPP 模块详细探讨空洞卷积及其用法。

class ASPP(torch.nn.Module):
    def __init__(self, in_channel, out_channel, kernel_size=3, dilations=None):
        super(ASPP, self).__init__()
        if dilations is None:
            dilations = [6, 12, 18]
        # 1.确定空洞卷积的填充。
        paddings = list()
        for dilation in dilations:
            equivalent_kernel_size = kernel_size + (kernel_size - 1) * (dilation - 1)
            paddings.append((equivalent_kernel_size - 1) // 2)
        # 2.创建三个尺度的空洞卷积。
        self.atrous_conv0 = torch.nn.Conv2d(in_channel, out_channel, kernel_size, 1, paddings[0], dilation=dilations[0])
        self.atrous_conv1 = torch.nn.Conv2d(in_channel, out_channel, kernel_size, 1, paddings[1], dilation=dilations[1])
        self.atrous_conv2 = torch.nn.Conv2d(in_channel, out_channel, kernel_size, 1, paddings[2], dilation=dilations[2])
   
    def forward(self, input):
        out0 = self.atrous_conv0(input)
        out1 = self.atrous_conv1(input)
        out2 = self.atrous_conv2(input)
        return torch.cat((out0, out1, out2), dim=1)

  上面的程序只给出了 ASPP 的主要部分,实际使用 ASPP 时,还需要一些卷积、BN 等层。
  我们在第 12、13、14 行创建了 3 个空洞卷积,并且设置卷积核边长为 3。假如用 kernel_size 代表这里设置的卷积核边长,用 dilation 代表空洞率,则空洞卷积的等效卷积核(或称为实际卷积核)边长 e q u i v a l e n t _ k e r n e l _ s i z e = k e r n e l _ s i z e + ( k e r n e l _ s i z e − 1 ) × ( d i l a t i o n − 1 ) equivalent\_kernel\_size = kernel\_size + (kernel\_size - 1) \times (dilation - 1) equivalent_kernel_size=kernel_size+(kernel_size1)×(dilation1)
  步长为 1 就是希望卷积的输入输出尺寸相同,所以 4 个边的填充数 p a d d i n g = ( e q u i v a l e n t _ k e r n e l _ s i z e − 1 ) / / 2 padding = (equivalent\_kernel\_size - 1) // 2 padding=(equivalent_kernel_size1)//2

1.3 可分离卷积


  无论是普通卷积还是空洞卷积,卷积核都是三维的。可分离卷积的通道为 1。

可分离卷积

1.4 卷积计算量


  假设输入特征的维度是 h i n × w i n × c i n h_{in}\times w_{in} \times c_{in} hin×win×cin,卷积核的维度是 h k × w k × c i n h_{k}\times w_{k} \times c_{in} hk×wk×cin。假如步长为 1,则卷积核的中心像素点会依次与尺寸为 h i n × w i n h_{in}\times w_{in} hin×win 的输入特征截面上的每一个像素点重合。所以卷积次数是 h i n × w i n h_{in}\times w_{in} hin×win
  每次卷积的计算包括卷积核的每个参数与输入特征对应相乘,然后求和,所以计算量是 h k × w k × c i n + 1 h_{k}\times w_{k} \times c_{in} + 1 hk×wk×cin+1。于是一个卷积核卷积输入特征的计算量是 h i n × w i n × ( h k × w k × c i n + 1 ) h_{in}\times w_{in} \times (h_{k}\times w_{k} \times c_{in} + 1) hin×win×(hk×wk×cin+1)。如果忽略求和,一个卷积核卷积输入特征的计算量是 h i n × w i n × ( h k × w k × c i n ) h_{in}\times w_{in} \times (h_{k}\times w_{k} \times c_{in}) hin×win×(hk×wk×cin),即 输 入 特 征 的 截 面 积 × 卷 积 核 的 截 面 积 × 输 入 特 征 通 道 数 输入特征的截面积\times 卷积核的截面积\times 输入特征通道数 ××
  假如输出特征的通道数是 c o u t c_{out} cout,即需要 c o u t c_{out} cout 个卷积核。于是考虑输入输出特征时,卷积计算量是 输 入 特 征 的 截 面 积 × 卷 积 核 的 截 面 积 × 输 入 特 征 通 道 数 × 输 出 特 征 通 道 数 输入特征的截面积\times 卷积核的截面积\times 输入特征通道数\times 输出特征通道数 ×××

2. 池化层


1. 自适应池化

data = torch.Tensor([[[11, 12, 14], [21, 22, 24], [31, 32, 34]]])
adapt_max_pool = torch.nn.AdaptiveMaxPool2d(output_size=4)
adapt_avg_pool = torch.nn.AdaptiveAvgPool2d(output_size=4)
y1 = adapt_max_pool(data)
y2 = adapt_avg_pool(data)

3. 激活函数层


  激活层没有参数。激活函数必须是非线性的,值域和单调性无要求。

  有界的激活函数会导致梯度消失。当特征的值在大概 [-5, 5] 的区间时,Sigmoid 和 tanh 函数的值才有明显变化。所以当特征或权重过大时,如果没有 BN 层,这两个函数很容易饱和,从而难以提取到有效特征。
  Sigmoid 和 tanh 函数的导数值大多数小于 1,如果连续的网络层都用这些激活函数,会导致梯度连续下降,从而引起梯度消失。

1. ReLU

ReLU

  梯度值恒为 1,不会造成梯度爆炸。在自变量小于 0 时把结果置 0,有利于网络的稀疏化。计算简便。对于 SGD 优化算法的收敛有巨大的加速作用。
  ReLU有很多变种,如ReLU6、Leaky ReLU、ELU。

2. Sigmoid

sigmoid

  梯度值域小,容易梯度消失。

3. tanh

tanh

  tanh:结果满足零均值但计算复杂。

4. Leak ReLU

Leak ReLU

  Faster R-CNN 使用 Leak ReLU 激活函数。

5. Mish

   YOLO4 中使用了 Mish 激活函数,它的公式是 M i s h = x ⋅ t a n h ( l n ( 1 + e x ) ) Mish = x \cdot tanh(ln(1+e^x)) Mish=xtanh(ln(1+ex))

Mish

  Mish 激活函数是对 ReLU 的改进,它的特点:

  1. 有下界无上界:最小值约为 0.31,最大值为无穷大。无上界,梯度不会饱和;有下界,强正则化效果。
  2. 不单调:在任何区间都不单调。和 ReLU 相比,即使输入为负,也能产生较小的负梯度值。
  3. 无穷阶连续和光滑。
  4. 计算量大。
  5. 自门控。

6. hard-Swish

  hard-Swish 激活函数在 MobileNet v3 中提出,它的公式是 h − s w i s h = x ⋅ R e L U 6 ( x + 3 ) ÷ 6 h-swish = x \cdot ReLU6(x+3) \div 6 hswish=xReLU6(x+3)÷6

hard-Swish

4. BN层


  BN 层的输入必须是 4 维 data = [b, c, h, w]。创建 BN 层时至少给出 num_features 参数且这个参数必须是 data.size(1)。

data = torch.Tensor(np.random.randint(0, 10, size=(1, 2, 3, 4)))
bn = torch.nn.BatchNorm2d(num_features=2, eps=1e-5, momentum=0.1, affine=True, track_running_stats=True)
output = bn(data)  # output.shape = data.shape.

  BN层的参数有 weight 和 bias,还有持久化缓存:

1. running_mean 和 running_var

  因为训练数据较多,直接求训练数据的均值、方差计算量过大,所以只能在运行过程中求均值、方差,所以叫做 running_mean、running_var。这两个参数根据一批数据(mini-batch)更新,作为整个训练集的均值、方差。当使用 net.eval() 模式时,BN 层会用这两个参数把测试数据转换成这样的均值和方差的正态分布。
  running_mean 和 running_var 是需要学习的参数,它们的初始值是 0 和 1,每次输入数据经过该 BN 层它们都会更新。假设 n、mean、var 分别是输入数据的元素个数、均值、方差,BN层参数 momentum 使用默认值 0.1。更新方法是:
{ r u n n i n g _ m e a n = r u n n i n g _ m e a n × ( 1 − m o m e n t u m ) + m e a n × m o m e n t u m r u n n i n g _ v a r = r u n n i n g _ v a r × ( 1 − m o m e n t u m ) + v a r × m o m e n t u m × n n − 1 \left\{ \begin{array}{lr} running\_mean = running\_mean \times (1 - momentum) + mean \times momentum\\ running\_var = running\_var \times (1 - momentum) + var \times momentum \times \frac{n}{n-1} \end{array} \right. {running_mean=running_mean×(1momentum)+mean×momentumrunning_var=running_var×(1momentum)+var×momentum×n1n

2. num_batches_tracked

  mini-batch 数量,也就是训练过程中网络进行了多少轮的输入输出。当 BN 层参数 momentum 为 None 时使该参数计算累计滑动平均值(cumulative moving average),否则计算指数滑动平均值(exponential moving average)。

if self.momentum is None:  # use cumulative moving average
    exponential_average_factor = 1.0 / self.num_batches_tracked.item()
else:  # use exponential moving average
    exponential_average_factor = self.momentum

5. Dropout层


  Dropout 以概率 p 把输入数据置 0,然后把所有数据乘 1 1 − p \frac{1}{1 - p} 1p1。如果 inplace = True,数据的操作是 in-place 类型,也就是在输入数据所在内存操作。

data = torch.Tensor(np.random.randint(1, 2, size=(3, 4)))
dropout = torch.nn.Dropout(p=0.5, inplace=False)
output = dropout(data)

  Dropout 可以处理 1 维数据,当处理多维数据时,它把每个 2 维平面上的每个元素随机置 0。Dropout2d 处理的数据维度至少是 2 维,它把每个平面上每一行随机置 0。

6. 全连接层


  全连接层的输入参数有 in_features、out_features、bias,分别指定每个输入样本的大小、每个输出样本的大小、是否使用偏置项。
  全连接层包含的参数有 weight、bias(可选)。全连接层包含的参数个数只与每个样本的输入大小和输出大小有关,与样本数量无关。

data = torch.Tensor(np.random.randint(0, 10, size=(3, 4)))
full_connect = torch.nn.Linear(in_features=4, out_features=5, bias=False)
output = full_connect(data)  # output.shape = [3, 5].
parameter_count = sum(x.numel() for x in full_connect.parameters())  # (4 + 1) * 5.

  data 是输入,output 是输出。输入包含 3 个样本,输出也会有 3 个样本。

7. 参考


  1. Mish 激活函数,CSDN
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值