0603-常用的神經網路層

二十三歲的有德發表於2021-04-25

0603-常用的神經網路層

pytorch完整教程目錄:https://www.cnblogs.com/nickchen121/p/14662511.html

一、影像相關層

影像相關層主要包括卷積層(Conv)、池化層(Pool)等

  • 這些層在實際的使用中也分為一維(1D)、二維(2D)和三維(3D)
  • 池化方式分為平均池化(AvgPool)、最大值池化(MaxPool)、自適應池化(AdaptiveAvgPool)
  • 卷積層除了常用的前向卷積,還有逆卷積(TransposeConv)
import torch as t
from torch import nn
from torch.autograd import Variable as V
from PIL import Image
from torchvision.transforms import ToTensor, ToPILImage
to_tensor = ToTensor()  # 把 img 轉為 Tensor
to_pil = ToPILImage()
nick = Image.open('img/0802程式輸出nick圖.jpeg')  # 只能在這裡犧牲我帥氣的面貌
nick

png

上圖是 nick 原圖

# 銳化卷積核後的卷積,可以看上一篇文章分享的  06-01 DeepLearning-影像識別 那篇文章裡講到的特徵提取

# 輸入是一個 batch,batch_size=1
inp = to_tensor(nick).unsqueeze(0)

# 卷積核的構造

kernel = t.ones(3, 3, 3) / -9  # 銳化卷積核,即刻畫人物邊緣特徵,去除掉背景
kernel[:, 1, 1] = 1

conv = nn.Conv2d(
    in_channels=3,  # 輸入通道數
    out_channels=1,  # 輸出通道數,等同於卷積核個數
    kernel_size=3,  # 核大小
    stride=1,
    bias=False)
conv.weight.data = kernel.view(1, 3, 3, 3)
"""
conv.weight.size() 輸出為 torch.Size([1, 3, 3, 3])
第 1 個 1 代表著卷積核的個數,
第 2 個 3 表示的輸入通道數,
後面兩個 3 是二維卷積核的尺寸
"""

out = conv(V(inp))
to_pil(out.data.squeeze(0))

png

# 普通卷積

# 輸入是一個 batch,batch_size=1
inp = to_tensor(nick).unsqueeze(0)

# 卷積核的構造
conv = nn.Conv2d(
    in_channels=3,  # 輸入通道數
    out_channels=1,  # 輸出通道數,等同於卷積核個數
    kernel_size=3,  # 核大小
    stride=1,
    bias=False)

out = conv(V(inp))
to_pil(out.data.squeeze(0))

png

上圖是卷積後處理的 nick圖,可以發現已經進行了一定程度的特徵提取,並且通過對卷積核進行特定的處理,對人物邊緣進行了一定的刻畫,並且去除了背景

pool = nn.AvgPool2d(2, 2)
list(pool.parameters())
[]
out = pool(V(inp))
print(inp.size(), out.size())
to_pil(out.data.squeeze(0))
torch.Size([1, 3, 318, 320]) torch.Size([1, 3, 159, 160])

png

上圖是池化後的 nick 圖,由於我們選擇 2 個畫素池化為 1 個,可以發現池化後的影像大小正好是原圖的 0.5 倍

除了卷積核池化,深度學習還經常用到以下幾個層:

  • Linear:全連線層
  • BatchNorm:批規範化層,分為 1D、2D 和 3D。除了標準的 BatchNorm 外,還有在風格遷移中常用到的 InstanceNorm 層
  • Dropout:dropout 層,用來防止過擬合,同樣分為 1D、2D 和 3D

下面講解它們的用法。

# 輸入 batch_size=2,維度為 3

inp = V(t.randn(2, 3))
linear = nn.Linear(3, 4)
h = linear(inp)  # (2,3)*(3,4)=(2,4)
h
tensor([[-0.7140, -0.0469,  1.1187,  2.0739],
        [-1.0575, -1.8866, -1.0009, -0.2695]], grad_fn=<AddmmBackward>)
# 4通道,初始化標準差為 4,均值為 0
bn = nn.BatchNorm1d(4)
bn.weight.data = t.ones(4) * 4
bn.bias.data = t.zeros(4)
bn_out = bn(h)
# 注意輸出的均值和方差
# 方差是標準差的平方,計算無偏方差分母會減 1
# 使用 unbiased=False,分母不減 1
bn_out.mean(0), bn_out.var(0, unbiased=False)
(tensor([0., 0., 0., 0.], grad_fn=<MeanBackward1>),
 tensor([15.9946, 15.9998, 15.9999, 15.9999], grad_fn=<VarBackward1>))
# 每個元素以 0.5 的概率捨棄
dropout = nn.Dropout(0.5)
o = dropout(bn_out)
bn_out, o  # 有一半左右的數變為 0
(tensor([[ 3.9993,  4.0000,  4.0000,  4.0000],
         [-3.9993, -4.0000, -4.0000, -4.0000]],
        grad_fn=<NativeBatchNormBackward>),
 tensor([[ 0.0000,  8.0000,  8.0000,  0.0000],
         [-7.9986, -8.0000, -0.0000, -8.0000]], grad_fn=<MulBackward0>))

上述的例子中都對 module 的屬性也就是 data 進行了直接操作,其中大多都是可學習引數,這些引數在學習中會不斷變化。在實際使用中,如非必要,還是不要去修改它們。

二、啟用函式

2.1 ReLU 函式

tocrh 實現了常見的啟用函式,具體的使用可以去檢視官方文件或檢視上面分享的文章,看文章的時候需要注意的是啟用函式也可以作為獨立的 layer 使用。在這裡我們就講講最常使用的 ReLU 函式,它的數學表示式為:\(ReLU(x) = max(0,x)\),其實就是小於 0 的數置為 0

relu = nn.ReLU(inplace=True)
inp = V(t.randn(2, 3))
inp
tensor([[-1.5703,  0.0868,  1.0811],
        [-0.9903,  0.5288,  0.5926]])
output = relu(inp)  # 小於 0 的都被置為 0,等價於 inp.camp(min=0)
output
tensor([[0.0000, 0.0868, 1.0811],
        [0.0000, 0.5288, 0.5926]])

其中 ReLU 函式有個 inplace 引數,如果設定為 True,它會讓輸出直接覆蓋輸入,這樣可以節省記憶體的佔用。

在上述例子中,是將每一層的輸出直接作為下一層的輸入,這種網路稱為前饋傳播網路。對於這類網路,如果每次也都寫上 forward 函式,就會特別麻煩。因此又兩者簡化方式——ModuleList 和 Sequential。其中 Sequential 是一個特殊的 Module,包含幾個子 module,可以像用 list 一樣使用它,但不能直接把輸入傳給它。

2.2 通過Sequential 構建前饋傳播網路

# Sequential 的三種寫法
net1 = nn.Sequential()
net1.add_module('conv', nn.Conv2d(3, 3, 3))
net1.add_module('batchnorm', nn.BatchNorm2d(3))
net1.add_module('activation_layer', nn.ReLU())

net2 = nn.Sequential(nn.Conv2d(3, 3, 3), nn.BatchNorm2d(3), nn.ReLU())

from collections import OrderedDict
net3 = nn.Sequential(
    OrderedDict([('conv1', nn.Conv2d(3, 3, 3)), ('bn1', nn.BatchNorm2d(3)),
                 ('relu1', nn.ReLU())]))
net1
Sequential(
  (conv): Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))
  (batchnorm): BatchNorm2d(3, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (activation_layer): ReLU()
)
net2
Sequential(
  (0): Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))
  (1): BatchNorm2d(3, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (2): ReLU()
)
net3
Sequential(
  (conv1): Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))
  (bn1): BatchNorm2d(3, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (relu1): ReLU()
)
# 可以根據名字或序號取出子 module
net1.conv, net2[0], net3.conv1
(Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1)),
 Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1)),
 Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1)))
# 通過 Sequential 構建的網路處理資料
inp = V(t.rand(1, 3, 4, 4))
output = net1(inp)
output = net2(inp)
output = net3(inp)
output = net3.relu1(net1.batchnorm(net1.conv(inp)))

2.3 通過 ModuleList 構建前饋傳播網路

modellist = nn.ModuleList([nn.Linear(3, 4), nn.ReLU(), nn.Linear(4, 2)])
inp = V(t.rand(1, 3))
for model in modellist:
    inp = model(inp)
# output = modellist(inp) # 報錯,因為 modellist 沒有實現 forward 方法,需要新建一個類定義 forward 方法

在這裡使用 ModuleList 而不是使用 list,是因為 ModuleList 是 Module 的子類,在 Module 中使用它時,可以自動識別為子 module,下面我們舉例說明。

class MyModule(nn.Module):
    def __init__(self):
        super(MyModule, self).__init__()
        self.list = [nn.Linear(3, 4), nn.ReLU()]
        self.module_list = nn.ModuleList([nn.Conv2d(3, 3, 3), nn.ReLU()])

    def forward(self):
        pass


model = MyModule()
model
MyModule(
  (module_list): ModuleList(
    (0): Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))
    (1): ReLU()
  )
)
for name, param in model.named_parameters():
    print(name, param.size())
module_list.0.weight torch.Size([3, 3, 3, 3])
module_list.0.bias torch.Size([3])

從上面的列印結果可以看出,list 中的子 module 並沒有被主 module 識別,而 ModuleList 中的子 module 卻被主 module 識別了。這也意味著如果用 list 儲存子 module,將無法調整其引數,因為這些引數會加入到 module 的引數中。

三、迴圈神經網路層

迴圈神經網路多用於自然語言處理(RNN),torch 中目前實現了最常用的三種 RNN:RNN(vanilla RNN)、LSTM 和 GRU,此外還有對應的三種 RNNCell。

RNN 和 RNNCell 的區別在於前者能夠處理整個序列,而後者一次只處理序列中的一個時間點和資料,前者更易於使用,後者的靈活性更高。因此 RNN 一般組合呼叫 RNNCell 一起使用。

t.manual_seed(1000)
inp = V(t.randn(2, 3, 4))  # 輸入:batch_size=3,序列長度為 2,序列中每個元素佔 4 維
lstm = nn.LSTM(4, 3, 1)  # lstm 輸入向量 4 維,3 個隱藏元,1 層

# 初始狀態:1 層,batch_size=3,3 個隱藏元
h0 = V(t.randn(1, 3, 3))
c0 = V(t.randn(1, 3, 3))

out, hn = lstm(inp, (h0, c0))
out
tensor([[[-0.3610, -0.1643,  0.1631],
         [-0.0613, -0.4937, -0.1642],
         [ 0.5080, -0.4175,  0.2502]],

        [[-0.0703, -0.0393, -0.0429],
         [ 0.2085, -0.3005, -0.2686],
         [ 0.1482, -0.4728,  0.1425]]], grad_fn=<StackBackward>)
t.manual_seed(1000)
inp = V(t.randn(2, 3, 4))  # 輸入:batch_size=3,序列長度為 2,序列中每個元素佔 4 維

# 一個 LSTMCell 對應的層數只能是一層
lstm = nn.LSTMCell(4, 3)  # lstm 輸入向量 4 維,3 個隱藏元,預設1 層也只能是一層
hx = V(t.randn(3, 3))
cx = V(t.randn(3, 3))

out = []
for i_ in inp:
    hx, cx = lstm(i_, (hx, cx))
    out.append(hx)

t.stack(out)
tensor([[[-0.3610, -0.1643,  0.1631],
         [-0.0613, -0.4937, -0.1642],
         [ 0.5080, -0.4175,  0.2502]],

        [[-0.0703, -0.0393, -0.0429],
         [ 0.2085, -0.3005, -0.2686],
         [ 0.1482, -0.4728,  0.1425]]], grad_fn=<StackBackward>)

詞向量在自然語言中應用十分廣泛,torch 同樣也提供了 Embedding 層

embedding = nn.Embedding(4, 5)  # 有 4 個詞,每個詞用 5 維的向量表示
embedding.weight.data = t.arange(0, 20).view(4, 5)  # 可以用預訓練好的詞向量初始化 embedding
with t.no_grad():  # 下面程式碼的梯度回傳有問題,因此去掉梯度
    inp = V(t.arange(3, 0, -1)).long()
    output = embedding(inp)
    print(output)
tensor([[15, 16, 17, 18, 19],
        [10, 11, 12, 13, 14],
        [ 5,  6,  7,  8,  9]])

四、損失函式

在深度學習中也要用到各種各樣的損失函式,這些損失函式可以看作是特殊的 layer,torch 也將這些損失函式實現為 nn.Module 的子類。

然而在實際的應用中,一般把損失函式獨立出來,作為獨立的一部分。詳細的 loss 使用可以參考官方文件,這裡僅以最常用的交叉熵損失為例講解。

# batch_size=3,計算對應每個類別的分數(只有兩個類別)
score = V(t.randn(3, 2))
# 三個樣本分別屬於 1,0,1 類,label 必須是 LongTensor
label = V(t.Tensor([1, 0, 1])).long()

# loss 與普通的 layer 沒有什麼差別
criterion = nn.CrossEntropyLoss()
loss = criterion(score, label)
loss
tensor([[ 1.0592,  1.4730],
        [-0.1558, -0.8712],
        [ 0.2548,  0.0817]])
tensor([1, 0, 1])





tensor(0.5630)

相關文章