假设你是一座大型水电站的总调度员。面前摊开一张庞大流域图,几十条支流从不同山头汇聚,最终注入水库。每条支流都有名字,比如“第 k 道溪流”“第 j 道引渠”。爱因斯坦发现一个规律:凡是名字相同的支流,最后必然在某个闸口汇合,水量直接相加。于是他索性在图纸上划掉所有 Σ 求和符号,只留下河道的名字——这就是“爱因斯坦求和约定”。 einsum 正是把这个约定变成一行字符串的魔法:它用 ik,kj->ij 这样短短一段“河网接线图”,让计算机像调度员一样,把张量之间的乘法、加法、转置、批量变换一次调度完毕。这篇文章会带你从零认识 einsum:先看懂求和约定怎么省略 Σ,再学会 einsum 的字符串语法,逐个破解点积、转置、矩阵乘法、批量乘法,最后看看它的效率优势和 PyTorch / NumPy 实战写法。学完之后你会发现,许多需要五六个函数拼凑的张量运算,一行 einsum 就能干净利落写出来。

岗位关联

对 AI 从业者来说,einsum 是张量代码的“通用语言”:模型里任何复杂的维度变换都能用一行表达,既减少维度 bug 又提升可读性,是算法工程与面试中的高频利器。

目录
  1. 可视化演示
  2. 求和约定精义
  3. einsum语法入门
  4. 基本运算映射
  5. 矩阵乘法核心
  6. 批量运算升华
  7. 效率优势解密
  8. 实战应用技巧
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。

本节配有交互动效(进入视口后自动加载,约 3KB)。

系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T

求和约定精义

你还是那座水电站的调度员。图纸上,每条支流都有自己的名字:上游来水叫 a,支流分别叫 i、j、k……每当图纸上出现两个名字完全相同的河,它们必然在某个闸口汇合,水量相加。爱因斯坦发现这个规律后,索性把纸面上所有的 Σ 求和符号都擦掉,只留下河道名字——名字重复,就意味着“在这里求和”。

在数学里,这种“名字”叫做下标。向量 a 的第 i 个分量写作 aᵢ,矩阵 A 的第 i 行第 j 列写作 Aᵢⱼ。当同一个下标在同一项里出现两次,比如 aᵢₖ bₖⱼ 里的 k,我们就知道 k 要从 1 到 n 全部加一遍。这就是求和约定的核心:重复下标即求和。

一个式子里其实有两类下标。一类叫“哑标”,它在某一项里出现两次、最终结果里消失——像汇流后被干流吸收的支流;另一类叫“自由下标”,它只在项里出现一次、会原样出现在结果里——像保留下来的主干河道。判断谁求和、谁保留,是读懂 einsum 字符串的第一步。

所以,读一张 einsum 图纸也好,读一个数学式子也好,你只需问一句:什么名字重复了?重复即汇流,汇流即相加。

cij=aikbkj≡∑k=1naikbkj\displaystyle c_{ij} = a_{ik} b_{kj} \equiv \sum_{k=1}^{n} a_{ik} b_{kj}
  1. 自由下标 i、j这两个下标同时出现在等式左右两侧,表示它们对应的维度会保留下来、不求和。
    i,j 保留i, j \text{ 保留}
  2. 哑标 kk 在同一项里出现两次,按约定自动求和,这就是“重复下标即求和”。
    k 重复⇒∑kk \text{ 重复} \Rightarrow \sum_k
  3. 求和上下界k 从 1 加到 n,n 是该维度的长度,也就是矩阵 a 的第二维大小。
    ∑k=1n\sum_{k=1}^{n}
  4. 恒等号 ≡表示爱因斯坦约定与显式写 Σ 的写法完全等价,没有信息丢失。
    ≡∑k=1naikbkj\equiv \sum_{k=1}^{n} a_{ik} b_{kj}

einsum语法入门

作为调度员,你光有口头约定还不够,得给计算机一张“接线图”。einsum 的字符串就是这张图:逗号把各条来水的河道分隔开,箭头左边标注每条河的维度名,箭头右边写出汇流后要保留的河道名。ik,kj->ij 这一行,就是完整的河网拓扑。

在代码里,一个 einsum 调用长这样:einsum('ik,kj->ij', A, B)。字符串里的每个字母对应一个张量的维度,字母顺序对应轴的顺序。箭头左侧是各输入张量的下标串,右侧是输出张量的下标串;凡是“进得了输入、却写不进输出”的下标,都会被自动求和。

具体规则可以拆成四条:第一,逗号分隔多个输入张量;第二,箭头左侧是输入的维度结构;第三,箭头右侧决定输出张量的布局;第四,如果完全省略箭头和输出串,比如只写 'ij',等价于把所有下标都求和,返回一个标量。

字符串虽短,却是一份完整的河网拓扑图。读懂了它,后面所有运算都只是“照着图纸接水管”。

einsum(ik,kj->ij,  A,  B)≡∑kAikBkj\displaystyle \text{einsum}(\texttt{ik,kj->ij},\; A,\; B) \equiv \sum_{k} A_{ik}B_{kj}
  1. 逗号把 A 的下标串 ik 和 B 的下标串 kj 分隔开,相当于图纸上两条入水河道各自的名字列表。
    ik,kj\texttt{ik} \texttt{,} \texttt{kj}
  2. 箭头左右分隔符,左侧是输入河道结构,右侧是汇流后保留的河道。
    ->\texttt{->}
  3. 输出下标 ij决定结果矩阵的布局,i 在前表示行维度,j 在后表示列维度。
    ij 输出\texttt{ij} \text{ 输出}
  4. 调换输出顺序如果箭头右边写成 ji,张量的两个轴就对调了,这正是转置的来源。
    ij→ji 即转置\texttt{ij} \to \texttt{ji} \text{ 即转置}

基本运算映射

调度员上岗前要背几张标准图纸:两支独流直接合并成一条干流、一张网格的行列名互换、一个方形水库的自循环水量……这些基础操作在 einsum 里都只有一行。

先看向量点积。两个向量 a、b 的下标串都是 i,箭头右边为空,表示所有维度都被求和,最后得到一个标量。再看矩阵转置,ij->ji 把输出下标顺序对调,数据布局随之翻转。最后看矩阵求迹,ii-> 让同一个下标 i 出现两次,自动取对角线元素并求和。

这三张图就是 einsum 的“九九乘法表”。背熟它们,复杂运算不过是三张图的叠加。

einsum(i,i->,  a,  b)=∑iaibi\displaystyle \text{einsum}(\texttt{i,i->},\; a,\; b) = \sum_i a_i b_i
  1. ij→ji输出时交换下标位置,数据的行列存放方式随之对调,得到转置。
    ij→ji\texttt{ij} \to \texttt{ji}
  2. ii→输入出现两个 i,等价于自动取方阵对角线,再把对角元素相加。
    ii→∑iAii\texttt{ii} \to \sum_i A_{ii}
  3. 直观理解一个是交换河道行列名字,一个是只数方形水库的自循环水量。
    AT与tr⁡(A)A^{\mathsf T} \quad \text{与} \quad \operatorname{tr}(A)

矩阵乘法核心

现在画最经典的一幅图:矩阵乘法。A 是上游河网(i 行、k 列),B 是下游河网(k 行、j 列),中间的 k 像一道公共闸门——A 放出的每一股水,要和 B 接住的水相乘,再汇入 C 的 (i, j) 位置。这就是 ik,kj->ij 的全部含义。

这也解释了“贴标签”的本质:写 ik,kj->ij 时,你已经告诉计算机,A 的第二维和 B 的第一维是同一个 k,必须对齐相乘再累加;其他维度各自保留。这比手动 reshape 再 matmul 清晰得多,因为维度契约是显式的。

一次汇流、一道公共闸门,矩阵乘法这张大图就被 einsum 收进了一张小纸条。

Cij=einsum(ik,kj->ij,  A,  B)=∑kAikBkj\displaystyle C_{ij} = \text{einsum}(\texttt{ik,kj->ij},\; A,\; B) = \sum_{k} A_{ik}B_{kj}
  1. ik 与 kjA 的下标表示行 i、列 k,B 的下标表示行 k、列 j。
    ik 与 kj\texttt{ik} \text{ 与 } \texttt{kj}
  2. 公共下标 k两个矩阵通过 k 对接,k 的维度长度必须一致,这一步叫“缩并”。
    k 对齐相乗后累加k \text{ 对齐相乗后累加}
  3. 输出 ij结果保留 i 和 j,形状为 (i, j),即 A 的行数 × B 的列数。
    ij⇒形状 (i,j)\texttt{ij} \Rightarrow \text{形状 } (i, j)
  4. 隐式三重循环等价于 for i / for j / for k 三层循环,最内层做累加。
    ∑kAikBkj\sum_k A_{ik}B_{kj}

批量运算升华

真实工程里很少只有一张河网,而是成千上万张同时调度。比如一个批次 64 张图,每张都要做矩阵乘法。给每张河网加一个相同的批次号 b,你只需要维护一张接线图,所有批次的河网同时执行——这就是批量矩阵乘法 bik,bkj->bij。

注意一个特别容易迷糊的点:b 在输入里出现了两次,但它会被求和吗?不会。einsum 的铁律是:只要下标写进了输出串,它就是自由下标,重复出现也不求和。b 出现在输出里,所以它是批次维;k 没出现在输出里,才被缩并。

再看一个“零缩并”的例子——外积。两个向量的下标 i、j 没有重复,所有维度都保留,结果是一个 i×j 的矩形网格。

给河道名字加个批次号,一张图纸就能调度一万条河网;让名字完全不重复,就得到外积。einsum 的灵活,全在这几个字母的增减之间。

Ybij=einsum(bik,bkj->bij,  X,  W)=∑kXbikWbkj\displaystyle Y_{bij} = \text{einsum}(\texttt{bik,bkj->bij},\; X,\; W) = \sum_k X_{bik}W_{bkj}
  1. 无重复下标整条式子没有任何需要汇流的同名河道。
    i,j\texttt{i} \texttt{,} \texttt{j}
  2. 全部保留i 和 j 都出现在输出里,结果是一个 i 行 j 列的矩阵。
    ij 输出\texttt{ij} \text{ 输出}
  3. 等价函数等价于 np.outer(u, v),是零缩并的“纯外积”。
    Mij=uivjM_{ij} = u_i v_j

效率优势解密

调度员最怕什么?怕水在半路被存进临时水库,再放出来。多步矩阵运算经常干这种事:先算 XW₁,把中间结果写进内存,再读出来和 W₂ 相乘。einsum 相当于挖一条直通隧道,中间结果根本不落地。

看这个三输入例子:bik,kl,lj->bij,把三个张量塞进一次调用,k 和 l 两个哑标依次缩并。对比传统的链式写法 (X @ W1) @ W2,链式写法必须先在内存里生成 XW₁ 这个中间张量,einsum 则在内部融合计算。

代价与收益要诚实说:融合并不总是碾压。在 GPU 上遇到很大的矩阵时,高度优化的 cuBLAS matmul 可能更快;但当你做的是大量小矩阵、或需要多步缩并合成一次调用时,einsum 往往能省下可观的内存带宽。NumPy 还提供 optimize=True 和 einsum_path 来寻找最优缩并顺序。

效率的秘诀不是“算得更快”,而是“少搬几次家”。水不落地,自然省力。

einsum(bik,kl,lj->bij,  X,  W(1),  W(2))=∑k,lXbikWkl(1)Wlj(2)对比:Z=(XW(1))W(2)\displaystyle \text{einsum}(\texttt{bik,kl,lj->bij},\; X,\; W^{(1)},\; W^{(2)}) = \sum_{k,l} X_{bik} W^{(1)}_{kl} W^{(2)}_{lj} \qquad \text{对比:} Z = (XW^{(1)})W^{(2)}
  1. 三个输入一次调用传入三个张量,逗号分隔,对应三条入水河。
    bik,kl,lj\texttt{bik} \texttt{,} \texttt{kl} \texttt{,} \texttt{lj}
  2. 哑标 k 与 lk 连接 X 与 W₁,l 连接 W₁ 与 W₂,依次缩并。
    k 与 l 两个求和维k \text{ 与 } l \text{ 两个求和维}
  3. 输出 b, i, j只保留批次和最终行列维度,中间维度全部汇入干流。
    bij 输出\texttt{bij} \text{ 输出}
  4. 关键差异链式写法生成中间张量 XW₁(形状 b,i,l),einsum 在内部融合,避免这趟“内存往返”。
    (XW(1))W(2)vs∑k,lXW(1)W(2)(XW^{(1)})W^{(2)} \quad \text{vs} \quad \sum_{k,l} XW^{(1)}W^{(2)}

实战应用技巧

上岗前,调度员还得看一遍操作手册:不同工厂的 API 怎么称呼、特殊情况怎么处理、哪些名字不能用作河道名。

先说 PyTorch:torch.einsum('bik,bkj->bij', x, w) 返回新张量,并且完全支持自动求导,可以放心写在神经网络前向里。NumPy 的 np.einsum 用法相同,还额外提供 np.einsum_path 打印优化后的缩并顺序。两者都支持省略箭头:只写 'ij' 表示对所有下标求和,返回标量。再一个实战要点:ASCII 字母总共 52 个,遇到更高阶张量或动态变化的批次维,要用省略号 ...,比如 '...ij,...jk->...ik',它把“位置未知的前导维度”全部交给框架处理。

最后看一个 AI 里出镜率最高的例子——注意力机制的 QKV 缩并。Transformer 的自注意力打分与加权可以浓缩成一行 einsum:

手册虽短,但有了它,任何河网图你都能一行接好。einsum 就是张量世界的通用接线图:读懂了河名,就读懂了整个模型。

einsum(bqk,bkv->bqv,  Q,  K,  V)=∑kQbqkKbkvVbqv\displaystyle \text{einsum}(\texttt{bqk,bkv->bqv},\; Q,\; K,\; V) = \sum_k Q_{bqk} K_{bkv} V_{bqv}
  1. b、q、k、v批次、查询、键、值四条河道的名字。
    bqkv\texttt{b} \texttt{q} \texttt{k} \texttt{v}
  2. 下标 k 缩并Q 和 K 通过 k 对齐相乘后求和,得到注意力打分矩阵。
    ∑kQbqkKbkv\sum_k Q_{bqk}K_{bkv}
  3. 输出 bqv查询维和值维保留,键维被缩并,再接 softmax 就是自注意力核心。
    bqv 输出\texttt{bqv} \text{ 输出}
  4. 工程价值手写 reshape + matmul 至少三行,einsum 一行保维度不错。
    (b,q,v) 一次到位(b,q,v) \text{ 一次到位}

AI 应用场景

注意力机制中的 QKV 缩并

阶段:模型前向计算 / 特征融合

解决的问题:Transformer 的多头注意力里,Q、K、V 三个张量要做批量矩阵乘法。传统写法需要先 reshape、再 transpose、再 bmm,维度稍不留神就错位;用 einsum 把维度契约写进字符串,编译器直接按河网图调度,Bug 率大幅下降。

具体案例:训练 BERT 时,自注意力的打分与加权可以写成 torch.einsum('bqk,bkv->bqv', Q, K, V)。假设 batch=32、序列长=128、头维度=64,这一行就完成了 32 个样本各自 128×64 与 64×128 的批量矩阵乘,代码可读性和执行效率都很好。

推荐系统双塔召回中的向量打分

阶段:召回阶段 / 相似度计算

解决的问题:双塔模型把用户和物品分别编码成向量,线上要对上亿物品计算相似度。手写循环逐条内积太慢,把全部物品向量堆成矩阵后用 einsum 批量内积,一条指令完成“百万向量 × 用户向量”的点积。

具体案例:在召回服务中,用户向量 u 形状 (d,),物品向量矩阵 V 形状 (N, d),用 np.einsum('d,nd->n', u, V) 一次性得到 N 个相似度分数,再 top-k 截断,延迟从几十毫秒降到几毫秒。

科学计算中的张量网络缩并

阶段:数值模拟 / 张量网络计算

解决的问题:量子物理、量子化学中经常要把多个高维张量的公共维度缩并,下标关系错综复杂。einsum 把“哪些维度相乘、哪些维度求和”显式写成字符串,既方便验证数学推导,也能借助优化器寻找最省内存的缩并顺序。

具体案例:在量子态演化模拟中,把两个三阶张量的公共物理维缩并,写 np.einsum('ijk,klm->ijlm', A, B, optimize=True),配合 einsum_path 观察最优路径,内存峰值显著下降。

Python 演示

python44 行
import numpy as np def naive_einsum(spec, arrays):    """极简 einsum:把输入张量按字母广播对齐后逐元相乘,再在非输出维度求和。"""    inputs, out = spec.replace(' ', '').split('->')    input_specs = inputs.split(',')    order = sorted(set(''.join(input_specs) + out))     aligned = []    for s, a in zip(input_specs, arrays):        if len(set(s)) != len(s):            raise ValueError('简化版不支持重复下标(如 ii->)')        dims = {ch: a.shape[t] for t, ch in enumerate(s)}        # 每个输入: 按 order 顺序排列轴,缺失字母补 1 维        target = [s.index(ch) for ch in order if ch in s]        shape = [dims[ch] if ch in dims else 1 for ch in order]        aligned.append(np.transpose(a, target).reshape(shape))     prod = aligned[0]    for arr in aligned[1:]:        prod = prod * arr     drop = [t for t, ch in enumerate(order) if ch not in out]    return prod.sum(axis=tuple(drop)) if drop else prod  if __name__ == '__main__':    A = np.array([[1., 2., 3.], [4., 5., 6.], [7., 8., 9.]])    B = np.array([[9., 8., 7.], [6., 5., 4.], [3., 2., 1.]])    u = np.array([1., 2., 3.])    v = np.array([4., 5., 6.])     assert np.allclose(naive_einsum('ik,kj->ij', [A, B]), A @ B)    assert np.allclose(naive_einsum('i,i->', [u, v]), u @ v)    assert np.allclose(naive_einsum('ij->ji', [A]), A.T)    assert np.allclose(naive_einsum('i,j->ij', [u, v]), np.outer(u, v))     X = np.random.rand(4, 2, 3)    W1 = np.random.rand(3, 5)    W2 = np.random.rand(5, 7)    Y = naive_einsum('bik,kl,lj->bij', [X, W1, W2])    assert np.allclose(Y, X @ W1 @ W2)     print('全部断言通过:naive_einsum 与 NumPy 结果一致')
目的:用 30 行实现一个简化版 einsum,演示“轴对齐、逐元素相乘、哑标求和”三大核心步骤。
  1. order = sorted(set(''.join(input_specs) + out))收集所有出现的字母,按字母序排成统一的“河网坐标系”。
  2. target = [s.index(ch) for ch in order if ch in s]找出当前输入张量各轴在统一坐标系里的位置,用来转置。
  3. shape = [dims[ch] if ch in dims else 1 for ch in order]缺失的字母补 1 维,为广播对齐做准备。
  4. aligned.append(np.transpose(a, target).reshape(shape))转置加 reshape,把每个张量都“摆”到统一坐标系里。
  5. prod = prod * arr所有对齐后的张量逐元素相乘,NumPy 广播自动补齐维度。
  6. drop = [t for t, ch in enumerate(order) if ch not in out]找出没写进输出串的下标位置,也就是要缩并的哑标。
  7. return prod.sum(axis=tuple(drop)) if drop else prod沿这些维度求和,得到最终输出张量。
  8. assert np.allclose(naive_einsum('ik,kj->ij', [A, B]), A @ B)用官方矩阵乘法验证自定义实现正确。

常见误区

误区`bik,bkj->bij` 里 b 重复出现了两次,按爱因斯坦约定肯定也被求和了,输出形状应该是 (i, j) 吧?

正解einsum 的铁律是:下标只要写进输出串,就永远是自由下标,重复出现也不求和。只有“进了输入却没进输出”的下标才会被求和。b 写在了输出里,所以它是批次维,k 才是被缩并的哑标。就像同名河道如果图纸上标注“保留”,它就不会汇入大海。

为何常见:很多人先学了数学里的爱因斯坦求和约定,误以为“重复 = 求和”无条件成立,忽略了 einsum 用输出串二次声明了哪些维度保留。

误区既然 einsum 能避免中间张量,它肯定比所有常规写法都快,以后一律用 einsum 就行。

正解避免中间张量通常能省内存、减少内核启动,但这不是必然。在 GPU 上做大矩阵乘法时,cuBLAS 的 matmul 经过厂商级优化,速度和 einsum 相当甚至更快;einsum 的优势更多体现在多步缩并、大量小矩阵、维度显式化等场景。性能要用 benchmark 说话,不要拍脑袋。

为何常见:许多博客强调“einsum 更快”,读者把特定场景的优化效果放大成了普适结论。

误区`ij->ji` 和 `ij->ij` 输出的都是同一个矩阵的数值,顶多长得不一样,值差不多。

正解在张量世界里,轴的顺序就是数据的布局。ij->ji 输出转置,ij->ij 保持原样。在深度学习里,序列维度和特征维度一旦放反,整个网络的形状全部错乱。einsum 字符串里,输出下标的顺序就是结果张量的轴顺序。

为何常见:低维情况下“转置后的数值集合还在”,很多人只盯着数值,忽略了位置语义才是张量的灵魂。

误区既然 einsum 能替代几十个函数,那排序、找最大值这些操作也该能用 einsum 表达。

正解einsum 只能表达“逐元素相乘 + 求和 / 轴变换”这一族运算。任何需要比较、条件分支、非线性变换的操作都无法用 einsum 描述;它擅长的是线性代数里乘加结合的运算。另外,ASCII 下标字母有限,动态维度要靠省略号 ... 处理。

为何常见:把“表达力强”误解成“无所不能”,忽视了 einsum 的代数本质是乘加运算的压缩表达。

面试题

Q1. einsum('ij->ji', A) 的作用是?

单选题 · easy

  • A.矩阵转置← 正确答案
  • B.全元素求和
  • C.向量内积
  • D.矩阵乘法

答案:A。箭头右侧只写 ji,把输入下标对调,相当于把矩阵的行列交换,即转置。B 是 'ij->' 的效果,C 需要两个向量输入,D 需要两个矩阵。

Q2. einsum('i,i->', a, b) 返回什么形状?

单选题 · easy

  • A.向量
  • B.标量← 正确答案
  • C.矩阵
  • D.三维张量

答案:B。箭头右边为空表示所有下标都被求和,两个向量缩并后得到单个数值,即标量,等价于向量内积 a·b。

Q3. 在 'ik,kj->ij' 中,下标 k 是什么?

单选题 · easy

  • A.输出维度
  • B.求和缩并← 正确答案
  • C.批次维度
  • D.对角维度

答案:B。k 在输入中出现两次但没出现在输出中,按约定自动求和,是典型的哑标。输出维度是 i 和 j,批次维通常用 b,对角维需要重复下标 ii。

Q4. 矩阵迹的 einsum 写法是?

单选题 · mid

  • A.'ij->ji'
  • B.'ii->'← 正确答案
  • C.'i,i->'
  • D.'ij->ij'

答案:B。重复下标 ii 表示自动取对角线并求和,得到矩阵的迹。A 是转置,C 是向量内积,D 是恒等变换。

Q5. 关于 einsum('bik,bkj->bij'),以下说法正确的是?

多选题 · mid

  • A.输出形状为 (b, i, j)← 正确答案
  • B.下标 k 被求和缩并← 正确答案
  • C.下标 b 是批次维← 正确答案
  • D.下标 i 被求和缩并

答案:ABC。输出串保留了 b、i、j,所以形状是 (b,i,j),且 b 是自由下标、k 是哑标。i 写在了输出里,属于自由下标,不会求和,D 错误。

Q6. 下列哪些运算可以用 einsum 表达?

多选题 · mid

  • A.向量内积← 正确答案
  • B.矩阵转置← 正确答案
  • C.批量矩阵乘法← 正确答案
  • D.对数组排序

答案:ABC。einsum 只能表达乘加轴变换,内积、转置、批量矩阵乘法都在此列。排序需要比较与交换,无法用逐元素乘加表达,D 错误。

Q7. einsum('ij', A) 省略箭头,其语义是?

单选题 · mid

  • A.返回原矩阵
  • B.全元素求和← 正确答案
  • C.对矩阵转置
  • D.直接报错

答案:B。省略箭头等价于输出串为空,所有下标都参与求和,返回标量。这与“箭头右侧为空”的语义一致,只是写法更简短。

Q8. einsum('i,j->ij', u, v) 等价于?

单选题 · hard

  • A.np.outer← 正确答案
  • B.np.dot
  • C.矩阵乘法
  • D.矩阵求迹

答案:A。两个输入没有重复下标,所有维度都保留,逐元素相乘形成外积矩阵,等价于 np.outer(u, v)。np.dot 会做缩并,矩阵乘需要公共下标,求迹需要重复下标。

Q9. 关于 einsum 效率优势的说法,正确的有?

多选题 · hard

  • A.可避免中间张量← 正确答案
  • B.融合内核减少内存读写← 正确答案
  • C.任何情况都快于 matmul
  • D.NumPy 支持路径优化← 正确答案

答案:ABD。einsum 融合多步缩并、免去中间张量是常见优势,但 GPU 上大矩阵 matmul 也很强,不能说任何情况都快,C 过于绝对。NumPy 的 einsum_path 可以寻找最优缩并顺序,D 正确。

Q10. 在 PyTorch 中处理动态批次维的推荐写法是?

单选题 · extreme

  • A.用大写字母
  • B.用省略号 ……← 正确答案
  • C.用数字下标
  • D.无法支持

答案:B。动态批次维的数量和长度在写代码时未知,ASCII 字母表无法表达。省略号 ... 可以匹配任意数量的前导维度,例如 '……ij,……jk->……ik' 能把所有批次维整体保留,是通用且健壮的写法。