10、Numpy廣播(Broadcast)

1、番外說明

大家好,我是小P,本系列是本人對Python模塊Numpy的一些學習記錄,總結於此一方面方便其它初學者學習,另一方面害怕自己遺忘,希望大家喜歡。此外,對“目標檢測/模型壓縮/語義分割”感興趣的小夥伴,歡迎加入QQ羣 813221712 討論交流,進羣請看羣公告!(可以點擊如下連接直接加入!)
點擊鏈接加入羣聊【Object Detection】:https://jq.qq.com/?_wv=1027&k=5kXCXF8

2、正題

參考鏈接:

https://www.runoob.com/numpy/numpy-broadcast.html
https://cloud.tencent.com/developer/article/1106669

廣播(Broadcast)是 numpy 對不同形狀(shape)的數組進行數值計算的方式, 對數組的算術運算通常在相應的元素上進行。

廣播的規則:

● 讓所有輸入數組都向其中形狀最長的數組看齊,形狀中不足的部分都通過在前面加 1 補齊。
● 輸出數組的形狀是輸入數組形狀的各個維度上的最大值。
● 如果輸入數組的某個維度和輸出數組的對應維度的長度相同或者其長度爲 1 時,這個數組能夠用來計算,否則出錯。
● 當輸入數組的某個維度的長度爲 1 時,沿着此維度運算時都用此維度上的第一組值。
在這裏插入圖片描述

簡單理解:對兩個數組,分別比較他們的每一個維度(若其中一個數組沒有當前維度則忽略),滿足:
● 數組擁有相同形狀。
● 當前維度的值相等。
● 當前維度的值有一個是 1。

若條件不滿足,拋出 “ValueError: frames are not aligned” 異常。

如果兩個數組 a 和 b 形狀相同,即滿足 a.shape == b.shape,那麼 a*b 的結果就是 a 與 b 數組對應位相乘。這要求維數相同,且各維度的長度相同。

實例:計算a*b(shape相同時)

import numpy as np 
a = np.array([1,2,3,4]) 
b = np.array([10,20,30,40]) 
c = a * b 
print (c)

輸出結果爲:

[ 10  40  90 160]

當運算中的 2 個數組的形狀不同時,numpy 將自動觸發廣播機制。如:

實例:計算a*b(shape不相同)

import numpy as np  
a = np.array([[ 0, 0, 0],
           [10,10,10],
           [20,20,20],
           [30,30,30]])
b = np.array([1,2,3])
print(a + b)

輸出結果爲:

[[ 1  2  3]
 [11 12 13]
 [21 22 23]
 [31 32 33]] 

下面的圖片展示了數組 b 如何通過廣播來與數組 a 兼容。
在這裏插入圖片描述
4x3 的二維數組與長爲 3 的一維數組相加,等效於把數組 b 在二維上重複 4 次再運算

實例:首先將b數組元素在二維上覆制4次再運算

import numpy as np 
a = np.array([[ 0, 0, 0],
           [10,10,10],
           [20,20,20],
           [30,30,30]])
b = np.array([1,2,3])
bb = np.tile(b, (4, 1))
print(a + bb)

輸出結果爲:

[[ 1  2  3]
 [11 12 13]
 [21 22 23]
 [31 32 33]]

其中,np.tile(b, (4, 1))是根據數組b來構造數組,複製的維度爲(4,1),首先從低維度開始複製

實例:通過減去列平均值的方式對數組的每一列進行距平化處理

In [82]: arr = np.random.randn(4, 3) 
In [83]: arr.mean(0)
Out[83]: array([-0.3928, -0.3824, -0.8768])

In [84]: demeaned = arr - arr.mean(0)
In [85]: demeaned
Out[85]: 
array([[ 0.3937,  1.7263,  0.1633],
       [-0.4384, -1.9878, -0.9839],
       [-0.468 ,  0.9426, -0.3891],
       [ 0.5126, -0.6811,  1.2097]])

In [86]: demeaned.mean(0)
Out[86]: array([-0.,  0., -0.])

其中:arr.mean(0)用於計算arr矩陣每一列的平均值,爲一個1X3的數組

再來看一下上面那個例子,假設你希望對各行減去那個平均值。由於arr.mean(0)的長度爲3,所以它可以在0軸向上進行廣播:因爲arr的後緣維度是3,所以它們是兼容的。根據該原則,要在1軸向上做減法(即各行減去行平均值),較小的那個數組的形狀必須是(4,1):

In [87]: arr
Out[87]: 
array([[ 0.0009,  1.3438, -0.7135],
       [-0.8312, -2.3702, -1.8608],
       [-0.8608,  0.5601, -1.2659],
       [ 0.1198, -1.0635,  0.3329]])

In [88]: row_means = arr.mean(1)

In [89]: row_means.shape
Out[89]: (4,)

In [90]: row_means.reshape((4, 1))
Out[90]: 
array([[ 0.2104],
       [-1.6874],
       [-0.5222],
       [-0.2036]])

In [91]: demeaned = arr - row_means.reshape((4, 1)) 
In [92]: demeaned.mean(1)
Out[92]: array([ 0., -0.,  0.,  0.])

過程如下圖:
在這裏插入圖片描述
高維度數組的廣播似乎更難以理解,而實際上它也是遵循廣播原則的。如果不然,你就會得到下面這樣一個錯誤:

In [93]: arr - arr.mean(1)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-93-7b87b85a20b2> in <module>()
----> 1 arr - arr.mean(1)
ValueError: operands could not be broadcast together with shapes (4,3) (4,)

人們經常需要通過算術運算過程將較低維度的數組在除0軸以外的其他軸向上廣播。根據廣播的原則,較小數組的“廣播維”必須爲1。在上面那個行距平化的例子中,這就意味着要將行平均值的形狀變成(4,1)而不是(4,):

In [94]: arr - arr.mean(1).reshape((4, 1))
Out[94]: 
array([[-0.2095,  1.1334, -0.9239],
       [ 0.8562, -0.6828, -0.1734],
       [-0.3386,  1.0823, -0.7438],
       [ 0.3234, -0.8599,  0.5365]])

對於三維的情況,在三維中的任何一維上廣播其實也就是將數據重塑爲兼容的形狀而已。下圖說明了要在三維數組各維度上廣播的形狀需求。
在這裏插入圖片描述
於是就有了一個非常普遍的問題(尤其是在通用算法中),即專門爲了廣播而添加一個長度爲1的新軸。雖然reshape是一個辦法,但插入軸需要構造一個表示新形狀的元組。這是一個很鬱悶的過程。因此,NumPy數組提供了一種通過索引機制插入軸的特殊語法。下面這段代碼通過特殊的np.newaxis屬性以及“全”切片來插入新軸:

In [95]: arr = np.zeros((4, 4))

In [96]: arr_3d = arr[:, np.newaxis, :]

In [97]: arr_3d.shape
Out[97]: (4, 1, 4)

In [98]: arr_1d = np.random.normal(size=3)

In [99]: arr_1d[:, np.newaxis]
Out[99]: 
array([[-2.3594],
       [-0.1995],
       [-1.542 ]])

In [100]: arr_1d[np.newaxis, :]
Out[100]: array([[-2.3594, -0.1995, -1.542 ]])

因此,如果我們有一個三維數組,並希望對軸2進行距平化,那麼只需要編寫下面這樣的代碼就可以了:

In [101]: arr = np.random.randn(3, 4, 5)

In [102]: depth_means = arr.mean(2)

In [103]: depth_means
Out[103]: 
array([[-0.4735,  0.3971, -0.0228,  0.2001],
       [-0.3521, -0.281 , -0.071 , -0.1586],
       [ 0.6245,  0.6047,  0.4396, -0.2846]])

In [104]: depth_means.shape
Out[104]: (3, 4)

In [105]: demeaned = arr - depth_means[:, :, np.newaxis]

In [106]: demeaned.mean(2)
Out[106]: 
array([[ 0.,  0., -0., -0.],
       [ 0.,  0., -0.,  0.],
       [ 0.,  0., -0., -0.]])

有些讀者可能會想,在對指定軸進行距平化時,有沒有一種既通用又不犧牲性能的方法呢?實際上是有的,但需要一些索引方面的技巧:

def demean_axis(arr, axis=0):
    means = arr.mean(axis)

    # This generalizes things like [:, :, np.newaxis] to N dimensions
    indexer = [slice(None)] * arr.ndim
    indexer[axis] = np.newaxis
    return arr - means[indexer]

通過廣播設置數組的值

算術運算所遵循的廣播原則同樣也適用於通過索引機制設置數組值的操作。對於最簡單的情況,我們可以這樣做:

In [107]: arr = np.zeros((4, 3))

In [108]: arr[:] = 5

In [109]: arr
Out[109]: 
array([[ 5.,  5.,  5.],
       [ 5.,  5.,  5.],
       [ 5.,  5.,  5.],
       [ 5.,  5.,  5.]])

但是,假設我們想要用一個一維數組來設置目標數組的各列,只要保證形狀兼容就可以了:

In [110]: col = np.array([1.28, -0.42, 0.44, 1.6])
In [111]: arr[:] = col[:, np.newaxis]

In [112]: arr
Out[112]: 
array([[ 1.28,  1.28,  1.28],
       [-0.42, -0.42, -0.42],
       [ 0.44,  0.44,  0.44],
       [ 1.6 ,  1.6 ,  1.6 ]])

In [113]: arr[:2] = [[-1.37], [0.509]]

In [114]: arr
Out[114]: 
array([[-1.37 , -1.37 , -1.37 ],
       [ 0.509,  0.509,  0.509],
       [ 0.44 ,  0.44 ,  0.44 ],
       [ 1.6  ,  1.6  ,  1.6  ]])
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章