深度學習與Pytorch入門實戰(九)卷積神經網絡&Batch Norm


筆記摘抄

1. 卷積層

1.1 torch.nn.Conv2d() 類式接口

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

參數:

  • in_channel:輸入數據的通道數,例RGB圖片通道數為3;

  • out_channel:輸出數據的通道數,也就是kernel數量;

  • kernel_size: 卷積核大小,可以是int,或tuple;kernel_size=2,意味着卷積大小(2,2),kernel_size=(2,3),意味着卷積大小(2,3)即非正方形卷積

  • stride:步長,默認為1,與kernel_size類似,stride=2,意味着步長上下左右掃描皆為2, stride=(2,3),左右掃描步長為2,上下為3;

  • padding:零填充

import  torch
import  torch.nn as nn

# batch, channel, height, width
x = torch.rand(1,1,28,28)                                  
# in_channel(和上面x中的channel數量一致), out_channel(kernel個數)
layer = nn.Conv2d(1,3,kernel_size=3,stride=2,padding=1)    

# 或者直接out=layer(x)
out = layer.forward(x)           
# torch.Size([1, 3, 14, 14]) 1指一張圖片,3指三個kernel,14*14指圖片大小
print(out.shape)                 
  • 第五行layer的相關屬性:

    • layer.weight.shape = torch.Size([3,1,3,3]): 第一個3指out_channel,即輸出數據的通道數(kernel個數);第二個1指in_channel,即輸入數據的通道數,這個值必須 和 x的channel數目一致。(Tip:前兩個參數順序是和Conv2d函數的前兩個參數順序相反的)

    • layer.bias.shape = torch.Size([3]): 這個3指kernel個數,不同的通道共用一個偏置。

1.2 F.conv2d() 函數式接口

  • PyTorch里一般小寫的都是 函數式的接口,相應的大寫的是類式接口

  • 函數式的更加 low-level 一些,如果不需要做特別復雜的配置 只要用 類式接口即可。

import torch
from torch.nn import functional as F

#手動定義卷積核(weight)和偏置
w = torch.rand(16, 3, 5, 5)                       # 16種3通道的5*5卷積核
b = torch.rand(16)

#定義輸入樣本
x = torch.randn(1, 3, 28, 28)                     # 1張3通道的28*28的圖像

#2D卷積得到輸出
# 1張圖片,16個輸出(16個filter),f = (28+2-5+1)
out = F.conv2d(x, w, b, stride=1, padding=1)
print(out.shape)                                  # torch.Size([1, 16, 26, 26])

out = F.conv2d(x, w, b, stride=2, padding=2)
print(out.shape)                                  # torch.Size([1, 16, 14, 14])

2. 池化層Pooling(下采樣)

torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, 
                   dilation=1, return_indices=False, ceil_mode=False)

參數:

  • kernel_size(int or tuple) - max pooling的窗口大小,

  • stride(int or tuple, optional) - max pooling的窗口移動的步長。默認值是kernel_size

  • padding(int or tuple, optional) - 輸入的每一條邊補充0的層數

  • dilation(int or tuple, optional) – 一個控制窗口中元素步幅的參數

  • return_indices - 如果等於True,會返回輸出最大值的序號,對於上采樣操作會有幫助

  • ceil_mode - 如果等於True,計算輸出信號大小的時候,會使用向上取整,代替默認的向下取整的操作

接着上面卷積運算得到的1張16通道的14*14的圖像,在此基礎上做池化。

2.1 torch.MaxPool2d() 類式接口

# Maxpool
x = out                              # torch.Size([1, 16, 14, 14])

layer = nn.MaxPool2d(2,stride=2)     # 池化層(池化核為2*2,步長為2),最大池化

out = layer(x)           

print(out.shape)                     # torch.Size([1, 16, 7, 7])

2.2 F.avg_pool2d() 函數式接口

x = out                                  # torch.Size([1, 16, 14, 14])

out = F.avg_pool2d(x, 2, stride=2)

print(out.shape)                         # torch.Size([1, 16, 7, 7])

Tip:池化后通道數不變。

3. upsample(上采樣)

F.interpolate(input, size=None, scale_factor=None, 
              mode='nearest', align_corners=None)

參數:

  • size(int):輸出的spatial大小;

  • scale_factor(float): spatial尺寸的縮放因子;

  • mode(string):上采樣算法:nearest,linear,bilinear,trilinear,area,默認nearest;

  • align_corners(bool, optional):如果align_corners=True,則對齊input和output的角點像素,只會對 mode=linear, bilinear 和 trilinear 有作用。默認是 False。

x = out                                                # torch.Size([1, 16, 7, 7])

out = F.interpolate(x, scale_factor=2, mode='nearest') # 向上采樣,放大2倍,最近插值

print(out.shape)                                       # torch.Size([1, 16, 14, 14])

4. RELU激活函數

4.1 torch.nn.RELU() 類式接口

x = out                                 # torch.Size([1, 16, 14, 14])

layer = nn.ReLU(inplace=True)          # ReLU激活,inplace=True表示直接覆蓋掉ReLU目標的內存空間
out = layer(x)
print(out.shape)                       # torch.Size([1, 16, 14, 14])

4.2 F.relu() 函數式接口

x = out                   # torch.Size([1, 16, 14, 14])

out = F.relu(x, inplace=True)
print(x.shape)            # torch.Size([1, 16, 14, 14])

5. Batch Norm

  • 歸一化:使代價函數平均起來看更對稱,使用梯度下降法更方便

  • 通常分為兩步:調整均值、方差歸一化

  • Batch Norm詳情

5.1 Batch Norm

  • 一個Batch的圖像數據shape為[樣本數N, 通道數C, 高度H, 寬度W]

  • 將其最后兩個維度flatten,得到的是[N, C, H*W]

  • 標准的Batch Normalization:

    • 通道channel這個維度上進行移動,對 所有樣本 的所有值求均值和方差

    • 有幾個通道,得到的就是幾個均值和方差

  • eg. [6, 3, 784]會生成[3],代表當前batch中每一個channel的特征均值,3個channel有3個均值和3個方差,只保留了channel維度,所以是[3]。

5.2 Layer Norm

  • 樣本N的維度上滑動,對每個樣本的 所有通道 的 所有值 求均值和方差

  • 一個Batch有幾個樣本實例,得到的就是幾個均值和方差。

  • eg. [6, 3, 784]會生成[6]

5.3 Instance Norm

  • 樣本N和通道C兩個維度上滑動,對Batch中的N個樣本里的每個樣本n,和C個通道里的每個樣本c,其組合[n, c]求對應的所有值的均值和方差,所以得到的是N*C個均值和方差。

5.4 Batch Norm詳解

  • 輸入數據:6張3通道784個像素點的數據,將其分到三個通道上,在每個通道上也就是[6, 784]的數據

  • 然后分別得到和通道數一樣多的統計數據 均值\(\mu\)方差\(\sigma\)

  • 將每個像素值減去 \(\mu\) 除以 \(\sigma\) 也就變換到了接近 \(N(0,1)\) 的分布

  • 后面又使用參數 \(\beta\)\(\gamma\) 將其變換到接近 \(N(β,γ)\) 的分布。

Tip:

  • \(\mu\)\(\sigma\) 只是樣本中的統計數據,是沒有梯度信息的,不過會保存在運行時參數里。

  • \(\gamma\)\(\beta\) 屬於要訓練的參數,他們是有梯度信息的。

正式計算:

nn.BatchNorm1d()

import torch
from torch import nn

x = torch.rand(100, 16, 784)     # 100張16通道784像素點的數據,均勻分布

layer = nn.BatchNorm1d(16)       # 傳入通道數,因為H和W已經flatten過了,所以用1d
out = layer(x)

print(layer.running_mean.shape ,layer.running_mean)
#torch.Size([16]) tensor([0.0499, 0.0501, 0.0501, 0.0501, 0.0501, 0.0502, 0.0500, 0.0499, 0.0499,
#        0.0501, 0.0500, 0.0500, 0.0500, 0.0501, 0.0500, 0.0500])
print(layer.running_var.shape,layer.running_var)
#tensor([0.9083, 0.9083, 0.9083, 0.9084, 0.9083, 0.9083, 0.9084, 0.9083, 0.9083,
#        0.9083, 0.9083, 0.9083, 0.9084, 0.9084, 0.9083, 0.9083])

Tip:

  • layer.running_mean和layer.running_var得到的是 全局 的均值和方差

  • 不是當前Batch上的,只不過這里只跑了一個Batch而已所以它就是這個Batch上的。

nn.BatchNorm2d()

import torch
from torch import nn

x = torch.rand(1, 16, 7, 7)     # 1張16通道的7*7的圖像

layer = nn.BatchNorm2d(16)      # 傳入通道數(必須和上面的通道數目一致)
out = layer(x)

print(out.shape)                # torch.Size([1, 16, 7, 7])
print(layer.running_mean)       # running-μ
print(layer.running_var)        # running-σ^2

print(layer.weight.shape)       # torch.Size([16]),對應上面的γ
print(layer.bias.shape)         # torch.Size([16]),對應上面的β
print(vars(layer))              # 查看網絡中一個層上的所有參數
# {'training': True,
#   '_parameters':
#       OrderedDict([('weight', Parameter containing:
#                               tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.], requires_grad=True)),
#                   ('bias', Parameter containing:
#                               tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], requires_grad=True))]),
#   '_buffers':
#       OrderedDict([('running_mean', tensor([0.0527, 0.0616, 0.0513, 0.0488, 0.0484, 0.0510, 0.0590, 0.0459, 0.0448, 0.0586, 0.0535, 0.0464, 0.0581, 0.0481, 0.0420, 0.0549])),
#                   ('running_var', tensor([0.9089, 0.9075, 0.9082, 0.9079, 0.9096, 0.9098, 0.9079, 0.9086, 0.9081, 0.9075, 0.9052, 0.9081, 0.9093, 0.9075, 0.9086, 0.9073])),
#                   ('num_batches_tracked', tensor(1))]),
# '_backward_hooks': OrderedDict(),
# '_forward_hooks': OrderedDict(),
# '_forward_pre_hooks': OrderedDict(),
# '_state_dict_hooks': OrderedDict(),
# '_load_state_dict_pre_hooks': OrderedDict(),
# '_modules': OrderedDict(),
# 'num_features': 16,
# 'eps': 1e-05,
# 'momentum': 0.1,
# 'affine': True,
# 'track_running_stats': True}

Tip:

  • layer.weight 和 layer.bias是當前batch上的;

  • 如果在定義層時使用了參數affine=False,那么就是固定 \(\gamma=1\)\(\beta=0\) 不自動學習,這時參數layer.weightlayer.bias將是None。

5.5 Train和Test

  • 類似於Dropout,Batch Normalization在訓練和測試時的行為不同。

  • 測試模式下,\(\mu\)\(\sigma_2\) 使用訓練集得到的 全局\(\mu\)\(\sigma_2\)

    • 歸一化前調用layer.eval()設置Test模式。

5.6 使用Batch Norm好處

  • 收斂更快(converge faster)

  • 表現的更好(Better performance)

  • 更穩定

    • Stable

    • larger learning rate(超參數沒有那么敏感)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM