赞
踩
说明:讲解时会对相关文章资料进行思想、结构、优缺点,内容进行提炼和记录,相关引用会标明出处,引用之处如有侵权,烦请告知删除。
转载请注明:DengBoCong
这里我们来好好探讨一下深度学习中,矩阵乘法的使用,其实主要是围绕einsum来进行探讨,即通过爱因斯坦求和约定来指导矩阵乘法,同时附带陈列其他矩阵乘法的API,方便进行直观感受。本文中的计算框架及版本如下:
我们讨论einsum绕不开爱因斯坦求和约定的,爱因斯坦求和约定(Einstein summation convention)是一种标记的约定,又称为爱因斯坦标记法(Einstein notation),在处理关于坐标的方程式时非常有用,用一句话来总结爱因斯坦求和约定,就是:
当式子中任何一个角标出现了两次,并且一次是上标、一次是下标时,那么该式表示的实际上是对这个角标一切可能值的求和。换言之,如果角标作为上标和下标各出现了一次,那么式子相当于添加了一个关于的求和符号
我们下面使用线性函数和矩阵运算来对爱因斯坦求和约定进行举例说明:
用爱因斯坦求和约定,
由于重复出现而实际上应该是求和的指标,被称为赝指标或者哑指标(dummy index),因为它们不是真正的指标,而是可以用任意字母代替的。没有求和的指标是固定的,是真正的指标.比如说
爱因斯坦求和约定的表示方法脱胎于矩阵乘法的要求,但是却不依赖于矩阵行和列的形式,转而关注指标间的配合,相比传统的矩阵表达,能更方便地推广到高阶张量的情形中。本文关于爱因斯坦求和约定的相关点到为止,如果感兴趣其公式,可以参考这一篇文章:
yang元祐:爱因斯坦求和约定zhuanlan.zhihu.com通过使用einsum函数,我们可以使用爱因斯坦求和约定(Einstein summation convention)在NumPy数组上指定操作。einsum函数由于其强大的表现力和智能循环,它在速度和内存效率方面通常可以超越我们常见的array函数。但缺点是,可能需要一段时间才能理解符号,有时需要尝试才能将其正确的应用于棘手的问题,当然熟悉之后得心应手才是使用关键。
einsum以一种优雅的方式,表示各种矩阵运算,好处在于你不需要去记和使用计算框架中(TensorFlow|PyTorch|Numpy)点积、外积、转置、矩阵-向量乘法、矩阵-矩阵乘法的函数名字和签名。从某种程度上解决引入不必要的张量变形或转置运算,以及可以省略的中间张量的现象。不仅如此,einsum有时可以编译到高性能代码,事实上,PyTorch最近引入的能够自动生成GPU代码并为特定输入尺寸自动调整代码的张量理解(Tensor Comprehensions)就基于类似einsum的领域特定语言。此外,可以使用opt einsum和tf einsum opt这样的项目优化einsum表达式的构造顺序。
假设我们有两个多维数组
einsum帮助我们更快,更高效地执行此操作,当然,NumPy函数的组合(例如multiply,sum和transpose)也可以实现。
我们在这一节用Numpy的einsum进行讲解说明(Numpy中einsum先被开发出来,TensorFlow和PyTorch都在一定程度上参考了它),在下一节,我会将TensorFlow、PyTorch和Numpy的API都贴出来。
在Numpy中,einsum使用格式字符串和任意数量的Numpy张量作为参数调用,并返回结果张量。
格式字符串包含分隔参数说明的逗号(,)和将参数说明与张量的参数分开的箭头(->)。参数说明中的数量和参数的数量必须匹配,并且必须精确地出现一个箭头,后跟一个结果说明
格式字符串中的字符数完全等于该张量的维数。
下面展示einsum()格式字符串的完整示例。 尝试猜测结果将是什么?注意了,0维张量(标量)对于参数和结果均有效,由空字符串“”表示。 再次提醒您,格式字符串必须仅由ASCII小写或大写字母组成。
我们这里使用一个实际的例子来进行说明,首先我们要相乘的两个数组是:
- A = array([[1, 1, 1],
- [2, 2, 2],
- [5, 5, 5]])
- B = array([[0, 1, 0],
- [1, 1, 0],
- [1, 1, 1]])
我们的矩阵乘法np.einsum('ij,jk->ik', A, B)大致如下:
要了解输出数组的计算方法,请记住以下三个规则:
现在,我们已经知道矩阵乘法是如何工作的。下图显示了如果我们不对 j 轴进行求和,而是通过写`np.einsum(‘ij,jk->ijk’, A, B)`将其包含在输出中,我们会得到什么。右边代表 j 轴已经求和:
注意,由于`np.einsum(‘ij,jk->ik’, A, B)`函数不构造3维数组然后求和,它只是将总和累加到2维数组中。下面是两个表格展示了einsum如何进行各种NumPy操作。我们可以用它来熟悉符号。
调用 | 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的外积(叉积) |
调用 | 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->ijk', A, B) | A[:, None] * B | A的每一行乘以B |
('ij,kl->ijkl', A, B) | A[:, :, None, None] * B | A的每个值乘以B |
当处理大量维度时,别忘了einsum允许使用省略号语法’…’。这提供了一种变量的方式标记我们不大感兴趣的轴,例如`np.einsum(‘…ij,ji->…’, a, b)`,仅将a的最后两个轴与2维数组b相乘。
通常,要将元素式方程式转换为方程式字符串,可以使用以下过程(右侧是矩阵乘法示例的中间字符串)
2. 删除变量名称,方括号和逗号:ik = sum_j ij * jk
3. 将“*”替换成“,”:ik = ij, jk
4. 去掉求和符号:ik = ij, jk
5. 输出移到右边,将将“=”替换成“->”:ij,jk->ik
- tf.einsum(equation, *inputs, **kwargs)
- 参数:
- equation:描述计算的字符串,格式与numpy.einsum相同。
- *inputs:输入(张量),其形状应与方程保持一致。
- **kwargs:
- optimize:用于使用opt_einsum查找计算路径的优化策略,可选项包括 'greedy', 'optimal', 'branch-2', 'branch-all' ,'auto',默认是'greedy'
- name:操作名称(可选)
- 返回值:张量,形状与方程中一致
- 示例:
- # 矩阵相乘
- einsum('ij,jk->ik', m0, m1) # output[i,k] = sum_j m0[i,j] * m1[j, k]
- # 点积
- einsum('i,i->', u, v) # output = sum_i u[i]*v[i]
- # 外积
- einsum('i,j->ij', u, v) # output[i,j] = u[i]*v[j]
- # 转置
- einsum('ij->ji', m) # output[j,i] = m[i,j]
- # 主对角线的和
- einsum('ii', m) # output[j,i] = trace(m) = sum_i m[i, i]
- # 批量矩阵相乘
- einsum('aij,ajk->aik', s, t) # out[a,i,k] = sum_j s[a,i,j] * t[a, j, k]
- torch.einsum(equation, *operands)
- 参数:
- equation:描述计算的字符串,格式与numpy.einsum相同。
- operands:输入(张量),其形状应与方程保持一致。
- 示例:
- torch.einsum('i,j->ij', x, y) # 外积
- torch.einsum('bn,anm,bm->ba', l, A, r) # 计算torch.nn.functional.bilinear
- torch.einsum('bij,bjk->bik', As, Bs) # 批量矩阵相乘
- torch.einsum('ii->i', A) # 对角线之和
- torch.einsum('...ii->...i', A) # 批量对角线之和
- torch.einsum('...ij->...ji', A).shape # 批量转置
- numpy.einsum(subscripts, *operands, out=None, dtype=None, order='K', casting='safe', optimize=False)
- 参数:
- subscripts:描述计算的字符串,除非包含显式指示符“->”以及精确输出形式的下标标签,否则将执行隐式(经典的爱因斯坦求和)计算
- operands:输入(张量),其形状应与方程保持一致。
- out:ndarray类型(可选),如果提供,则将计算结果放入此数组中
- dtype:{data-type, None}(可选),如果提供,则强制使用指定的数据类型计算。 请注意,你可能还必须提供一个更宽松的转换参数以允许进行转换。 默认为None。
- order:{‘C’, ‘F’, ‘A’, ‘K’}(可选),控制输出的内存布局。其中,‘C’表示C连续的。‘F’表示它应该是Fortran连续的。如果输入全为‘F’,则‘A’表示应为‘F’,否则为‘C’。‘K’表示它应尽可能与输入尽可能靠近布局,包括任意排列的轴。
- casting:{‘no’, ‘equiv’, ‘safe’, ‘same_kind’, ‘unsafe’}(可选),控制可能发生的数据类型转换。 不建议将其设置为“unsafe”,因为这会积聚不利影响。其中,“no”表示完全不应该转换数据类型,“equiv”表示仅允许按照字节顺序转换,“safe”表示只允许保留值的强制类型转换。“same_kind”表示仅允许安全类型转换或同一类型(例如float64到float32)内的类型转换。“unsafe”表示可能会进行任何数据转换。
- optimize:{False, True, ‘greedy’, ‘optimal’}(可选),控制优化策略,如果True将默认设置为“贪心”算法,如果是False则不会进行优化。 还接受np.einsum_path函数的显式收缩列表。
TensorFlow2.3
- tf.linalg.matmul(a, b, transpose_a=False, transpose_b=False, adjoint_a=False, adjoint_b=False,a_is_sparse=False, b_is_sparse=False, name=None)
- 参数:
- a:类型为float16, float32, float64, int32, complex64, complex128,并且秩大于1
- b:和a相同类型和秩
- transpose_a:如果为True,a在计算前会被转置
- transpose_b:如果为True,b在计算前会被转置
- adjoint_a:如果为True,a在计算前会被共轭和转置
- adjoint_b:如果为True,b在计算前会被共轭和转置
- a_is_sparse:如果为True,则将a视为稀疏矩阵。
- b_is_sparse:如果为True,则将b视为稀疏矩阵。
- name:操作名称(可选)
- tf.sparse.sparse_dense_matmul(sp_a, b, adjoint_a=False, adjoint_b=False, name=None)
- 参数:
- a:SparseTensor或者是密度矩阵,并且秩为2
- b:和a相同类型,密度矩阵或者是a:SparseTensor
- adjoint_a:如果为True,a在计算前会被共轭和转置
- adjoint_b:如果为True,b在计算前会被共轭和转置
- name:操作名称(可选)
- tf.math.multiply(x, y, name=None)
- 参数:
- a:类型为bfloat16, half, float32, float64, uint8, int8, uint16, int16, int32, int64, complex64, complex128的张量
- b:和a相同类型
- name:操作名称(可选)
Pytorch1.7.0
- torch.matmul(input, other, *, out=None)
- 参数:
- input:第一个用于乘法的张量
- other:第二个用于乘法的张量
- out:可选,输出张量
Numpy1.19
- numpy.matmul(x1, x2, /, out=None, *, casting='same_kind', order='K', dtype=None, subok=True[, signature, extobj])
- 参数:
- x1, x2:输入数组
- out:输出储存的位置,如果提供,则其形状必须与签名(n,k),(k,m)->(n,m)相匹配。 如果未提供或没有,则返回一个新分配的数组。
- **kwargs:对于其他仅关键字参数,参考文档
参考文献
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。