看來你對愛因斯坦求和的理解是不正確的。您已經寫出的下標操作具有正確的乘法,但總和超出了錯誤的軸。
想想這是什麼意思:np.einsum('i,ij->i', A, B)
。
A
具有形狀(i,)
和B
具有形狀(i, j)
。
- 將
B
的每一列乘以A
。
- 在
B
的第二軸上的和,即在標記爲j
的軸上。
這給出了形狀(i,) == (3,)
的輸出,而您的下標符號給出形狀(j,) == (4,)
的輸出。你正在總結錯誤的軸。
更多細節:
記住乘法總是首先發生。左手下標告訴np.einsum
函數輸入數組中的哪些行/列/等將相互相乘。該步驟的輸出總是具有與最高維輸入數組相同的形狀。即,在這一點上,假設的「中間」陣列具有形狀(3, 4) == B.shape
。
乘法後有總和。這由右側的省略控制。在這種情況下,j
被省略,這意味着沿數組的第一個軸求和。 (你正在第零次總結。)
如果你改爲寫:np.einsum('i,ij->ij', A, B)
,將會有沒有求和,因爲沒有下標被省略。因此,您可以在問題結束時獲得您擁有的陣列。
這裏有幾個例子:
例1:
沒有省略下標,所以沒有總和。只需將B
的列乘以A
即可。這是你寫出的最後一個數組。
>>> (np.einsum('i,ij->ij', A, B) == (A[:, None] * B)).all()
True
出2:
同的例子。乘以列,然後求和輸出的列。
>>> (np.einsum('i,ij->i', A, B) == (A[:, None] * B).sum(axis=-1)).all()
True
出3:
爲你寫它上面的總和。乘以列,然後求和輸出的行。
>>> (np.einsum('i,ij->j', A, B) == (A[:, None] * B).sum(axis=0)).all()
True
出4:
需要注意的是,我們可以在最後省略所有軸,只得到在整個陣列的總和。
>>> np.einsum('i,ij->', A, B)
98
例5:
注意總和真的發生,因爲我們重複輸入標籤'i'
。如果我們改用不同的標籤輸入數組的每個軸,我們可以計算的東西類似克羅內克產品:
>>> np.einsum('i,jk', A, B).shape
(3, 3, 4)
編輯
的NumPy的實施愛因斯坦總和從傳統有些不同定義。從技術上講,愛因斯坦總和沒有「輸出標籤」的概念。這些總是暗示重複的輸入標籤。
來自文檔:"Whenever a label is repeated, it is summed."
因此,傳統上,我們會寫如np.einsum('i,ij', A, B)
之類的東西。這相當於np.einsum('i,ij->j', A, B)
。重複i
,因此它被求和,只留下標記爲j
的軸。您可以考慮將輸出標籤指定爲否的總和與僅指定輸入中不重複的標籤相同。也就是說,標籤'i,ij'
與'i,ij->j'
相同。
輸出標籤是在NumPy中實現的擴展或擴充,它允許調用方強制求和或強制在軸上求和。從文檔:"The output can be controlled by specifying output subscript labels as well. This specifies the label order, and allows summing to be disallowed or forced when desired."
事實上,我有這個問題的聲明「請記住,乘法總是首先發生」,這也可以在其他stackoverflow答案中找到。文檔中的哪些部分正確解釋了這一點? 「左邊的下標告訴np.einsum函數輸入數組的哪些行/列/等要相互相乘」文檔中解釋了哪裏? – FenryrMKIII
說「乘法總是先發生」並不是真的在文檔中,它來自符號的定義。有關詳細信息(https://en.wikipedia.org/wiki/Einstein_notation#Statement_of_convention),請參閱維基百科頁面,以及我的編輯以獲取有關文檔如何解釋輸出下標的想法的更多詳細信息。 – bnaecker
我瞭解愛因斯坦的符號。在流體力學中使用它很多。正如你在編輯中所說的那樣,這裏的實現似乎偏離了傳統的愛因斯坦表示法。正如你所說,愛因斯坦符號將是'ij,i',然後你知道你在改變j時總結我。那麼,「i,ij-> i」是指關於傳統的愛因斯坦符號?它有什麼作用 ?它抑制了我的總和? – FenryrMKIII