上層鏈接:PyTorch 學習筆記-CSDN博客
Tensor
初始化Tensor
import torch
import numpy as np# 1、直接從數據創建張量。數據類型是自動推斷的
data = [[1, 2],[3, 4]]
x_data = torch.tensor(data)torch.tensor([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
''' 輸出:
tensor([[2, 1, 4, 3],[1, 2, 3, 4],[4, 3, 2, 1]])
'''# 2、從 NumPy 數組創建張量(反之亦然)
np_array = np.array(data)
x_np = torch.from_numpy(np_array)
3、從另一個張量創建:
# 從另一個張量創建張量,新張量保留參數張量的屬性(形狀、數據類型),除非顯式覆蓋
x_ones = torch.ones_like(x_data) # retains the properties of x_data 保留原有屬性
print(f"Ones Tensor: \n {x_ones} \n")x_rand = torch.rand_like(x_data, dtype=torch.float) # overrides the datatype of x_data 覆蓋原有類型
print(f"Random Tensor: \n {x_rand} \n")
?
4、使用隨機值或常量值:(三個皆是數據類型默認為浮點型(torch.float32
))
# 使用隨機值或常量值創建張量:
shape = (2,3,) # shape是張量維度的元組,確定輸出張量的維數
rand_tensor = torch.rand(shape) # 元素為 [0, 1) 中的隨機浮點型,
ones_tensor = torch.ones(shape) # 元素為全 1
zeros_tensor = torch.zeros(shape) # 元素為全 0print(f"Random Tensor: \n {rand_tensor} \n")
print(f"Ones Tensor: \n {ones_tensor} \n")
print(f"Zeros Tensor: \n {zeros_tensor}")
torch.zeros((2, 3, 4))
''' 輸出:
tensor([[[0., 0., 0., 0.],[0., 0., 0., 0.],[0., 0., 0., 0.]],[[0., 0., 0., 0.],[0., 0., 0., 0.],[0., 0., 0., 0.]]])
'''torch.ones((2, 3, 4))
''' 輸出:
tensor([[[1., 1., 1., 1.],[1., 1., 1., 1.],[1., 1., 1., 1.]],[[1., 1., 1., 1.],[1., 1., 1., 1.],[1., 1., 1., 1.]]])
'''
若想指定生成其他數據類型的張量,可以通過?
dtype
?參數顯式指定。例如:# 整數類型 rand_tensor_int = torch.rand((2, 3), dtype=torch.int32) print(rand_tensor_int.dtype) # 輸出: torch.int32# 雙精度浮點型 ones_tensor_double = torch.ones((2, 3), dtype=torch.float64) print(ones_tensor_double.dtype) # 輸出: torch.float64
?動手學深度學習的內容
x = torch.arange(12) # 創建行向量 x,其包含以0開始的前12個整數,默認創建為整數
# 除非額外指定,新的張量將存儲在內存中,并采用基于CPU的計算。
# 輸出:tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])x.shape # 訪問張量(沿每個軸的長度)的形狀
# 輸出:torch.Size([12])x.numel() # 張量中元素的總數,即形狀的所有元素乘積,可以檢查它的大小(size)。因為這里在處理的是一個向量,所以它的shape與它的size相同
# 輸出:12X = x.reshape(3, 4) # 改變張量的形狀,而不改變其元素數量和元素值。
'''
把張量x從形狀為(12,)的行向量轉換為形狀為(3,4)的矩陣。
這個新的張量包含與轉換前相同的值,但是它被看成一個3行4列的矩陣。
重點說明:雖然張量的形狀發生了改變,但其元素值并沒有變。
注意,通過改變張量的形狀,張量的大小不會改變。
輸出:
tensor([[ 0, 1, 2, 3],[ 4, 5, 6, 7],[ 8, 9, 10, 11]])
'''
屬性
Tensor 屬性描述其形狀、數據類型和存儲它們的設備
tensor = torch.rand(3,4)
print(f"Shape of tensor: {tensor.shape}") # 形狀
print(f"Datatype of tensor: {tensor.dtype}") # 數據類型
print(f"Device tensor is stored on: {tensor.device}") # 存儲其的設備
操作(形狀相同的兩個矩陣)
索引和切片:(類似 numpy )
tensor = torch.ones(4, 4)
# tensor = torch.tensor([[1, 2, 3, 4],
# [5, 6, 7, 8],
# [9, 10, 11, 12],
# [13, 14, 15, 16]])print(f"First row: {tensor[0]}")
print(f"First column: {tensor[:, 0]}")
print(f"Last column: {tensor[..., -1]}")
tensor[:,1] = 0
print(tensor)
與任何Python數組一樣:第一個元素的索引是0,最后一個元素索引是-1; 可以指定范圍以包含第一個元素和最后一個之前的元素,即。
x = torch.arange(12) # 創建行向量 x,其包含以0開始的前12個整數,默認創建為整數
# 除非額外指定,新的張量將存儲在內存中,并采用基于CPU的計算。
# 輸出:tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])X = x.reshape(3, 4) # 改變張量的形狀,而不改變其元素數量和元素值。
'''
把張量x從形狀為(12,)的行向量轉換為形狀為(3,4)的矩陣。
這個新的張量包含與轉換前相同的值,但是它被看成一個3行4列的矩陣。
重點說明:雖然張量的形狀發生了改變,但其元素值并沒有變。
注意,通過改變張量的形狀,張量的大小不會改變。
輸出:
tensor([[ 0, 1, 2, 3],[ 4, 5, 6, 7],[ 8, 9, 10, 11]])
'''X[-1], X[1:3] # [-1]選擇最后一個元素,[1:3]選擇第二個和第三個元素
'''
(tensor([ 8., 9., 10., 11.]),tensor([[ 4., 5., 6., 7.],[ 8., 9., 10., 11.]]))
'''X[1, 2] = 9 # 指定索引第二行第三列,將元素9寫入矩陣
'''
array([[ 0., 1., 2., 3.],[ 4., 5., 9., 7.],[ 8., 9., 10., 11.]])
'''# 多個元素賦值相同的值
X[0:2, :] = 12 # [0:2, :]訪問第1行和第2行,其中“:”代表沿軸1(列)的所有元素
'''
array([[12., 12., 12., 12.],[12., 12., 12., 12.],[ 8., 9., 10., 11.]])
'''
torch.cat() 拼接張量?(沿給定維度連接一系列張量)。
另請參見?torch.stack, 另一個與 . 略有不同的 Tensor Joining 運算符。torch.cat
torch.cat
'''
dim=1 :沿著第 1 維(通常是列)進行拼接
如果 tensor 的形狀是 (a, b),
則沿著第 1 維拼接三次后,結果張量 t1 的形狀將是 (a, b * 3)。
'''
t1 = torch.cat([tensor, tensor, tensor], dim=1) # 沿著第 1 維拼接三次
print(t1)
- 沿行連結(軸-0,形狀的第一個元素)
- 按列連結(軸-1,形狀的第二個元素)
X = torch.arange(12, dtype=torch.float32).reshape((3,4))
Y = torch.tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
torch.cat((X, Y), dim=0), torch.cat((X, Y), dim=1)
''' 輸出:
(tensor([[ 0., 1., 2., 3.],[ 4., 5., 6., 7.],[ 8., 9., 10., 11.],[ 2., 1., 4., 3.],[ 1., 2., 3., 4.],[ 4., 3., 2., 1.]]),tensor([[ 0., 1., 2., 3., 2., 1., 4., 3.],[ 4., 5., 6., 7., 1., 2., 3., 4.],[ 8., 9., 10., 11., 4., 3., 2., 1.]]))
'''
單個張量 (tensor.sum()求和 & 轉int/float)
.sum() 聚合所有 值轉換為一個值;.item() 將其轉換為 Python 數值使用。
agg = tensor.sum() # 所有元素求和,返回新的張量(標量張量)
agg_item = agg.item() # 將標量張量agg轉成Python的基本數據類型(如 float或int,具體取決于張量中數據的類型)
print(agg_item, type(agg_item))# 輸出 agg的值為 tensor(12.)
a = torch.tensor([3.5])
a, a.item(), float(a), int(a)
# (tensor([3.5000]), 3.5, 3.5, 3)
算術運算
矩陣乘法 和 元素積(逐元素乘積)
計算兩張量間的 矩陣乘法 和 元素積(逐元素乘積)
# 計算兩個張量間的矩陣乘法
# This computes the matrix multiplication between two tensors. y1, y2, y3 will have the same value
# ``tensor.T`` 返回張量的轉置 returns the transpose of a tensor
y1 = tensor @ tensor.T # “@”是矩陣乘法的簡寫,用于張量之間的矩陣乘法; tensor.T 返回 tensor 的轉置
y2 = tensor.matmul(tensor.T) # matmul用于矩陣乘法,與 @ 功能等價y3 = torch.rand_like(y1) # 創建與 y1 形狀相同的新張量,元素為隨機值
torch.matmul(tensor, tensor.T, out=y3) # 進行矩陣乘法,并將結果存儲在 y3 中print("Matrix Multiplication Results:") # y1, y2, y3 三者相等
print("y1:\n", y1)
print("y2:\n", y2)
print("y3:\n", y3)# 計算元素積
# This computes the element-wise product. z1, z2, z3 will have the same value
z1 = tensor * tensor # 對 tensor 進行逐元素相乘
z2 = tensor.mul(tensor) # 與 * 相同的逐元素相乘z3 = torch.rand_like(tensor)
torch.mul(tensor, tensor, out=z3) # 使用 torch.mul 函數對 tensor 進行逐元素相乘,并將結果存儲在 z3 中print("\nElement-wise Product Results:") # z1, z2, z3 三者相等
print("z1:\n", z1)
print("z2:\n", z2)
print("z3:\n", z3)
按元素操作 (加減乘除 **冪 等)
對于任意具有相同形狀的張量, 常見的標準算術運算符(+
、-
、*
、/
和**
)都可以被升級為按元素運算。 我們可以在同一形狀的任意兩個張量上調用按元素操作。
在下面的例子中,使用逗號來表示一個具有5個元素的元組,其中每個元素都是按元素操作的結果。
x = torch.tensor([1.0, 2, 4, 8])
y = torch.tensor([2, 2, 2, 2])
x + y, x - y, x * y, x / y, x ** y # **運算符是求冪運算
''' 輸出:
(tensor([ 3., 4., 6., 10.]),tensor([-1., 0., 2., 6.]),tensor([ 2., 4., 8., 16.]),tensor([0.5000, 1.0000, 2.0000, 4.0000]),tensor([ 1., 4., 16., 64.]))
'''
邏輯運算符構建二元張量
以X == Y為例: 對于每個位置,如果X和Y在該位置相等,則新張量中相應項的值為1。 這意味著邏輯語句X == Y在該位置處為真,否則該位置為0。
X = torch.arange(12, dtype=torch.float32).reshape((3,4))
Y = torch.tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
X == Y
''' 輸出:
tensor([[False, True, False, True],[False, False, False, False],[False, False, False, False]])
'''
torch.exp() 對張量中每個元素計算自然指數
“按元素”方式可以應用更多的計算,包括像求冪這樣的一元運算符:
用于對張量中每個元素計算自然指數函數??的函數,常用于?實現 softmax、log-normalization、指數增長建模?等場景:
對輸入張量中的每個元素??執行:
其中??
import torch
x = torch.tensor([1.0, 2, 4, 8])
torch.exp(x)
''' 輸出:
tensor([2.7183e+00, 7.3891e+00, 5.4598e+01, 2.9810e+03])
'''x = torch.tensor([0.0, 1.0, 2.0])
y = torch.exp(x)
print(y) # 輸出:tensor([1.0000, 2.7183, 7.3891])x = torch.tensor([[0.0, -1.0], [1.0, -2.0]])
print(torch.exp(x))
''' 輸出:
tensor([[1.0000, 0.3679],[2.7183, 0.1353]])
'''
常見應用:Softmax 的實現
x = torch.tensor([1.0, 2.0, 3.0]) softmax = torch.exp(x) / torch.sum(torch.exp(x)) print(softmax)
常見應用:概率建模中的對數概率反變換
log_probs = torch.tensor([-1.0, -2.0]) probs = torch.exp(log_probs)
常見應用:正態化、注意力機制等。
注意:
- 輸入為負數時,輸出仍為正數(因為?
?對任意實數?
?成立)。
- 大數值可能導致數值溢出(輸出為?
inf
),因此常配合數值穩定性處理(如在 softmax 前減去最大值)使用。
使用NumPy橋接
- 共享內存:Tensor 和 NumPy 數組在?
.numpy()
?和?torch.from_numpy()
?轉換時,會 共享底層內存(共享底層數據存儲),因此對一方的修改會直接影響另一方。 - 潛在風險:如果對共享內存的張量或數組進行了非原地安全的操作(如直接賦值),可能導致數據競爭或意外覆蓋。
以下例子中 t
?和?n
?的值始終同步,因為它們共享相同的內存。這種特性在需要高效數據傳遞時非常有用,但需要謹慎操作以避免數據競爭。
Tensor 轉 NumPy 數組
t = torch.ones(5) # 創建一個包含 5 個 1.0 的張量
print(f"t: {t}")# 將張量 t 轉換為 NumPy 數組
n = t.numpy() # .numpy() 方法將 PyTorch 張量轉換為 NumPy 數組
print(f"n: {n}")
張量的變化反映在 NumPy 數組中:
t.add_(1) # 使用 add_ 進行原地加法
print(f"t: {t}")
print(f"n: {n}") # n 的值也會改變,因為 t 和 n 共享內存
NumPy 數組 轉 Tensor
n = np.ones(5) # 創建一個包含 5 個 1.0 的 NumPy 數組
t = torch.from_numpy(n) # 將 NumPy 數組轉換為 PyTorch 張量
NumPy 數組中的更改反映在張量中:
np.add(n, 5, out=n) # 對 NumPy 數組,使用 out 參數 進行原地加法操作
print(f"t: {t}") # 由于 t 和 n 底層共享內存,t 的值也會隨之改變
print(f"n: {n}")
x = torch.arange(12) # 創建行向量 x,其包含以0開始的前12個整數,默認創建為整數
# 除非額外指定,新的張量將存儲在內存中,并采用基于CPU的計算。
# 輸出:tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])X = x.reshape(3, 4) # 改變張量的形狀,而不改變其元素數量和元素值。
'''
把張量x從形狀為(12,)的行向量轉換為形狀為(3,4)的矩陣。
這個新的張量包含與轉換前相同的值,但是它被看成一個3行4列的矩陣。
重點說明:雖然張量的形狀發生了改變,但其元素值并沒有變。
注意,通過改變張量的形狀,張量的大小不會改變。
輸出:
tensor([[ 0, 1, 2, 3],[ 4, 5, 6, 7],[ 8, 9, 10, 11]])
'''A = X.numpy()
B = torch.tensor(A)
type(A), type(B) # 輸出:(numpy.ndarray, torch.Tensor)
廣播機制(形狀不同的兩個矩陣)
在上面的部分中,我們看到了如何在相同形狀的兩個張量上執行按元素操作。 在某些情況下,即使形狀不同,我們仍然可以通過調用?廣播機制(broadcasting mechanism)來執行按元素操作。 這種機制的工作方式如下:
-
通過適當復制元素來擴展一個或兩個數組,以便在轉換之后,兩個張量具有相同的形狀;
-
對生成的數組執行按元素操作。
在大多數情況下,我們將沿著數組中長度為1的軸進行廣播,如下例子:
a = torch.arange(3).reshape((3, 1))
b = torch.arange(2).reshape((1, 2))
a, b
''' 輸出
(tensor([[0],[1],[2]]),tensor([[0, 1]]))
'''
由于a
和b
分別是形狀不同的 3*3 和 1*2 矩陣,若讓它們相加,它們的形狀不匹配。 我們將兩個矩陣廣播為一個更大的3*2 矩陣,如下所示:
# 矩陣a將復制列, 矩陣b將復制行,然后再按元素相加。
a + b
''' 輸出
tensor([[0, 1],[1, 2],[2, 3]])
'''
節省內存
運行一些操作可能會導致為新結果分配內存。
例如,執行?Y?=?X?+?Y
,我們將取消引用Y
指向的張量,而是指向新分配的內存處的張量。
id() 返回內存中引用對象的確切地址
before = id(Y)
Y = Y + X
id(Y) == before # 輸出:False
如上,運行Y = Y + X后,id(Y) 指向了另一個位置。 這是因為Python首先計算Y + X,為結果分配新的內存,然后使Y指向內存中的這個新位置。
這可能是不可取的,原因有兩個:
-
在機器學習中,可能會有數百兆的參數,且在一秒內多次更新所有參數。因此,為避免不必要地分配內存,我們希望原地執行這些參數更新;
-
若不原地更新,其他引用仍然會指向舊的內存位置,這樣某些代碼可能會無意中引用舊的參數。
執行原地操作 (避免不必要地分配內存)
使用切片表示法將操作的結果分配給先前分配的數組,例如Y[:]?=?<expression>
。
Z = torch.zeros_like(Y) # 創建 形狀與Y相同的新矩陣Z,zeros_like將元素設全0
print('id(Z):', id(Z))
Z[:] = X + Y
print('id(Z):', id(Z))
'''
id(Z): 140327634811696
id(Z): 140327634811696
'''
若后續計算中沒有重復使用X
,也可以使用?X[:]=X+Y?
或?X+=Y?
來減少操作的內存開銷:
before = id(X)
X += Y
id(X) == before # 輸出:True
In-place 操作
add_
?是一個 in-place 操作,會直接修改原張量?tensor
?的值,而不會創建新的張量。- 若不想修改原張量,可使用非 in-place 操作?
tensor + 5
,這樣會返回一個新的張量,而原張量保持不變。
# 使用 in-place 操作對張量中的每個元素加 5
print(f"{tensor} \n")
tensor.add_(5) # add_ 是 in-place 操作,會直接修改原張量
print(tensor)
in-place 的優缺點
優點:節省內存。(直接在原張量上操作,避免額外分配內存)
缺點:因為是直接修改原數據,會丟失歷史記錄,因此不鼓勵使用。