Lesson 3.张量的广播和科学运算
- 數學運算與算子
??作為PyTorch中執行深度學習的基本數據類型,張量(Tensor)也擁有非常多的數學運算函數和方法,以及對應的一系列計算規則。在PyTorch中,能夠作用與Tensor的運算,被統一稱作為算子。并且相比于NumPy,PyTorch給出了更加規范的算子(運算)的分類,從而方便用戶在不同場景下調用不同類型的算子(運算)。
- 數學運算的分類
PyToch總共為Tensor設計了六大類數學運算,分別是:- 1.逐點運算(Pointwise Ops):指的是針對Tensor中每個元素執行的相同運算操作;
- 2.規約運算(Reduction Ops):指的是對于某一張量進行操作得出某種總結值;
- 3.比較運算(Comparison Ops):指的是對多個張量進行比較運算的相關方法;
- 4.譜運算(Spectral Ops):指的是涉及信號處理傅里葉變化的操作;
- 5.BLAS和LAPACK運算:指的是基礎線性代數程序集(Basic Linear Algeria Subprograms)和線性代數包(Linear Algeria Package)中定義的、主要用于線性代數科學計算的函數和方法;
- 6.其他運算(Other Ops):其他未被歸類的數學運算。
??由于譜運算(Spectral Ops)前期不會涉及,而要理解傅里葉變換本身需要更多額外的數學基礎,而很多其他運算,我們在前面介紹張量的基本方法時已經介紹,因此接下來將主要圍繞逐點運算、規約運算、比較運算和線性代數運算四塊進行講解,而線性代數部分由于涉及到大量的數學內容,因此將放在Lesson 4中單獨進行講解。
import torch import numpy as np關于數學運算的另一種分類方法,是根據運算使用場景進行分類,如基礎數學運算、數理統計運算等。由于PyTorch官網是按照六類算子進行的分類,因此本節將結合兩種分類方法進行講解。
一、張量的廣播(Broadcast)特性
??在具體介紹張量的運算操作之前,我們先要了解張量的運算規則,其中最重要的一點,就是張量具備和NumPy相同的廣播特性,也就是允許不同形狀的張量之間進行計算。
1.相同形狀的張量計算
??根據官網說法,“same shapes are always broadcastable”,相同形狀數組總是可以進行廣播計算。這里簡單強調一下,雖然我們往往覺得不同形狀之間的張量計算才是應用到廣播特性,但其實相同形狀的張量計算,盡管是對應位置元素進行計算,但本質上也是應用到了廣播特性。
t1 = torch.arange(3) t1 #tensor([0, 1, 2])t1 + t1 # 對應位置元素依次相加 #tensor([0, 2, 4])思考:如果是兩個list相加,是什么結果?
2.不同形狀的張量計算
??廣播的特性是在不同形狀的張量進行計算時,一個或多個張量通過隱式轉化,轉化成相同形狀的兩個張量,從而完成計算的特性。但并非任何兩個不同形狀的張量都可以通過廣播特性進行計算,因此,我們需要了解廣播的基本規則及其核心依據。
2.1 標量和任意形狀的張量
??標量可以和任意形狀的張量進行計算,計算過程就是標量和張量的每一個元素進行計算。
t1 + 1 # 1是標量,可以看成是零維 #tensor([1, 2, 3])# 二維加零維 t1 + torch.tensor(1) #tensor([1, 2, 3])t2 = torch.zeros((3, 4)) t2 #tensor([[0., 0., 0., 0.], # [0., 0., 0., 0.], # [0., 0., 0., 0.]])t2 + 1 #tensor([[1., 1., 1., 1.], # [1., 1., 1., 1.], # [1., 1., 1., 1.]])2.2 相同維度、不同形狀的張量之間計算
??對于不同形狀的張量計算,我們首先需要回顧張量的形狀屬性,并深化對其的理解。
??首先,我們都知道,張量的形狀可以用.shape屬性查看
t2.shape #torch.Size([3, 4])? ? ? ?對于返回結果,我們可以看成是一個序列,代表著張量各維度的信息。當然,對于二維張量,由于我們可以將其視作一個矩陣,因此我們可以說t2是一個擁有三行四列的二維張量,但這種理解方式對于更高維度張量就存在一定的局限,因此我們需要樹立另外一種理解方法,那就是:t2是由3個一維張量組成,并且該一維張量、每個都包含四個元素。類似的,我們可以創建更高維度張量并對其形狀進行解釋。
t3 = torch.zeros(3, 4, 5) t3 #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., 0.], # [0., 0., 0., 0., 0.], # [0., 0., 0., 0., 0.], # [0., 0., 0., 0., 0.]], # # [[0., 0., 0., 0., 0.], # [0., 0., 0., 0., 0.], # [0., 0., 0., 0., 0.], # [0., 0., 0., 0., 0.]]])t3.shape #torch.Size([3, 4, 5])我們可以將t3解釋為:t3是3個二維張量組成了三維張量,并且這些每個二維張量,都是由四個包含五個元素的一維張量所組成。由二維拓展至三維,即可拓展至N維。
接下來,我們以t2為例,來探討相同維度、不同形狀的張量之間的廣播規則。
t2 #tensor([[0., 0., 0., 0.], # [0., 0., 0., 0.], # [0., 0., 0., 0.]])t2.shape #torch.Size([3, 4])t21 = torch.ones(1, 4) t21 #tensor([[1., 1., 1., 1.]])t21的形狀是(1, 4),和t2的形狀(3, 4)在第一個分量上取值不同,但該分量上t21取值為1,因此可以廣播,也就可以進行計算
t21 + t2 #tensor([[1., 1., 1., 1.], # [1., 1., 1., 1.], # [1., 1., 1., 1.]])而t21和t2的實際計算過程如下:
注意理解:此處的廣播相當于將t21的形狀(1, 4)拓展成了t2的(3, 4),也就是復制了第一行三次,然后二者進行相加。當然,也可以理解成t21的第一行和t2的三行分別進行了相加。?
t22 = torch.ones(3, 1) t22 #tensor([[1.], # [1.], # [1.]])t2 #tensor([[0., 0., 0., 0.], # [0., 0., 0., 0.], # [0., 0., 0., 0.]])t2.shape #torch.Size([3, 4])t22 + t2 # 形狀為(3,1)的張量和形狀為(3,4)的張量相加,可以廣播 #tensor([[1., 1., 1., 1.], # [1., 1., 1., 1.], # [1., 1., 1., 1.]])t2和t22實際計算過程如下:?
t23 = torch.ones(2, 4) t23.shape #torch.Size([2, 4])t2.shape #torch.Size([3, 4])'''注:此時t2和t23的形狀第一個分量維度不同,但二者取值均不為1,因此無法廣播''' t2 + t23 --------------------------------------------------------------------------- #RuntimeError Traceback (most recent call last) #<ipython-input-21-994547ec6516> in <module> #----> 1 t2 + t23 # #RuntimeError: The size of tensor a (3) must match the size of tensor b (2) at non-#singleton dimension 0 t24 = torch.arange(3).reshape(3, 1) t24 #tensor([[0], # [1], # [2]])t25 = torch.arange(3).reshape(1, 3) t25 #tensor([[0, 1, 2]])'''此時,t24的形狀是(3, 1),而t25的形狀是(1, 3),二者的形狀在兩個份量上均不相同,但都有存在1的情況,因此也是可以廣播的''' t24 + t25 #tensor([[0, 1, 2], # [1, 2, 3], # [2, 3, 4]])二者計算過程如下:
?三維張量的廣播
t3 = torch.zeros(3, 4, 5) t3 #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., 0.], # [0., 0., 0., 0., 0.], # [0., 0., 0., 0., 0.], # [0., 0., 0., 0., 0.]], # # [[0., 0., 0., 0., 0.], # [0., 0., 0., 0., 0.], # [0., 0., 0., 0., 0.], # [0., 0., 0., 0., 0.]]])t31 = torch.ones(3, 4, 1) t31 #tensor([[[1.], # [1.], # [1.], # [1.]], # # [[1.], # [1.], # [1.], # [1.]], # # [[1.], # [1.], # [1.], # [1.]]])t3 + t31 #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., 1.], # [1., 1., 1., 1., 1.], # [1., 1., 1., 1., 1.], # [1., 1., 1., 1., 1.]], # # [[1., 1., 1., 1., 1.], # [1., 1., 1., 1., 1.], # [1., 1., 1., 1., 1.], # [1., 1., 1., 1., 1.]]])t32 = torch.ones(3, 1, 5) t32 #tensor([[[1., 1., 1., 1., 1.]], # # [[1., 1., 1., 1., 1.]], # # [[1., 1., 1., 1., 1.]]])t32 + t3 #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., 1.], # [1., 1., 1., 1., 1.], # [1., 1., 1., 1., 1.], # [1., 1., 1., 1., 1.]], # # [[1., 1., 1., 1., 1.], # [1., 1., 1., 1., 1.], # [1., 1., 1., 1., 1.], # [1., 1., 1., 1., 1.]]])兩個張量的形狀上有兩個分量不同時,只要不同的分量仍然有一個取值為1,則仍然可以廣播?
t3.shape #torch.Size([3, 4, 5])t33 = torch.ones(1, 1, 5) t33 #tensor([[[1., 1., 1., 1., 1.]]])t3 + t33 #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., 1.], # [1., 1., 1., 1., 1.], # [1., 1., 1., 1., 1.], # [1., 1., 1., 1., 1.]], # # [[1., 1., 1., 1., 1.], # [1., 1., 1., 1., 1.], # [1., 1., 1., 1., 1.], # [1., 1., 1., 1., 1.]]])?t3和t33計算過程如下
注:此處標注的兩次廣播,我們也可認為上述全部過程的實現是一次“大的”廣播。同時,此處最開始的t33也就相當于一個一維的、包含五個元素的張量,因此上述過程也可視為一個一維張量和一個三維張量計算時的廣播過程。
2.3 不同維度的張量計算過程中廣播
??在理解相同維度、不同形狀的張量廣播之后,對于不同維度的張量之間的廣播其實就會容易很多,因為對于不同維度的張量,我們首先可以將低維的張量升維,然后依據相同維度不同形狀的張量廣播規則進行廣播。而低維向量的升維也非常簡單,只需將更高維度方向的形狀填充為1即可,例如:
# 二維張量轉化為三維張量 t2 = torch.arange(4).reshape(2, 2) t2 #tensor([[0, 1], # [2, 3]])# 轉化為三維張量 t2.reshape(1, 2, 2) #tensor([[[0, 1], # [2, 3]]]) '''轉化之后表示只包含一個二維張量的三維張量,且二維張量就是t2'''# 轉化為四維張量 t2.reshape(1, 1, 2, 2) #tensor([[[[0, 1], # [2, 3]]]]) '''轉化之后表示只包含一個三維張量的四維張量,且三維張量只包含一個二維張量,且二維張量就是t2'''t3 = torch.zeros(3, 2, 2) '''t3和t2的計算過程,就相當于形狀為(1,2,2)和(3,2,2)的兩個張量進行計算'''t3 + t2 #tensor([[[0., 1.], # [2., 3.]], # # [[0., 1.], # [2., 3.]], # # [[0., 1.], # [2., 3.]]])t3 + t2.reshape(1, 2, 2) #tensor([[[0., 1.], # [2., 3.]], # # [[0., 1.], # [2., 3.]], # # [[0., 1.], # [2., 3.]]])思考:形狀為(2,1)的張量和形狀為(3,2,3)的張量可以進行廣播計算么?計算過程是怎樣的?
二、逐點運算(Pointwise Ops)
??PyTorch中逐點運算大部分都是可以針對Tensor中每個元素都進行的數學科學運算,并且都是較為通用的數學科學運算,和NumPy中針對Array的科學運算類似。在PyTorch中文文檔中有全部運算符的相關介紹,此處僅針對常用計算函數進行介紹。
??逐點運算主要包括數學基本運算、數值調整運算和數據科學運算三塊,相關函數如下:
Tensor基本數學運算
t1 = torch.tensor([1, 2]) t1 #tensor([1, 2])t2 = torch.tensor([3, 4]) t2 #tensor([3, 4])torch.add(t1, t2) #tensor([4, 6])t1 + t2 #tensor([4, 6])t1 / t2 #tensor([0.3333, 0.5000])Tensor數值調整函數
t = torch.randn(5) t #tensor([ 1.1971, 1.7523, 1.5678, -2.2227, -0.3082])torch.round(t) #tensor([ 1., 2., 2., -2., -0.])torch.abs(t) #tensor([1.1971, 1.7523, 1.5678, 2.2227, 0.3082])torch.neg(t) #tensor([-1.1971, -1.7523, -1.5678, 2.2227, 0.3082])'''注:雖然此類型函數是數值調整函數,但并不會對原對象進行調整,而是輸出新的結果。''' t # t本身并未發生變化 #tensor([ 1.1971, 1.7523, 1.5678, -2.2227, -0.3082])而若要對原對象本身進行修改,則可考慮使用方法_()的表達形式,對對象本身進行修改。此時方法就是上述同名函數。
t.abs_() #tensor([1.1971, 1.7523, 1.5678, 2.2227, 0.3082])t #tensor([1.1971, 1.7523, 1.5678, 2.2227, 0.3082])t.neg_() #tensor([-1.1971, -1.7523, -1.5678, -2.2227, -0.3082])t #tensor([-1.1971, -1.7523, -1.5678, -2.2227, -0.3082])'''除了上述數值調整函數有對應的同名方法外,本節介紹的許多科學計算都有同名方法。''' t.exp_() #tensor([0.3021, 0.1734, 0.2085, 0.1083, 0.7348])t #tensor([0.3021, 0.1734, 0.2085, 0.1083, 0.7348])Tensor常用科學計算
- tensor的大多數科學計算只能作用于tensor對象
理解:相比于Python原生數據類型,張量是一類更加特殊的對象,例如張量可以指定運行在CPU或者GPU上,因此很多張量的科學計算函數都不允許張量和Python原生的數值型對象混合使用。
- tensor的大多數科學運算具有一定的靜態性
??所謂靜態性,指的是對輸入的張量類型有明確的要求,例如部分函數只能輸入浮點型張量,而不能輸入整型張量。
t = torch.arange(1, 4) t.dtype #torch.int64torch.exp(t) #tensor([ 2.7183, 7.3891, 20.0855])需要注意的是,雖然Python是動態編譯的編程語言,但在PyTorch中,由于會涉及GPU計算,因此很多時候元素類型不會在實際執行函數計算時進行調整。此處的科學運算大多數都要求對象類型是浮點型,我們需要提前進行類型轉化。
t1 = t.float() t1 #tensor([1., 2., 3.])torch.exp(t1) #tensor([ 2.7183, 7.3891, 20.0855])torch.expm1(t1) #tensor([ 1.7183, 6.3891, 19.0855])注,此處返回結果是𝑒^𝑡?1,在數值科學計算中,expm1函數和log1p函數是一對對應的函數關系,后面再介紹log1p的時候會講解這對函數的實際作用。
torch.exp2(t1) #tensor([2., 4., 8.])torch.pow(t, 2) #tensor([1, 4, 9]) '''注意區分2的t次方和t的2次方'''torch.square(t) #tensor([1, 4, 9])torch.sqrt(t1) #tensor([1.0000, 1.4142, 1.7321])torch.pow(t1, 0.5) #tensor([1.0000, 1.4142, 1.7321]) '''開根號也就相當于0.5次冪'''torch.log10(t1) #tensor([0.0000, 0.3010, 0.4771])torch.log(t1) #tensor([0.0000, 0.6931, 1.0986])torch.log2(t1) #tensor([0.0000, 1.0000, 1.5850])同時,我們也可簡單回顧冪運算和對數運算之間的關系
torch.exp(torch.log(t1)) #tensor([1., 2., 3.])torch.exp2(torch.log2(t1)) #tensor([1., 2., 3.])a = torch.tensor(-1).float() a #tensor(-1.)torch.exp2(torch.log2(a)) #tensor(nan)- 排序運算:sort
??在PyTorch中,sort排序函數將同時返回排序結果和對應的索引值的排列。
t = torch.tensor([1.0, 3.0, 2.0]) t #tensor([1., 3., 2.])# 升序排列 torch.sort(t) #torch.return_types.sort( #values=tensor([1., 2., 3.]), #indices=tensor([0, 2, 1]))# 降序排列 torch.sort(t, descending=True) #torch.return_types.sort( #values=tensor([3., 2., 1.]), #indices=tensor([1, 2, 0]))三、規約運算
??規約運算指的是針對某張量進行某種總結,最后得出一個具體總結值的函數。此類函數主要包含了數據科學領域內的諸多統計分析函數,如均值、極值、方差、中位數函數等等。
Tensor統計分析函數
# 生成浮點型張量 t = torch.arange(10).float() t #tensor([0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])# 計算均值 torch.mean(t) #tensor(4.5000)# 計算標準差、均值 torch.std_mean(t) #(tensor(3.0277), tensor(4.5000))# 計算最大值 torch.max(t) #tensor(9.)# 返回最大值的索引 torch.argmax(t) #tensor(9)# 計算中位數 torch.median(t) #tensor(4.)# 求和 torch.sum(t) #tensor(45.)# 求積 torch.prod(t) #tensor(0.)torch.prod(torch.tensor([1, 2, 3])) #tensor(6)torch.topk(t, 2)
- dist計算距離
??dist函數可計算閔式距離(閔可夫斯基距離),通過輸入不同的p值,可以計算多種類型的距離,如歐式距離、街道距離等。閔可夫斯基距離公式如下:
p取值為2時,計算歐式距離
t1 = torch.tensor([1.0, 2]) t1 #tensor([1., 2.])t2 = torch.tensor([3.0, 4]) t2 #tensor([3., 4.])t #tensor([0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])torch.dist(t1, t2, 2) #tensor(2.8284)torch.sqrt(torch.tensor(8.0)) #tensor(2.8284)'''p取值為1時,計算街道距離''' torch.dist(t1, t2, 1) #tensor(4.)- 規約運算的維度
??由于規約運算是一個序列返回一個結果,因此若是針對高維張量,則可指定某維度進行計算。
# 創建一個3*3的二維張量 t2 = torch.arange(12).float().reshape(3, 4) t2 #tensor([[ 0., 1., 2., 3.], # [ 4., 5., 6., 7.], # [ 8., 9., 10., 11.]])t2.shape #torch.Size([3, 4])# 按照第一個維度求和(每次計算三個)、按列求和 torch.sum(t2, dim = 0) #tensor([12., 15., 18., 21.])# 按照第二個維度求和(每次計算四個)、按行求和 torch.sum(t2, dim = 1) #tensor([ 6., 22., 38.])# 創建一個2*3*4的三維張量 t3 = torch.arange(24).float().reshape(2, 3, 4) t3 #tensor([[[ 0., 1., 2., 3.], # [ 4., 5., 6., 7.], # [ 8., 9., 10., 11.]], # # [[12., 13., 14., 15.], # [16., 17., 18., 19.], # [20., 21., 22., 23.]]])t3.shape #torch.Size([2, 3, 4])torch.sum(t3, dim = 0) #tensor([[12., 14., 16., 18.], # [20., 22., 24., 26.], # [28., 30., 32., 34.]]) #. 0+12 1+13...torch.sum(t3, dim = 1) #tensor([[12., 15., 18., 21.], # [48., 51., 54., 57.]])torch.sum(t3, dim = 2) #tensor([[ 6., 22., 38.], # [54., 70., 86.]])理解:dim參數和shape返回結果一一對應。
- 二維張量的排序
和上述過程類似,在進行排序過程中,二維張量也可以按行或者按列進行排序
t22 = torch.randn(3, 4) # 創建二維隨機數張量 t22 #tensor([[-0.7781, -2.3530, 1.6711, -0.2056], # [-0.2429, 0.6134, 0.8724, 0.5893], # [ 1.2448, 0.3643, -0.2511, 1.4750]])# 默認情況下,是按照行進行升序排序 torch.sort(t22) #torch.return_types.sort( #values=tensor([[-2.3530, -0.7781, -0.2056, 1.6711], # [-0.2429, 0.5893, 0.6134, 0.8724], # [-0.2511, 0.3643, 1.2448, 1.4750]]), #indices=tensor([[1, 0, 3, 2], # [0, 3, 1, 2], # [2, 1, 0, 3]]))# 修改dim和descending參數,使得按列進行降序排序 torch.sort(t22, dim = 0, descending=True) #torch.return_types.sort( #values=tensor([[ 1.2448, 0.6134, 1.6711, 1.4750], # [-0.2429, 0.3643, 0.8724, 0.5893], # [-0.7781, -2.3530, -0.2511, -0.2056]]), #indices=tensor([[2, 1, 0, 2], # [1, 2, 1, 1], # [0, 0, 2, 0]]))四、比較運算
??比較運算是一類較為簡單的運算類型,和Python原生的布爾運算類似,常用于不同張量之間的邏輯運算,最終返回邏輯運算結果(邏輯類型張量)。基本比較運算函數如下所示:
Tensor比較運算函數
t1 = torch.tensor([1.0, 3, 4]) t2 = torch.tensor([1.0, 2, 5])t1 == t2 #tensor([ True, False, False])torch.equal(t1, t2) # 判斷t1、t2是否是相同的張量 #Falsetorch.eq(t1, t2) #tensor([ True, False, False])t1 > t2 #tensor([False, True, False])t1 >= t2 #tensor([ True, True, False])總結
以上是生活随笔為你收集整理的Lesson 3.张量的广播和科学运算的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: LESSON 9.1 随机森林回归器的实
- 下一篇: Lesson 4.张量的线性代数运算