等变Music Transformer:让音乐生成在移调中保持一致 在实际的音乐生成、风格迁移和自动作曲任务里Transformer 已经是很常见的基座模型但大部分实现把 MIDI 音高当成普通离散 token 去学习。这个做法有一个隐蔽问题旋律整体移调后模型的输出往往不稳定原本 C 大调上表现很好的生成结果换到 D 大调或者升调几个半音就变样。Equivariant Music Transformer 想解决的正是这个问题。它把“等变性”这种几何上的对称约束带到 Transformer 结构中让模型在音高平移、时间平移这类音乐本身拥有的对称变换下输出行为保持一致。如果之前只接触过标准自注意力可以从等变性的定义开始逐步拆解音乐 Transformer 在哪里破坏了对称性再用一个可运行的 PyTorch 最小示例把相对音高注意力、移调一致性验证和常见坑完整过一遍。对自注意力的 Q/K/V 还不熟悉的读者建议先找《The Illustrated Transformer》这类图解资料把基础过一遍再回到这里会顺利很多。1. 先从“等变性”这个词说起音乐为什么自带对称结构1.1 等变性与不变性一个例子讲清楚通俗地说等变性描述的是“输入先变换再计算”和“先计算再变换”这两种顺序是否等价。假设我们已经训练好一个模型输入是一段旋律输出是下一段的预测分布。如果我们把整段旋律统一往上移 12 个半音也就是升一个八度那么合理的行为应该是输出分布也跟着往上移 12 个半音。写成数学形式就是f(g·x) g·f(x)其中 g 是一个变换。如果这个等式成立函数 f 对这个变换是等变的。如果模型输出的是“旋律属于哪个调”这类标签我们希望的是 f(g·x) f(x)也就是不变性。等变和不变的区别在于等变保留了变换本身的信息不变则把变换信息丢弃。在音乐任务里旋律补全、生成、和弦标注这类输出和音高位置强相关的任务需要的是等变性而调性分类、情绪识别这类任务通常只需要不变性。设计模型之前先分清需求后面的结构选型才不会出错。1.2 音乐里的群结构移调、时间平移与音高翻转音乐数据里的对称性最明显的有三组。第一是音高平移也就是移调。把 MIDI note 60中央 C整体换成 note 72高八度 C旋律走向不变。第二是时间平移把整首曲子往右平移几个小节音乐内容不应该变。第三是音高翻转与时间反转例如把上行音程变成下行音程这在调式分析、节奏建模里也有意义但实现起来比前两个复杂。这些对称性放在群论里看构成一组变换群。音高平移对应整数加法群时间平移对应另一个整数加法群音高翻转对应镜像变换。等变 Music Transformer 的基本思路就是在网络结构里“利用”这些变换的性质而不是完全靠训练数据去生硬学习。这里有一个容易误解的点数据增强也能让模型把某个变换学得近似等变。比如训练时随机移调几十次模型有机会见过多种调式。但数据增强本质是“哄模型学会”不能保证推理时对没有见过的变换组合也成立而且会额外消耗参数量和训练数据。结构上做等变是把对称性直接写进计算方式属于“从一开始就不允许破坏”。1.3 等变 Music Transformer 与普通 Transformer 的差别普通 Transformer 处理音乐时通常把一个 note 表示成绝对音高、相对时长、力度和位置信息再用 learned embedding 映射成向量。标准多头注意力对 token 顺序是置换等变的但这种等变只针对 token 本身的位置排列和音乐里“音高平移后旋律不变”不在一个语义层面。问题出在两点第一绝对音高进入 embedding 后attention 的 Q/K 计算会把绝对音高差异混进相似度里第二绝对位置编码对时间平移并不友好同一段旋律从第 1 小节开始和从第 20 小节开始表示完全不同。等变 Music Transformer 并不会完全抛弃 Transformer 结构而是对三个地方做改造用相对音高间隔替代绝对音高主特征用相对时间间隔替代绝对位置在注意力偏置里加入“音高差”维度的可学习参数。这样模型在结构上就不会对绝对调式和绝对时间位置敏感。改造后的模型仍然用标准 Q/K/V 注意力做序列建模只是注意力分数的来源多了一个“音高间隔”通道。2. Transformer 处理音乐时哪些地方“不等变”2.1 音乐怎么变成 token 序列常见做法是把 MIDI 文件解析成一个事件流note on、note off、time shift、velocity以及开始结束这类控制 token。下面是一个最简化的表示方式。实际项目中会用类似 REMI、CP Word 这类词表方案但核心思想一致把一个音符拆成音高、时长、力度三个维度分别或联合编码。from dataclasses import dataclass dataclass class NoteEvent: pitch: int # MIDI note number例如 60 表示中央 C start: int # 起始位置单位可以是 tick 或一个 beat 的整数倍 duration: int # 时长单位同上 velocity: int # 力度一般 0-127 def melody_to_tokens(notes, step1): # 返回 (pitch_tokens, time_tokens, velocity_tokens) 三个平行序列 pitch_tokens [note.pitch for note in notes] time_tokens [note.start // step for note in notes] velocity_tokens [note.velocity for note in notes] return pitch_tokens, time_tokens, velocity_tokens这段代码刻意把音高和时间分开方便后面做等变改造。要注意的是这里 time token 使用的是节拍相对步长不是绝对时间戳如果使用“这首歌第 3 分钟”这种绝对时间模型天然无法对时间平移等变。2.2 绝对音高嵌入与绝对位置编码的问题标准做法里pitch 60 和 pitch 72 是完全不同的 embedding 向量这本身没问题问题在于 attention 计算把这两个向量的相似度当成动态权重来源。模型如果只见过 C 大调它隐含学到的相似度规律可能依赖“根音在 60 附近”这个统计事实。移调后根音变成 62 或 72Q/K 相似度整体变化生成路径也随之漂移。同样的道理也适用于绝对位置编码。Transformer 的原论文使用正弦位置编码或学习式位置编码本质是绝对位置。对自然语言来说这还好因为“第 5 个词”本身有语言学意义但音乐里旋律从第 1 小节开始还是从第 3 小节开始通常不是语义差异只是时间起点不同。绝对位置编码会让同一个旋律在不同起点得到不同表示。2.3 自注意力中需要改动的部分从工程角度需要检查四个位置token embedding音高是否以绝对 MIDI 数值直接进入 embedding。位置编码使用绝对位置还是相对位置。attention 的相似度计算Q/K 是否包含了绝对音高位置信息。输出头预测绝对音高还是相对音高。一个常见的改造方案是保留绝对音高 embedding 供输出层使用但在 attention 内部计算注意力分数时额外使用“相对音高间隔”作为偏置。这样模型可以感知音程关系同时不把绝对调式信息注入注意力权重里。下面用一张表汇总普通实现和等变化处理。| 模块 | 普通实现 | 等变化处理