技术背景
Einsum和MindSpore框架中的爱因斯坦求和算子Einsum的基本用法。而我们需要知道,爱因斯坦求和其实还可以实现非常多的功能,甚至可以替代大部分的矩阵运算,比如常见的点乘、元素乘、求和等等这些都是可以的。那我们就逐一看一下可以用爱因斯坦求和来替代的那些函数和方法。
案例演示
In [1]: import numpy as np
In [2]: x = np.arange(3
In [3]: x
Out[3]: array([0, 1, 2]
In [4]: y = np.arange(3, 6
In [5]: y
Out[5]: array([3, 4, 5]
In [6]: P = np.arange(1, 10.reshape(3,3
In [7]: P
Out[7]:
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
矩阵转置
矩阵转置,或者是调换矩阵的某两个维度,这个功能用爱因斯坦求和来做是非常清晰的,我们先看一下相应的公式:
\[P^T=\left[ \begin{matrix} P_{00}&P_{01}&P_{02}\\ P_{10}&P_{11}&P_{12}\\ P_{20}&P_{21}&P_{22} \end{matrix} \right]^T= \left[ \begin{matrix} P_{00}&P_{10}&P_{20}\\ P_{01}&P_{11}&P_{21}\\ P_{02}&P_{12}&P_{22} \end{matrix} \right] \]
In [40]: np.allclose(P.T, np.einsum('kl->lk', P
Out[40]: True
这里有一个比较有意思的事情是,如果不指定生成的序号,但是给定的爱因斯坦算符顺序如果前面的大于后面的,也可以实现矩阵转置的功能,比如下面的一个案例:
In [41]: np.allclose(P.T, np.einsum('ji', P
Out[41]: True
元素乘
对应于两个矩阵(矢量、张量)之间的元素乘法,普通操作我们可以直接用\(x*y\来实现(假定维度大小为3):
\[x*y = \left[ \begin{matrix} x_0\\x_1\\x_2 \end{matrix} \right]* \left[ \begin{matrix} y_0\\y_1\\y_2 \end{matrix} \right]=\left[ \begin{matrix} x_0y_0\\x_1y_1\\x_2y_2 \end{matrix} \right] \]
In [8]: np.allclose(x*y, np.einsum('k,k->k', x, y
Out[8]: True
矩阵内求和
把矩阵中的所有元素相加:
\[SUM(x=SUM(\left[ \begin{matrix} x_0\\x_1\\x_2 \end{matrix} \right]=x_0+x_1+x_2 \]
In [9]: np.allclose(np.sum(x, np.einsum('k->', x
Out[9]: True
In [12]: np.allclose(np.sum(P, np.einsum('kl->', P
Out[12]: True
In [13]: np.allclose(np.sum(P, axis=-1, np.einsum('kl->k', P
Out[13]: True
In [14]: np.allclose(np.sum(P, axis=0, np.einsum('kl->l', P
Out[14]: True
那么,既然求和能算,同样的也是可以计算的,这里就不展开介绍了。
矩阵点乘
\[x\cdot y = \left[ \begin{matrix} x_0\\x_1\\x_2 \end{matrix} \right]\cdot \left[ \begin{matrix} y_0\\y_1\\y_2 \end{matrix} \right]=x_0y_0+x_1y_1+x_2y_2 \]
对应的Python代码实现如下所示:
In [15]: np.allclose(np.dot(x, y, np.einsum('k,k->', x, y
Out[15]: True
矩阵向量乘
这个应用场景也非常多,比如我们经常所用到的向量的伸缩、旋转等,都可以用一系列的矩阵作用在一个向量上来表示,相关的计算公式为:
\[P\cdot x=\left[ \begin{matrix} P_{00}&P_{01}&P_{02}\\ P_{10}&P_{11}&P_{12}\\ P_{20}&P_{21}&P_{22} \end{matrix} \right]\cdot \left[ \begin{matrix} x_0\\x_1\\x_2 \end{matrix} \right]= \left[ \begin{matrix} P_{00}x_0+P_{01}x_1+P_{02}x_2\\P_{10}x_0+P_{11}x_1+P_{12}x_2\\P_{20}x_0+P_{21}x_1+P_{22}x_2 \end{matrix} \right] \]
In [16]: np.allclose(np.dot(P, x, np.einsum('kl,l->k', P, x
Out[16]: True
In [25]: np.allclose(np.dot(P, x[:, None], np.einsum('kl,lm->km', P, x[:, None]
Out[25]: True
In [31]: np.allclose(np.dot(P, P.T, np.einsum('kl,lm->km', P, P.T
Out[31]: True
在上述案例中我们还包含了矩阵跟矩阵之间的乘法,这些基本运算都是可以通用的。
克罗内克积
\[x\otimes y^{T}=\left[ \begin{matrix} x_0\\x_1\\x_2 \end{matrix} \right]\otimes \left[y_0, y_1, y_2\right]=\left[ \begin{matrix} x_0y_0&x_0y_1&x_0y_2\\ x_1y_0&x_1y_1&x_1y_2\\ x_2y_0&x_2y_1&x_2y_2 \end{matrix} \right] \]
对应Python代码实现如下所示:
In [36]: np.allclose(np.kron(x[:, None], y, np.einsum('kl,l->kl', x[:, None], y
Out[36]: True
In [37]: np.allclose(np.kron(x, y, np.einsum('kl,l->kl', x[:, None], y.reshape(9
Out[37]: True
需要注意的是,爱因斯坦求和运算只能减少总的维度数量,但是不可改变维度大小,因此有时候会需要用到reshape的功能配合使用。
取对角元
\[diag(P=diag(\left[ \begin{matrix} P_{00}&P_{01}&P_{02}\\ P_{10}&P_{11}&P_{12}\\ P_{20}&P_{21}&P_{22} \end{matrix} \right]=\left[P_{00}, P_{11}, P_{22}\right] \]
相关的Python代码实现如下所示:
In [46]: np.allclose(np.diag(P, np.einsum('ii->i', P
Out[46]: True
求矩阵迹
矩阵的迹(Trace),就是对所有的对角元进行求和,那么有了上一步使用爱因斯坦求和函数提取所有的对角元之后,其实我们可以稍微调整一下,就能得到求矩阵迹的方法。首先看下矩阵迹的公式定义:
\[Tr(P = Tr(\left[ \begin{matrix} P_{00}&P_{01}&P_{02}\\ P_{10}&P_{11}&P_{12}\\ P_{20}&P_{21}&P_{22} \end{matrix} \right]=P_{00}+P_{11}+P_{22} \]
In [47]: np.allclose(np.trace(P, np.einsum('ii->', P
Out[47]: True
多重运算
有时候会涉及到一系列的矩阵按照顺序作用在一个向量上,如果从张量的角度来考虑的话,其中的维度还可以非常灵活的变化,不一定全都是方阵。应该说,这也是爱因斯坦求和算子的重大意义所在。如果不使用爱因斯坦求和算子,那么要计算\(A\cdot B\cdot C\cdot x\这样的一个过程,可以多次嵌套使用numpy的dot点乘函数。但是这样比较麻烦,一般推荐可以使用numpy中的另外一个函数:,相关的Python代码实现如下所示:
In [39]: np.allclose(np.linalg.multi_dot((P, P, P, x, np.einsum('ij,jk,kl,l->i', P, P, P, x
Out[39]: True
在这种多重运算的过程中,可以使用einsum_path去找到一条更好的归并路径,以达到提升算法性能的效果。
版权声明
版权声明
本文首发链接为:https://www.cnblogs.com/dechinphy/p/einsum-examples.html