目錄
PyTorch的Tensor,它可以是零維(又稱爲標量或一個數)、一維、二維及多維的數組。Tensor自稱爲神經網絡界的Numpy,它與Numpy相似,二者可以共享內存,且之間的轉換非常方便和高效。不過它們也有不同之處,最大的區別就是Numpy會把ndarray放在CPU中進行加速運算,而由Torch產生的Tensor會放在GPU中進行加速運算(假設當前環境有GPU)。
一、Tensor概述
對Tensor的操作很多,從接口的角度來劃分,可以分爲兩類:
- torch.function:如torch.sum、torch.add等。
- tensor.function:如tensor.view、tensor.add等。
這些操作對大部分Tensor都是等價的,如torch.add(x,y)與x.add(y)等價。在實際使用時,可以根據個人愛好選擇。
如果從修改方式的角度來劃分,可以分爲以下兩類:
- 不修改自身數據:如x.add(y),x的數據不變,返回一個新的Tensor。
- 修改自身數據:如x.add_(y)(運行符帶下劃線後綴),運算結果存在x中,x被修改。
import torch
x = torch.tensor([1, 2])
y = torch.tensor([3, 4])
z = x.add(y)
print(z)
print(x)
x.add_(y)
print(x)
tensor([4, 6])
tensor([1, 2])
tensor([4, 6])
二、創建Tensor
創建Tensor的方法有很多,可以從列表或ndarray等類型進行構建,也可根據指定的形狀構建。下表是常見的創建Tensor的方法。
舉例:
# 根據list數據生成Tensor
t1 = torch.Tensor([[1, 2, 3], [4, 5, 6]])
# 生成和t1形狀一樣的張量,元素初始值爲1
print(torch.ones(t1.size()))
# torch.ones_like(t1)
t2 = torch.linspace(1, 10, 10)
t3 = torch.logspace(1, 10, 10)
print(t2)
print(t3)
t4 = torch.rand(t1.size())
print(t4)
t5 = torch.arange(1, 10, 1)
print(t5)
import numpy as np
t6 = torch.from_numpy(np.array([1, 2, 3]))
print(t6)
tensor([[1., 1., 1.],
[1., 1., 1.]])
tensor([ 1., 2., 3., 4., 5., 6., 7., 8., 9., 10.])
tensor([1.0000e+01, 1.0000e+02, 1.0000e+03, 1.0000e+04, 1.0000e+05, 1.0000e+06,
1.0000e+07, 1.0000e+08, 1.0000e+09, 1.0000e+10])
tensor([[0.5150, 0.6570, 0.0718],
[0.8774, 0.7458, 0.3008]])
tensor([1, 2, 3, 4, 5, 6, 7, 8, 9])
tensor([1, 2, 3], dtype=torch.int32)
注意torch.Tensor與torch.tensor的幾點區別:
- torch.Tensor是torch.empty和torch.tensor之間的一種混合,但是,當傳入數據時,torch.Tensor使用全局默認dtype(FloatTensor),而torch.tensor是從數據中推斷數據類型。
- torch.tensor(1)返回一個固定值1,而torch.Tensor(1)返回一個大小爲1的張量,它是隨機初始化的值。
t1 = torch.Tensor(1)
t2 = torch.tensor(1)
print('t1的值{}, t1的數據類型{}'.format(t1, t1.type()))
print('t2的值{}, t2的數據類型{}'.format(t2, t2.type()))
t1的值tensor([0.]), t1的數據類型torch.FloatTensor
t2的值1, t2的數據類型torch.LongTensor
三、修改Tensor形狀
在處理數據、構建網絡層等過程中,經常需要了解Tensor的形狀、修改Tensor的形狀。與修改Numpy的形狀類似,修改Tenor的形狀也有很多類似函數,下表是tensor常用修改形狀的函數。
舉例:
#生成一個形狀爲2x3的矩陣
x = torch.randn(2, 3)
print(x)
print(x.dim()) #查看x的維度
print(x.numel())
#把x變爲3x2的矩陣
w = x.view(3,2)
print(w)
#把x展平爲1維向量
y = x.view(-1)
print(y)
#添加一個維度
z = torch.unsqueeze(y, 0)
print(z)
tensor([[ 0.7251, -0.4526, 0.5193],
[ 0.6478, -0.7698, -0.0487]])
2
6
tensor([[ 0.7251, -0.4526],
[ 0.5193, 0.6478],
[-0.7698, -0.0487]])
tensor([ 0.7251, -0.4526, 0.5193, 0.6478, -0.7698, -0.0487])
tensor([[ 0.7251, -0.4526, 0.5193, 0.6478, -0.7698, -0.0487]])
torch.view與torch.reshpae的異同:
- reshape()可以由torch.reshape(),也可由torch.Tensor.reshape()調用。但view()只可由torch.Tensor.view()來調用。
- 對於一個將要被view的Tensor,新的size必須與原來的size與stride兼容。否則,在view之前必須調用contiguous()方法。
- 同樣也是返回與input數據量相同,但形狀不同的Tensor。若滿足view的條件,則不會copy,若不滿足,則會copy。
- 如果你只想重塑張量,請使用torch.reshape。如果你還關注內存使用情況並希望確保兩個張量共享相同的數據,請使用torch.view
四、索引操作
Tensor的索引操作與Numpy類似,一般情況下索引結果與源數據共享內存。從Tensor獲取元素除了可以通過索引,也可以藉助一些函數,下表是常用選擇操作函數。
舉例:
#設置一個隨機種子
torch.manual_seed(100)
#生成一個形狀爲2x3的矩陣
x = torch.randn(2, 3)
print(x)
#根據索引獲取第1行,所有數據
print(x[0, :])
#獲取最後一列數據
print(x[:, -1])
#生成是否大於0的Byter張量
mask = x>0
print(mask)
#獲取大於0的值
print(torch.masked_select(x, mask))
#獲取非0下標,即行,列索引
torch.nonzero(mask)
#獲取指定索引對應的值,輸出根據以下規則得到
#out[i][j] = input[index[i][j]][j] # if dim == 0
#out[i][j] = input[i][index[i][j]] # if dim == 1
index1 = torch.LongTensor([[0, 1, 1]])
print(torch.gather(x, 0, index1))
index2 = torch.LongTensor([[0, 1, 1], [1, 1, 1]])
a = torch.gather(x, 1, index2)
print(a)
#把a的值返回到一個2x3的0矩陣中
z = torch.zeros(2, 3)
print(z.scatter_(1, index2, a))
tensor([[ 0.3607, -0.2859, -0.3938],
[ 0.2429, -1.3833, -2.3134]])
tensor([ 0.3607, -0.2859, -0.3938])
tensor([-0.3938, -2.3134])
tensor([[1, 0, 0],
[1, 0, 0]], dtype=torch.uint8)
tensor([0.3607, 0.2429])
tensor([[ 0.3607, -1.3833, -2.3134]])
tensor([[ 0.3607, -0.2859, -0.2859],
[-1.3833, -1.3833, -1.3833]])
tensor([[ 0.3607, -0.2859, 0.0000],
[ 0.0000, -1.3833, 0.0000]])
五、廣播機制
廣播機制是向量運算的重要技巧。PyTorch也支持廣播機制,以下通過幾個示例進行說明。
import torch
import numpy as np
A = np.arange(0, 40, 10).reshape(4, 1)
B = np.arange(0, 3)
print(A)
print(B)
#把ndarray轉換爲Tensor
A1 = torch.from_numpy(A) #形狀爲4x1
B1 = torch.from_numpy(B) #形狀爲3
#Tensor自動實現廣播
C1 = A1+B1
print(C1)
#我們可以根據廣播機制,手工進行配置
#根據規則1,B1需要向A1看齊,把B變爲(1,3)
B2 = B1.unsqueeze(0) #B2的形狀爲1x3
print(B2)
#使用expand函數重複數組,分別的4x3的矩陣
A2=A1.expand(4, 3)
B3=B2.expand(4, 3)
#然後進行相加,C1與C結果一致
C2 = A2 + B3
print(C2)
[[ 0]
[10]
[20]
[30]]
[0 1 2]
tensor([[ 0, 1, 2],
[10, 11, 12],
[20, 21, 22],
[30, 31, 32]], dtype=torch.int32)
tensor([[0, 1, 2]], dtype=torch.int32)
tensor([[ 0, 1, 2],
[10, 11, 12],
[20, 21, 22],
[30, 31, 32]], dtype=torch.int32)
六、逐元素操作
與Numpy一樣,Tensor也有逐元素操作(Element-Wise),且操作內容相似,但使用函數可能不盡相同。大部分數學運算都屬於逐元素操作,其輸入與輸出的形狀相同。下表是常見的逐元素操作。
舉例:
t = torch.randn(1, 3)
print(t)
t1 = torch.randn(3, 1)
t2 = torch.randn(1, 3)
#t+0.1*(t1/t2)
print(torch.addcdiv(t, 0.1, t1, t2))
#計算sigmoid
print(torch.sigmoid(t))
#將t限制在[0,1]之間
print(torch.clamp(t, 0, 1))
#t+2進行就地運算
t.add_(2)
print(t)
tensor([[-0.2586, -0.2510, 0.4770]])
tensor([[-0.3861, -0.1857, 0.6969],
[-0.3915, -0.1830, 0.7061],
[-0.1649, -0.2989, 0.3154]])
tensor([[0.4357, 0.4376, 0.6170]])
tensor([[0.0000, 0.0000, 0.4770]])
tensor([[1.7414, 1.7490, 2.4770]])
這些操作均會創建新的Tensor,如果需要就地操作,可以使用這些方法的下劃線版本,例如abs_。
七、歸併操作
歸併操作顧名思義,就是對輸入進行歸併或合計等操作,這類操作的輸入輸出形狀一般並不相同,而且往往是輸入大於輸出形狀。歸併操作可以對整個Tensor,也可以沿着某個維度進行歸併。下表是常見的歸併操作。
歸併操作一般涉及一個dim參數,指定沿哪個維進行歸併。另一個參數是keepdim,說明輸出結果中是否保留維度1,缺省情況是False,即不保留。
舉例:
# 生成一個含6個數的向量
a = torch.linspace(0, 10, 6)
#使用view方法,把a變爲2x3矩陣
a = a.view((2, 3))
print(a)
#沿y軸方向累加,即dim=0
b = a.sum()
print(b)
#沿y軸方向累加,即dim=0,並保留含1的維度
c = a.sum(dim=0, keepdim=True)
print(c)
print(torch.cumprod(a, 0))
print(torch.cumprod(a, 1))
print(torch.cumsum(a, 1))
print(torch.mean(a, 1))
tensor([[ 0., 2., 4.],
[ 6., 8., 10.]])
tensor(30.)
tensor([[ 6., 10., 14.]])
tensor([[ 0., 2., 4.],
[ 6., 8., 10.]])
tensor([[ 0., 2., 4.],
[ 0., 16., 40.]])
tensor([[ 0., 0., 0.],
[ 6., 48., 480.]])
tensor([[ 0., 2., 6.],
[ 6., 14., 24.]])
tensor([2., 8.])
八、比較操作
比較操作一般是進行逐元素比較,有些是按指定方向比較。下表是常用的比較函數。
舉例:
x = torch.linspace(0, 12, 12).view(4,3)
print(x)
#求所有元素的最大值
print(torch.max(x))
#求y軸方向的最大值
print(torch.max(x, dim=1))
#求最大的2個元素
print(torch.topk(x, 2, dim=0))
tensor([[ 0.0000, 1.0909, 2.1818],
[ 3.2727, 4.3636, 5.4545],
[ 6.5455, 7.6364, 8.7273],
[ 9.8182, 10.9091, 12.0000]])
tensor(12.)
torch.return_types.max(
values=tensor([ 2.1818, 5.4545, 8.7273, 12.0000]),
indices=tensor([2, 2, 2, 2]))
torch.return_types.topk(
values=tensor([[ 9.8182, 10.9091, 12.0000],
[ 6.5455, 7.6364, 8.7273]]),
indices=tensor([[3, 3, 3],
[2, 2, 2]]))
九、矩陣操作
機器學習和深度學習中存在大量的矩陣運算,常用的算法有兩種:一種是逐元素乘法,另外一種是點積乘法。下表是PyTorch中常用的矩陣函數。
【說明】
- Torch的dot與Numpy的dot有點不同,Torch中的dot是對兩個爲1D張量進行點積運算,Numpy中的dot無此限制。
- mm是對2D的矩陣進行點積,bmm對含batch的3D進行點積運算。
- 轉置運算會導致存儲空間不連續,需要調用contiguous方法轉爲連續。
舉例:
a = torch.tensor([2, 3])
b = torch.tensor([3, 4])
print(torch.dot(a, b))
x = torch.randint(10, (2,3))
y = torch.randint(6, (3,4))
print(x)
print(x.t())
print(y)
print(torch.mm(x, y))
x = torch.randint(10, (2,2,3))
y = torch.randint(6, (2,3,4))
print(torch.bmm(x, y))
tensor(18)
tensor([[6, 5, 4],
[1, 1, 3]])
tensor([[6, 1],
[5, 1],
[4, 3]])
tensor([[3, 2, 5, 4],
[2, 1, 1, 0],
[4, 4, 2, 1]])
tensor([[44, 33, 43, 28],
[17, 15, 12, 7]])
tensor([[[10, 50, 42, 43],
[ 2, 60, 21, 46]],
[[37, 72, 72, 0],
[ 4, 14, 49, 0]]])
十、PyTorch與Numpy比較
PyTorch與Numpy有很多類似的地方,並且有很多相同的操作函數名稱,或雖然函數名稱不同但含義相同;當然也有一些雖然函數名稱相同,但含義不盡相同。有些很容易混淆,下面我們把一些主要的區別進行彙總。