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
上圖是 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))
# 普通卷積
# 輸入是一個 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))
上圖是卷積後處理的 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])
上圖是池化後的 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)