
第一次用普通 Transformer 做音乐生成实验时我遇到了一个让人困惑的现象同样一段旋律整体从 C 大调移到 G 大调人耳听上去几乎是同一首歌但模型生成的续写完全走样。起初我以为只是训练数据太少于是补了更多转调样本情况确实好了一些但训练成本变得很高。后来我才意识到问题不是数据量而是模型根本不理解“移调”这个操作。这也是 Equivariant Music Transformer 这个方向最吸引我的地方。它试图解决的不是让音乐生成模型跑得更快也不是单纯让输出“更好听”而是让模型天生就理解音乐中的对称结构。换句话说它想把“移调后还是同一首旋律”这个人类常识直接写进网络结构里。这篇文章不是某个开源项目的完整教程因为我手上也没有一份可运行的仓库代码。我更想把它当作一个方向来拆解普通 Transformer 做音乐生成到底卡在哪里等变性为什么是音乐建模里值得关注的设计原则以及如果要从零开始做一个 Equivariant Music Transformer大致应该走哪几步、怎么验证、别踩哪些坑。1. 一个奇怪的现象模型能作曲却听不出“同一首歌”先从我遇到的场景说起。当时我在跑一个音高序列生成任务输入是一段 MIDI 旋律的前半段让模型续写后半段。训练集里包含了多个调的旋律单看 loss模型表现不错生成的旋律在音高分布上也像模像样。但只要把测试旋律整体移调质量就会明显下降。某些情况下模型甚至会把移调后的旋律理解成完全陌生的输入生成结果与原来的续写模式没有任何关系。我当时的第一反应是数据不够。于是把训练集里的 MIDI 文件做了一次批量移调扩增C 调来一遍D 调来一遍E 调来一遍……数据量确实涨了模型在扩增后的测试集上也有改善。但这里面有一个让人不安的点如果我的目标是让模型学会一首曲子在不同调上的等价结构那靠堆数据去“记住”各种移调版本显然不是真正理解了音乐。1.1 这个现象背后是“结构理解”缺失普通 Transformer 处理音乐序列时默认把音符当成一串离散 token。模型看到的是一个按时间展开的音高序列比如[60, 62, 64, 65, 64, 62]。在这个表示里[60, 62, 64]和[65, 67, 69]是两个完全不同的序列它们之间唯一的联系是 token 之间的距离恰好都是 2 个半音。如果模型没有提前学习到“相距 12 个半音的音高具有某种结构等价性”那么它就只能从海量数据里隐式地统计这种关系。问题是这种统计非常脆弱。训练集里 C 调曲子多模型就对 C 调附近的音高关系更敏感G 调曲子少模型遇到 G 调时表现就自然会下降。整体移调后生成质量崩掉本质上是模型在“靠记忆”而不是“靠结构”处理音符。1.2 Transformer 擅长长程依赖但不擅长对称性建模Transformer 的长处是建模长距离依赖它能通过 attention 看到序列里距离很远的音符并建立它们之间的关系。这对音乐特别重要因为一首曲子的主题、发展、再现往往跨越几十甚至几百个音符。但 Transformer 并不会主动发现“音高平移是一种等价操作”。它的位置编码是给每个位置一个相对或绝对的标记token embedding 也默认每个音高是独立符号。对网络来说C 大调的三和弦和 G 大调的三和弦是两个需要分别学习的概念而不是同一个概念在不同调上的投影。这里就引出了这个方向的真正切入点能不能让 Transformer 本身的对称性设计和音乐的结构知识对齐2. Music Transformer 解决的问题和它没解决的问题讨论 Equivariant Music Transformer 之前得先分清“Music Transformer”和“Equivariant Music Transformer”之间的差别。前者是一个具体的模型方向后者是对前者的结构改造。2.1 Music Transformer 的贡献相对位置注意力Music Transformer 是音乐生成里绕不开的参考工作它由 Google Magenta 团队提出核心贡献是把 Transformer 的长程建模能力用到了音乐序列上并针对音乐性能做了关键调整。最广为人知的是它使用了相对位置注意力机制而不是原始的绝对位置编码。为什么这个改动对音乐这么重要因为音乐段落经常出现“重复 - 变化 - 再现”的结构。一段旋律可能在 16 小节之后稍微变形再次出现。如果使用绝对位置编码模型必须从头记忆旋律在位置 0 和位置 40 的两次出现是不同的这会浪费大量参数。而相对位置注意力让模型只需要学到“隔了 16 个小节再次出现的旋律关系”这个规律可以迁移到任意位置。这是 Music Transformer 最有价值的思路把一个通用 Transformer 改造成更适合音乐序列结构的模型。2.2 相对位置编码还不够但相对位置编码解决的主要是时间维度的结构。在音高维度普通 Transformer 依然把每个音高当作孤立的 token。音高距离、调性关系、八度等价这些信息模型并没有直接获得。试想一个很简单的例子。C 大调的和弦进行C - G - Am - F在音高上是[60, 55, 57, 53]这样的根音序列。把它整体移调成 D 大调就是[62, 57, 59, 55]。这两个序列在绝对表示上完全不同但它们内部的高度差完全一致。人类听感上这两个和弦进行的“功能”一样只是音高位置不同。普通 Music Transformer 可以学到“C 后面通常跟 G”但如果数据里 D 调的出现频率较低它就不太容易自动泛化出“每个调的 I 级后面通常跟 V 级”。要让它学到这一步需要数据里恰好覆盖足够多的移调版本。2.3 普通文本与音乐的差异结构层级不同文本处理里一个词的含义一般不会因为整体平移到另一个位置就改变“我爱你”和“你爱我”甚至因为词序变化而产生完全不同的含义。但音乐不是这样。整段旋律移调之后含义功能基本不变只是绝对音高位置变了。这就是音乐序列和普通文本序列之间的核心差异音乐同时具有时间结构旋律的先后展开和音高结构调性、音程、和声功能而普通 Transformer 主要是为时间结构设计的。要让 Transformer 真正理解音乐不能只靠堆数据最好把这种结构对齐到模型本身的机制里。3. 等变性把音乐结构变成模型的天生能力“等变”这个词听起来很数学其实背后的直觉非常朴素。3.1 什么是等变性从几何变换讲起一个函数或者模型是“等变”的意思是先对输入做某个变换再让模型处理和处理完再做同一个变换结果应该一致。用公式表示就是f(变换(x)) 变换(f(x))比如图像识别里的平移等变。一张猫的图片往左平移 20 个像素识别模型如果先是输出“猫”的类别然后平移输出结果和直接输入平移后的图片并输出“猫”应该得到同样结果。模型具备平移等变性就不需要训练集里覆盖所有可能的猫的位置。如果变换之后结果完全不变那叫“不变性”。不变性是等变的特例。二者区别在于不变性是说输出不受变换影响等变性是说输出会以相同方式跟着变换。3.2 音乐里的平移时间、音高、调性音乐里常见的变换有两类。一类是时间上的平移。同样的旋律提前或延后几个音符出现音乐的“意义”不改变。这正是 Music Transformer 里相对位置编码想解决的。另一类是音高上的平移。把整段旋律向上或向下移若干个半音调性变了但旋律结构不变。从 C 调移到 G 调就是整体加 7 个半音。从 C 调移到 D 调就是整体加 2 个半音。模型的输出如果是一段续写旋律也应该跟着同等移调。这就是音高平移等变性。还有更精细的八度平移同一个音符移到更高八度音高值变化 12 个半音在音乐功能上高度相似。这也是一种可建模的对称性。3.3 等变 vs 不变模型输出该怎么跟着变化这里要特别区分一下。等变不等于“不管移没移调输出都完全一样”。比如我输入一段 C 大调的旋律模型续写了一段 C 大调的旋律。当我输入同一段旋律整体移到 G 大调时理想输出是模型生成的续写旋律也应该在 G 大调上并且与原 C 调输出保持同样的相对音高关系。也就是说输出本身发生了“移调”变换但输出的结构和输入的变化是同构的。如果模型是音高不变性那它就会在两个输入下生成完全一样的音高序列——这反而错误因为 G 大调的真实验续写应该落在 G 调的音高坐标里。所以这类模型设计的目标是让网络在不同调式之间具备可预测的行为而不是不管什么调都输出同一套东西。4. 一个等变音乐 Transformer 大致是怎么被搭出来的由于手头没有官方开源代码下面这段不是某个仓库的完整实现只是基于常见设计思路的工程推演。理解了这个思路你再看相关论文或代码时会有更清楚的参照系。4.1 设计目标把音乐对称性写进网络结构通用的办法是在三个层面注入等变性输入表示让音符 token 编码时显式携带音高距离信息而不是让模型自己猜。位置编码在相对位置编码中加入音高方向的相对距离。注意力机制让 attention score 的计算函数对音高平移保持友好关系。核心思想是不要只把音符当作[60, 62, 64]这样的离散符号而要同时告诉模型“这些音符之间的距离是多少”“它们之间有什么调性关系”。4.2 位置编码改造从绝对位置到等变位置编码普通 Music Transformer 用相对位置编码解决的是一维时间序列上的位置关系。在等变音乐 Transformer 里维度会变成两个时间是t音高是p。每个音符可以被表示成一个坐标对(t, p)。时间上的相对距离t_j - t_i和音高上的相对距离p_j - p_i会被分别编码再作为 bias 加到注意力分数上。一个常见的简化示意# 示意结构不是完整实现 def relative_position_bias(q_keys, q_pitch, k_keys, k_pitch): time_dist q_keys[:, :, None] - k_keys[:, None, :] pitch_dist q_pitch[:, :, None] - k_pitch[:, None, :] time_bias time_embedding(time_dist) pitch_bias pitch_embedding(pitch_dist) return time_bias pitch_bias这个设计的巧妙之处在于当整段旋律移调时所有音符的p都加上同一个常数但音符之间的p_j - p_i完全不变。于是注意力分数不变模型行为自然具备音高平移等变性。4.3 注意力机制中的音高距离更进一步可以在注意力权重计算里直接加入音高距离约束。比如让注意力分数不仅取决于“时间相隔多远”还取决于“音高相隔多远”。如果两个音符之间的音高距离正好是 12一个八度模型可以被设计成对它们赋予类似权重因为它们在一个更宽泛的调性意义上属于同一类音高级。这种设计本质上是把音乐理论里的“音高级空间”当成一个结构先验注入网络。模型不再需要从零学习“音高距离 2 比音高距离 3 更接近”而是直接通过 bias 获得这种结构提示。4.4 数据增强与等变结构的关系很多人会问我已经做了移调数据增强还需要等变结构吗需要但两者的角色不同。移调数据增强是让模型被动地从样本里统计出“这些移调版本之间有一定的相关性”。等变结构是让模型天生就知道这种相关性是必然成立的。前者需要大量数据才能学得稳后者用很少的数据也能让模型表现出正确的泛化行为。实际工程里等变结构可以减少移调数据增强的依赖但不代表完全不需要数据增强。模型还需要学习的是在 G 大调上哪些和弦连接是自然的哪些旋律走向是符合风格预期的。等变性保证了移调后“结构等价”但“风格合理性”仍然需要从真实音乐数据中学习。5. 从零开始做等变音乐 Transformer最小实验路径如果你也想动手验证这个方向我的建议是不要一上来就把目标设定成“做一个能生成完整曲目的模型”。先做一个最小的可验证实验把最核心的问题回答清楚在音高平移等变性上模型是否真的比普通 Transformer 更稳定5.1 第一步数据准备与符号化数据层面最合适的起点是简单旋律的 MIDI 文件。不需要复杂编曲单旋律就行。把 MIDI 事件转成序列时需要记录两类基本信息音符开始时间或者与上一个音符的时间差音符音高一个常见的简单 token 化方案是“时间差 token 音高 token”比如[0, 60, 1, 62, 0, 64, 1, 65]表示 0 时刻出现音高 601 个时间单位后出现音高 62紧接着出现音高 64再 1 个时间单位后出现音高 65。音高优先考虑用 MIDI 数字表示因为 MIDI 数字本身是半音单位便于计算音高距离。比如 C4 是 60C#4 是 61D4 是 62最直观。第 0 步还要做一次基本的统计分析不同音高的分布是否均匀训练集里的调性覆盖是否足够这一步决定后面实验结果可信度。如果训练集里 90% 都是 C 调旋律那后面的等变实验几乎没法说明问题因为模型本来就有强烈的 C 调先验。5.2 第二步先跑一个普通基线不要一开始就上等变结构。先实现一个普通的 Music Transformer 基线训练它测试它。基线的作用是建立对照组。具体做法用相对位置编码的 Transformer训练目标用下一个音符预测先在小数据集上跑通记录训练 loss 曲线和测试集准确率然后做一个重要的评估把测试集里每一首旋律整体移调比如分别上移 2、5、7、12 个半音再让模型续写看移调前后的生成结果是否保持等价结构。这一步你大概率会看到普通基线在原始调上表现不错但移调后生成的旋律容易偏离原本的调性结构。5.3 第三步改造位置编码并加入音高距离项在基线基础上只做一处改动把相对位置编码扩展为“时间相对位置编码 音高相对距离编码”的组合。具体代码可以是这样的示意# 示意代码需要根据你的实际框架调整 class PitchRelativeAttention(nn.Module): def __init__(self, emb_dim, max_time_dist, max_pitch_dist): super().__init__() self.time_bias nn.Embedding(max_time_dist * 2 1, emb_dim) self.pitch_bias nn.Embedding(max_pitch_dist * 2 1, emb_dim) def forward(self, attn_logits, time_dists, pitch_dists): time_bias self.time_bias(time_dists).squeeze(-1) pitch_bias self.pitch_bias(pitch_dists).squeeze(-1) return attn_logits time_bias pitch_bias这里time_dists是注意力 query 和 key 之间的时间差pitch_dists是音高差。attn_logits是原本的注意力分数。注意这不是一个完整可跑的模型它只是展示核心思路的最小片段。5.4 第四步验证“等变性”是否真的生效训练完加音高距离项的模型后重复第二步的移调测试。比较两组结果原调输入时模型的生成结果结构。移调后的输入模型的生成结果是否也等比例移调。一个实用的验证方法把模型在 C 调输入下生成的旋律记为seq_C把在 G 调输入下生成的旋律记为seq_G。然后检查seq_G是否约等于seq_C 7按半音计算。如果模型具备较强的音高平移等变性这个关系应该近似成立。不是要求完全逐音符一致因为生成本身有随机性但统计上的音高分布、相邻音高差、落在目标调式的比例应该明显优于普通基线。5.5 用日志和曲线判断哪一层出了问题如果实验效果不好按这个顺序排查第一看训练 loss 是否正常下降。如果连训练集上的损失都降不下去问题可能在数据 token 化和模型容量上。第二看普通的非移调测试集上模型表现。如果普通测试都不过关说明模型本身没训练好不一定是等变结构的问题。第三看移调测试。如果原始调表现好、移调后崩掉说明等变结构还没有真正生效。重点关注音高相对距离项是否正确加到了注意力分数里以及音高距离的截断范围是否合理。第四检查调式分布。如果训练集仍然严重偏向某一个调模型先验会很强这时候要额外增加调式覆盖度。注意不要一上来就把移调测试的失败归因于“数据不够”。先确认普通测试和训练 loss 是否正常再去看等变结构有没有生效。6. 等变不万能真正适合它的场景与边界这个方向有很强的吸引力但它不是音乐生成的银弹。6.1 适合的场景与不应期望的效果等变结构最适合的场景是那些结构规则明确、移调行为可预期的音乐形式。比如流行和声进行、古典旋律、巴洛克对位。这类音乐里移调后功能保持不变等变建模非常契合。但如果你要做的是自由即兴、氛围音乐、实验电子或者高度依赖音色和噪声结构的作品纯音高平移等变模型的作用就会明显减弱。这些音乐类型的“结构”不一定体现在调性和音程关系上模型真正的瓶颈可能不在“移动几个半音”这个问题上。还要有一个清醒的认识等变音乐 Transformer 解决的是“结构正确性”不是“审美优越性”。它能让模型移调不变能让模型更高效地利用数据但不会让模型自动生成更动人的旋律。旋律有没有感染力取决于数据质量、训练目标、采样策略和音乐审美这些是另一个层面的问题。6.2 等变性可能的副作用等变性结构有时候也会引入不必要的限制。比如如果你把音高平移等变性做得非常严格模型会倾向于对所有调一视同仁。但实际音乐里有些调性因为乐器定弦、音域、演奏手感的原因天然有不同偏好。吉他手写出的旋律往往集中在几个方便按和弦的调上钢琴上某些调性的织体写起来更顺手。一个过强等变迁制的模型可能会忽略这些真实音乐中的调性偏好。所以在工程实现里我建议做成“软等变”而不是“硬等变”把音高距离信息作为强先验加入但不完全约束模型必须对所有调完全一致。换句话说让模型大概率能泛化到新调但又保留它从数据里学到真实调性偏好的能力。6.3 常见失败原因与排错思路如果你复现实验或多轮迭代中发现效果不理想常见原因大概落在这几层音高距离编码范围设置不合理。MIDI 音高范围是 0 到 127但实际旋律通常集中在 60 到 80 之间。如果 embedding 表设置过大参数浪费设置过短超出范围的距离信息会被截断。先统计数据里的音高范围再设置合理的max_pitch_dist。时间距离和音高距离权重没有平衡。如果音高 bias 数量级固定而时间 bias 很大模型会被时间信息主导等变效果不明显。需要在交叉验证里观察两者各自的梯度量级。训练目标不适合。如果你使用“下一个音符预测”模型学到的更多是局部平滑性不一定能展现出全局调性结构的等变能力。可以考虑加入调性预测辅助任务或使用更强的条件信息。验证集中数据没有真正的调性多样性。如果所有测试旋律都是相近调等变测试结果看起来不错但并不能说明模型真的理解了移调一致性。最好构造一组极端的测试集C 调移调到 Db 调移调 1 个半音和移调 11 个半音观察模型表现是否有突变。6.4 长期价值音乐生成开始进入“结构可解释”阶段虽然这个方向还不够大众化但它代表了一个重要变化音乐生成开始从“拟合 token 分布”走向“显式建模音乐结构”。普通 Transformer 靠规模和数据量去隐式学习音乐规则而 Equivariant Music Transformer 这类思路是尝试把音乐理论的先验直接注入模型。这样做有三个直接好处更少数据能学到更鲁棒的调性知识。模型在小数据、低资源环境下更容易训练。模型行为更可预测、更可解释因为你知道它内置了哪条结构规则。长期看音乐生成模型的竞争不只会在参数和算力上也会在“模型内部的结构假设”上分高下。能做到“用更少数据理解更多音乐规律”的模型更容易适应小众风格、低资源场景和个性化创作需求。回到开头那个问题为什么移调之后模型会崩真正的答案不是数据少而是模型结构里没有“移调等价”这个概念。等变音乐 Transformer 的意义不是把它变成一个更强的生成器而是把音乐中的结构知识变成模型天生具备的能力。如果你正在做音乐生成方向的实验我建议从一个小实验开始做一个普通基线做一个加入音高相对距离项的改造版本用移调测试去对比它们。这个实验成本不高但它会让你走出“靠堆数据解决问题”的惯性开始认真思考一个问题——模型里的哪一种结构假设才是真正不可替代的。