系列文章
李沐《動(dòng)手學(xué)深度學(xué)習(xí)》預(yù)備知識(shí) 張量操作及數(shù)據(jù)處理
李沐《動(dòng)手學(xué)深度學(xué)習(xí)》預(yù)備知識(shí) 線性代數(shù)及微積分
李沐《動(dòng)手學(xué)深度學(xué)習(xí)》線性神經(jīng)網(wǎng)絡(luò) 線性回歸
李沐《動(dòng)手學(xué)深度學(xué)習(xí)》線性神經(jīng)網(wǎng)絡(luò) softmax回歸
李沐《動(dòng)手學(xué)深度學(xué)習(xí)》多層感知機(jī) 模型概念和代碼實(shí)現(xiàn)
李沐《動(dòng)手學(xué)深度學(xué)習(xí)》多層感知機(jī) 深度學(xué)習(xí)相關(guān)概念
教材:李沐《動(dòng)手學(xué)深度學(xué)習(xí)》
一、層和塊
(一)塊的概念
塊(block):可以描述單個(gè)層、由多個(gè)層組成的組件或整個(gè)模型本身
- 使用塊進(jìn)行抽象的一個(gè)好處是可以將一些塊組合成更大的組件, 這一過(guò)程通常是遞歸的;
- 通過(guò)定義代碼來(lái)按需生成任意復(fù)雜度的塊, 我們可以通過(guò)簡(jiǎn)潔的代碼實(shí)現(xiàn)復(fù)雜的神經(jīng)網(wǎng)絡(luò)。
(二)塊的實(shí)現(xiàn)
- 從編程的角度來(lái)看,塊由類表示。每個(gè)塊必須提供的基本功能:
- 將輸入數(shù)據(jù)作為其前向傳播函數(shù)的參數(shù);
- 通過(guò)前向傳播函數(shù)來(lái)生成輸出;
- 計(jì)算其輸出關(guān)于輸入的梯度,可通過(guò)其反向傳播函數(shù)進(jìn)行訪問(wèn),通常這是自動(dòng)發(fā)生的;
- 存儲(chǔ)和訪問(wèn)前向傳播計(jì)算所需的參數(shù);
- 根據(jù)需要初始化模型參數(shù)。
- 層和塊的順序連接由Sequential塊處理:
- nn.Sequential定義了一種特殊的Module, 即在PyTorch中表示一個(gè)塊的類, 它維護(hù)了一個(gè)由Module組成的有序列表;
- 通過(guò)net(X)調(diào)用模型來(lái)獲得模型的輸出,這實(shí)際上是net.call(X)的簡(jiǎn)寫。 這個(gè)前向傳播函數(shù)非常簡(jiǎn)單: 它將列表中的每個(gè)塊連接在一起,將每個(gè)塊的輸出作為下一個(gè)塊的輸入。
net = nn.Sequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))
X = torch.rand(2, 20)
net(X)
- 一個(gè)塊可以由許多層組成;一個(gè)塊可以由許多塊組成。
class NestMLP(nn.Module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(nn.Linear(20, 64), nn.ReLU(),
nn.Linear(64, 32), nn.ReLU())
self.linear = nn.Linear(32, 16)
def forward(self, X):
return self.linear(self.net(X))
chimera = nn.Sequential(NestMLP(), nn.Linear(16, 20), FixedHiddenMLP())
chimera(X)
二、參數(shù)管理
具有單隱藏層的多層感知機(jī):
import torch
from torch import nn
net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 1))
X = torch.rand(size=(2, 4))
net(X)
(一)參數(shù)訪問(wèn):用于調(diào)試、診斷和可視化
當(dāng)通過(guò)Sequential類定義模型時(shí),可以通過(guò)索引來(lái)訪問(wèn)模型的任意層。
參數(shù)訪問(wèn)方式一:
print(net[2].state_dict()) #獲得第二個(gè)全連接層的參數(shù)
print(type(net[2].bias)) #第二個(gè)全連接層偏置的類型
print(net[2].bias) #第二個(gè)全連接層的偏置 (參數(shù)是復(fù)合的對(duì)象,包含值、梯度和額外信息。)
print(net[2].bias.data) #第二個(gè)全連接層偏置的數(shù)值
參數(shù)訪問(wèn)方式二:
net.state_dict()['2.bias'].data
從嵌套塊收集參數(shù)時(shí),也可以像通過(guò)嵌套列表索引一樣訪問(wèn)它們
#獲得第一個(gè)主要的塊中、第二個(gè)子塊的第一層的偏置項(xiàng)。
rgnet[0][1][0].bias.data
(二)參數(shù)初始化(內(nèi)置初始化、自定義初始化)
深度學(xué)習(xí)框架提供默認(rèn)隨機(jī)初始化, 也允許我們創(chuàng)建自定義初始化方法, 滿足我們通過(guò)其他規(guī)則實(shí)現(xiàn)初始化權(quán)重。
- 內(nèi)置初始化:PyTorch的nn.init模塊提供了多種預(yù)置初始化方法
調(diào)用內(nèi)置初始化器,將所有權(quán)重參數(shù)初始化為標(biāo)準(zhǔn)差為0.01的高斯隨機(jī)變量,偏置參數(shù)初始化為0:
def init_normal(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, mean=0, std=0.01)
nn.init.zeros_(m.bias)
net.apply(init_normal)
net[0].weight.data[0], net[0].bias.data[0]
調(diào)用內(nèi)置初始化器,將參數(shù)初始化為1:
def init_constant(m):
if type(m) == nn.Linear:
nn.init.constant_(m.weight, 1)
nn.init.zeros_(m.bias)
net.apply(init_constant)
net[0].weight.data[0], net[0].bias.data[0]
使用Xavier初始化第一個(gè)神經(jīng)網(wǎng)絡(luò)層,將第三個(gè)神經(jīng)網(wǎng)絡(luò)層初始化為常量值42:
def init_xavier(m):
if type(m) == nn.Linear:
nn.init.xavier_uniform_(m.weight)
def init_42(m):
if type(m) == nn.Linear:
nn.init.constant_(m.weight, 42)
net[0].apply(init_xavier)
net[2].apply(init_42)
print(net[0].weight.data[0])
print(net[2].weight.data)
-
自定義初始化
使用以下的分布為任意權(quán)重參數(shù) w w w定義初始化方法:
w { U ( 5 , 10 ) 可能性?0.25? 0 可能性?0.5 U ( ? 10 , ? 5 ) 可能性?0.25 w \begin{cases} U(5,10) & \text{可能性 0.25 } \\ 0 & \text{可能性 0.5}\\ U(-10,-5) & \text{可能性 0.25} \end{cases} w? ? ??U(5,10)0U(?10,?5)?可能性?0.25?可能性?0.5可能性?0.25?
def my_init(m):
if type(m) == nn.Linear:
print("Init", *[(name, param.shape)
for name, param in m.named_parameters()][0])
nn.init.uniform_(m.weight, -10, 10)
m.weight.data *= m.weight.data.abs() >= 5
net.apply(my_init)
net[0].weight[:2]
- 也可以直接設(shè)置參數(shù)值
net[0].weight.data[:] += 1
net[0].weight.data[0, 0] = 42
net[0].weight.data[0]
(三)參數(shù)綁定:在不同模型組件間共享參數(shù)
為了在多個(gè)層間共享參數(shù),可以定義一個(gè)稠密層,然后使用它的參數(shù)來(lái)設(shè)置另一個(gè)層的參數(shù)
- 第三個(gè)和第五個(gè)神經(jīng)網(wǎng)絡(luò)層的參數(shù)是綁定的,不僅值相等,而且由相同的張量表示;
- 改變其中一個(gè)參數(shù),另一個(gè)參數(shù)也會(huì)改變;
- 反向傳播期間第二個(gè)隱藏層 (即第三個(gè)神經(jīng)網(wǎng)絡(luò)層)和第三個(gè)隱藏層(即第五個(gè)神經(jīng)網(wǎng)絡(luò)層)的梯度會(huì)加在一起。
# 我們需要給共享層一個(gè)名稱,以便可以引用它的參數(shù)
shared = nn.Linear(8, 8)
net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(),
shared, nn.ReLU(),
shared, nn.ReLU(),
nn.Linear(8, 1))
net(X)
# 檢查參數(shù)是否相同
print(net[2].weight.data[0] == net[4].weight.data[0])
net[2].weight.data[0, 0] = 100
# 確保它們實(shí)際上是同一個(gè)對(duì)象,而不只是有相同的值
print(net[2].weight.data[0] == net[4].weight.data[0])
三、延后初始化
- 框架的延后初始化:直到數(shù)據(jù)第一次通過(guò)模型傳遞時(shí),框架才會(huì)動(dòng)態(tài)地推斷出每個(gè)層的大小。
四、自定義層
(一)不帶參數(shù)的層
構(gòu)建一個(gè)CenteredLayer類,要從其輸入中減去均值:
import torch
import torch.nn.functional as F
from torch import nn
class CenteredLayer(nn.Module):
def __init__(self):
super().__init__()
def forward(self, X):
return X - X.mean()
(二)帶參數(shù)的層
實(shí)現(xiàn)自定義版本的全連接層:
- 需要兩個(gè)參數(shù),一個(gè)用于表示權(quán)重,另一個(gè)用于表示偏置項(xiàng);
- 使用修正線性單元作為激活函數(shù);
- in_units和units分別表示輸入數(shù)和輸出數(shù)。
class MyLinear(nn.Module):
def __init__(self, in_units, units):
super().__init__()
self.weight = nn.Parameter(torch.randn(in_units, units))
self.bias = nn.Parameter(torch.randn(units,))
def forward(self, X):
linear = torch.matmul(X, self.weight.data) + self.bias.data
return F.relu(linear)
五、讀寫文件
(一)加載和保存張量
save和load函數(shù)可用于張量對(duì)象的文件讀寫:
import torch
from torch import nn
from torch.nn import functional as F
x = torch.arange(4)
torch.save(x, 'x-file')
x2 = torch.load('x-file')
x2
(二)加載和保存模型參數(shù)
class MLP(nn.Module):
def __init__(self):
super().__init__()
self.hidden = nn.Linear(20, 256)
self.output = nn.Linear(256, 10)
def forward(self, x):
return self.output(F.relu(self.hidden(x)))
net = MLP()
X = torch.randn(size=(2, 20))
Y = net(X)
深度學(xué)習(xí)框架提供了內(nèi)置函數(shù)來(lái)保存和加載整個(gè)網(wǎng)絡(luò),但是只會(huì)保存模型的參數(shù)而不是保存整個(gè)模型。 因?yàn)槟P捅旧砜梢园我獯a,所以模型本身難以序列化。 因此,為了恢復(fù)模型,需要用代碼生成架構(gòu), 然后從磁盤加載參數(shù)。
#模型保存
torch.save(net.state_dict(), 'mlp.params')
#用代碼生成架構(gòu)
clone = MLP()
#加載保存好的參數(shù)
clone.load_state_dict(torch.load('mlp.params'))
clone.eval()
六、GPU
(一)計(jì)算設(shè)備
- 可以指定用于存儲(chǔ)和計(jì)算的設(shè)備,如CPU和GPU。 默認(rèn)情況下,張量是在內(nèi)存中創(chuàng)建的,然后使用CPU計(jì)算它。
在PyTorch中,CPU和GPU可以用torch.device(‘cpu’) 和torch.device(‘cuda’)表示。 應(yīng)該注意的是,cpu設(shè)備意味著所有物理CPU和內(nèi)存, 這意味著PyTorch的計(jì)算將嘗試使用所有CPU核心。 然而,gpu設(shè)備只代表一個(gè)卡和相應(yīng)的顯存。 如果有多個(gè)GPU,我們使用torch.device(f’cuda:{i}') 來(lái)表示第 i i i 塊GPU(從0開(kāi)始)。 另外,cuda:0和cuda是等價(jià)的。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-814644.html
import torch
from torch import nn
torch.device('cpu'), torch.device('cuda'), torch.device('cuda:1')
- 查詢可用GPU的數(shù)量:
torch.cuda.device_count()
- 定義函數(shù)try_gpu,如果申請(qǐng)的GPU存在,就返回GPU(i),不存在就使用CPU
def try_gpu(i=0):
if torch.cuda.device_count() >= i + 1:
return torch.device(f'cuda:{i}')
return torch.device('cpu')
- 定義函數(shù)try_all_gpus,返回所有可用的GPU,如果沒(méi)有GPU,則返回[cpu(),]"
def try_all_gpus():
devices = [torch.device(f'cuda:{i}')
for i in range(torch.cuda.device_count())]
return devices if devices else [torch.device('cpu')]
try_gpu(), try_gpu(10), try_all_gpus()
(二)張量與GPU
- 可以查詢張量所在的設(shè)備。 默認(rèn)情況下,張量是在CPU上創(chuàng)建的
x = torch.tensor([1, 2, 3])
x.device
#返回device(type='cpu')
- 將張量存儲(chǔ)在GPU上
X = torch.ones(2, 3, device=try_gpu())
X
Y = torch.rand(2, 3, device=try_gpu(1))
Y
- 復(fù)制:對(duì)多個(gè)項(xiàng)進(jìn)行操作時(shí)不同項(xiàng)目必須在同一個(gè)設(shè)備上
Z = X.cuda(1)
print(X)
print(Z)
#返回結(jié)果顯示X在cuda0,Z在cuda1
(三)神經(jīng)網(wǎng)絡(luò)與GPU
神經(jīng)網(wǎng)絡(luò)模型可以指定設(shè)備。 下面的代碼將模型參數(shù)放在GPU上。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-814644.html
net = nn.Sequential(nn.Linear(3, 1))
net = net.to(device=try_gpu())
到了這里,關(guān)于李沐《動(dòng)手學(xué)深度學(xué)習(xí)》深度學(xué)習(xí)計(jì)算的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!