python 笔记:爱因斯坦求和 einsum
1 einsum簡介
????????使用愛因斯坦求和約定,可以以簡單的方式表示許多常見的多維線性代數數組運算。
????????給定兩個矩陣A和B,我們想對它們做一些操作,比如 multiply、sum或者transpose等。雖然numpy里面有可以直接使用的接口,能夠實現這些功能,但是使用enisum可以做的更快、更節省空間。
????????舉例說明,我們現在有兩個矩陣A和B。我們想計算A和B的哈達瑪乘積(即逐元素乘積),然后按行求和。
import numpy as np A = np.array([0, 1, 2]) B = np.array([[ 0, 1, 2, 3],[ 4, 5, 6, 7],[ 8, 9, 10, 11]])? ? ? ? 如果我們不適用einsum的話,也不是不能計算,就是需要寫幾步來完成:
A.reshape(-1,1) ''' array([[0],[1],[2]]) '''A.reshape(-1,1)*B ''' array([[ 0, 0, 0, 0],[ 4, 5, 6, 7],[16, 18, 20, 22]]) '''(A.reshape(-1,1)*B).sum(axis=1) #array([ 0, 22, 76])? ? ? ? 如果我們使用einsum的話,一行就可以實現:
np.einsum('i,ij->i', A, B) #array([ 0, 22, 76])? 2 einsum原理
????????????????使用einsum的關鍵是,正確地labelling(標記)輸入數組和輸出數組的axes(軸)。
????????????????我們可以使用字符串(比如:ijk,這種表示方式更常用)或者一個整數列表(比如:[0,1])來標記axes。
? ? ? ? ? ? ? ? 比如,為了實現矩陣乘法,我們可以用einsum這么寫(至于為什么這個是矩陣乘法,我們在后面會說明)
np.einsum('ij,jk->ik', A, B)????????????????字符串'ij,jk->ik'可以根據'->'的位置來切分,左邊的部分('ij,jk')標記了輸入的axes,右邊的('ik')標記了輸出的axes。
????????????????輸入標記又根據','的位置進行切分,'ij'標記了第一個輸入A的axes,'jk'標記了第二個輸入B的axes。
????????????????'ij'、'jk'的字符長度都是2,對應著A和B為2D數組,'ik'的長度也為2,因此輸出也是2D數組。
? ? ? ? ? ? ? ? 給定輸入
A = np.array([[1, 3, 5],[7, 9, -7],[-5, -3, -1]]) B = np.array([[0, 2,4],[6, 8, 6],[4, 2, 0]])?????????np.einsum('ij,jk->ik', A, B)可以看作是:
- 在輸入數組的標記之間,重復字母表示沿這些軸的值將相乘,這些乘積構成輸出數組的值。比如圖中沿著j軸做乘積。
- 從輸出標記中省略的字母表示沿該軸的值將被求和。比如圖中的輸出沒有包含j軸,因此沿著j軸求和得到了輸出數組中的每一項。
-
如果輸出的標記是'ijk',那么會得到一個 3x3x3 的矩陣。?
-
?輸出標記是'ik'的時候,并不會創建中間的 3x3x3 的矩陣,而是直接將總和累加到2D數組中。
A = np.array([[1, 3, 5],[7, 9, -7],[-5, -3, -1]]) B = np.array([[0, 2,4],[6, 8, 6],[4, 2, 0]]) np.einsum('ij,jk->ik', A, B) ''' array([[ 38, 36, 22],[ 26, 72, 82],[-22, -36, -38]]) '''如果輸出的標記是空,那么輸出整個矩陣的和
A = np.array([[1, 3, 5],[7, 9, -7],[-5, -3, -1]]) B = np.array([[0, 2,4],[6, 8, 6],[4, 2, 0]]) np.einsum('ij,jk->', A, B) #180?我們可以按任意順序排序不求和的軸。
A = np.array([[1, 3, 5],[7, 9, -7],[-5, -3, -1]]) B = np.array([[0, 2,4],[6, 8, 6],[4, 2, 0]]) np.einsum('ij,jk->kji', A, B)''' array([[[ 0, 0, 0],[ 18, 54, -18],[ 20, -28, -4]],[[ 2, 14, -10],[ 24, 72, -24],[ 10, -14, -2]],[[ 4, 28, -20],[ 18, 54, -18],[ 0, 0, 0]]]) '''3 einsum分析
3.1?'ij,jk->ijk'? 與 'ij,jk->kji'
?我們一個一個分析一下
A = np.array([[1, 3, 5],[7, 9, -7],[-5, -3, -1]]) B = np.array([[0, 2,4],[6, 8, 6],[4, 2, 0]]) np.einsum('ij,jk->ijk', A, B)''' array([[[ 0, 2, 4],[ 18, 24, 18],[ 20, 10, 0]],[[ 0, 14, 28],[ 54, 72, 54],[-28, -14, 0]],[[ 0, -10, -20],[-18, -24, -18],[ -4, -2, 0]]]) '''首先,這幾個數字是怎么得到的?
| 0=1*0 | 2=1*2 | 4=1*4 |
| 18=3*6 | 24=3*8 | 18=3*6 |
| 20=5*4 | 10=5*2 | 0=5*0 |
| 0=7*0 | 14=7*2 | 28=7*4 |
| 54=9*6 | 72=9*8 | 54=9*6 |
| -28=-7*4 | -14=-7*2 | 0=-7*0 |
| 0=-5*0 | -10=-5*2 | -20=-5*4 |
| -18=-3*6 | -24=-3*8 | -18=-3*6 |
| -4=-1*4 | -2=-1*2 | 0=-1*0 |
轉換成坐標,有:
| [0,0]*[0,0] | [0,0]*[0,1] | [0,0]*[0,2] |
| [0,1]*[1,0] | [0,1]*[1,1] | [0,1]*[1,2] |
| [0,2]*[2,0] | [0,2]*[2,1] | [0,2]*[2,2] |
| [1,0]*[0,0] | [1,0]*[0,2] | [1,0]*[0,4] |
| [1,1]*[1,0] | [1,1]*[1,1] | [1,1]*[1,2] |
| [1,2]*[2,0] | [1,2]*[2,1] | [1,2]*[2,2] |
| [2,0]*[0,0] | [2,0]*[0,1] | [2,0]*[0,2] |
| [2,1]*[1,0] | [2,1]*[1,1] | [2,1]*[1,2] |
| [2,2]*[2,0] | [2,2]*[2,1] | [2,2]*[2,2] |
?與上面類似,我們就看第一個3*3的矩陣吧
| 0=1*0 | 0=7*0 | 0=-5*0 |
| 18=3*6 | 54=9*6 | -18=-3*6 |
| 20=5*4 | -28=-7*4 | -4=-1*4 |
| [0,0]*[0,0] | [1,0]*[0,0] | [2,0]*[0,0] |
| [0,1]*[1,0] | [1,1]*[1,0] | [2,1]*[1,0] |
| [0.2]*[2,0] | [1,2]*[2,0] | [2,2]*[2,0] |
可以這么考慮 對于 結果矩陣(比如ijk),第【i,j,k】元素的結果等于【i,j】乘以【j,k】
3.2?'ij,jk->ik'??
A = np.array([[1, 3, 5],[7, 9, -7],[-5, -3, -1]]) B = np.array([[0, 2,4],[6, 8, 6],[4, 2, 0]]) np.einsum('ij,jk->ik', A, B) ''' array([[ 38, 36, 22],[ 26, 72, 82],[-22, -36, -38]]) '''我們前面?'ij,jk->ijk'的結果是?
| 0=1*0 | 2=1*2 | 4=1*4 |
| 18=3*6 | 24=3*8 | 18=3*6 |
| 20=5*4 | 10=5*2 | 0=5*0 |
| 0=7*0 | 14=7*2 | 28=7*4 |
| 54=9*6 | 72=9*8 | 54=9*6 |
| -28=-7*4 | -14=-7*2 | 0=-7*0 |
| 0=-5*0 | -10=-5*2 | -20=-5*4 |
| -18=-3*6 | -24=-3*8 | -18=-3*6 |
| -4=-1*4 | -2=-1*2 | 0=-1*0 |
這邊相當于
????????
| 38=0+18+20 | 36=2+24+10 | 22=4+18 |
| 26=54-28 | 72=14+72-14 | 82=54+28 |
| -22=-18-4 | -36=-10-24-2 | -38=-20-18 |
可以這么考慮 對于 結果矩陣(比如ik),第【i,k】元素的結果等于:對所有的j,【i,j】乘以【j,k】的結果的和
4 常用的Einsum
4.1 向量篇
| ('i',A) | 向量A的一個視圖 可以看成'i->i',即結果的第i位,是A的第i位 |
| ('i->', A) | sum(A) 可以看成'i->0',即結果的第0位,是A的第i位的和 |
| ('i,i->i', A,B) | 向量A,B對應位置相乘 'i,i->i':結果的第i位,是A和B的第i位的積? |
| ('i,i->', A,B) | 向量A,B的內積 ?'i,i->':可以看成'i,i->0' 結果的第0位,是A和B的第i位的積 再求和? |
| ('i,j->ij', A,B) | 向量A,B的外積 ?'i,j->ij':結果的第i行第j列,是A的第i個元素和B的第j個元素的乘積 |
?4.2 矩陣篇
| ('ij', A) | 返回矩陣A 看成'ij->ij' ,結果的第i行第j列,是A的第i行第j列 |
| ('ji->ij', A) | 返回矩陣A的轉置 結果的第i行第j列的元素是A的第j行第i列的元素? |
| ('ii->i', A) | 矩陣A的對角線元素 結果的第i個元素是A的第i行第i列的元素? |
| ('ij->', A) | 矩陣A的元素之和 可以看成'ij->0' 結果的第0位是A的第i行第j列的元素,再求和? |
| ('ij->j', A) | A縱向求和 結果的第j個元素是,對所有的i,A的第(i,j)個元素的和? |
| ('ij->i', A) | A橫向求和 結果的第i個元素是,對所有的j,A的第(i,j)個元素的和? |
| ('ij,ij->ij', A,B) | 矩陣A,B相應位置的乘積 結果第i,j個元素,是A的第(i,j)個元素和B的第(i,j)個元素的乘積? |
| ('ij,ji->ij', A,B) | 矩陣A和? 矩陣B的轉置? ?相應位置的乘積 結果第i,j個元素,是A的第(i,j)個元素和B的第(j,i)個元素的乘積? |
| ('ij,jk->ik', A,B) | A,B的矩陣乘積 結果的第(i,k)個元素等于A的第(i,j)個元素乘以B的第(j,k)個元素? |
| ('ij,kj->ik', A,B) | 矩陣A和矩陣B的內積 |
| ('ij,kl->jikl', A,B) | A的每個元素乘以矩陣B 結果的 第(j,i,k,l)個元素是A的(i,j)和B的(k,l)的乘積? |
| ('dn,nd->',A,B) | 相當于tr(AB) ? |
?5 顯示表明和隱式表明
我們將指定'->'和輸出標記稱為 explicit mode。
如果不指定'->'和輸出標記,numpy會將輸入標記中只出現一次的標記按照字母表順序,作為輸出標記(也就是 implicit mode)。
'ij,jk->ik' 等價于 'ij,jk'
參考文章:einsum初探 - 知乎 (zhihu.com)
總結
以上是生活随笔為你收集整理的python 笔记:爱因斯坦求和 einsum的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 文巾解题 113. 路径总和 II
- 下一篇: python 库整理: collecti