请想象一座名为“张量城”的邮局:邮递员手中的每一封信,都写着“寄自某室、寄往某室”。这座邮局有一条不成文的自动化规则——只要同一封信上出现两个相同的地址,邮局就自动把这封信投递一次,并在投递完成后把该地址从信封上抹去。例如信封上同时写着“寄自 2 室”和“寄往 2 室”,2 室就会收到一次投递,随后“2 室”这个字眼消失。这使邮局无需费力书写“请把每一封寄自 2 室的信分别送往 2 室”之类的冗长指令。 一百多年前,物理学家爱因斯坦在写张量公式时也遇到了同样的困境:大量求和符号 Σ 让表达式臃肿到难以阅读。于是他立下约定——凡是在同一项中重复出现的下标,自动代表对该下标求和。这就是“爱因斯坦求和约定”。今天,PyTorch 和 NumPy 中的 einsum 函数正是这座邮局的自动化分拣机:你只需写下“暗号”表达式,系统便自动完成所有配对与求和。本文将从邮局的故事出发,带你彻底理解这套记号系统,并学会在深度学习代码中使用它。

岗位关联

对 AI 从业者而言,爱因斯坦求和约定是读懂论文公式、编写高效张量运算代码的核心基本功,能显著提升模型实现的简洁度与可读性。

目录
  1. 可视化演示
  2. 求和记号背景
  3. 求和基本规则
  4. 哑指标与自由指标
  5. 常见张量运算
  6. einsum函数入门
  7. einsum实战应用
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示Einstein求和规则(哑指标求和)、张量缩并机制(AijBjk→Cik)、常见运算einsum表示及Transformer注意力QKV计算应用。

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

爱因斯坦求和约定规则 重复指标(哑指标)自动求和省略Sigma 自由指标保留在结果中标识维度 a_i * b_i = sum(a_i * b_i) 同一项中出现两次的指标默认对其求和

求和记号背景

在“张量城”邮局建立之初,所有投递指令都要写成完整文字:“请将寄自 1 室的信送往 1 室,寄自 2 室的信送往 2 室……”一个简单任务要写满整页纸。物理学家爱因斯坦在 1916 年前后面临同样的苦恼:广义相对论中的张量表达式动辄出现三四个求和符号,公式密密麻麻难以辨认,计算错误率极高。正如邮局需要一条“同名地址自动配对”的通用规则,物理学家也需要一种让公式“自动完成求和”的简洁记号。

在张量运算中,求和符号 Σ 就像那些反复出现的“从 1 室到 100 室”字样。矩阵乘积展开式 \(C_{ij}=\sum_{k} A_{ik}B_{kj}\) 中的 \(\sum_k\) 每次都不得不手写,占用了大量版面。爱因斯坦提出的约定只有一句话:凡是在一项中重复出现的下标,自动对它的所有可能取值求和,求和符号省略不写。这一约定把张量公式中大量的 Σ 彻底清除,使表达式变得干净利落。

以双重求和为例对比传统写法与爱因斯坦记号:

就像邮局把“逐号投递”简化为“同名地址自动配对”,爱因斯坦约定让张量公式彻底瘦身。今天,PyTorch 与 NumPy 的 einsum 函数正是继承自这套记号——你在代码里写下暗号表达式,框架自动完成全部配对与求和,正如那座邮局的自动化分拣机。

∑i=1n∑j=1mAijBji=AijBji\displaystyle \sum_{i=1}^{n}\sum_{j=1}^{m} A_{ij} B_{ji} = A_{ij} B_{ji}
  1. 传统双重求和等式左侧需要两个求和符号 \(\sum_i\) 与 \(\sum_j\),并明确标注 i 从 1 到 n、j 从 1 到 m,书写冗长。
    ∑i=1n∑j=1mAijBji\displaystyle \sum_{i=1}^{n}\sum_{j=1}^{m} A_{ij} B_{ji}
  2. 下标 i 成对出现在 \(A_{ij}B_{ji}\) 中,i 同时作为 A 的第一下标和 B 的第二下标出现,按约定自动对 i 求和。
    Aij→∑i=1nAij\displaystyle A_{ij} \rightarrow \sum_{i=1}^{n} A_{ij}
  3. 下标 j 成对出现同理,j 在两项中各出现一次,自动对 j 求和。
    Bji→∑j=1mBji\displaystyle B_{ji} \rightarrow \sum_{j=1}^{m} B_{ji}
  4. 省略后含义不变右侧省去全部求和符号,但计算结果与左侧完全一致,这正是“省略大Σ”的威力。
    AijBji=∑i=1n∑j=1mAijBji\displaystyle A_{ij} B_{ji} = \sum_{i=1}^{n}\sum_{j=1}^{m} A_{ij} B_{ji}

求和基本规则

邮局的核心规则可以这样表述:同一封信上“寄自”与“寄往”出现相同地址,即自动配对投递一次。爱因斯坦求和约定的核心同样简洁:一个下标在一项中出现两次,就对该下标求和。求和范围是隐含的——下标遍历它所对应维度的全部可能取值,正如邮局投递范围是整个城市所有信箱编号。

以两个向量的内积为例。传统写法必须写出求和符号:\(y = \sum_{i=1}^{n} a_i b_i\)。在爱因斯坦约定下,因为 i 出现了两次,求和符号可以省略:

这条规则还有三个细节需要留意。第一,下标不能出现三次——同一项中一个指标出现三次意味着求和范围与求和顺序不明确,是非法表达式。第二,多个成对下标同时出现时,各自独立求和,例如 \(A_{ij}B_{jk}C_{ki}\) 中 i、j、k 都出现两次,需分别对三个维度求和。第三,指标名字本身没有特殊含义,把 i 换成 k 不改变运算结果,只要保持成对关系即可。

回看邮局,你不需要通知它“请遍历所有信箱”,规则自动完成匹配。但正如“寄自 2 室寄往 2 室”合法、“寄自 2 室寄往 2 室再寄往 2 室”就会逻辑混乱,爱因斯坦约定也严格限制同一下标最多出现两次——这也是下一节区分哑指标与自由指标的前提。

y=aibi=∑i=1naibi\displaystyle y = a_i b_i = \sum_{i=1}^{n} a_i b_i
  1. 下标 i 成对出现\(a_i b_i\) 中 i 在第一个向量中作为下标出现一次,在第二个向量中又出现一次,共两次。
    aibi\displaystyle a_i b_i
  2. 自动触发求和出现两次的 i 自动激活求和,无需书写 Σ;求和范围是 i 的定义域,这里为 1 到 n。
    ∑i=1naibi\displaystyle \sum_{i=1}^{n} a_i b_i
  3. 求和后下标消失求和完成后 i 不再出现在结果中,因此 y 是一个标量——这正是“配对即投递、投递即消去”的体现。
    y=a1b1+a2b2+⋯+anbn\displaystyle y = a_1b_1 + a_2b_2 + \cdots + a_nb_n

哑指标与自由指标

邮局里有两类地址:一类是“寄自 2 室寄往 2 室”这样的配对地址,投递完成后从信封上消失,如同一个只办事不出声的“中间人”;另一类只写“寄往 i 室”这样的开放地址,不会被自动配对,而是作为待定收件人保留在信封上。在爱因斯坦约定中,前者称为哑指标(成对出现、求和后消失),后者称为自由指标(只出现一次、保留在结果中)。

以矩阵乘法这一最核心的张量运算为例:

判别方法可以总结为一句话:一个下标在某一项中出现 2 次是哑指标,出现 1 次是自由指标,出现 3 次是非法表达式。另外,自由指标必须保持全式一致——等号两侧的自由指标集合必须相同。若写出 \(C_i = A_{ij} B_{jk}\),右侧多出自由指标 k 而左侧没有,等式就失去了张量意义。

哑指标就像邮局里的中间投递员,他既接收信件又转出信件,名字出现两次;任务完成后名字自动隐去。自由指标则是仍在等待指定收件人的公开任务,必须出现在最终结果上。理解了这两类指标的“生死规则”,你就能读懂任何一种张量公式的简化写法。

Cij=AikBkj=∑k=1pAikBkj\displaystyle C_{ij} = A_{ik} B_{kj} = \sum_{k=1}^{p} A_{ik} B_{kj}
  1. 自由指标 i 与 ji 与 j 各自只出现一次,不参与求和,决定了结果矩阵 C 的两个维度。
    Cij⇒i=1…n,  j=1…m\displaystyle C_{ij} \Rightarrow i = 1\ldots n,\; j = 1\ldots m
  2. 哑指标 kk 在 A 的第二个下标和 B 的第一个下标中各出现一次,成对出现,触发求和。
    AikBkj\displaystyle A_{\color{red}{i}k}B_{k{\color{blue}{j}}}
  3. k 自动求和消去\(\sum_k\) 遍历 A 的列数(即 B 的行数),逐个相乘后累加,计算完成后 k 从表达式中消失。
    ∑k=1pAikBkj\displaystyle \sum_{k=1}^{p} A_{ik} B_{kj}
  4. 结果维度由自由指标决定C 的形状为 n × m,完全由自由指标 i、j 刻画;哑指标 k 不保留在结果中。
    Cij∈Rn×m\displaystyle C_{ij} \in \mathbb{R}^{n \times m}

常见张量运算

掌握了配对规则,几乎所有常用张量运算都能用爱因斯坦记号一眼看穿。它们是邮局不同类型信件的暗号:迹是“寄回自己”的回环信,转置是“交换收发地址”的信,内积是“两串号码完全配对”的信,外积则是“两串号码各自保留”的信。下面用一组公式统一展示:

这些运算在深度学习框架中都有对应的 einsum 表达式:'ij,jk->ik' 是矩阵乘法,'ii->' 是迹,'ij->ji' 是转置,'i,i->' 是内积,'i,j->ij' 是外积。更复杂的运算如批量矩阵乘法 'bij,bjk->bik'、注意力打分 'bqd,bkd->bqk' 也都是同一套规则的延伸。

邮局对照这张“信件类型表”,就能自动分拣每一类邮件。当你熟悉了这些记号,看到一条 einsum 表达式就像邮局老员工看到信封——瞟一眼收发地址,就知道该走哪条自动化流水线。

Tr(A)=Aii=∑iAii,Bij=Aji,c=aibi,Dij=aibj\displaystyle \mathrm{Tr}(A) = A_{ii} = \sum_{i} A_{ii}, \quad B_{ij} = A_{ji}, \quad c = a_i b_i, \quad D_{ij} = a_i b_j
  1. 迹运算\(A_{ii}\) 中 i 出现两次,自动配对求和,结果等于对角线元素之和。
    Tr(A)=A11+A22+⋯+Ann\displaystyle \mathrm{Tr}(A) = A_{11} + A_{22} + \cdots + A_{nn}
  2. 转置运算\(B_{ij} = A_{ji}\) 中 i 与 j 都是自由指标,等号两侧指标集合一致,只是位置互换。
    Bij=Aji⇔B=AT\displaystyle B_{ij} = A_{ji} \Leftrightarrow B = A^{T}
  3. 内积运算\(c = a_i b_i\) 中 i 成对求和,两个向量收缩为一个标量。
    c=∑iaibi=a⋅b\displaystyle c = \sum_{i} a_i b_i = a \cdot b
  4. 外积运算\(D_{ij} = a_i b_j\) 中 i 与 j 都只出现一次,全部保留,得到一个二维矩阵。
    Dij=aibj⇔D=a⊗b\displaystyle D_{ij} = a_i b_j \Leftrightarrow D = a \otimes b

einsum函数入门

邮局引进的自动化分拣机,就是 NumPy 与 PyTorch 中的 einsum 函数。传统手写三重循环实现矩阵乘法,如同邮局工人一趟趟跑腿逐户投递,代码冗长、易错且难以阅读;而 einsum 只需一行“暗号”字符串,机器自动完成全部配对投递。它把爱因斯坦记号的规则原封不动地搬进了编程语言。

einsum 的语法是爱因斯坦记号的直接映射:字符串 'ij,jk->ik' 中,逗号分隔多个输入张量的下标,箭头右侧标注输出下标。以矩阵乘法为例:

einsum 的强大之处在于一条表达式能涵盖矩阵乘法、批量矩阵乘法、迹、转置、按元素乘加等几乎所有常见运算。输出下标还可通过 '...' 省略号处理批量维度,例如 '...ij,...jk->...ik' 可作用于任意维度的输入。更重要的是,einsum 把循环、广播、转置的细节全部隐藏,让代码与论文公式一一对应,极大降低出错概率。

给邮局写下“暗号”字符串,分拣机便替你完成所有地址配对与投递。这正是 einsum 在深度学习社区被称为“暗号神器”的原因——一行代码,读懂与复现同时完成。

Cik=∑jAijBjk⟺einsum(′ij,jk→ik′,A,B)\displaystyle C_{ik} = \sum_{j} A_{ij} B_{jk} \quad \Longleftrightarrow \quad \mathrm{einsum}('ij,jk\rightarrow ik', A, B)
  1. 逗号左侧第一个输入'ij' 表示第一个输入张量 A 的两个维度分别命名为 i 与 j,维度顺序即下标顺序。
    A∈RI×J⇒Aij\displaystyle A \in \mathbb{R}^{I \times J} \Rightarrow A_{ij}
  2. 逗号分隔多输入'jk' 表示第二个输入张量 B 的维度命名为 j 与 k,多个输入的下标之间用逗号隔开。
    B∈RJ×K⇒Bjk\displaystyle B \in \mathbb{R}^{J \times K} \Rightarrow B_{jk}
  3. 重复下标自动求和j 同时出现在两个输入串中,是哑指标,einsum 自动对 j 求和,无需任何循环代码。
    ∑jAijBjk\displaystyle \sum_{j} A_{ij} B_{jk}
  4. 箭头右侧定义输出'ik' 说明输出保留 i 与 k 两个维度,j 已被求和消去;改变右侧字母顺序如 'ki' 即可得到转置结果。
    Cik∈RI×K\displaystyle C_{ik} \in \mathbb{R}^{I \times K}

einsum实战应用

张量城邮局承接了全城最复杂的任务:Transformer 的注意力机制与图神经网络的消息传递。这些任务本质上都是一次大规模“地址配对投递”——多个张量的维度需要按特定规则配对、求和、重排。用 einsum 暗号书写,每项任务都浓缩为一行代码。

注意力机制的核心是计算查询 Q 与键 K 的相似度分数。对每个 batch、每个 query 位置、每个 key 位置做特征维度的点积求和:

图神经网络的消息传递同样简洁:邻居特征聚合 \(h_i' = \sum_j e_{ji} h_j\) 可用 einsum('ij,j->i', edge_weight, h) 一行完成。批量张量运算如 'bik,bkj->bij' 实现多阶批量矩阵乘法,在多头注意力中替代成堆的 for 循环,代码可读性大幅提升。

工程实践上有一个重要建议:einsum 是“写法优雅”,但不保证“速度最快”。它不会自动调用最优化 BLAS 内核,大规模矩阵乘法通常用 @ 或 torch.matmul 更快。邮局的经验是——先用自动分拣机跑通流程、验证正确性,再对吞吐量瓶颈处升级“航空运输”(BLAS 优化内核)。先用 einsum 搭原型,再针对性优化,是工业界的标准路径。

scoreb,q,k=∑dQb,q,dKb,k,d⟺einsum(′bqd,bkd→bqk′,Q,K)\displaystyle \mathrm{score}_{b,q,k} = \sum_{d} Q_{b,q,d} K_{b,k,d} \quad \Longleftrightarrow \quad \mathrm{einsum}('bqd,bkd\rightarrow bqk', Q, K)
  1. 批量维度 b两个输入都带有下标 b,它是自由指标,在输出中保留,对应 batch 中每个样本独立计算。
    Q∈RB×Q×D,  K∈RB×K×D\displaystyle Q \in \mathbb{R}^{B \times Q \times D},\; K \in \mathbb{R}^{B \times K \times D}
  2. 哑指标 d 自动求和d 在 Q 与 K 中都出现一次,成对触发求和,即特征维度的点积。
    ∑dQb,q,dKb,k,d\displaystyle \sum_{d} Q_{b,q,d} K_{b,k,d}
  3. 自由指标 q 与 k 保留q 与 k 各出现一次,保留在输出中,分别对应 query 位置与 key 位置。
    scoreb,q,k∈RB×Q×K\displaystyle \mathrm{score}_{b,q,k} \in \mathbb{R}^{B \times Q \times K}
  4. 输出顺序决定转置输出串 'bqk' 决定了注意力分数矩阵的排列方式,加 softmax 后即可与 V 做加权求和。
    Attention(Q,K,V)=softmax(scoredk)V\displaystyle \mathrm{Attention}(Q,K,V) = \mathrm{softmax}\left(\frac{\mathrm{score}}{\sqrt{d_k}}\right) V

AI 应用场景

场景一:Transformer 注意力打分

阶段:模型前向计算

解决的问题:传统写法需要手动遍历 batch、query、key 三个维度,再用 reshape 和转置凑出矩阵乘法,代码冗长且极易混淆维度顺序。einsum 将这一过程压缩为一行暗号 'bqd,bkd->bqk',自动完成特征维度的点积求和,不仅代码量减少 80%,也让公式与实现一一对应、更容易验证正确性。

具体案例:在 LLaMA 类大语言模型的注意力实现中,输入形状为 [batch, seq_len, num_heads, head_dim] 的 Q、K 张量,使用 einsum('bhqd,bhkd->bhqk', Q, K) 一次性完成所有头的注意力分数计算,省去多轮 permute 与 reshape,训练速度与可读性同时提升。

场景二:图神经网络消息聚合

阶段:模型推理与训练

解决的问题:图神经网络需要对每个节点聚合邻居特征,传统实现逐边循环计算消息加权和,时间复杂度高且难以利用 GPU 并行。利用 einsum 将“边权重 × 邻居特征求和”表达为 'eij,ej->ei' 或 'ij,j->i',框架自动完成批量聚合,将 O(E) 次循环折叠为一次张量收缩。

具体案例:在 GraphSAGE 或 GAT 的实现中,边权重矩阵与节点特征矩阵的聚合操作通过 einsum('eij,ej->ei', edge_weight, node_feat) 完成,单张卡上即可处理百万级边的图数据,训练速度提升数倍。

场景三:推荐模型中的批量张量收缩

阶段:特征交互与模型结构设计

解决的问题:推荐系统常需对用户向量、商品向量做双线性交互或批量相似度计算,传统做法需要手动 broadcast 与转置,稍有不慎就产生维度错位。einsum 表达式如 'bd,de->be'、'bmd,bnd->bmn' 让多路张量的配对关系一目了然。

具体案例:在双塔召回模型中,用户塔输出形状 [batch, dim],商品塔输出 [batch, item_num, dim],使用 einsum('bd,bid->bi', user_vec, item_vec) 得到所有用户对商品的相似度评分,单行完成全量打分,服务端延迟显著降低。

Python 演示

python57 行
import numpy as npimport time def loop_matmul(A, B):    """C_ik = A_ij B_jk: 按爱因斯坦记号手动展开循环求和"""    I = A.shape[0]; J = A.shape[1]; K = B.shape[1]    C = np.zeros((I, K))    for i in range(I):        for k in range(K):            for j in range(J):                C[i, k] += A[i, j] * B[j, k]    return C def loop_trace(M):    """Tr(M) = M_ii: 重复指标 i 自动配对求和"""    s = 0.0    for i in range(M.shape[0]):        s += M[i, i]    return s def loop_batch(B, x):    """y_bi = B_bij x_bj: 批量矩阵-向量乘法"""    Bn, I, J = B.shape    y = np.zeros((Bn, I))    for b in range(Bn):        for i in range(I):            for j in range(J):                y[b, i] += B[b, i, j] * x[b, j]    return y if __name__ == "__main__":    rng = np.random.default_rng(0)    A = rng.normal(size=(8, 8))    B = rng.normal(size=(8, 8))     C_loop = loop_matmul(A, B)    C_ein = np.einsum('ij,jk->ik', A, B)    C_blas = A @ B    print('循环与einsum一致:', np.allclose(C_loop, C_ein))    print('循环与BLAS一致:', np.allclose(C_loop, C_blas))     M = rng.normal(size=(5, 5))    print('迹一致:', np.isclose(loop_trace(M), np.einsum('ii->', M)))     Bb = rng.normal(size=(4, 3, 5))    x = rng.normal(size=(4, 5))    y_loop = loop_batch(Bb, x)    y_ein = np.einsum('bij,bj->bi', Bb, x)    print('批量乘一致:', np.allclose(y_loop, y_ein))     big = rng.normal(size=(200, 200))    t0 = time.perf_counter()    np.einsum('ij,jk->ik', big, big)    t1 = time.perf_counter()    big @ big    t2 = time.perf_counter()    print(f'einsum耗时: {(t1-t0)*1000:.2f}ms; BLAS耗时: {(t2-t1)*1000:.2f}ms')
目的:用朴素三重循环逐一展开爱因斯坦记号中的求和,并与 np.einsum 及 BLAS 矩阵乘对比,验证三种写法结果一致并观察性能差异。
  1. def loop_matmul(A, B):定义与爱因斯坦记号 \(C_{ik}=A_{ij}B_{jk}\) 对应的朴素循环版本
  2. for j in range(J):这就是被省略的求和符号:内层循环对哑指标 j 逐个累加
  3. C[i, k] += A[i, j] * B[j, k]与公式 \(C_{ik} = A_{ij}B_{jk}\) 一一对应的核心计算行
  4. s += M[i, i]迹 \(\mathrm{Tr}(M)=M_{ii}\) 的实现,重复下标 i 等于主对角线取值
  5. y[b, i] += B[b, i, j] * x[b, j]批量运算 \(y_{bi}=B_{bij}x_{bj}\) 的逐元素展开
  6. C_ein = np.einsum('ij,jk->ik', A, B)einsum 一行完成与三重循环完全相同的矩阵乘法
  7. np.einsum('ii->', M)对角线求和即迹,空输出表示结果为标量
  8. np.einsum('bij,bj->bi', Bb, x)批量乘法中哑指标 j 自动配对,b 与 i 保留
  9. np.allclose(C_loop, C_ein)验证循环展开与 einsum 结果完全一致
  10. np.einsum('ij,jk->ik', big, big)大规模矩阵乘的 einsum 写法,用于对比性能

常见误区

误区既然重复指标自动求和,那 \(A_{ii}B_{ii}\) 中的 i 出现四次,应该能自动完成求和吧?我经常在图省事时直接多写几个相同下标。

正解爱因斯坦约定要求同一项中一个下标最多出现两次。出现三次或更多,就意味着求和范围、配对关系都不再唯一,表达式是歧义甚至非法的。就像一封写着“寄自 2 室寄往 2 室再寄往 2 室”的信,邮递员根本不知道要投递几次、投给谁。遇到这种情况,必须换用不同的字母区分各维度。

为何常见:初学者只记住了“重复=求和”这半句话,却忽略了约定自带的“成对”前提,误以为重复次数越多求和层次越丰富。

误区我一直以为 \(a_i b_i\) 里的 i 必须写成 i,换成 k 结果就变了,所以写公式时总是不敢换字母。

正解哑指标只是占位符,名字可以任意替换而结果不变。\(a_i b_i\) 与 \(a_k b_k\) 完全等价,因为两个都在对所有可能值求和。唯一要注意的是替换时不能与同式中其它指标重名——把 \(A_{ik}B_{kj}\) 中的 k 换成 i,就会变成 \(A_{ii}B_{ij}\),反而产生非法表达式。

为何常见:编程语言中变量名影响作用域,初学者容易把这种直觉迁移到数学记号上,忽略了求和指标只是“局部临时变量”。

误区einsum 把循环自动展开,应该比 `@` 和手写循环都快,所以我所有矩阵运算都用 einsum。

正解einsum 的优势在于可读性和通用性,而非绝对性能。NumPy 的 einsum 默认不调用 BLAS 优化内核,大矩阵乘法通常不如 A @ B 快;PyTorch 的 einsum 虽然会尝试派发到后端优化,但表达式解析与调度仍有开销。工程实践应先用 einsum 搭原型验证正确性,对热点路径再用矩阵乘原语或专用算子优化。

为何常见:很多人看到 einsum“一行替代三重循环”的表象,误以为循环被消除即等价于更快,忽略了底层实现差异和调度开销。

误区写公式时顺手写出 \(C_i = A_{ij}B_{jk}\),觉得反正都有求和,多一个指标少一个指标无所谓,结果检查半天对不上。

正解自由指标必须保持全式一致:等号左右两边出现的自由指标集合必须相同。\(A_{ij}B_{jk}\) 中 k 是自由指标,左侧 C 却只有 i,缺失了 k 这个维度,等式根本无法成立。正确写法应让结果带上所有自由指标,如 \(C_{ik} = A_{ij}B_{jk}\)。这是张量等式“量纲守恒”的体现。

为何常见:哑指标的“消去”特性让人误以为自由指标也可以随意隐去,忽略了自由指标是张量的“对外接口”,决定了结果的形状。

面试题

Q1. 爱因斯坦求和约定中,同一项内出现两次的指标称为?

单选题 · easy

  • A.哑指标,配对求和后消失← 正确答案
  • B.自由指标,保留在结果中
  • C.高维指标,不参与任何运算
  • D.张量指标,仅用于标记维度

答案:A。哑指标(dummy index)在同一项中出现两次,自动触发求和并在求和完成后从表达式中消失。自由指标只出现一次,保留在结果中;高维指标与张量指标都不是标准术语,属于干扰项。

Q2. 表达式 \(a_i b_i\) 在爱因斯坦约定下表示什么?

单选题 · easy

  • A.向量 a 与 b 的外积
  • B.向量 a 与 b 的内积← 正确答案
  • C.矩阵 a 与 b 的矩阵乘积
  • D.张量逐元素相乘并广播

答案:B。i 在 a 和 b 中各出现一次,成对触发求和,即 \(a_i b_i = \sum_i a_i b_i\),这正是内积的定义。外积应写作 \(a_i b_j\)(i、j 都只出现一次),矩阵乘积需要两个二维张量的下标串。

Q3. 矩阵乘法 \(C = AB\) 的 Einstein 记号写法是?

单选题 · easy

  • A.\(C_{ij} = A_{ij} B_{ij}\)
  • B.\(C_{ij} = A_{ik} B_{kj}\)← 正确答案
  • C.\(C_{ij} = A_{ji} B_{ik}\)
  • D.\(C_{ij} = A_{kk} B_{ij}\)

答案:B。矩阵乘法要求 A 的列数与 B 的行数配对,即哑指标 k 同时出现在 A 的第二个下标和 B 的第一个下标上,自动求和。选项 A 是逐元素乘法,C 相当于 \(A^T B\),D 中 \(A_{kk}\) 是迹而非矩阵本身。

Q4. 迹运算 \(\mathrm{Tr}(A)\) 在爱因斯坦记号下表示为?

单选题 · mid

  • A.\(\mathrm{Tr}(A) = A_{ij}\)
  • B.\(\mathrm{Tr}(A) = A_{ji}\)
  • C.\(\mathrm{Tr}(A) = A_{ii}\)← 正确答案
  • D.\(\mathrm{Tr}(A) = A_{kkj}\)

答案:C。下标 i 在 \(A_{ii}\) 中重复出现两次,自动配对求和,结果恰为对角线元素之和。\(A_{ij}\) 是完整矩阵,\(A_{ji}\) 是转置,\(A_{kkj}\) 中 k 出现两次 j 出现一次,是外积形式而非迹。

Q5. 表达式 \(C_{ij} = A_{ik} B_{kj}\) 中哪些是自由指标?

单选题 · mid

  • A.i 与 k
  • B.j 与 k
  • C.i 与 j← 正确答案
  • D.k 与 k

答案:C。自由指标是那些在同一项中只出现一次、不参与求和的下标。这里 i 出现在 A 的第一个下标和 C 的第一个下标,j 出现在 B 的第二个下标和 C 的第二个下标,都只出现一次;k 出现两次,是哑指标。

Q6. NumPy 中 np.einsum('ij,jk->ik', A, B) 实现的操作是?

单选题 · mid

  • A.A 与 B 的矩阵乘法← 正确答案
  • B.A 与 B 的逐元素乘法
  • C.B 与 A 的矩阵乘法
  • D.A 与 B 的克罗内克积

答案:A。'ij,jk->ik' 中 j 在两个输入串中各出现一次,是哑指标自动求和;输出保留 i 与 k,与 \(C_{ik}=\sum_j A_{ij}B_{jk}\) 完全对应,正是 \(A\cdot B\)。若要求 B×A 应写 'ij,jk->ik' 并交换输入参数。

Q7. 关于哑指标,下列说法正确的有?

多选题 · mid

  • A.哑指标求和后不再出现在结果中← 正确答案
  • B.哑指标可以在一项中出现三次
  • C.哑指标可以改为任意未占用的字母← 正确答案
  • D.哑指标只能出现在单个张量内部

答案:AC。哑指标成对出现、求和后消失,且改名不影响结果,只要不与其它指标冲突。出现三次是非法表达式;哑指标常跨张量出现,如 \(A_{ik}B_{kj}\) 中的 k 同时属于两个张量,故 D 错误。

Q8. 下列哪些表达式在爱因斯坦约定下是合法的?

多选题 · hard

  • A.\(y = a_i b_i c_i\)(i 出现三次)
  • B.\(C_{ij} = A_{ik} B_{kj}\)(k 成对出现)← 正确答案
  • C.\(D_i = A_{ij} B_{jk}\)(右侧有自由指标 k)
  • D.\(E_j = A_{ij} x_i\)(i 成对,j 自由)← 正确答案

答案:BD。B 中 k 是哑指标、i 与 j 自由,是标准矩阵乘法;D 中 i 成对求和、j 自由,合法。A 中 i 出现三次违反“最多两次”规则;C 右侧多出自由指标 k 而左侧没有,自由指标不一致,非法。

Q9. 对表达式 einsum('bqd,bkd->bqk', Q, K),下列说法正确的有?

多选题 · hard

  • A.b 是批量维度,会保留在输出中← 正确答案
  • B.d 是哑指标,会被自动求和← 正确答案
  • C.输出张量的形状为 [batch, query, key]← 正确答案
  • D.q 与 k 都是哑指标,会被消去

答案:ABC。b 在两个输入中都出现且不配对,是自由指标;d 在每个输入中各出现一次,成对触发求和;输出串 'bqk' 保留三个维度,形状为 [batch, query, key]。q 与 k 都只出现一次,是自由指标而非哑指标,D 错误。

Q10. A 形状为 (B, I, J),B 形状为 (B, J, K),用 einsum 计算批量矩阵乘法的最简写法是?

单选题 · extreme

  • A.einsum('bij,bjk->bik', A, B)← 正确答案
  • B.einsum('ij,jk->ik', A, B)
  • C.einsum('bij,jk->bik', A, B)
  • D.einsum('bik,bkj->bij', A, B)

答案:A。批量矩阵乘法要求批量维 b 对两个输入分别命名并保留在输出中,哑指标 j 在两个输入中各出现一次完成求和,输出保留 b、i、k。B 忽略了批量维,无法通过维度检查;C 中 B 缺少批量维;D 中输出顺序变成了 [batch, i, j],与输入 B 的维度契约不符。