數(shù)據(jù)科學(xué) IPython 筆記本 9.7 數(shù)組上的計(jì)算:廣播

9.7 數(shù)組上的計(jì)算:廣播

本節(jié)是《Python 數(shù)據(jù)科學(xué)手冊(cè)》(Python Data Science Handbook)的摘錄。

譯者:飛龍

協(xié)議:CC BY-NC-SA 4.0

我們?cè)谏弦还?jié)中看到,NumPy 的通用函數(shù)如何用于向量化操作,從而消除緩慢的 Python 循環(huán)。向量化操作的另一種方法是使用 NumPy 的廣播功能。廣播只是一組規(guī)則,用于在不同大小的數(shù)組上應(yīng)用二元ufunc(例如,加法,減法,乘法等)。

廣播簡(jiǎn)介

回想一下,對(duì)于相同大小的數(shù)組,二元操作是逐元素執(zhí)行的:

import numpy as np

a = np.array([0, 1, 2])
b = np.array([5, 5, 5])
a + b

# array([5, 6, 7])

廣播允許在不同大小的數(shù)組上執(zhí)行這類二元操作 - 例如,我們可以輕松將數(shù)組和標(biāo)量相加(將其視為零維數(shù)組):

a + 5

# array([5, 6, 7])

我們可以將此視為一個(gè)操作,將值5拉伸或復(fù)制為數(shù)組[5,5,5],并將結(jié)果相加。

NumPy 廣播的優(yōu)勢(shì)在于,這種值的重復(fù)實(shí)際上并沒(méi)有發(fā)生,但是當(dāng)我們考慮廣播時(shí),它是一種有用的心理模型。

我們可以類似地,將其擴(kuò)展到更高維度的數(shù)組。 將兩個(gè)二維數(shù)組相加時(shí)觀察結(jié)果:

M = np.ones((3, 3))
M

'''
array([[ 1.,  1.,  1.],
       [ 1.,  1.,  1.],
       [ 1.,  1.,  1.]])
'''

M + a

'''
array([[ 1.,  2.,  3.],
       [ 1.,  2.,  3.],
       [ 1.,  2.,  3.]])
'''

這里,一維數(shù)組a被拉伸,或者在第二維上廣播,來(lái)匹配M的形狀。

雖然這些示例相對(duì)容易理解,但更復(fù)雜的情況可能涉及兩個(gè)數(shù)組的廣播。請(qǐng)考慮以下示例:

a = np.arange(3)
b = np.arange(3)[:, np.newaxis]

print(a)
print(b)

'''
[0 1 2]
[[0]
 [1]
 [2]]
'''

a + b

'''
array([[0, 1, 2],
       [1, 2, 3],
       [2, 3, 4]])
'''

就像之前我們拉伸或廣播一個(gè)值來(lái)匹配另一個(gè)的形狀,這里我們拉伸a```和b``來(lái)匹配一個(gè)共同的形狀,結(jié)果是二維數(shù)組!

這些示例的幾何圖形為下圖(產(chǎn)生此圖的代碼可以在“附錄”中找到,并改編自 astroML 中發(fā)布的源碼,經(jīng)許可而使用)。

Broadcasting Visual
Broadcasting Visual

淺色方框代表廣播的值:同樣,這個(gè)額外的內(nèi)存實(shí)際上并沒(méi)有在操作過(guò)程中分配,但是在概念上想象它是有用的。

廣播規(guī)則

NumPy 中的廣播遵循一套嚴(yán)格的規(guī)則來(lái)確定兩個(gè)數(shù)組之間的交互:

  • 規(guī)則 1:如果兩個(gè)數(shù)組的維數(shù)不同,則維數(shù)較少的數(shù)組的形狀,將在其左側(cè)填充。
  • 規(guī)則 2:如果兩個(gè)數(shù)組的形狀在任何維度上都不匹配,則該維度中形狀等于 1 的數(shù)組將被拉伸來(lái)匹配其他形狀。
  • 規(guī)則 3:如果在任何維度中,大小不一致且都不等于 1,則會(huì)引發(fā)錯(cuò)誤。

為了講清楚這些規(guī)則,讓我們?cè)敿?xì)考慮幾個(gè)例子。

廣播示例 1

讓我們看一下將二維數(shù)組和一維數(shù)組相加:

M = np.ones((2, 3))
a = np.arange(3)

讓我們考慮這兩個(gè)數(shù)組上的操作。數(shù)組的形狀是。

  • M.shape = (2, 3)
  • a.shape = (3,)

我們?cè)谝?guī)則 1 中看到數(shù)組a的維數(shù)較少,所以我們?cè)谧筮吿畛渌?/p>

  • M.shape -> (2, 3)
  • a.shape -> (1, 3)

根據(jù)規(guī)則 2,我們現(xiàn)在看到第一個(gè)維度不一致,因此我們將此維度拉伸來(lái)匹配:

  • M.shape -> (2, 3)
  • a.shape -> (2, 3)

形狀匹配了,我們看到最終的形狀將是(2, 3)

M + a

'''
array([[ 1.,  2.,  3.],
       [ 1.,  2.,  3.]])
'''

廣播示例 2

我們來(lái)看一個(gè)需要廣播兩個(gè)數(shù)組的例子:

a = np.arange(3).reshape((3, 1))
b = np.arange(3)

同樣,我們將首先寫出數(shù)組的形狀:

  • a.shape = (3, 1)
  • b.shape = (3,)

規(guī)則 1 說(shuō)我們必須填充b的形狀:

  • a.shape -> (3, 1)
  • b.shape -> (1, 3)

規(guī)則 2 告訴我們,我們更新這些中的每一個(gè),來(lái)匹配另一個(gè)數(shù)組的相應(yīng)大小:

  • a.shape -> (3, 3)
  • b.shape -> (3, 3)

因?yàn)榻Y(jié)果匹配,所以這些形狀是兼容的。我們?cè)谶@里可以看到:

a + b

'''
array([[0, 1, 2],
       [1, 2, 3],
       [2, 3, 4]])
'''

廣播示例 3

現(xiàn)在讓我們來(lái)看一個(gè)兩個(gè)數(shù)組不兼容的例子:

M = np.ones((3, 2))
a = np.arange(3)

這與第一個(gè)例子略有不同:矩陣M是轉(zhuǎn)置的。這對(duì)計(jì)算有何影響?數(shù)組的形狀是

  • M.shape = (3, 2)
  • a.shape = (3,)

同樣,規(guī)則 1 告訴我們必須填充a的形狀:

  • M.shape -> (3, 2)
  • a.shape -> (1, 3)

根據(jù)規(guī)則 2,a的第一個(gè)維度被拉伸來(lái)匹配M

  • M.shape -> (3, 2)
  • a.shape -> (3, 3)

現(xiàn)在我們到了規(guī)則 3 - 最終的形狀不匹配,所以這兩個(gè)數(shù)組是不兼容的,正如我們可以通過(guò)嘗試此操作來(lái)觀察:

M + a

'''
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-13-9e16e9f98da6> in <module>()
----> 1 M + a


ValueError: operands could not be broadcast together with shapes (3,2) (3,) 
'''

注意這里潛在的混淆:你可以想象使aM兼容,比如在右邊填充a的形狀,而不是在左邊。但這不是廣播規(guī)則的運(yùn)作方式!

在某些情況下,這種靈活性可能會(huì)有用,但這會(huì)導(dǎo)致潛在的二義性。如果在右側(cè)填充是你想要的,你可以通過(guò)數(shù)組的形狀調(diào)整,來(lái)明確地執(zhí)行此操作(我們將使用“NumPy 數(shù)組基礎(chǔ)”中介紹的np.newaxis關(guān)鍵字):

a[:, np.newaxis].shape

# (3, 1)

M + a[:, np.newaxis]

'''
array([[ 1.,  1.],
       [ 2.,  2.],
       [ 3.,  3.]])
'''

還要注意,雖然我們一直專注于+運(yùn)算符,但這些廣播規(guī)則適用于任何二元ufunc。

例如,這里是logaddexp(a, b)函數(shù),它比原始方法更精確地計(jì)算log(exp(a) + exp(b))

np.logaddexp(M, a[:, np.newaxis])

'''
array([[ 1.31326169,  1.31326169],
       [ 1.69314718,  1.69314718],
       [ 2.31326169,  2.31326169]])
'''

對(duì)于可用的通用函數(shù)的更多信息,請(qǐng)參閱“NumPy 數(shù)組上的計(jì)算:通用函數(shù)”。

實(shí)戰(zhàn)中的廣播

廣播操作是我們將在本書(shū)中看到的許多例子的核心。我們現(xiàn)在來(lái)看一些它們可能有用的簡(jiǎn)單示例。

數(shù)組中心化

在上一節(jié)中,我們看到ufunc允許 NumPy 用戶不再需要顯式編寫慢速 Python 循環(huán)。廣播擴(kuò)展了這種能力。一個(gè)常見(jiàn)的例子是數(shù)據(jù)數(shù)組的中心化。

想象一下,你有一組 10 個(gè)觀測(cè)值,每個(gè)觀測(cè)值由 3 個(gè)值組成。使用標(biāo)準(zhǔn)約定(參見(jiàn)“Scikit-Learn 中的數(shù)據(jù)表示”),我們將其存儲(chǔ)在10x3數(shù)組中:

X = np.random.random((10, 3))

我們可以使用第一維上的“均值”聚合,來(lái)計(jì)算每個(gè)特征的平均值:

Xmean = X.mean(0)
Xmean

# array([ 0.53514715,  0.66567217,  0.44385899])

現(xiàn)在我們可以通過(guò)減去均值(這是一個(gè)廣播操作)來(lái)中心化X數(shù)組:

X_centered = X - Xmean

要仔細(xì)檢查我們是否已正確完成此操作,我們可以檢查中心化的數(shù)組是否擁有接近零的均值:

X_centered.mean(0)

# array([  2.22044605e-17,  -7.77156117e-17,  -1.66533454e-17])

在機(jī)器精度范圍內(nèi),平均值現(xiàn)在為零。

繪制二維函數(shù)

廣播非常有用的一個(gè)地方是基于二維函數(shù)展示圖像。如果我們想要定義一個(gè)函數(shù)z = f(x, y),廣播可用于在網(wǎng)格中計(jì)算函數(shù):

# x 和 y 是從 0 到 5 的 50 步
x = np.linspace(0, 5, 50)
y = np.linspace(0, 5, 50)[:, np.newaxis]

z = np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x)

我們將使用 Matplotlib 繪制這個(gè)二維數(shù)組(這些工具將在“密度和等高線圖”中完整討論):

%matplotlib inline
import matplotlib.pyplot as plt

plt.imshow(z, origin='lower', extent=[0, 5, 0, 5],
           cmap='viridis')
plt.colorbar();
png

結(jié)果是引人注目的二維函數(shù)的圖形。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容