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_path
、dot
、inner
、outer
、tensordot
、linalg.multi_dot
einsum
einops 套件提供了類似的詳細介面,以涵蓋其他運算:轉置、重塑/展平、重複/平鋪、擠壓/取消擠壓和縮減。opt_einsum 以與後端無關的方式優化類似 einsum 表達式的收縮順序。
註釋
愛因斯坦求和約定可用於計算許多多維度線性代數陣列運算。
einsum
提供了一種簡潔的方式來表示這些運算。以下列出了這些運算(非詳盡列表),這些運算可以由
einsum
計算,以及範例陣列的跡,
numpy.trace
。傳回對角線,
numpy.diag
。陣列軸求和,
numpy.sum
。轉置和置換,
numpy.transpose
。- 矩陣乘法和點積,
numpy.matmul
- 矩陣乘法和點積,
- 向量內積和外積,
numpy.inner
- 向量內積和外積,
- 廣播、元素級和純量乘法,
張量收縮,
numpy.tensordot
。- 鏈式陣列運算,以有效率的計算順序,
下標字串是以逗號分隔的下標標籤列表,其中每個標籤都參照對應運算元的維度。每當標籤重複時,它就會被求和,因此
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)