第一次用普通 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 时刻出现音高 60,1 个时间单位后出现音高 62,紧接着出现音高 64,再 1 个时间单位后出现音高 65。
音高优先考虑用 MIDI 数字表示,因为 MIDI 数字本身是半音单位,便于计算音高距离。比如 C4 是 60,C#4 是 61,D4 是 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 的意义,不是把它变成一个更强的生成器,而是把音乐中的结构知识变成模型天生具备的能力。
如果你正在做音乐生成方向的实验,我建议从一个小实验开始:做一个普通基线,做一个加入音高相对距离项的改造版本,用移调测试去对比它们。这个实验成本不高,但它会让你走出“靠堆数据解决问题”的惯性,开始认真思考一个问题——模型里的哪一种结构假设,才是真正不可替代的。