天天看點

0603-常用的神經網絡層

0603-常用的神經網絡層

目錄

  • 一、圖像相關層
  • 二、激活函數
    • 2.1 ReLU 函數
    • 2.2 通過Sequential 建構前饋傳播網絡
    • 2.3 通過 ModuleList 建構前饋傳播網絡
  • 三、循環神經網絡層
  • 四、損失函數

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
           
0603-常用的神經網絡層

上圖是 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))
           
0603-常用的神經網絡層
# 普通卷積

# 輸入是一個 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))
           
0603-常用的神經網絡層

上圖是卷積後處理的 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])
           
0603-常用的神經網絡層

上圖是池化後的 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)