Hacker News 中文摘要

RSS订阅

Show HN:我训练了一个1.25亿参数的模型,用于在设备上自动完成钢琴演奏 -- Show HN: I trained a 125M model to autocomplete piano on-device

文章摘要

作者训练了一个1.25亿参数的Transformer模型,用于实时自动续写钢琴演奏(iPhone 15上每秒约108个音符)。关键改进在于优化MIDI表示、严格清洗训练数据,以及加入DPO后训练。

文章总结

我训练了一个拥有1.25亿参数的Transformer模型,用于实时自动续写钢琴演奏(在iPhone 15上每秒可处理约108个音符)。项目取得最大改进的关键在于:找到合适的MIDI数据表示方式、严格清洗训练数据,以及采用DPO(直接偏好优化)进行后训练。

大约一年前,我开始尝试一个想法:将MIDI钢琴连接到手机,弹奏一些音符,然后让人工智能自动完成整首歌曲——就像GitHub Copilot,但用于钢琴演奏。这比预想的要复杂得多。经过14次实验,最终成果达到了让我满意的程度,值得记录下来。

MIDI文件与音频格式有何不同? MIDI文件与MP3等音频格式截然不同。它不存储录制的声音,而是将音乐存储为一系列事件:按下某个音高和力度的琴键、释放琴键、延音踏板状态变化等。这些事件通常组织成多个音轨。本项目专注于钢琴续写,因此主要保留钢琴类素材,移除或减少了其他部分。

如何对音乐进行分词? 为了训练Transformer处理这些演奏,我需要将MIDI事件转化为模型可读取和预测的离散序列。最直接的映射是为每个MIDI事件创建一个词元。如果直接在NOTE_ON词元中包含音高和力度,词汇表会迅速膨胀。一种常见的改进是使用语法结构来分解表示,并在生成时通过掩码强制语法规则,确保输出在语法上有效。

我尝试了基于按下/释放音符的表示方式,但模型容易“跑偏”,忘记释放音符或丢失状态。另一种表示方式将音符和时移分开,避免了释放音符的漂移问题,但速度较慢,一个音符大约需要四个自回归Transformer步骤,且会快速消耗上下文窗口。

最终采用的表示方式 我最终确定的表示方式是:NOTE(音高, 起始偏移, 时长, 力度)。没有单独的时移事件,静默由下一个音符的起始偏移表示。和弦由多个起始偏移为0的音符表示,并按音高排序。关键创新在于:Transformer不是逐个生成音符的属性,而是每次生成一个完整的音符。这使得大型模型在iPhone上达到约108个音符/秒的速度。每个音符有五个分类字段,各有自己的词汇表,时序被量化为固定步长。每个字段有自己的嵌入,音符词元是所有嵌入的总和。模型有独立的输出头,字段间有一个小型嵌套解码器,但昂贵的Transformer主干每个音符只运行一次。

延音踏板处理 我没有显式添加延音踏板事件,而是在预处理阶段将延音效果融入音符时长。如果琴键释放时延音踏板仍被踩下,音符时长会延长到踏板抬起时。如果同一音高被再次弹奏,则前一个音符在重触发时被截断。这样简化了建模问题:模型只需预测音高、起始、时长和力度。

数据集 我搜索了大量公开数据集和合集,主要关注公有领域的古典音乐。最终数据集包含几十万个MIDI文件,约3亿个音符事件。最终处理流程包括:选择钢琴素材、移除或减少多音轨混合、按密度和音高/时间覆盖过滤、通过指纹去重(忽略全局移调和统一速度变化)、将同一作品的不同版本分组。我曾尝试将数据集扩大到约5倍,但效果反而更差。数据清洗和选择比单纯增加数据量更重要。

训练 初始训练是五个输出头的交叉熵损失之和。这便于分别追踪音高、时长和力度的准确率。但训练目标有一个重要局限:音乐续写没有唯一正确答案。交叉熵有助于学习音乐机制,但不是衡量续写效果的好指标。

数据增强 由于现场输入并非完美的MIDI文件,数据增强很重要。我最终采用了全局移调、统一速度缩放、时长/力度抖动以及丢弃提示音符等增强方法。

模型架构 模型是标准的仅解码器Transformer:RMSNorm、旋转位置嵌入、因果自注意力、SwiGLU/MLP块和自回归生成。我主要训练了三个规模:小(约3300万参数)、中(约6400万)、大(约1.25亿)。小模型适合快速实验,中模型几乎总是胜出,大模型表现更好但提升幅度不大。

计划采样 我在每个音符的字段间使用了计划采样。训练时有时会向模型提供其自身预测的音高而非正确音高。这增加了验证损失,但改善了续写质量。

评估 最初评估全靠我手动听。后来我编写了自动指标(如重复音高n-gram、音高熵等),这些指标有助于发现明显失败,但不足以选择最佳模型。最终我使用Gemini 3.5 Flash进行成对评估,要求它判断两个续写中哪个更好,并镜像比较以减少位置偏差。我还将评估分为两个标准:续写得分(衡量输出是否遵循提示)和音质得分(衡量音乐质量),以续写得分作为DPO的主要信号。

DPO(直接偏好优化) DPO在预训练后带来了最大改进。对于每个提示,我生成多个续写,使用成对评估选择更好和更差的版本。DPO训练模型使被选中的续写比被拒绝的更可能发生,同时保持与原始模型合理接近。经过DPO后,超过69%的续写优于基础模型。β值控制DPO惩罚偏离基础模型的强度,β=0.01和0.03改善了模型,而β=0.10则过强。使用“共识”数据集(仅保留评估者一致同意的偏好对)产生了最佳结果。

无效尝试 许多方法没有奏效:按下/释放音符表示方式漂移严重;语法掩码词元流有效但缓慢;噪声数据使结果更差;更大模型未能神奇解决循环问题;Mirostat减少重复但常使输出不连贯;额外局部辅助损失使训练变慢;绝对标量评分不如成对判断;验证损失无法反映续写质量差异;重生网络未能提升质量。

打包部署 我将PyTorch模型导出为Core ML,并将权重量化为INT8。首次启动较慢,因为Apple的运行时需要针对硬件优化模型。模型仅训练了最多512个音符的上下文,但为了支持更长的会话,当上下文接近限制时,我保留最近384个音符并重建上下文。由于Core ML不直接暴露Q、K、V,我无法使用更优雅的环形缓冲区方案。

结论 这是一个非常有趣的项目。我刻意避免一开始就深入阅读相关论文,而是享受自己解决问题的乐趣。项目仍远非完美:偶尔会循环、短提示困难、还有很多改进空间——可以理解为钢琴版的GPT-2。但我终于达到了一个阶段:真正享受坐在钢琴前,弹奏几个音符,然后看看我们(我和AI)能共同创作出什么。

评论总结

根据评论内容,总结主要观点如下:

1. 高度认可与赞赏(多数评论) - 评论普遍认为该项目“非常棒”(awesome)、“神奇”(magic)、“鼓舞人心”(inspiring)。 - 关键引用:yoouareperfect: "This is really awesome thanks for sharing";pil0u: "I find your application absolutely magic. This is very inspiring"

2. 改进建议与功能扩展 - 部分用户建议提升生成质量(如节奏、和声、音乐形式),而非追求高音符速率。 - 有人希望实现“用户演奏旋律,AI自动生成巴洛克风格伴奏”的功能。 - 关键引用:evalystai: "there's definitely much room for improvement, first of all rythm and composition";heikkilevanto: "I play the melody, and it produces proper 3-4 part accompaniment, preferably in good baroque style"

3. 技术讨论与对比 - 评论提及类似项目(如Francois Pachet的Continuator、AllTheMusic),并讨论模型优化(如DPO训练、设备端运行效率)。 - 关键引用:timmb: "Reminds me of Francois Pachet’s Continuator";goda90: "Reminds me of this project to generate every melody possible algorithmically"

4. 输入方式与扩展性 - 有用户希望支持麦克风输入(如哼唱或钢琴录音),而非仅限MIDI。 - 有人询问如何建模演奏细节(如力度、装饰音、踏板)。 - 关键引用:leobg: "How about whistling or playing the piano via microphone?";davidajackson: "How would you expand this to support elements like attack, grace notes, timing"

5. 怀旧与趣味性 - 部分评论提及早期互联网MIDI音乐的怀旧感,或设想将项目与合成器结合玩音乐。 - 关键引用:hliyan: "Ah, MIDI files. The only type of music you could realistically download from the internet back in the day";isoprophlex: "Gonna hook this up to my little synthesizer and blast some square wave arpeggiated ML music"