einsum,又称为爱因斯坦求和约定,是爱因斯坦在张量运算中引入的一种简化符号规则,用于省略求和符号()。对于操作高维张量的深度学习系统来说,einsum非常的有用,可以简化很多的操作,例如转置,增加维度等。看到求和标记,经常会一脸懵逼不知道在做什么。
einsum
规则
先看一个例子:np.einsum('ij,jk->ik', A, B)
,其中的'ij,jk->ik'
就是所谓的求和约定,ij
就是A的维度,jk
就是B的维度,A和B在j
这个维度上长度是一致的的,->ik
就是输出的维度,并且观察到j
这个维度被省略了,这就是einsum
的求和规则,从输入中消失的标记,表示在这个维度上进行了求和操作。对于维度较低的矩阵操作,比较好想象,下面也有速查表。
我们再来一个稍微复杂一些例子:einsum('abc,cd->abd', A, B)
,这里的求和标记到底意味着什么呢?发生了哪些的操作呢?
最简单的理解方式是从多层的for loop
的角度来看,参考这里1:
for a in range(A.shape[0]): for b in range(A.shape[1]): for d in range(B.shape[1]): total = 0 for c in range(A.shape[2]): total += A[a, b, c] * B[c, d] C[a, b, d] = total
可以观察到,从输出的维度的a,b,d
作为外层循环,并且观察输出的维度中相比输入的维度少了c
,那就是在这个维度上求和,体现在上面的代码中,然后在c
这个维度上进行遍历求和!
另外一种理解方式是从广播的角度来看的,还是以上面的求和标记为例:
- 首先,观察输出是
abcd
,并在c
的维度求和 - 用
abcd
对比A的abc
,在最后一个维度上缺失了,于是补1,变成(a, b, c, 1)
- 用
abcd
对比B的cd
,在前面两个维度上缺失了,于是补1,变成(1, 1, c, d)
- 根据广播规则复制A和B,使得A和B的维度一致,进行逐元素相乘,得到
(a, b, c, d)
- 然后再对
c
这个维度求和,得到(a, b, d)
einsum
的速查表
向量
设A
和B
是一维向量:
标记符号 | Numpy 等价形式 | 描述 |
---|---|---|
('i', A) | A | 向量A 本身 |
('i->', A) | sum(A) | 向量A 所有元素求和 |
('i,i->i', A, B) | A * B | 向量A 和向量B 逐元素相乘 |
('i,i', A, B) | inner(A, B) | 向量A 和向量B 的内积 |
('i,j->ij', A, B) | outer(A, B) | 向量A 和向量B 的外积 |
二维矩阵
设A
和B
是二维向量:
标记符号 | Numpy 等价形式 | 描述 |
---|---|---|
('ij', A) | A | 矩阵 A 本身 |
('ji', A) | A.T | 矩阵 A 转置 |
('ii->i', A) | diag(A) | 矩阵 A 对角元素 |
('ii', A) | trace(A) | 矩阵 A 的 迹(主对角线之和) |
('ij->', A) | sum(A) | 矩阵 A 所有元素求和 |
('ij->j', A) | sum(A, axis=0) | 矩阵 A 列求和 |
('ij->i', A) | sum(A, axis=1) | 矩阵 A 行求和 |
('ij,ij->ij', A, B) | A * B | A 和 B 哈达玛积(逐元素相乘) |
('ij,ji->ij', A, B) | A * B.T | A 和 B 的转置逐元素相乘 |
('ij,jk', A, B) | dot(A, B) | A 和 B 的矩阵乘法 |
('ij,kj->ik', A, B) | inner(A, B) | 矩阵 A 和 矩阵 B 的内积 |
('ij,kj->ikj', A, B) | A[:, None, :] * B[None, ...] | |
('ij,kl->ijkl', A, B) | A[:, :, None, None] * B[None, None, :, :] |