主頁 >  其他 > 【動手學深度學習】第五章筆記:層與塊、引數管理、自定義層、讀寫檔案、GPU

【動手學深度學習】第五章筆記:層與塊、引數管理、自定義層、讀寫檔案、GPU

2023-04-28 10:16:11 其他

為了更好的閱讀體驗,請點擊這里

由于本章內容比較少且以后很顯然會經常回來翻,因此會寫得比較詳細,

5.1 層和塊

事實證明,研究討論“比單個層大”但“比整個模型小”的組件更有價值,例如,在計算機視覺中廣泛流行的ResNet-152 架構就有數百層,這些層是由層組(groups of layers)的重復模式組成,

為了實作這些復雜的網路,我們引入了神經網路的概念,(block)可以描述單個層、由多個層組成的組件或整個模型本身,使用塊進行抽象的一個好處是可以將一些塊組合成更大的組件,通過定義代碼來按需生成任意復雜度的塊,我們可以通過簡潔的代碼實作復雜的神經網路,

從編程的角度來看,塊由(class)表示,它的任何子類都必須定義一個將其輸入轉換為輸出的前向傳播函式,并且必須存盤任何必需的引數,注意,有些塊不需要任何引數,最后,為了計算梯度,塊必須具有反向傳播函式,在定義我們自己的塊時,由于自動微分提供了一些后端實作,我們只需要考慮前向傳播函式和必需的引數

之后原書中舉的例子為實體化一個包含兩個線性層的多層感知機,該代碼中,通過實體化 nn.Sequential 來構建模型,層的執行順序是作為引數傳遞的,簡而言之,nn.Sequential 定義了一種特殊的 Module,即在 PyTorch 中表示一個塊的類,它維護了一個由 Module 組成的有序串列,注意,兩個全連接層都是 Linear 類的實體,Linear 類本身就是 Module 的子類,另外,到目前為止,我們一直在通過 net(X) 呼叫我們的模型來獲得模型的輸出,這實際上是 net.__call__(X) 的簡寫,

5.1.1 自定義塊

實作自定義塊之前,簡要總結一下每個塊必須提供的基本功能,

  1. 將輸入資料作為其前向傳播函式的引數,
  2. 通過前向傳播函式來生成輸出,請注意,輸出的形狀可能與輸入的形狀不同,例如,我們上面模型中的第一個全連接的層接收一個20維的輸入,但是回傳一個維度為256的輸出,
  3. 計算其輸出關于輸入的梯度,可通過其反向傳播函式進行訪問,通常這是自動發生的,
  4. 存盤和訪問前向傳播計算所需的引數,
  5. 根據需要初始化模型引數,

在下面的代碼片段中,我們從零開始撰寫一個塊,它包含一個多層感知機,其具有 \(256\) 個隱藏單元的隱藏層和一個 \(10\) 維輸出層,注意,下面的 MLP 類繼承了表示塊的類,我們的實作只需要提供我們自己的建構式(Python中的 __init__ 函式)和前向傳播函式,

class MLP(nn.Module):
    def __init__(self):
        super().__init__()
        self.hidden = nn.Linear(20, 256)
        self.out = nn.Linear(256, 10)
    
    def forward(self, X):
        return self.out(F.relu(self.hidden(X)))

注意一些關鍵細節:首先,我們定制的 __init__ 函式通過 super().__init__() 呼叫父類的 __init__ 函式,省去了重復撰寫模版代碼的痛苦,然后,我們實體化兩個全連接層,分別為 self.hiddenself.out,注意,除非我們實作一個新的運算子,否則我們不必擔心反向傳播函式或引數初始化,系統將自動生成這些,

塊的一個主要優點是它的多功能性,我們可以子類化塊以創建層(如全連接層的類)、整個模型(如上面的MLP類)或具有中等復雜度的各種組件,

5.1.2 順序塊

構建簡化的 MySequential,只需要定義兩個關鍵函式:

  1. 一種將塊逐個追加到串列中的函式;
  2. 一種前向傳播函式,用于將輸入按追加塊的順序傳遞給塊組成的“鏈條”,

下面的 MySequential 類提供了與默認 Sequential 類相同的功能,

class MySequential(nn.Module):
    def __init__(self, *args):
        super().__init__()
        for idx, module in enumerate(args):
            # 這里,module 是 Module 子類的一個實體,我們把它保存在 'Module' 類的成員
            # 變數 _modules 中,_module 的型別是 OrderedDict
            self._modules[str(idx)] = module

    def forward(self, X):
        # OrderedDict 保證了按照成員添加的順序遍歷它們
        for block in self._modules.values():
            X = block(X)
        return X

__init__ 函式將每個模塊逐個添加到有序字典 _modules 中,讀者可能會好奇為什么每個 Module 都有一個 _modules 屬性?以及為什么我們使用它而不是自己定義一個Python串列?簡而言之,_modules 的主要優點是:在模塊的引數初始化程序中,系統知道在 _modules 字典中查找需要初始化引數的子塊,

5.1.3 在前向傳播函式中執行代碼

當需要更強的靈活性時,我們需要定義自己的塊,例如,可能希望在前向傳播函式中執行Python的控制流,此外,可能希望執行任意的數學運算,而不是簡單地依賴預定義的神經網路層,

那么,就可以在前向傳播的函式中實作復雜的代碼,

練習題

(1)如果將 MySequential 中存盤塊的方式更改為 Python 串列,會出現什么樣的問題?

class MySequential(nn.Module):
    def __init__(self, *args):
        super().__init__()
        self.modules_list = []
        for idx, module in enumerate(args):
            self.modules_list.append(module)
        print(self.modules_list)
    
    def forward(self, X):
        for block in self.modules_list:
            X = block(X)
        return X
    
net = MySequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))
net(X)

接下來如果呼叫 net.parameters() 迭代器來遍歷引數或者用 net.state_dict() 來查看狀態字典,你會發現什么也不會輸出,原因在于 parameter 型別的引數只能從 _modules 中以及其他顯示定義在表層的 nn.Module 類及子類獲得,即使你把 list 換成另一個 OrderedDict 也并不好用,現在沒辦法自動獲取了,

除此之外,由于無法自動獲取 parameter 型別的引數,因此初始化很難做,

(2)實作一個塊,它以兩個塊為引數,例如 net1net2,并回傳前向傳播中兩個網路的串聯輸出,這也被稱為平行塊,

class ParallelBlock(nn.Module):
    def __init__(self, net1, net2):
        super().__init__()
        self.net1 = net1
        self.net2 = net2
    
    def forward(self, X):
        return self.net2(self.net1(X))
    
net = ParallelBlock(nn.Linear(16, 20), nn.Linear(20, 10))
print(net)
for param in net.parameters():
    print(param)

(3)假設我們想要連接同一網路的多個實體,實作一個函式,該函式生成同一個塊的多個實體,并在此基礎上構建更大的網路,

一般而言 Sequential 就足夠完成這個任務:

class multilayer(nn.Module):
    def __init__(self, num):
        super().__init__()
        layer_list = []
        for i in range(num):
            layer_list.append(nn.Linear(20, 10))
        self.ln = nn.Sequential(*layer_list)
    
    def forward(self, X):
        return self.ln(X)
multilayer(
  (ln): Sequential(
    (0): Linear(in_features=20, out_features=10, bias=True)
    (1): Linear(in_features=20, out_features=10, bias=True)
    (2): Linear(in_features=20, out_features=10, bias=True)
    (3): Linear(in_features=20, out_features=10, bias=True)
    (4): Linear(in_features=20, out_features=10, bias=True)
  )
)

當然,也可以使用 nn.ModuleList

class multilayer(nn.Module):
    def __init__(self, num):
        super().__init__()
        layer_list = []
        for i in range(num):
            layer_list.append(nn.Linear(20, 10))
        self.ln = nn.ModuleList(layer_list)
    
    def forward(self, X):
        return self.ln(X)
multilayer(
  (ln): ModuleList(
    (0): Linear(in_features=20, out_features=10, bias=True)
    (1): Linear(in_features=20, out_features=10, bias=True)
    (2): Linear(in_features=20, out_features=10, bias=True)
    (3): Linear(in_features=20, out_features=10, bias=True)
    (4): Linear(in_features=20, out_features=10, bias=True)
  )
)

5.2 引數管理

有時我們希望提取引數,以便在其他環境中復用它們,將模型保存下來,以便它可以在其他軟體中執行,或者為了獲得科學的理解而進行檢查,

本節,我們將介紹以下內容:

  • 訪問引數,用于除錯、診斷和可視化;
  • 引數初始化;
  • 在不同模型組件間共享引數,

假定此時有一個單隱藏層的多層感知機

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)
tensor([[-0.5471], [-0.5554]], grad_fn=<AddmmBackward0>)

5.2.1 引數訪問

同時,對于 Sequential 中,可以使用索引來訪問模型的任意層,除此之外,可以使用 .state_dict() 來檢查引數,比如,第二個全連接層的呼叫方法為 net[2].state_dict()

OrderedDict([('weight', tensor([[-0.2183, -0.2935, -0.2471,  0.3105, -0.0285, -0.0140, -0.1047, -0.0894]])), ('bias', tensor([-0.0456]))])

1. 目標引數

parameter 是復合的類,包含值、梯度和額外資訊,這就是我們需要顯式引數值的原因,除了值之外,我們還可以訪問每個引數的梯度,

print(type(net[2].bias))
print(net[2].bias)
print(net[2].bias.data)
<class 'torch.nn.parameter.Parameter'>
Parameter containing:
tensor([0.2615], requires_grad=True)
tensor([0.2615])

2. 一次性訪問所有引數

當我們需要對所有引數執行操作時,逐個訪問它們可能會很麻煩,當我們處理更復雜的塊(例如,嵌套塊)時,情況可能會變得特別復雜,因為我們需要遞回整個樹來提取每個子塊的引數,下面,我們將通過演示來比較訪問第一個全連接層的引數和訪問所有層,

module.named_parameters 回傳一個所有 module 引數的迭代器,回傳引數名字和引數,

print(*[(name, param.shape) for name, param in net[0].named_parameters()])
print(*[(name, param.shape) for name, param in net.named_parameters()])
('weight', torch.Size([8, 4])) ('bias', torch.Size([8]))
('0.weight', torch.Size([8, 4])) ('0.bias', torch.Size([8])) ('2.weight', torch.Size([1, 8])) ('2.bias', torch.Size([1]))

也有另一種訪問網路引數的方式:

net.state_dict()['2.bias'].data
tensor([0.2615])

3. 從嵌套塊收集引數

def block1():
    return nn.Sequential(nn.Linear(4, 8), nn.ReLU(),
                        nn.Linear(8, 4), nn.ReLU())

def block2():
    net = nn.Sequential()
    for i in range(4):
        net.add_module(f'block {i}', block1())
    return net

rgnet = nn.Sequential(block2(), nn.Linear(4, 1))
rgnet(X)
tensor([[0.2608],
        [0.2611]], grad_fn=<AddmmBackward0>)

輸出一下看看

print(rgnet)
Sequential(
  (0): Sequential(
    (block 0): Sequential(
      (0): Linear(in_features=4, out_features=8, bias=True)
      (1): ReLU()
      (2): Linear(in_features=8, out_features=4, bias=True)
      (3): ReLU()
    )
    (block 1): Sequential(
      (0): Linear(in_features=4, out_features=8, bias=True)
      (1): ReLU()
      (2): Linear(in_features=8, out_features=4, bias=True)
      (3): ReLU()
    )
    (block 2): Sequential(
      (0): Linear(in_features=4, out_features=8, bias=True)
      (1): ReLU()
      (2): Linear(in_features=8, out_features=4, bias=True)
      (3): ReLU()
    )
    (block 3): Sequential(
      (0): Linear(in_features=4, out_features=8, bias=True)
      (1): ReLU()
      (2): Linear(in_features=8, out_features=4, bias=True)
      (3): ReLU()
    )
  )
  (1): Linear(in_features=4, out_features=1, bias=True)
)

由于是嵌套了三層 Sequential 因此可以使用索引來訪問層,

rgnet[0][1][0].bias.data
tensor([-0.0647,  0.1259, -0.3926, -0.3025, -0.1323,  0.3075,  0.4889,  0.1187])

5.2.2 引數初始化

深度學習框架提供默認隨機初始化,也允許我們創建自定義初始化方法,滿足我們通過其他規則實作初始化權重,

默認情況下,PyTorch 會根據一個范圍均勻地初始化權重和偏置矩陣,這個范圍是根據輸入和輸出維度計算出的,PyTorch 的 nn.init 模塊提供了多種預置初始化方法,

1. 內置初始化

首先呼叫內置的初始化器,下面的代碼將所有權重引數初始化為標準差為 \(0.01\) 的高斯隨機變數,且將偏置引數設定為 \(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]
(tensor([-0.0261,  0.0005,  0.0169,  0.0050]), tensor(0.))

還可以將所有引數初始化為給定的常量,如初始化為 \(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[0]
(tensor([1., 1., 1., 1.]), tensor(0., grad_fn=<SelectBackward0>))

我們還可以對某些塊應用不同的初始化方法,例如,下面我們使用 Xavier 初始化方法初始化第一個神經網路層,然后將第三個神經網路層初始化為常量值 \(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)
tensor([ 0.3676,  0.3810,  0.5257, -0.0244])
tensor([[42., 42., 42., 42., 42., 42., 42., 42.]])

2. 自定義初始化

有時,深度學習框架沒有提供我們需要的初始化方法,在下面的例子中,使用以下的分布為任意權重引數 \(w\) 定義初始化方法:

\[w \sim \begin{cases} U(5, 10), &\text{可能性} \frac{1}{4} \\ 0, &\text{可能性}\frac{1}{2} \\ U(-10, -5), &\text{可能性} \frac{1}{4} \end{cases} \]

同樣,實作了一個 my_init 函式來應用到 net

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]
Init weight torch.Size([8, 4])
Init weight torch.Size([1, 8])
tensor([[-7.2929, -0.0000, -0.0000, -5.2074],
        [ 9.1947, -8.8687,  0.0000,  0.0000]], grad_fn=<SliceBackward0>)

注意,始終可以直接設定引數,

net[0].weight.data[:] += 1
net[0].weight.data[0, 0] = 42
net[0].weight.data[0]
tensor([42.0000,  1.0000,  1.0000, -4.2074])

5.2.3 引數系結

有時我們希望在多個層間共享引數:我們可以定義一個稠密層,然后使用它的引數來設定另一個層的引數,

# 我們需要給共享層一個名稱,以便可以參考它的引數
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)
# 檢查引數是否相同
print(net[2].weight.data[0] == net[4].weight.data[0])
net[2].weight.data[0, 0] = 100
# 確保它們實際上是同一個物件,而不只是有相同的值
print(net[2].weight.data[0] == net[4].weight.data[0])
tensor([True, True, True, True, True, True, True, True])
tensor([True, True, True, True, True, True, True, True])

這個例子表明第三個和第五個神經網路層的引數是系結的,它們不僅值相等,而且由相同的張量表示,因此,如果我們改變其中一個引數,另一個引數也會改變,這里有一個問題:當引數系結時,梯度會發生什么情況?答案是由于模型引數包含梯度,因此在反向傳播期間第二個隱藏層(即第三個神經網路層)和第三個隱藏層(即第五個神經網路層)的梯度會加在一起,

練習題

(1)使用之前沒寫的 NestMLP (FancyMLP) 模型訪問各個層的引數,

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))

net = NestMLP()
for name, param in net.named_parameters():
    print(name, param.shape)
net.0.weight torch.Size([64, 20])
net.0.bias torch.Size([64])
net.2.weight torch.Size([32, 64])
net.2.bias torch.Size([32])
linear.weight torch.Size([16, 32])
linear.bias torch.Size([16])

(2)查看初始化模塊檔案以了解不同的初始化方法,

官方檔案鏈接

(3)構建包含共享引數層的多層感知機并對其進行訓練,在訓練程序中,觀察模型各層的引數和梯度,

舉個簡單的例子,\(z=wy, y=wx\),不妨假設此時復制了兩個與 \(w\) 相同的值 \(w_1, w_2\),那么在反向傳播中 \(\frac{\mathrm{d} z}{\mathrm{d} w} = \frac{\mathrm{d}z}{\mathrm{d} w_1} + \frac{\mathrm{d} z}{\mathrm{d} y} \frac{\mathrm{d} y}{\mathrm{d} w_2} = y + wx = 2wx\),因此會是多倍梯度加和,

(4)為什么共享引數是個好方式?

可以減少引數,空間占用更小,但是正確性有待商榷,

5.3 延后初始化

延后初始化(defers initialization),即直到資料第一次通過模型傳遞時,框架才會動態地推斷出每個層的大小,

在以后,當使用卷積神經網路時,由于輸入維度(即影像的解析度)將影響每個后續層的維數,有了該技術將更加方便,現在我們在撰寫代碼時無須知道維度是什么就可以設定引數,這種能力可以大大簡化定義和修改模型的任務,

延后初始化中只有第一層需要延遲初始化,但是框架仍是按順序初始化的,等到知道了所有的引數形狀,框架就可以初始化引數,

書上沒有關于延后初始化的代碼,原因在于 PyTorch 中的延后初始化層 nn.LazyLinear() 仍然還是一個開發中的 feature,所以這一節在 PyTorch 版的書里有什么存在的必要嗎?

5.4 自定義層

本節將展示如何構建自定義層,

5.4.1 不帶引數的層

首先,構造一個沒有任何引數的自定義層,下面的 CenteredLayer 類要從其輸入中減去均值,要構建它,我們只需繼承基礎層類并實作前向傳播功能,

class CenteredLayer(nn.Module):
    def __init__(self):
        super().__init__()
    
    def forward(self, X):
        return X - X.mean()

5.4.2 帶引數的層

下面繼續定義具有引數的層, 這些引數可以通過訓練進行調整,可以使用內置函式來創建引數,這些函式提供一些基本的管理功能,比如管理訪問、初始化、共享、保存和加載模型引數,這樣做的好處之一是:我們不需要為每個自定義層撰寫自定義的序列化程式,

下面實作自定義版本的全連接層:

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)

練習題

(1)設計一個接受輸入并計算張量降維的層,它回傳 \(y_k = \sum_{i,j} W_{ijk} x_i x_j\)

最好使用 transpose() 或者是 permute()\(W_{ijk}\) 轉換一個維度,變成 \(W_{kij}\),這樣就可以寫成如下的形式了:

\[y_k = \boldsymbol{x}^T \boldsymbol{W}_k \boldsymbol{x} \]

class testlayer1(nn.Module):
    def __init__(self, in_units, units):
        super().__init__()
        self.W = nn.Parameter(torch.randn(units, in_units, in_units))
    def forward(self, x):
        h1 = torch.matmul(x, self.W.data)
        h2 = torch.matmul(h1, x)
        return h2
    
net = testlayer1(4, 2)
a = torch.rand(4)
print(a, net(a))
# 驗證一下第一個對不對
print(torch.matmul(a, torch.matmul(net.W[0], a)))
tensor([0.2971, 0.8508, 0.0615, 0.5073]) tensor([-0.5827, -1.1151])
tensor(-0.5827, grad_fn=<DotBackward0>)

第二題看不懂 QWQ

5.5 讀寫檔案

5.5.1 加載和保存張量

本節內容為如何加載和存盤權重向量和整個模型,

  • torch.save(obj, f) 存盤張量 obj 到 f 位置,
  • torch.load(f) 讀取 f 位置的檔案,

書中給出了保存與讀取張量、張量串列、張量字典的示例,

5.5.2 加載和保存模型引數

深度學習框架提供了內置函式來保存和加載整個網路,需要注意的一個重要細節是,這將保存模型的引數而不是保存整個模型,例如,如果有一個 \(3\) 層多層感知機,則需要單獨指定架構,因為模型本身可以包含任意代碼,所以模型本身難以序列化,因此,為了恢復模型,需要用代碼生成架構,然后從磁盤加載引數,從多層感知機開始:

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)

接下來,將模型的引數 net.state_dict() 存盤在一個 mlp.params 的檔案中,

torch.save(net.state_dict(), 'mlp.params')

為了恢復模型,我們實體化了原始多層感知機模型的一個備份,這里不需要隨機初始化模型引數,而是直接讀取檔案中存盤的引數,

clone = MLP()
clone.load_state_dict(torch.load('mlp.params'))

這樣即完成了模型的保存和加載,

練習題

(1)即使不需要將經過訓練的模型部署到不同的設備上,存盤模型引數還有什么實際的好處?

可以讓其他人復用模型,做重復實驗,

(2)假設我們只想復用網路的一部分,以將其合并到不同的網路架構中,比如想在一個新的網路中使用之前網路的前兩層,該怎么做?

這里僅使用上文中多層感知機的第一層作為例子,

old_net_state_dict = torch.load('mlp.params')
clone2 = MLP()
# 假設此處預處理剩下層已經完成
clone2.hidden.weight.data = https://www.cnblogs.com/bringlu/archive/2023/04/27/old_net_state_dict["hidden.weight"]
clone2.hidden.bias.data = https://www.cnblogs.com/bringlu/archive/2023/04/27/old_net_state_dict["hidden.bias"]

或者直接從這個基于 OrderedDictstate_dict 里面拿引數就行,

(3)如何同時保存網路架構和引數?需要對架構加上什么限制?

直接 torch.save(net) 即可,但是這個網路架構不包括 forward 函式,

5.6 GPU

可以使用 nvidia-smi 命令來查看顯卡資訊,

我用的 Kaggle 平臺的 T4 2 張,可以完成本節的代碼任務,

!nvidia-smi
Thu Apr 27 09:27:16 2023       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 470.161.03   Driver Version: 470.161.03   CUDA Version: 11.4     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  Tesla T4            Off  | 00000000:00:04.0 Off |                    0 |
| N/A   36C    P8     9W /  70W |      0MiB / 15109MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
|   1  Tesla T4            Off  | 00000000:00:05.0 Off |                    0 |
| N/A   34C    P8    10W /  70W |      0MiB / 15109MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+

5.6.1 計算設備

在 PyTorch 中,CPU 和 GPU 可以用 torch.device('cpu')torch.device('cuda') 表示,應該注意的是,cpu 設備意味著所有物理 CPU 和記憶體,這意味著 PyTorch 的計算將嘗試使用所有 CPU 核心,然而,gpu 設備只代表一個卡和相應的顯存,如果有多個 GPU,我們使用 torch.device(f'cuda:{i}') 來表示第 \(i\) 塊 GPU(\(i\)\(0\) 開始),另外,cuda:0cuda 是等價的,

import torch
from torch import nn

torch.device('cpu'), torch.device('cuda'), torch.device('cuda:1')
(device(type='cpu'), device(type='cuda'), device(type='cuda', index=1))

還可以查詢可用的 GPU 的數量,

torch.cuda.device_count()
2

原書中定義了兩個方便的函式,這兩個函式允許在不存在所需 GPU 的情況下運行代碼,

  • try_gpu(i) 嘗試使用 \(i\) 號 GPU,如果存在回傳 torch.device(f'cuda:{i}'),如果不存在回傳 torch.device('cpu'),默認引數為 i=0
  • try_all_gpus() 嘗試使用所有 GPU,如果存在 GPU 回傳所有 GPU 的串列,如果不存在回傳 [torch.device('cpu')]

5.6.2 張量與 GPU

默認情況下,張量是在 CPU 上創建的,需要注意的是,無論何時我們要對多個項進行操作,它們都必須在同一個設備上,

1. 存盤在 GPU 上

有幾種方法可以在 GPU 上存盤張量,例如,我們可以在創建張量時指定存盤設備,接下來,我們在第一個 gpu 上創建張量變數 X,在 GPU 上創建的張量只消耗這個 GPU 的顯存,我們可以使用 nvidia-smi 命令查看顯存使用情況, 一般來說,我們需要確保不創建超過 GPU 顯存限制的資料,

X = torch.ones(2, 3, device = try_gpu())
X
tensor([[1., 1., 1.],
        [1., 1., 1.]], device='cuda:0')

假設還存在另一個 GPU,那么在另一個 GPU 上創建隨機張量,

Y = torch.rand(2, 3, device = try_gpu(1))
Y
tensor([[0.4099, 0.3582, 0.8877],
        [0.7732, 0.8459, 0.1519]], device='cuda:1')

2. 復制

如果要計算 \(\sf X + Y\),那么需要將它們弄到同一個設備上,然后才能執行運算操作,例如,下面的代碼是將 \(\sf X\) 復制到第二個 GPU,然后執行加法運算,

Z = X.cuda(1)
print(X)
print(Z)
tensor([[1., 1., 1.],
        [1., 1., 1.]], device='cuda:0')
tensor([[1., 1., 1.],
        [1., 1., 1.]], device='cuda:1')

當然,也可以使用 .to() 來執行復制:

Z = X.to(torch.device('cuda:1'))
Z
tensor([[1., 1., 1.],
        [1., 1., 1.]], device='cuda:1')

相加:

Y + Z
tensor([[1.4099, 1.3582, 1.8877],
        [1.7732, 1.8459, 1.1519]], device='cuda:1')

假設變數 \(\sf Z\) 已經存在于第二個 GPU 上,如果我們還是呼叫 Z.cuda(1) 會發生什么?它將回傳 \(\sf Z\),而不會復制并分配新記憶體,

Z.cuda(1) is Z
True

注意呼叫 Z.to(torch.device("cuda:1")) is Z 也同樣回傳 True

所以這個 .to().cuda() 有啥區別啊

5.6.3 神經網路與 GPU

類似地,可以神經網路模型可以指定設備,下面的代碼將模型引數放在 GPU 上,

net = nn.Sequential(nn.Linear(3, 1))
net = net.to(device=try_gpu())
net(X)
tensor([[-0.3980],
        [-0.3980]], device='cuda:0', grad_fn=<AddmmBackward0>)

練習題

只做第(4)題,

(4)測量同時在兩個 GPU 上執行兩個矩陣乘法與在一個 GPU 上按順序執行兩個矩陣乘法所需的時間,提示:應該看到近乎線性的縮放,

同時在兩個 GPU 上執行矩陣乘法:

a = torch.rand(1000, 1000).to(try_gpu(0))
b = torch.rand(1000, 1000).to(try_gpu(0))
c = torch.rand(1000, 1000).to(try_gpu(1))
d = torch.rand(1000, 1000).to(try_gpu(1))
begintime = time.time()
for i in range(1000):
    e = torch.matmul(a, b)
    f = torch.matmul(c, d)
print(time.time() - begintime)
0.34023451805114746

在一個 GPU 上按順序執行兩個矩陣乘法所需的時間:

a = torch.rand(1000, 1000).to(try_gpu(0))
b = torch.rand(1000, 1000).to(try_gpu(0))
c = torch.rand(1000, 1000).to(try_gpu(0))
d = torch.rand(1000, 1000).to(try_gpu(0))
begintime = time.time()
for i in range(1000):
    e = torch.matmul(a, b)
    f = torch.matmul(c, d)
print(time.time() - begintime)
0.8642914295196533

差不多是兩倍的差距,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/551386.html

標籤:其他

上一篇:Langchain框架 prompt injection注入

下一篇:返回列表

標籤雲
其他(158245) Python(38107) JavaScript(25396) Java(18003) C(15217) 區塊鏈(8260) C#(7972) AI(7469) 爪哇(7425) MySQL(7151) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5870) 数组(5741) R(5409) Linux(5332) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4564) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2432) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1961) Web開發(1951) HtmlCss(1928) python-3.x(1918) 弹簧靴(1913) C++(1912) xml(1889) PostgreSQL(1874) .NETCore(1855) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 【動手學深度學習】第五章筆記:層與塊、引數管理、自定義層、讀寫

    為了更好的閱讀體驗,請點擊這里 由于本章內容比較少且以后很顯然會經常回來翻,因此會寫得比較詳細。 5.1 層和塊 事實證明,研究討論“比單個層大”但“比整個模型小”的組件更有價值。例如,在計算機視覺中廣泛流行的ResNet-152 架構就有數百層,這些層是由層組(groups of layers)的 ......

    uj5u.com 2023-04-28 10:16:11 more
  • Langchain框架 prompt injection注入

    Langchain框架 prompt injection注入 Prompt Injection 是一種攻擊技術,黑客或惡意攻擊者操縱 AI 模型的輸入值,以誘導模型回傳非預期的結果 Langchain框架 LangChain 是一個基于大語言模型進行應用開發的框架。 所謂大語言模型(Large La ......

    uj5u.com 2023-04-28 10:15:57 more
  • 記一次峰回路轉的注入

    自己之前寫過一篇記錄,當時是由于之前是一位校友剛做開發,叫我友情幫忙測驗一波,由于是開發的新手,漏洞比較多,所以直接從注入開始講起,但是到getshell的程序也算是一場峰回路轉再跌跌撞撞的路程。 ......

    uj5u.com 2023-04-28 10:14:28 more
  • 一次失敗的面試經歷:我只想找個作業,你卻用面試題羞辱我

    面對跳槽的高峰期,很多軟體測驗人員都希望能拿一個滿意的高薪offer,但是隨著招聘職位的不斷增多,面試的難度也隨之加大,而面試官更是會擇優錄取
    小王最近為面試已經焦頭爛額了,他說看著招聘條件里寫的崗位職責、任職要求,幾乎就是為自己量身定制的,滿懷信心的去面試,然而: ......

    uj5u.com 2023-04-28 08:59:15 more
  • 【Docker】鏡像制作和管理

    一、Docker鏡像說明 二、基于容器通過 docker commit 手動制作鏡像 1、基于容器手動制作鏡像步驟 1、下載官方系統鏡像 2、基于官方基礎鏡像啟動容器,并進入容器 3、在容器中進行配置操作 3.1、安裝基礎工具 3.2、配置運行環境 3.3、安裝并配置服務 3.4、存放業務程式代碼 ......

    uj5u.com 2023-04-27 08:31:52 more
  • 吾日三省吾身|最近反思-2023-04-27

    做專案得出對自我的認知 最近接了一個后臺管理的專案,由于自己身處自由狀態,且很多課很多事情沒有完成,也不知這個專案會不會給自己增加物質上的回饋,本身可能由于處女座吧,又不能直接放手,前幾天很是糾結,覺得自己應該做其他更重要的事。昨天想放棄了,但是又不能直接撒手不管,那要是直接放棄了,萬一這件事對人家 ......

    uj5u.com 2023-04-27 08:31:42 more
  • 解決macOSwifi已連接但上不了網的問題

    問題發生條件 剛剛更新macOS 13.3.1(非強相關) 連接的是校園網(可能有關系) 突然發生的,無任何預兆 問題現象 wifi圖示為灰色,且感嘆號 wifi詳情顯示:- 已連接 -無網路連接 - 無ip地址 上不了網,但是連接手機熱點沒問題 解決程序 解決方法1(無用) 忘記wifi,重新連接 ......

    uj5u.com 2023-04-27 08:31:37 more
  • 【飲食與健康】【AIGC創作】表觀生理年齡逆轉指北

    一、引言 我們都知道,歲月不饒人,但是誰又不想在歲月的長河中留下青春的容顏呢?在這個人人都追求健康和美麗的時代,我們的生活節奏卻愈發緊張,高壓的作業和不規律的作息讓我們的身體時刻處于亞健康狀態。這時候,你是不是開始想:“要是有一個方法能讓我變年輕,那該有多好啊!”好訊息是,科學家們已經找到了一個辦法 ......

    uj5u.com 2023-04-27 08:31:30 more
  • SRC相關知識分享

    SRC是企業采用眾測的方式,將企業內部的部分系統開發出來,供社會上散布的白帽子黑客進行滲透測驗,通過獎金和榮譽等激勵措施,鼓勵白帽在黑客在SRC平臺上傳漏洞,以此獲取實時的漏洞,進而第一時間修復漏洞。 ......

    uj5u.com 2023-04-27 08:31:22 more
  • [ML&DL] 正規方程

    正規方程 正規方程用于一次性求解 $\theta$ 的最優值。 在計算的時候,將資料集構造為一個矩陣(第一列為 $x_0$ 均等于$1$): 通過公式: $$ \theta = (X^TX)^{-1}X^Ty $$ 計算得到最優解 $\theta$。 關于$X$的設計 對于第 $i$ 組資料: $$ ......

    uj5u.com 2023-04-27 08:31:15 more