尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

张量基础与爱因斯坦求和:从指标记号到PyTorch einsum实战

张量基础与爱因斯坦求和:从指标记号到PyTorch einsum实战 1. 为什么值得把张量这两个字重新掰开看刚入行那会儿我对张量的理解停留在比矩阵多几个维度的层次——反正就是嵌套的数组numpy里ndarray能装pytorch里Tensor能装shape一打出来几个数字这事儿就算懂了。后来真去啃那些推导才发现自己一直停在最表层。张量本身是有严格数学身份的东西求和指标、上下标、协变逆变这些东西看着像是搞理论的人自我折磨实际上是你理解很多东西的钥匙为什么torch.einsum写起来那么爽为什么张量网络的图能画成那样为什么有些记忆张量的设计里维度排布那么讲究。说到底这些工具的表达方式全都长在同一个根上。这篇东西我打算当成自己学习笔记来写主题就是张量的基础概念、求和指标、符号。不会一上来就堆定义而是按我当年踩过的顺序来先搞清楚它到底是个什么东西再搞懂那套省略 Σ 号的求和指标写法最后把符号体系捋顺让你看别人的公式不再一头雾水。适合谁看如果你是做深度学习、科学计算、数值仿真的日常跟pytorch张量、numpy数组、einsum、形状变换打交道但对背后那套指标记号一直半懂不懂那这篇就是给你准备的。纯小白也能看我会尽量用生活化的例子把抽象的东西拽下来。我先说结论张量不是高维数组这么一句就能打发的。数组是它在某个坐标系下的一组分量张量是分量 变换规则的整体。搞不清这个区别后面求和指标为什么长成那样你就永远是在背规则而不是在用规则。2. 概念层面张量到底是个什么东西2.1 从标量、向量到张量的阶数视角按老套路从阶数往上数一遍。零阶张量是标量比如温度 25 摄氏度一个数没有方向也没有分量结构。一阶张量是向量比如速度有大小有方向在三维空间里你可以写成三个分量(v1, v2, v3)。二阶张量是矩阵那种形态比如应力、形变、惯性张量在三维里是 3×3 一共九个分量。再往上三阶、四阶往工程里走像弹性刚度张量就是四阶的三维下是 3^481 个分量。这里有个特别容易被忽略的点阶数不等于有多少个下标阶数等于这张量在坐标变换下有几个方向参与变换。一个向量为什么是一阶因为换一个坐标系它的分量要跟着转一次。一个标量换坐标系纹丝不动因为它零阶。这个思路你拿去套多阶的也顺。那高维数组的说法错在哪错在它只描述了数据的摆放形式没描述变换行为。一个 3×3 的数组如果只是随手存了一张表格它跟张量一点关系都没有只有当它遵循特定的坐标变换规律时它才是张量。我打个不太严谨但很好懂的比方一个人有身份证号也有身高体重。身份证号代表身份换到哪个城市都不变身高体重换到不同测量标准厘米/英寸要换算。张量就像那个需要按规则换算的量而数组只是它在某个标准下记下来的一串数字。2.2 分量、基向量与坐标系理解上下标的来源要讲清上下标必须把基向量请出来。向量v可以写成v v^1 e1 v^2 e2 v^3 e3这里先假装是欧氏空间的正交基。注意分量用了上标v^i基向量用了下标e_i。这个上标下标的搭配不是随便写的它背后是对偶空间的概念一个空间有基e_i它的对偶空间有对应的对偶基e^i指标一上一下正好配对求和。那为什么会发展出逆变上标和协变下标两套指标根本原因是当你换一套坐标时空间里的基发生了改变而一个物理量本身没变为了保证v这个整体不变它的分量和基必须反向补偿。基往某个方向变分量就得往反方向变这种叫逆变分量反过来跟着基一起变的叫协变分量。所以同一个东西你需要上标和下标两套表示法来区分它扮演的角色。在正交坐标系比如我们平时的直角坐标里度规张量就是单位矩阵逆变和协变在数值上没差别于是很多人干脆忽视上下标。这是个危险的偷懒习惯。等你在曲线坐标、或者做广义相对论、连续介质力学那套东西时上标下标的区别立马要命——一不留神把协变当逆变用方程直接错。提示初学阶段你可以不深究度规但一定要养成上下标是有含义的这个意识。尤其在 PyTorch 里写einsum的时候指标名字其实就是你的上下标替身命名混乱就会出错。2.3 为什么工程实践也要在意这些理论细节有人会说我又不做广义相对论学这些干嘛。我的经验是三个地方你会直接受益。第一读论文。这几年很火的张量网络把高维张量拆成低阶张量缩并的图图里每条边就是一个指标。你要是不知道指标缩并是怎么回事那些图你根本读不懂哪条线连哪条线意味着什么。第二写高效代码。torch.einsum(ij,jk-ik, A, B)这种写法本质就是求和指标理解了它你就能把一堆矩阵、批量矩阵、迹、外积统一成一套写法比手写matmulreshape清爽太多。第三设计数据布局。所谓记忆张量很多时候就是在说数据怎么摆、哪些维度该合并、哪些该保留。维度排布搞错卷积、注意力、序列建模全乱套。我实测下来最直观的收益是以前做批量计算dim参数老记错、transpose老忘记现在习惯先把指标画出来谁跟谁求和、谁保留心里一张表错误率断崖式下降。下文慢慢展开。3. 求和指标爱因斯坦求和约定的读写方法3.1 为什么要把 Σ 号省掉先看一个老实的写法c_i Σ_j A_ij b_j。这是矩阵乘向量的标准形式每次求和都写个Σ下标一堆看清它到底在对谁求和得盯半天。爱因斯坦当年的做法很聪明如果一个指标在同一个表达式里出现了两次一次上标一次下标或约定下同为上标那它自动代表对该指标求和Σ 号省掉。上面那句就变成c_i A_ij b_j。清爽到飞起。再看矩阵乘矩阵C_ik Σ_j A_ij B_jk约定写法C_ik A_ij B_jk。你注意到没有求和的那个指标j出现了两次——一次在A的第二个位置一次在B的第一个位置。这就是自动求和的地方。而i和k各出现一次它们是结果里保留的指标。这个约定刚接触时你会觉得省这几个字能省多少但你写复杂式子的时候Σ 号会把眼睛晃花尤其是高阶张量缩并Σ 套 Σ括号套括号谁受得了。指标写法反而更接近张量的本质。3.2 哑标、自由标两条铁律记牢理解求和指标核心就两个概念哑标dummy index和自由标free index。哑标在单项里出现两次的指标代表被求和也就是被吃掉的指标不出现在结果里。名字随便换j换成m结果不变因为它只是个跑腿的。自由标在单项里只出现一次的指标代表结果里保留的维度。同一个自由标在等式两边分量对分量对应。配上两条铁律就不会错。铁律一一个指标在同一项里最多出现两次。出现三次基本是写错了除非你有特殊的度规协定不然就别这么干。铁律二等式两边的自由标必须一致。左边有i, k右边最后也必须是i, k而且最好位置上下也对齐。这两条我当年写在便利贴上贴显示器边上犯了半个月错误才刻进肌肉记忆。我拿一个常搞混的例子说明。A_ij B_ij这个式子i和j都出现了两次所以两个指标都被求和结果是一个标量。这在数值上等于Σ_i Σ_j A_ij B_ij也叫 Frobenius 内积或者元素级点乘求和。很多人第一次看到A_ij B_ij以为得到矩阵其实是标量。对照铁律一两个指标都被吃掉符合预期。3.3 手算几个高频例子把规则用熟光看规则没用得动手。我列几个每天都会用到的例一矩阵的迹。tr(A) A_ii。这里i出现了两次求和结果是A_11 A_22 A_33三维情况下。注意这个上下一对i在很多书里写成A^i_i两种写法意思一样取决于协变逆变约定。例二两个向量的内积。a · b a_i b_i Σ_i a_i b_i。结果标量。对比外积a_i b_j两个自由标都保留结果是个二阶张量矩阵。例三矩阵乘向量两次。想表达y A B x指标写就是y_i A_ij B_jk x_k。你看j出现两次被求和k也出现两次被求和最后只剩i。整串式子不用一个 Σ干净利落。学到这里你可以试着把下面这些常见运算用指标写法默写出来转置、矩阵的乘法结合律验证、张量的缩并。写错的往往是自由标对齐问题多练几遍就顺了。4. 张量符号体系怎么写、怎么读不出错4.1 上下标、希腊字母与拉丁字母的约定符号体系没有全球统一标准但有大量约定俗成的惯例。拉丁字母i, j, k, l, m, n一般用来表示空间指标取值范围是 1 到空间维度常见的就是 3 或 4。希腊字母μ, ν, ρ, σ, α, β通常用在哪两个场景一是标号较高或需要跟拉丁字母区分的指标二是四维时空里的指标比如 0 到 3。不过这不是铁律很多作者会混用所以最重要的是每篇文章里看它自己的声明别想当然。上下标位置得说清楚。上标很多时候表示逆变分量或者坐标比如x^i下标表示协变分量或者对坐标的偏导比如∂_i ∂/∂x^i注意这里下标配因子下标在导数上、上标在坐标上正好配对。指标配对求和时原则是一个上、一个下最安全因为在一般非正交坐标系里只有这样才能保证结果与坐标系无关。你如果全用下标求和所谓同指标求和也有约定那基本只在正交坐标系下才成立。我个人的实操建议除非你明确在做非欧几何否则统一上一下配对然后在心里默念这对指标是一上一下的。这能避免 90% 的错误。写代码时用的einsum字符串虽然没有上下标之分但你把每个字母当作一个指标同一字母出现两次就是求和逻辑完全一致。4.2 常用符号与运算速查表下面这张表是我自己整理的高频符号日常对照着看最省事。声明一下这是常见约定的合理归纳具体符号请以你参考的教材为准。符号含义常见写法指标说明标量零阶张量s,φ无指标向量一阶张量v^i或v_i一个自由指标矩阵二阶张量A_ij两个自由指标迹对角线求和tr(A) A_ii一对哑标内积向量缩并为标量a_i b_i一对哑标外积两向量生成矩阵a_i b_j两个自由标度规度量空间距离g_ij二阶对称克罗内克符号单位矩阵分量δ_ij1 当 ij否则 0列维-奇维塔符号叉乘/行列式工具ε_ijk三阶反对称偏导对坐标求导∂_i ∂/∂x^i一个指标缩并一对指标求和C_ik A_ij B_jk吃掉一对指标关于δ_ij和ε_ijk我用得最多尤其是做交叉验证时用ε_ijk手推叉乘公式用δ_ij简化指标你会发现一大堆向量恒等式推起来跟玩似的。这就是求和指标的威力把几何运算代数化。5. 落到代码PyTorch 张量与形状变换里的指标思维5.1 einsum 就是把求和指标搬进代码torch.einsum这个函数之所以好用是因为它几乎就是求和指标的代码化。你在公式里怎么写指标就在einsum字符串里怎么写字母。举几个对照import torch A torch.randn(3, 4) B torch.randn(4, 5) C torch.einsum(ij,jk-ik, A, B) # 等价于 A B矩阵乘 v torch.randn(4) w torch.einsum(ij,j-i, A, v) # 矩阵乘向量 tr torch.einsum(ii-, A) # 注意这里示意实际需方阵迹 inner torch.einsum(i,i-, v, v) # 内积 outer torch.einsum(i,j-ij, v, v) # 外积看懂了吗ij,jk-ik里j出现了两次被自动求和i,i-里i出现两次结果标量-后面空i,j-ij里i和j各一次都保留。这跟纸上的笔算规则一字不差。为什么强调这个因为很多人写代码时是一边查文档一边试试dim试到对为止效率低还容易埋坑。你把指标先画出来谁求和谁保留写einsum时几乎是翻译工作。我几个大项目里凡是涉及多维缩并的地方一律先写指标表达式再转代码返工率明显低。提示einsum可读性极好但要注意性能。多维大规模缩并有时候手工组合reshapematmul比einsum更快。两种都试试别迷信。5.2 张量的形状变换与指标视角所谓张量的形状变换本质就是指标的重排与合并。reshape、view、transpose、permute、unsqueeze、squeeze这一套全都能翻译成指标语言。transpose/permute指标换位置。A_ij变A_ji就是转置。批量场景下把[batch, seq, hidden]通过permute变成[seq, batch, hidden]就是交换前两个指标。reshape/view合并或拆分指标。比如把一个[H, W]的图拆成[H//p, p, W//p, p]这步在注意力、卷积里特别常见。合并指标时要记住数组里元素的物理顺序不变变的只是你的解读方式。squeeze/unsqueeze删掉或加上长度是 1 的指标。这类哑维没有实际信息但在广播机制里能决定计算形状。关于这几组操作我踩过的最大坑是**view和reshape的区别**。view要求内存连续否则报错reshape会视情况复制一份。批量数据处理经常是permute之后view一下报错就是因为内存不连续了。稳妥做法是permute后先.contiguous()再view或者直接用reshape它会自己判断要不要复制代价是有可能多占内存。这俩哪个好看你更怕报错还是更怕显存暴涨。再补充一个热词相关的张量网络。它的图里每个节点是一个低阶张量每条边是一个共享指标。两个节点连一条边就代表这两个张量沿那条边的指标缩并结果生成一个新的张量。所以缩并在代码里就是一次einsum或一次tensordot。你看图论那一套和指标那一套在代码里是一个东西。6. 常见问题与排查技巧实录6.1 指标写法的高频错误清单这部分我按错误现象 → 原因 → 修正整理都是我自己或身边人翻过的车。错误现象可能原因修正方法结果维度对不上自由标个数和位置不匹配检查等式两边自由标是否一一对应本该是标量却出矩阵把哑标当成自由标确认出现两次的指标是否被自动求和一个指标出现三次写法笔误拆成多项或换字母别硬凑上下标配对混乱电离散记协变逆变统一一上一下配对einsum报维度错误输入张量的实际维度和字符串不符先print(x.shape)再写字符串view报内存不连续permute 后直接 view补.contiguous()或改用reshape这张表里最容易被忽略的其实是第一行和最后一行。前者是数学层面的错误后者是工程层面的错误两类坑我都栽过。数学错误往往更隐蔽因为它不一定当场报错而是算出一个看起来差不多但就是不对的结果调试起来更烦。6.2 排查思路从指标图到代码逐层验证遇到问题时我有个固定流程分享一下。第一步先把公式用指标表达一遍标出哪些是哑标、哪些是自由标写下期望输出形状。第二步检查每个输入张量的实际shape跟指标一一对照看有没有哪个维度对不上。第三步用最小例子跑通。比如本来要处理[64, 128, 512]的大张量先缩到[2, 3, 4]这种小尺寸手算或跟numpy的参考实现比对。第四步才上真实规模。我印象最深的一次调试是做一个多头注意力的批量实现einsum字符串写成了bqd,bkd-bqk结果输出形状对数值却不对。排查半天发现是d这个共享指标在k上和q上没有正确对齐——因为两个张量的d维度语义不同一个是 key 的、一个是 query 的虽然都是d但写进同一个字母就被强行缩并了。教训einsum的字母是按名字配对的语义不同的维度千万不要用同一个字母。这一条我现在贴在脑门上凡是多输入缩并先确认每个字母的语义唯一。还有一个实用技巧用torch.allclose做交叉验证。同一件事你可以用einsum写一遍用matmul组合写一遍两个结果对比能对齐就说明你优化后的写法没引入 bug。这个习惯让我省下了大量改完不确定对不对的时间。最后再分享一个小习惯。学指标这套东西的时候别急着看复杂公式先找一本薄一点的教材把哑标自由标上下标配对缩并这几个概念练到能对着公式口述出它在干嘛。等你能一眼看出某个式子在对哪个指标求和、保留哪些指标回头看pytorch张量和einsum会有种原来你们都是老熟人的感觉。这种通透感不是看十篇教程能给的是自己动手算几遍才有的量。
返回列表