從頭學pytorch(一):數據操作


跟着Dive-into-DL-PyTorch.pdf從頭開始學pytorch,夯實基礎.

Tensor創建

創建未初始化的tensor

import torch
x = torch.empty(5,3)
print(x)

輸出

tensor([[ 2.0909e+21,  3.0638e-41, -2.4612e-30],
        [ 4.5650e-41,  3.0638e-41,  1.7753e+28],
        [ 4.4339e+27,  1.3848e-14,  6.8801e+16],
        [ 1.8370e+25,  1.4603e-19,  6.8794e+11],
        [ 2.7253e+20,  3.0866e+29,  1.5835e-43]])

創建隨機初始化的tensor

x = torch.rand(5,3)
print(x)

輸出

tensor([[0.7302, 0.0805, 0.9499],
        [0.9323, 0.2995, 0.2943],
        [0.7428, 0.8312, 0.6465],
        [0.7843, 0.7300, 0.7509],
        [0.4965, 0.1318, 0.9063]])

創建全0的tensor,指定類型為long

x = torch.zeros(5,3,dtype=torch.long)

輸出

tensor([[0, 0, 0],
        [0, 0, 0],
        [0, 0, 0],
        [0, 0, 0],
        [0, 0, 0]])

指定數據創建

x = torch.tensor([5.5,3])
print(x)

輸出

tensor([5.5000, 3.0000])

利用已有tensor來創建,新創建的tensor和已有tensor具有相同數據類型,除非手動指定.

print(x.shape,x.dtype)
x = x.new_ones(5, 3, dtype=torch.float64)  # 返回的tensor默認具有相同的torch.dtype和torch.device
print(x)

輸出

torch.Size([2]) torch.float32
tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]], dtype=torch.float64)
x=torch.rand_like(x)
print(x)

輸出

tensor([[0.0646, 0.4401, 0.3052],
        [0.7657, 0.7720, 0.5770],
        [0.2951, 0.4179, 0.6013],
        [0.2007, 0.0473, 0.7378],
        [0.1789, 0.1498, 0.1430]], dtype=torch.float64)

還有很多創建tensor的方法

函數 功能
Tensor(*sizes) 基礎構造函數
tensor(data,) 類似np.array的構造函數
ones(*sizes) 全1Tensor
zeros(*sizes) 全0Tensor
eye(*sizes) 對角線為1,其他為0
arange(s,e,step 從s到e,步長為step
linspace(s,e,steps) 從s到e,均勻切分成steps份
rand/randn(*sizes) 均勻/標准分布
normal(mean,std)/uniform(from,to) 正態分布/均勻分布
randperm(m) 隨機排列

這些創建方法都可以在創建的時候指定數據類型dtype和存放device(cpu/gpu)。

各種操作

  • 算數操作
  • 索引
  • 改變形狀
  • 線性代數

算數操作

在PyTorch中,同一種操作可能有很多種形式,下面用加法作為例子。

  • 加法形式一
    y = torch.rand(5, 3)
    print(x + y)
    
  • 加法形式二
    print(torch.add(x, y))
    
    還可指定輸出:
    result = torch.empty(5, 3)
    torch.add(x, y, out=result)
    print(result)
    
  • 加法形式三、inplace
    # adds x to y
    y.add_(x)
    print(y)
    

    注:PyTorch操作inplace版本都有后綴"_", 例如x.copy_(y), x.t_()

索引

我們還可以使用類似NumPy的索引操作來訪問Tensor的一部分,需要注意的是:索引出來的結果與原數據共享內存,也即修改一個,另一個會跟着修改。

y = x[0, :]
y += 1
print(y)
print(x[0, :]) # 源tensor也被改了

輸出:

tensor([1.6035, 1.8110, 0.9549])
tensor([1.6035, 1.8110, 0.9549])

除了常用的索引選擇數據之外,PyTorch還提供了一些高級的選擇函數:

函數 功能
index_select(input, dim, index) 在指定維度dim上選取,比如選取某些行、某些列
masked_select(input, mask) 例子如上,a[a>0],使用ByteTensor進行選取
non_zero(input) 非0元素的下標
gather(input, dim, index) 根據index,在dim維度上選取數據,輸出的size與index一樣

view()來改變Tensor的形狀:

y = x.view(15)
z = x.view(-1, 5)  # -1所指的維度可以根據其他維度的值推出來
print(x.size(), y.size(), z.size())

輸出:

torch.Size([5, 3]) torch.Size([15]) torch.Size([3, 5])

注意view()返回的新tensor與源tensor共享內存(其實是同一個tensor),也即更改其中的一個,另外一個也會跟着改變。(顧名思義,view僅僅是改變了對這個張量的觀察角度)

x += 1
print(x)
print(y) # 也加了1

輸出:

tensor([[1.6035, 1.8110, 0.9549],
        [1.8797, 2.0482, 0.9555],
        [0.2771, 3.8663, 0.4345],
        [1.1604, 0.9746, 2.0739],
        [3.2628, 0.0825, 0.7749]])
tensor([1.6035, 1.8110, 0.9549, 1.8797, 2.0482, 0.9555, 0.2771, 3.8663, 0.4345,
        1.1604, 0.9746, 2.0739, 3.2628, 0.0825, 0.7749])

所以如果我們想返回一個真正新的副本(即不共享內存)該怎么辦呢?Pytorch還提供了一個reshape()可以改變形狀,但是此函數並不能保證返回的是其拷貝,所以不推薦使用。推薦先用clone創造一個副本然后再使用view參考此處

x_cp = x.clone().view(15)
x -= 1
print(x)
print(x_cp)

輸出:

tensor([[ 0.6035,  0.8110, -0.0451],
        [ 0.8797,  1.0482, -0.0445],
        [-0.7229,  2.8663, -0.5655],
        [ 0.1604, -0.0254,  1.0739],
        [ 2.2628, -0.9175, -0.2251]])
tensor([1.6035, 1.8110, 0.9549, 1.8797, 2.0482, 0.9555, 0.2771, 3.8663, 0.4345,
        1.1604, 0.9746, 2.0739, 3.2628, 0.0825, 0.7749])

使用clone還有一個好處是會被記錄在計算圖中,即梯度回傳到副本時也會傳到源Tensor

另外一個常用的函數就是item(), 它可以將一個標量Tensor轉換成一個Python number:

x = torch.randn(1)
print(x)
print(x.item())

輸出:

tensor([2.3466])
2.3466382026672363

線性代數

另外,PyTorch還支持一些線性函數,這里提一下,免得用起來的時候自己造輪子,具體用法參考官方文檔。如下表所示:

函數 功能
trace 對角線元素之和(矩陣的跡)
diag 對角線元素
triu/tril 矩陣的上三角/下三角,可指定偏移量
mm/bmm 矩陣乘法,batch的矩陣乘法
addmm/addbmm/addmv/addr/badbmm.. 矩陣運算
t 轉置
dot/cross 內積/外積
inverse 求逆矩陣
svd 奇異值分解

PyTorch中的Tensor支持超過一百種操作,包括轉置、索引、切片、數學運算、線性代數、隨機數等等,可參考官方文檔

2.2.3 廣播機制

前面我們看到如何對兩個形狀相同的Tensor做按元素運算。當對兩個形狀不同的Tensor按元素運算時,可能會觸發廣播(broadcasting)機制:先適當復制元素使這兩個Tensor形狀相同后再按元素運算。例如:

x = torch.arange(1, 3).view(1, 2)
print(x)
y = torch.arange(1, 4).view(3, 1)
print(y)
print(x + y)

輸出:

tensor([[1, 2]])
tensor([[1],
        [2],
        [3]])
tensor([[2, 3],
        [3, 4],
        [4, 5]])

由於xy分別是1行2列和3行1列的矩陣,如果要計算x + y,那么x中第一行的2個元素被廣播(復制)到了第二行和第三行,而y中第一列的3個元素被廣播(復制)到了第二列。如此,就可以對2個3行2列的矩陣按元素相加。

2.2.4 運算的內存開銷

前面說了,索引、view是不會開辟新內存的,而像y = x + y這樣的運算是會新開內存的,然后將y指向新內存。為了演示這一點,我們可以使用Python自帶的id函數:如果兩個實例的ID一致,那么它們所對應的內存地址相同;反之則不同。

x = torch.tensor([1, 2])
y = torch.tensor([3, 4])
id_before = id(y)
y = y + x
print(id(y) == id_before) # False 

如果想指定結果到原來的y的內存,我們可以使用前面介紹的索引來進行替換操作。在下面的例子中,我們把x + y的結果通過[:]寫進y對應的內存中。

x = torch.tensor([1, 2])
y = torch.tensor([3, 4])
id_before = id(y)
y[:] = y + x
print(id(y) == id_before) # True

我們還可以使用運算符全名函數中的out參數或者自加運算符+=(也即add_())達到上述效果,例如torch.add(x, y, out=y)y += x(y.add_(x))。

x = torch.tensor([1, 2])
y = torch.tensor([3, 4])
id_before = id(y)
torch.add(x, y, out=y) # y += x, y.add_(x)
print(id(y) == id_before) # True

2.2.5 Tensor和NumPy相互轉換

我們很容易用numpy()from_numpy()Tensor和NumPy中的數組相互轉換。但是需要注意的一點是:
這兩個函數所產生的的Tensor和NumPy中的數組共享相同的內存(所以他們之間的轉換很快),改變其中一個時另一個也會改變!!!

還有一個常用的將NumPy中的array轉換成Tensor的方法就是torch.tensor(), 需要注意的是,此方法總是會進行數據拷貝(就會消耗更多的時間和空間),所以返回的Tensor和原來的數據不再共享內存。

Tensor轉NumPy

使用numpy()Tensor轉換成NumPy數組:

a = torch.ones(5)
b = a.numpy()
print(a, b)

a += 1
print(a, b)
b += 1
print(a, b)

輸出:

tensor([1., 1., 1., 1., 1.]) [1. 1. 1. 1. 1.]
tensor([2., 2., 2., 2., 2.]) [2. 2. 2. 2. 2.]
tensor([3., 3., 3., 3., 3.]) [3. 3. 3. 3. 3.]

NumPy數組轉Tensor

使用from_numpy()將NumPy數組轉換成Tensor:

import numpy as np
a = np.ones(5)
b = torch.from_numpy(a)
print(a, b)

a += 1
print(a, b)
b += 1
print(a, b)

輸出:

[1. 1. 1. 1. 1.] tensor([1., 1., 1., 1., 1.], dtype=torch.float64)
[2. 2. 2. 2. 2.] tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
[3. 3. 3. 3. 3.] tensor([3., 3., 3., 3., 3.], dtype=torch.float64)

所有在CPU上的Tensor(除了CharTensor)都支持與NumPy數組相互轉換。

此外上面提到還有一個常用的方法就是直接用torch.tensor()將NumPy數組轉換成Tensor,需要注意的是該方法總是會進行數據拷貝,返回的Tensor和原來的數據不再共享內存。

c = torch.tensor(a)
a += 1
print(a, c)

輸出

[4. 4. 4. 4. 4.] tensor([3., 3., 3., 3., 3.], dtype=torch.float64)

2.2.6 Tensor on GPU

用方法to()可以將Tensor在CPU和GPU(需要硬件支持)之間相互移動。

# 以下代碼只有在PyTorch GPU版本上才會執行
if torch.cuda.is_available():
    device = torch.device("cuda")          # GPU
    y = torch.ones_like(x, device=device)  # 直接創建一個在GPU上的Tensor
    x = x.to(device)                       # 等價於 .to("cuda")
    z = x + y
    print(z)
    print(z.to("cpu", torch.double))       # to()還可以同時更改數據類型

輸出

tensor([2, 3], device='cuda:0')
tensor([2., 3.], dtype=torch.float64)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM