2.2 數據操作 ---動手學深度學習——pytorch1.10

學了本文你能學到什麼?僅供學習,如有疑問,請留言。。。

注:紅色是小結,     紫色是重點,能像被課文一樣背誦,初次系統性的學習,請多多指點

目錄

2.2.1 創建Tensor

2.2.2 操作

"算術操作"

"索引:"

        "改變形狀"

2.2.3 廣播機制

2.2.4 運算的內存開銷

2.2.5 Tensor和NumPy相互轉換

Tensor轉NumPy

 NumPy數組轉Tensor

 2.2.6 Tensor on GPU



在PyTorch中,torch.Tensor是存儲和變換數據的主要工具。
如果你之前用過NumPy,你會發現Tensor和NumPy的多維數組非常類似。
由於,Tensor提供GPU計算和自動求梯度等更多功能,這些使Tensor更加適合深度學習。
""
"""
請注意:
"tensor"這個單詞一般可譯作“張量”,張量可以看作是一個多維數組。
標量可以看作是0維張量,向量可以看作1維張量,矩陣可以看作是二維張量。


2.2.1 創建Tensor


# todo 我們先介紹Tensor的最基本功能,即Tensor的創建。
# 首先導入PyTorch:
import torch
# 然後我們創建一個5x3的未初始化的Tensor:
x1 = torch.empty(5, 3)
# print(x1)
# 創建一個5x3的隨機初始化的Tensor:
x2 = torch.rand(5, 3)
# print(x2)
# 創建一個5x3的long型全0的Tensor:
x3 = torch.zeros(5, 3, dtype = torch.long)
# print(x3)
#還可以直接根據數據創建:
x4 = torch.tensor([5.5,3])
# print(x4)
# 還可以通過現有的Tensor來創建,此方法會默認重用輸入Tensor的一些屬性,例如數據類型,除非自定義數據類型。
x5 = x4.new_ones(5, 3, dtype=torch.float64)   # # 返回的tensor默認具有相同的torch.dtype和torch.device
"""
補充
#torch.dtype -----torch.dtype是表示torch.Tensor的數據類型的對象。PyTorch有八種不同的數據類型
x = torch.Tensor([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]])
# print(x.type()) #torch.FloatTensor
#torch.device ------torch.device代表將torch.Tensor分配到的設備的對象。
# print(torch.device("cpu"))
#torch.device ------torch.layout表示torch.Tensor內存佈局的對象。 torch.strided代表密集張量,是最常用的內存佈局。
l  = torch.Tensor([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]])
# print(l.stride())
"""
# print(x5)
x6 = torch.randn_like(x5, dtype=torch.float) # 指定新的數據類型
# print(x6)

# 我們可以通過shape或者size()來獲取Tensor的形狀:
# print(x6.size()) #torch.Size([5, 3])
# print(type(x6.size())) #<class 'torch.Size'>
# print(x6.shape) #torch.Size([5, 3])
# print(type(x6.shape)) #<class 'torch.Size'>
"""
常用的函數可以創建Tensor

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

"%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%鹹魚分割線%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"

 

# 2.2.2 操作

 

"算術操作"

"""
#在PyTorch中,同一種操作可能有很多種形式,
# 下面用加法作爲例子。
# 加法形式一
y = torch.rand(5, 3)
# print(x6 + y )
# 加法形式二
print(torch.add(x6, y))
# 還可以指定輸出
result = torch.empty(5, 3)
torch.add(x6, y, out=result)
print(result)
# 加法形式三、inplace
y.add_(x6)
print(y)
# 注:PyTorch操作inplace版本都有後綴_, 例如x.copy_(y), x.t_()
輸出::
tensor([[-0.3456,  0.6993, -0.8920],
        [ 0.5509,  0.0353, -0.2075],
        [-0.0784, -0.0182, -0.5664],
        [-2.2738, -0.1742, -0.1791],
        [-0.4831, -0.6773,  0.4412]])
tensor([[-0.2199,  0.7551, -0.1112],
        [ 0.6695,  0.1825,  0.5941],
        [ 0.3049,  0.1702, -0.4085],
        [-1.6745, -0.0533,  0.7709],
        [ 0.4048, -0.5268,  0.8098]])
tensor([[-0.2199,  0.7551, -0.1112],
        [ 0.6695,  0.1825,  0.5941],
        [ 0.3049,  0.1702, -0.4085],
        [-1.6745, -0.0533,  0.7709],
        [ 0.4048, -0.5268,  0.8098]])
tensor([[-0.2199,  0.7551, -0.1112],
        [ 0.6695,  0.1825,  0.5941],
        [ 0.3049,  0.1702, -0.4085],
        [-1.6745, -0.0533,  0.7709],
        [ 0.4048, -0.5268,  0.8098]])

"""

"索引:"

"""
我們還可以使用類似NumPy的索引操作來訪問Tensor的一部分,
需要注意的是:索引出來的結果與原數據共享內存,也即修改一個,另一個會跟着修改。
y = x6[0, :]
y += 1
print(y)
print(x6[0,:])
輸出結果:
tensor([[-1.4177,  0.1480, -0.2594],
        [ 1.9609,  0.1357,  2.1043],
        [-1.1173, -0.0053,  0.0223],
        [ 0.8649, -1.0293,  1.1089],
        [-0.0630,  0.1328,  0.0994]])
tensor([-0.4177,  1.1480,  0.7406])
tensor([-0.4177,  1.1480,  0.7406])

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

 


"改變形狀"


# 用view()來改變Tensor的形狀:
y = x6.view(15)
z = x6.view(-1, 5) #z = x6.view(3, 5)   # -1所指的維度可以根據其他維度的值推出來
print(x6.size(), y.size(), z.size())
運行結果:
tensor([[ 2.0844e-01,  7.9637e-01,  3.3590e+00],
        [-1.0522e+00,  1.3039e+00,  4.2128e-01],
        [ 9.3867e-01,  1.8803e+00, -8.0998e-01],
        [-1.7901e+00,  3.7803e-01, -2.9662e-03],
        [ 7.3772e-02, -1.0128e+00, -1.1299e+00]])
torch.Size([5, 3]) torch.Size([15]) torch.Size([3, 5])

# 注意view()返回的新Tensor與原Tensor雖然可能有不同的size,但是是共享data的,也即更改其中的一個,另外一個也會跟着改變。
# (顧名思義,view僅僅是改變了對這個張量的觀察角度,內部數據並未改變)

x6 += 1
print(x6)
print(y)
運行結果:
tensor([[ 1.2084,  1.7964,  4.3590],
        [-0.0522,  2.3039,  1.4213],
        [ 1.9387,  2.8803,  0.1900],
        [-0.7901,  1.3780,  0.9970],
        [ 1.0738, -0.0128, -0.1299]])
tensor([ 1.2084,  1.7964,  4.3590, -0.0522,  2.3039,  1.4213,  1.9387,  2.8803,
         0.1900, -0.7901,  1.3780,  0.9970,  1.0738, -0.0128, -0.1299])

# 思考:所以如果我們想返回一個真正新的副本(即不共享data內存)該怎麼辦呢?
# Pytorch還提供了一個reshape()可以改變形狀,但是此函數並不能保證返回的是其拷貝,所以不推薦使用。推薦先用clone創造一個副本然後再使用view
x_cp = x6.clone().view(15)
x6 -= 1
print(x6)
print(x_cp)
運行結果:
tensor([[ 2.0844e-01,  7.9637e-01,  3.3590e+00],
        [-1.0522e+00,  1.3039e+00,  4.2128e-01],
        [ 9.3867e-01,  1.8803e+00, -8.0998e-01],
        [-1.7901e+00,  3.7803e-01, -2.9662e-03],
        [ 7.3772e-02, -1.0128e+00, -1.1299e+00]])
tensor([ 1.2084,  1.7964,  4.3590, -0.0522,  2.3039,  1.4213,  1.9387,  2.8803,
         0.1900, -0.7901,  1.3780,  0.9970,  1.0738, -0.0128, -0.1299])

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

x = torch.randn(1)
print(x)
print(x.item())
運行結果:
tensor([0.0796])
0.07955377548933029

"""
"線性代數"
"""
# PyTorch還支持一些線性函數,這裏提一下,免得用起來的時候自己造輪子

 

# 2.2.3 廣播機制
 

當對兩個形狀不同的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]])

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


2.2.4 運算的內存開銷

前面說了,索引操作是不會開闢新內存的,而像y = x + y這樣的運算是會新開內存的,然後將y指向新內存。

 

"""
x = torch.tensor([1, 2])
print(x)
y = torch.tensor([3, 4])
id_before = id(y)
# y = y + x
s= torch.add(x, y, out=y)
print(s)
print(y)
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_())達到上述效果,

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

注:雖然view返回的Tensor與源Tensor是共享data的,但是依然是一個新的Tensor(因爲Tensor除了包含data外還有一些其他屬性),二者id(內存地址)並不一致。

# 2.2.5 Tensor和NumPy相互轉換

 

"""
#我們很容易用.numpy()和torch.from_numpy()將Tensor和NumPy中的數組相互轉換。


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

#Tensor轉NumPy


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

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

# 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)
# 此外上面提到還有一個常用的方法就是直接用torch.tensor()將NumPy數組轉換成Tensor,

需要注意的是該方法總是會進行數據拷貝,返回的Tensor和原來的數據不再共享內存。
輸出結果:
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.]
[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)
"""

# 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()還可以同時更改數據類型

"""

# 此節知識的補充 https://www.jianshu.com/p/7dbfc7076e5a

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章