numpy.einsum#

numpy.einsum(subscripts, *operands, out=None, dtype=None, order='K', casting='safe', optimize=False)[原始碼]#

在運算元上評估愛因斯坦求和約定。

使用愛因斯坦求和約定,許多常見的多維度、線性代數陣列運算可以用簡單的方式表示。在隱式模式下,einsum 計算這些值。

顯式模式下,einsum 提供更大的彈性來計算其他陣列運算,這些運算可能不被視為經典的愛因斯坦求和運算,方法是停用或強制對指定的下標標籤求和。

請參閱註釋和範例以獲得澄清。

參數:
subscriptsstr

指定用於求和的下標,以逗號分隔的下標標籤列表形式表示。除非包含顯式指示符「->」以及精確輸出形式的下標標籤,否則執行隱式(經典愛因斯坦求和)計算。

operandsarray_like 列表

這些是用於運算的陣列。

outndarray,選用

如果提供,則將計算結果寫入此陣列。

dtype{資料類型, None},選用

如果提供,則強制計算使用指定的資料類型。請注意,您可能還必須提供更寬鬆的 casting 參數以允許轉換。預設值為 None。

order{‘C’, ‘F’, ‘A’, ‘K’},選用

控制輸出的記憶體佈局。「C」表示它應該是 C 連續的。「F」表示它應該是 Fortran 連續的,「A」表示如果輸入全部為「F」,則應為「F」,否則為「C」。「K」表示它應該盡可能接近輸入的佈局,包括任意置換的軸。預設值為「K」。

casting{‘no’, ‘equiv’, ‘safe’, ‘same_kind’, ‘unsafe’},選用

控制可能發生的資料類型轉換種類。不建議將此設定為「unsafe」,因為它可能會對累積產生不利影響。

  • 「no」表示根本不應轉換資料類型。

  • 「equiv」表示僅允許位元組順序變更。

  • 「safe」表示僅允許保留值的轉換。

  • 「same_kind」表示僅允許安全轉換或種類內的轉換,例如 float64 到 float32。

  • 「unsafe」表示可以進行任何資料轉換。

預設值為「safe」。

optimize{False, True, ‘greedy’, ‘optimal’},選用

控制是否應進行中間優化。如果為 False,則不進行優化,True 將預設為「greedy」演算法。也接受來自 np.einsum_path 函數的顯式收縮列表。請參閱 np.einsum_path 以取得更多詳細資訊。預設值為 False。

傳回值:
outputndarray

基於愛因斯坦求和約定的計算結果。

另請參閱

einsum_pathdotinneroutertensordotlinalg.multi_dot
einsum

einops 套件提供了類似的詳細介面,以涵蓋其他運算:轉置、重塑/展平、重複/平鋪、擠壓/取消擠壓和縮減。opt_einsum 以與後端無關的方式優化類似 einsum 表達式的收縮順序。

註釋

愛因斯坦求和約定可用於計算許多多維度線性代數陣列運算。einsum 提供了一種簡潔的方式來表示這些運算。

以下列出了這些運算(非詳盡列表),這些運算可以由 einsum 計算,以及範例

下標字串是以逗號分隔的下標標籤列表,其中每個標籤都參照對應運算元的維度。每當標籤重複時,它就會被求和,因此 np.einsum('i,i', a, b) 等效於 np.inner(a,b)。如果標籤僅出現一次,則不會求和,因此 np.einsum('i', a) 會產生 a 的視圖,而不會進行任何變更。另一個範例 np.einsum('ij,jk', a, b) 描述了傳統的矩陣乘法,並且等效於 np.matmul(a,b)。一個運算元中重複的下標標籤會取得對角線。例如,np.einsum('ii', a) 等效於 np.trace(a)

隱式模式下,選擇的下標很重要,因為輸出的軸會依字母順序重新排序。這表示 np.einsum('ij', a) 不會影響 2D 陣列,而 np.einsum('ji', a) 會取得其轉置。此外,np.einsum('ij,jk', a, b) 傳回矩陣乘法,而 np.einsum('ij,jh', a, b) 傳回乘法的轉置,因為下標「h」在下標「i」之前。

顯式模式下,可以透過指定輸出下標標籤來直接控制輸出。這需要識別符「->」以及輸出下標標籤的列表。此功能提高了函數的彈性,因為可以在需要時停用或強制求和。呼叫 np.einsum('i->', a) 類似於 np.sum(a)(如果 a 是 1 維陣列),而 np.einsum('ii->i', a) 類似於 np.diag(a)(如果 a 是方形 2 維陣列)。不同之處在於,預設情況下,einsum 不允許廣播。此外,np.einsum('ij,jh->ih', a, b) 直接指定輸出下標標籤的順序,因此傳回矩陣乘法,這與上述隱式模式下的範例不同。

若要啟用和控制廣播,請使用省略號。預設的 NumPy 樣式廣播是透過在每個項目的左側新增省略號來完成的,例如 np.einsum('...ii->...i', a)np.einsum('...i->...', a) 類似於 np.sum(a, axis=-1),適用於任何形狀的陣列 a。若要沿著第一個和最後一個軸取跡,您可以執行 np.einsum('i...i', a);若要使用最左邊的索引而不是最右邊的索引來執行矩陣-矩陣乘積,則可以執行 np.einsum('ij...,jk...->ik...', a, b)

當只有一個運算元,沒有軸被求和,並且未提供輸出參數時,會傳回運算元的視圖,而不是新陣列。因此,將對角線設為 np.einsum('ii->i', a) 會產生一個視圖(在 1.10.0 版中已變更)。

einsum 也提供了一種替代方法來提供下標和運算元,格式為 einsum(op0, sublist0, op1, sublist1, ..., [sublistout])。如果未在此格式中提供輸出形狀,則 einsum 將以隱式模式計算,否則將以顯式模式執行。以下範例具有使用兩種參數方法的對應 einsum 呼叫。

從 einsum 傳回的視圖現在在輸入陣列可寫入時即可寫入。例如,np.einsum('ijk...->kji...', a) 現在將具有與 np.swapaxes(a, 0, 2) 相同的效果,而 np.einsum('ii->i', a) 將傳回 2D 陣列對角線的可寫入視圖。

新增了 optimize 引數,它將優化 einsum 表達式的收縮順序。對於具有三個或更多運算元的收縮,這可以大大提高計算效率,但會以計算期間更大的記憶體佔用量為代價。

通常應用「greedy」演算法,經驗測試表明,在大多數情況下,它會傳回最佳路徑。在某些情況下,「optimal」將透過更昂貴的窮舉搜尋傳回最佳路徑。對於迭代計算,建議計算一次最佳路徑,並透過將其作為引數提供來重複使用該路徑。以下提供一個範例。

請參閱 numpy.einsum_path 以取得更多詳細資訊。

範例

>>> a = np.arange(25).reshape(5,5)
>>> b = np.arange(5)
>>> c = np.arange(6).reshape(2,3)

矩陣的跡

>>> np.einsum('ii', a)
60
>>> np.einsum(a, [0,0])
60
>>> np.trace(a)
60

提取對角線(需要顯式形式)

>>> np.einsum('ii->i', a)
array([ 0,  6, 12, 18, 24])
>>> np.einsum(a, [0,0], [0])
array([ 0,  6, 12, 18, 24])
>>> np.diag(a)
array([ 0,  6, 12, 18, 24])

對軸求和(需要顯式形式)

>>> np.einsum('ij->i', a)
array([ 10,  35,  60,  85, 110])
>>> np.einsum(a, [0,1], [0])
array([ 10,  35,  60,  85, 110])
>>> np.sum(a, axis=1)
array([ 10,  35,  60,  85, 110])

對於更高維度的陣列,可以使用省略號對單個軸求和

>>> np.einsum('...j->...', a)
array([ 10,  35,  60,  85, 110])
>>> np.einsum(a, [Ellipsis,1], [Ellipsis])
array([ 10,  35,  60,  85, 110])

計算矩陣轉置,或重新排序任意數量的軸

>>> np.einsum('ji', c)
array([[0, 3],
       [1, 4],
       [2, 5]])
>>> np.einsum('ij->ji', c)
array([[0, 3],
       [1, 4],
       [2, 5]])
>>> np.einsum(c, [1,0])
array([[0, 3],
       [1, 4],
       [2, 5]])
>>> np.transpose(c)
array([[0, 3],
       [1, 4],
       [2, 5]])

向量內積

>>> np.einsum('i,i', b, b)
30
>>> np.einsum(b, [0], b, [0])
30
>>> np.inner(b,b)
30

矩陣向量乘法

>>> np.einsum('ij,j', a, b)
array([ 30,  80, 130, 180, 230])
>>> np.einsum(a, [0,1], b, [1])
array([ 30,  80, 130, 180, 230])
>>> np.dot(a, b)
array([ 30,  80, 130, 180, 230])
>>> np.einsum('...j,j', a, b)
array([ 30,  80, 130, 180, 230])

廣播和純量乘法

>>> np.einsum('..., ...', 3, c)
array([[ 0,  3,  6],
       [ 9, 12, 15]])
>>> np.einsum(',ij', 3, c)
array([[ 0,  3,  6],
       [ 9, 12, 15]])
>>> np.einsum(3, [Ellipsis], c, [Ellipsis])
array([[ 0,  3,  6],
       [ 9, 12, 15]])
>>> np.multiply(3, c)
array([[ 0,  3,  6],
       [ 9, 12, 15]])

向量外積

>>> np.einsum('i,j', np.arange(2)+1, b)
array([[0, 1, 2, 3, 4],
       [0, 2, 4, 6, 8]])
>>> np.einsum(np.arange(2)+1, [0], b, [1])
array([[0, 1, 2, 3, 4],
       [0, 2, 4, 6, 8]])
>>> np.outer(np.arange(2)+1, b)
array([[0, 1, 2, 3, 4],
       [0, 2, 4, 6, 8]])

張量收縮

>>> a = np.arange(60.).reshape(3,4,5)
>>> b = np.arange(24.).reshape(4,3,2)
>>> np.einsum('ijk,jil->kl', a, b)
array([[4400., 4730.],
       [4532., 4874.],
       [4664., 5018.],
       [4796., 5162.],
       [4928., 5306.]])
>>> np.einsum(a, [0,1,2], b, [1,0,3], [2,3])
array([[4400., 4730.],
       [4532., 4874.],
       [4664., 5018.],
       [4796., 5162.],
       [4928., 5306.]])
>>> np.tensordot(a,b, axes=([1,0],[0,1]))
array([[4400., 4730.],
       [4532., 4874.],
       [4664., 5018.],
       [4796., 5162.],
       [4928., 5306.]])

可寫入的傳回陣列(自 1.10.0 版起)

>>> a = np.zeros((3, 3))
>>> np.einsum('ii->i', a)[:] = 1
>>> a
array([[1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]])

省略號用法的範例

>>> a = np.arange(6).reshape((3,2))
>>> b = np.arange(12).reshape((4,3))
>>> np.einsum('ki,jk->ij', a, b)
array([[10, 28, 46, 64],
       [13, 40, 67, 94]])
>>> np.einsum('ki,...k->i...', a, b)
array([[10, 28, 46, 64],
       [13, 40, 67, 94]])
>>> np.einsum('k...,jk', a, b)
array([[10, 28, 46, 64],
       [13, 40, 67, 94]])

鏈式陣列運算。對於更複雜的收縮,透過重複計算「greedy」路徑或預先計算「optimal」路徑並重複應用它,使用 einsum_path 插入(自 1.12.0 版起),可以實現速度提升。效能提升在較大的陣列中尤其顯著

>>> a = np.ones(64).reshape(2,4,8)

基本 einsum:~1520 毫秒(在 3.1GHz Intel i5 上基準測試。)

>>> for iteration in range(500):
...     _ = np.einsum('ijk,ilm,njm,nlk,abc->',a,a,a,a,a)

次佳 einsum(由於重複路徑計算時間):~330 毫秒

>>> for iteration in range(500):
...     _ = np.einsum('ijk,ilm,njm,nlk,abc->',a,a,a,a,a,
...         optimize='optimal')

Greedy einsum(更快的最佳路徑近似):~160 毫秒

>>> for iteration in range(500):
...     _ = np.einsum('ijk,ilm,njm,nlk,abc->',a,a,a,a,a, optimize='greedy')

Optimal einsum(在某些用例中是最佳使用模式):~110 毫秒

>>> path = np.einsum_path('ijk,ilm,njm,nlk,abc->',a,a,a,a,a,
...     optimize='optimal')[0]
>>> for iteration in range(500):
...     _ = np.einsum('ijk,ilm,njm,nlk,abc->',a,a,a,a,a, optimize=path)