news 2026/10/12 5:54:19

机器学习音乐生成实战:LSTM钢琴曲自动生成毕设源码全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
机器学习音乐生成实战:LSTM钢琴曲自动生成毕设源码全解析

简介:一份基于机器学习的音乐自动生成软件设计与实现源码包,面向计算机、人工智能、自动化等专业的学生、老师或从业者,可用于毕业设计、课程大作业或期末项目参考。项目为个人高分毕设成果,答辩评审分达到95分,代码均经过调试与测试,确保可运行,能够直接作为音乐自动生成方向的学习范例,也支持在此基础上调整模型或界面实现更多功能。压缩包共7个文件,包含2个Jupyter Notebook(主程序与UI交互)、Python脚本、项目说明Markdown、介绍txt、详细设计说明书PDF及LICENSE,整体仅461KB,轻量便携,便于快速下载与本地运行。目录内既有可直接调用的代码,也有完整的设计说明书,能够支撑从原理理解到动手复现的全过程。已有224人学习下载。详细设计说明书与项目说明覆盖了模型选择、特征处理、生成流程等关键模块,适合需要系统参考毕设方案或快速搭建音乐生成Demo的读者,整体具有较高的学习借鉴价值。

1. 机器学习生成古典钢琴曲:这套高分毕设源码到底能跑出什么

想用机器学习做音乐自动生成,最怕的不是看不懂论文,而是拿到一个"看起来能跑"的源码后,在数据、训练、生成、播放这条链路上反复卡壳。这套高分毕设「基于机器学习的音乐自动生成软件设计与实现」把整条链路补齐了:MIDI 数据解析、LSTM 模型训练、采样生成钢琴曲、界面展示,源码和项目说明、详细设计说明书都在一个包里,代码已调试可直接运行。适合正在做机器学习课程设计、期末大作业、毕业设计的学生,也适合想快速走通一遍音乐自动生成完整流程的从业者。需要的直接下载这套资源,按第 3 章流程复现即可。

2. 音乐生成的技术底座:为什么是 LSTM,MIDI 数据怎么变成训练样本

2.1 音乐生成的三条路线:规则、统计、深度学习

音乐自动生成不是新话题。早年间常见的是两种路线:一种是基于规则的生成,预先写死和声进行、旋律模板去拼装,结果稳定但缺乏变化,听几首就能摸清套路;另一种是基于统计模型,用隐马尔可夫模型(HMM)或 n-gram 给音符序列建模,生成的旋律在局部有统计合理性,但一到长句就散架,根本无法形成乐句结构。

深度学习路线,尤其是循环神经网络家族,主要解决的是"长程依赖"这个问题。LSTM 通过门控机制让信息在序列中传递更远,训练目标很直接:给定前面若干音符,预测下一个音符出现的概率分布。这套毕设选的正是 LSTM,而非 Transformer 或 VAE,原因很实际:课程设计和毕设场景下,机器资源和训练时间都有限,LSTM 对数据量和训练成本更友好,模型结构也更容易在答辩时讲清楚。Transformer 在长旋律生成上确实更强,但训练数据量通常要翻几倍,对个人毕设来说性价比不高。

一个常被忽视的点是,LSTM 在这里把整首乐曲当成"一维音符流"处理,而不是组织成小节、乐句的二维结构。这个简化会带来生成上的某些缺陷,后面避坑章会专门讲,但它换来的是代码直观、入门低、可视化方便。对课程设计和毕业设计这个定位来说,这个选型是合理的,答辩时也好解释:"我为什么不用更复杂的模型"比"我堆了多少层网络"更打动评委。

2.2 把旋律当序列预测问题:音符、时值、力度怎么编码

所有序列模型的前提是"把音乐变成数字"。MIDI 文件里一条音符事件至少包含四个字段:音高(pitch,0~127)、起始时间(start)、结束时间(end)、力度(velocity,0~127)。想喂给 LSTM,最直接的编码方式是把每个音符压成一个整数索引,建立一张音高表,C4 对应 60、C#4 对应 61,以此类推。

但这里有个取舍:只用音高索引,模型能学会旋律走向,却学不会节奏,因为时值信息被丢掉了。更常见的做法是把"音高 + 时值"拼接成联合编码。下面这段解析代码用 pretty_midi 读取 MIDI,并按起始时间排序,得到干净的音符事件序列:

import pretty_midi def midi_to_notes(midi_path): midi = pretty_midi.PrettyMIDI(midi_path) targets = [] for inst in midi.instruments: # 跳过打击乐轨,只保留钢琴类音色(program 0~7) if inst.is_drum or inst.program > 7: continue for n in inst.notes: targets.append((n.pitch, n.start, n.end, n.velocity)) targets.sort(key=lambda x: (x[1], x[0])) return targets

代码逻辑说明:先过滤掉鼓组和非钢琴音轨,再把所有音符事件按"开始时间优先、音高其次"排序,保证进入模型的序列在时间上是单向递增的。这一步不做的话,多轨 MIDI 解析出来的事件会交叉错乱,训练出来的模型根本学不到时间结构。解析完的 (pitch, start, end, velocity) 元组,后续还要做时值离散化:确定一个最小时间单位(比如 0.25 拍),把每个音符的起止换算成"第几个单位",再压成 (pitch, duration) 联合索引。这样 LSTM 的输入是整数序列,输出是下一个音符的索引分布,训练就成了一个标准的分类任务。

2.3 模型结构拆解:Embedding + LSTM + 全连接

这套项目的核心模型不复杂,拆开看就是三层:Embedding 层把音符索引映射成稠密向量;两层 LSTM 负责捕捉序列依赖;全连接层把隐状态映射回词表大小的 logits。用 PyTorch 写出来大概是这样:

import torch.nn as nn class NoteLSTM(nn.Module): def __init__(self, vocab_size, embed_dim=128, hidden_dim=256, num_layers=2): super().__init__() self.embedding = nn.Embedding(vocab_size, embed_dim) self.lstm = nn.LSTM(embed_dim, hidden_dim, num_layers=num_layers, batch_first=True) self.fc = nn.Linear(hidden_dim, vocab_size) def forward(self, x): emb = self.embedding(x) # (B, T, E) out, _ = self.lstm(emb) # (B, T, H) return self.fc(out) # (B, T, V)

参数说明:embed_dim 控制在 128 左右就够,词表通常只有几百个联合符号;hidden_dim 取 256 属于保守配置,纯 CPU 也能跑;num_layers=2 是旋律生成任务的常见折中,层数再往上加,参数量变大,数据不够时更容易过拟合。vocab_size 训练前由数据统计得到,就是联合编码里出现的不同符号总数。

训练用交叉熵损失,优化器一般选 Adam,学习率从 1e-3 起步。每个 batch 的输入是长度 T 的连续音符窗口,标签是窗口右移一位的序列,也就是用前 T 个音符预测第 2 到第 T+1 个音符。这个窗口式构造是整个训练代码里最关键的部分,窗口长度和滑动步长直接决定模型能看到多长的上下文,也直接影响后面生成旋律的连贯性。

3. 本地复现全流程:从解压源码到生成第一段钢琴曲

3.1 先过一遍项目结构:源码、说明文档、设计说明书都在哪

拿到压缩包解压后,第一件事不是急着跑代码,而是先看懂包里的文件各管什么。这个包的顶层结构很好认:项目说明.md 和 介绍.txt 是快速上手入口,用来交代项目定位和运行前提;详细设计说明书.pdf 是答辩用的完整设计文档;Classical-piano-composer 是项目主体目录;main.ipynb 是训练主流程;UI.ipynb 是可视化界面;create_music_py.py 是脱离 notebook 的独立生成脚本;LICENSE 说明开源许可方式。

文件/目录作用
项目说明.md快速上手与运行前提
介绍.txt项目简介
详细设计说明书.pdf答辩用完整设计文档
Classical-piano-composer项目主体目录(数据与模型)
main.ipynb训练主流程
UI.ipynb可视化交互界面
create_music_py.py独立生成脚本
LICENSE开源许可说明

建议按这个顺序过文件:先花十分钟读项目说明.md,再翻一遍详细设计说明书.pdf 的目录页,弄清训练数据和生成脚本的约定,最后才打开 main.ipynb 看代码。很多人在这一步就翻车——直接从上到下执行 notebook,结果卡在数据路径和依赖版本上。设计说明书里通常把目录结构、依赖清单、预期输出都写清楚了,先看文档比先跑代码省力得多。

提示:运行前先确认 MIDI 数据目录路径和权重保存路径是否可写。notebook 报错里很大一部分是路径写死导致的,先改路径再跑。

3.2 训练环节:main.ipynb 里数据加载与训练循环的对应关系

main.ipynb 的典型流程是:读取 MIDI 数据目录 → 批量解析成音符事件 → 构建联合编码 → 切分训练/验证序列 → 定义模型 → 训练循环 → 保存权重。整个 notebook 的单元格顺序,和上面 2.2、2.3 节的逻辑一一对应。其中序列切分这一步最容易写错,常见做法是设置窗口长度和滑动步长两个参数:

import numpy as np def create_sequences(indices, seq_len=64, stride=32): xs, ys = [], [] for i in range(0, len(indices) - seq_len - 1, stride): xs.append(indices[i:i + seq_len]) ys.append(indices[i + 1:i + seq_len + 1]) return np.array(xs), np.array(ys)

参数说明:seq_len 是模型每次看到的音符数量,stride 是滑动步长。步长等于 seq_len 时样本之间无重叠,样本量会少很多;步长为 1 时样本量爆炸但高度重复,训练效率低。取 seq_len 的一半是折中做法,既保证样本量又保留上下文多样性。

训练循环本身不长,核心代码如下:

for epoch in range(num_epochs): total_loss = 0.0 for x_batch, y_batch in train_loader: out = model(x_batch) # (B, T, V) loss = criterion(out.view(-1, vocab_size), y_batch.view(-1)) optimizer.zero_grad() loss.backward() optimizer.step() total_loss += loss.item() print(f"epoch {epoch+1}/{num_epochs} loss {total_loss/len(train_loader):.4f}")

逻辑说明:模型输出是 (B, T, V),要展平成 (B*T, V) 再和标签算交叉熵。y_batch 是 x_batch 右移一位的结果,所以模型在每个时间步都在做"下一个音符预测"。训练中重点看两件事:训练 loss 是否单调下降,验证 loss 是否在某个 epoch 后开始回升——回升就是过拟合信号,该考虑加数据或减小模型规模。

3.3 生成环节:create_music_py.py 的调用参数与输出

create_music_py.py 的作用是训练完成后脱离 notebook 直接生成一段旋律。常见调用是命令行传参:权重路径、种子序列长度、生成音符个数、temperature、输出 MIDI 路径。如果从 notebook 里跑,也可以函数式调用。

生成的核心是采样循环:给定种子序列,模型预测下一个音符的概率分布,按 temperature 采样出音符,追加到序列末尾,同时丢掉序列最前面的音符,保持窗口长度不变,迭代到指定长度。对应采样代码如下:

def generate_notes(model, seed_seq, num_to_generate, temperature=1.0): model.eval() seq = list(seed_seq) for _ in range(num_to_generate): x = torch.tensor([seq[-SEQ_LEN:]], dtype=torch.long) with torch.no_grad(): logits = model(x)[0, -1, :] # 只取最后一个时间步 probs = torch.softmax(logits / temperature, dim=-1) next_idx = torch.multinomial(probs, 1).item() seq.append(next_idx) return seq

参数说明:temperature 等于 1.0 时按模型原始分布采样;小于 1.0(如 0.6)分布变尖锐,结果保守、重复更多;大于 1.0(如 1.2)分布变平滑,旋律更跳跃,更容易出现不和谐音。生成完要把联合索引反解回 (pitch, duration),再换算成绝对时间,用 pretty_midi 写回 MIDI 文件。注意生成和训练是分离的,调 temperature 不需要重新训练。

3.4 交互界面:UI.ipynb 怎么把生成过程可视化

UI.ipynb 的作用是把训练和生成从"命令行黑匣子"变成可视化操作。这个 notebook 一般会展示音符分布、loss 曲线和生成片段的钢琴卷帘图,有的版本支持在界面上选 temperature、点生成、在线试听。

对答辩和课程设计来说,UI.ipynb 的价值不在于功能多强,而在于演示效果好:评委看到的不只是"我训练了一个模型",而是"模型在界面上持续采样、实时渲染旋律"。如果时间紧张,优先把 UI.ipynb 跑通,而不是在 main.ipynb 里反复调参。界面代码不强依赖显卡,CPU 上也能跑,生成速度取决于序列长度和 batch 设置。跑通之后,一定要把界面上看到的 loss 曲线和生成片段截图存下来,这些图是答辩材料里最实在的"实验结果"。

4. 生成效果的关键参数:temperature、序列长度与训练轮数怎么调

4.1 temperature:决定旋律是"四平八稳"还是"天马行空"

生成阶段最值得花时间调的参数就是 temperature。很多人第一次生成用默认值 1.0,出来的旋律要么像随机音阶,要么过度重复,于是怀疑模型没训练好,其实问题常出在采样策略上。temperature 的本质是改变 softmax 分布的"尖锐程度":温度越低,高概率音符被选中的概率越大,旋律越保守;温度越高,低概率音符也有机会被选中,旋律越有冒险性。

实用经验是:先固定 temperature=0.8 生成三段,再固定 1.2 生成三段,对比听感。如果 0.8 听起来机械重复,1.2 听起来杂乱无章,说明问题不在温度,而在模型欠拟合或数据本身有问题。如果 0.8 已经比较自然,那就以 0.8 为基准逐步微调。这里的判断要落到"听感"上,不要只看 loss 数值。

提示:temperature 只影响生成,不影响训练,调参时不需要重新训练,可以反复试。

参数推荐区间影响判断依据
temperature0.6~1.2重复度/跳跃度0.8 起步,多段对比听感
SEQ_LEN64~128乐句连贯性过短循环、过长不收敛
strideSEQ_LEN 的一半样本量多样性看训练集样本总量
batch_size16~64训练稳定与显存CPU 用 16~32

4.2 序列长度与窗口重叠:影响句子级连贯性

SEQ_LEN 决定模型预测下一个音符时能"回头看"多远。SEQ_LEN 太小,比如 16,模型只能学到几个音符的局部搭配,生成结果没有乐句感;SEQ_LEN 太大,比如 256,训练成本上升,梯度传导也容易出问题,效果不一定更好。对钢琴独奏这种音符密度不高的场景,64~128 是合理区间。

窗口滑动步长同样要留意。步长等于 SEQ_LEN 时样本完全无重叠,样本量骤减;步长为 1 时样本量虽大,但绝大多数高度重复,模型容易过拟合到高频片段上。我一般设 stride 为 SEQ_LEN 的一半,既保证样本量,又保留上下文多样性。这一步在 main.ipynb 里往往是一行切片代码,很多人忽略,导致生成旋律在句法上时断时续。

4.3 训练轮数与 batch_size:先看图再调参

训练轮数没有标准答案,只能看 loss 曲线。常见错误是一上来就跑 100 个 epoch,训练完发现过拟合严重;另一个极端是跑 5 个 epoch 就开始生成,出来的全是噪声。稳妥做法是每 5 个 epoch 记录一次验证 loss,当验证 loss 连续多个 epoch 不降反升时,停下来用当前权重生成试听。

batch_size 和硬件强相关。CPU 上跑 32 以内比较稳,GPU 上可以提到 128,但音符序列通常不长,batch 太大不会显著加速收敛。还有个容易被忽略的点:如果 MIDI 数据集太小,比如只有几十首曲子,LSTM 很容易过拟合,此时与其加轮数,不如换更大的数据,或者用八度移位、变速这类数据增强。数据量和参数要一起看,单调一个变量很难同时解决欠拟合和过拟合。

5. 避坑与排查:跑音乐生成项目最容易翻车的五个点

这一章整理了复现这类项目时出现频率最高的五个问题,按出现概率排序。每一条都按现象、原因、解决的顺序写,照着一一排查能省下大量"看起来在跑、实际在乱"的时间。

5.1 MIDI 解析报错或音轨乱入

现象:解析某些 MIDI 文件时直接报错,或者生成的旋律里混进鼓声、贝斯声,旋律线被其他乐器淹没。

原因:MIDI 文件结构和质量差异很大。有的多轨多乐器,有的包含控制码、歌词等非音符事件;不加过滤直接收集所有音符,会把打击乐轨和伴奏轨一起算进来,训练数据里全是"不该出现的音"。

解决:解析阶段按 2.2 节的方式过滤 is_drum 和 program 范围,只保留钢琴类音轨;对解析异常的文件用 try-except 跳过,并在日志里记录文件名,不要让单个坏文件中断整个数据构建。对课程设计来说,先筛选出能正常解析的 20~30 首曲子,比追求数据量更重要。

5.2 训练 loss 降了但生成的是噪音

现象:训练 loss 一路从 4.0 降到 1.2,看着很健康,结果生成的旋律听上去像随机音符堆砌,毫无调性。

原因:两个因素叠加。一是模型欠拟合或数据本身混乱——比如和弦音符没做折叠,每个时间步同时出现多个音,序列语义混乱,模型学到的"下一个音"实际上是不确定的;二是生成时 temperature 过高,把大量低概率音符采样了出来。

解决:先用 temperature=0.5 生成一段。如果仍然混乱,说明模型确实没学到调性结构,回数据检查和弦与多音处理;如果调低温度后明显变顺,说明模型没问题,是采样策略的事。判断规则记住一条:生成效果差时先换温度,再查数据,最后才动模型结构。

5.3 生成旋律全是同一个音或短句无限循环

现象:生成结果里某个音符反复出现,或者两小节的旋律被无限循环,前 10 个音符和后 50 个音符几乎一样。

原因:两个常见来源。一是采样用了 argmax,直接取概率最大的那个音符,模型过度自信时会把概率全压到少数高概率音符上;二是训练数据里某几个连续组合出现频率过高,而 SEQ_LEN 又太短,模型只学会了局部概率最高点。

解决:改用带 temperature 的 multinomial 采样,并给概率分布加最低下限,避免某些符号概率被压到零后彻底消失:

probs = torch.softmax(logits / temperature, dim=-1) probs = probs.clamp(min=1e-4) # 保底概率,避免零概率符号 probs = probs / probs.sum() # 重新归一化

同时调大 SEQ_LEN。如果循环仍然严重,检查训练数据里是不是同一首曲子被重复拷贝了多份,这种情况在从网上下载的 MIDI 合集里很常见。

5.4 笔记本内核崩溃或显存不足

现象:main.ipynb 训练到一半内核无征兆被杀掉,或者 GPU 直接 OOM 报错。

原因:最常见的是 batch_size 太大加上序列长度太长,输入张量形状膨胀,显存被瞬间占满;其次是训练循环里把每个 epoch 的完整输出都存进列表,内存被日志数据拖爆,CPU 机器上表现为内核被杀。

解决:先把 batch_size 降到 16 甚至 8,确认能完整跑完一个 epoch 再逐步增大;loss 日志只保留最近几轮数值,不要累积全量历史;如果用了 GPU,训练完立即释放不再使用的张量,必要时加一句 torch.cuda.empty_cache()。这一步在课程设计里被反复踩,宁可慢一点也不能中途崩。

5.5 生成的 MIDI 播放速度不对或音高怪

现象:生成的 MIDI 用播放器打开,要么飞快要么极慢,或者音高整体偏高偏低半个八度。

原因:时值编码与绝对时间换算不一致。训练时如果用的是"每个音符持续几拍"的相对时值,生成后也要按同一拍速换算成秒;如果把相对时值直接当绝对秒数写进 MIDI,速度必然混乱。音高问题则通常是 pitch 数值偏移,MIDI 音高和音名的转换搞错了基准。

解决:反解时先用几条已知的 MIDI 做"编码 → 生成 → 解码 → 比对原曲"的往返测试。这一步最好在训练前就做:拿一首训练数据里的曲子,编码成索引再解码回 MIDI,如果还原出来的和原曲一致,说明整条编码链路是通的,后面生成才有意义。往返测试通过之后再谈调参,顺序不能反。

6. 进阶验证:把生成的 MIDI 变成能听的作品,并量化评估好坏

6.1 后处理:拼接片段、加伴奏层、导出成完整曲目

生成的裸旋律往往是单音线,听感单薄。低成本的后处理思路是:生成 8~12 个短片段,挑出调性一致的,按主旋律加简单左手分解和弦的方式叠两层,再处理尾音让曲子有终止感。写回 MIDI 时设置好 initial_tempo,片段之间留 0.5~1 秒静音,避免拼接处突兀。工具上还是用 pretty_midi,加载、加轨、写盘都很方便。

6.2 量化验证:音符分布、重复率与自相似度

主观听感之外,我习惯加三个量化指标:音符分布是否集中在少数几个音级,看直方图有没有明确主音;相邻音符的重复率,过高说明模型在偷懒;生成片段与训练集的自相似度,用来判断是不是背下了原曲。这三个指标不用做成复杂系统,在 notebook 里画三张图就够。答辩时能讲清楚"我如何判断生成结果不是随机噪音",比直接放一段音频更有说服力。

6.3 复现这类项目我固定的验证顺序

从那以后我每次复现音乐生成项目,都强制走一遍这个顺序:先做编码往返测试,再跑 20 个 epoch 看验证 loss,然后固定 temperature=0.8 生成三段对比,最后才写后处理脚本。这套流程能过滤掉大部分"看起来能跑、听起来不能听"的情况,省下的时间远比多跑几组实验值钱。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/12 5:53:46

Python+MySQL图书馆管理系统开发实战指南

简介:这是一套基于Python与MySQL开发的图形化界面图书馆管理系统源码,面向Python初学者与数据库应用开发者,解决图书信息管理、读者资料维护及借阅流程自动化等实际业务问题,适用于课程设计、毕业设计或小型机构内部管理系统搭建。…

作者头像 李华
网站建设 2026/10/12 5:52:03

工业PoE交换机选型与部署:从PoE供电原理到远距离传输故障排查

1. 为什么工业现场越来越离不开PoE交换机先讲一个我实际见过的场景:某自动化产线改造,现场要部署一批IP摄像机和无线AP。电工班老张跟我说,新设备装起来不难,难的是布线——摄像头装在钢梁上,附近根本没有电源插座&…

作者头像 李华
网站建设 2026/10/12 5:51:22

非Docker环境下手动安装视频标注工具VATIC全攻略

做视频数据集最大的痛点从来不是算法选型,而是标注。尤其是视频目标检测任务,动辄几千帧画面,一帧一帧画框能把人逼疯。我在2020年初接到一个内部项目,需要快速攒一批标注数据,专门去调研了一圈视频标注工具&#xff0…

作者头像 李华
网站建设 2026/10/12 5:51:04

中国省市区城市列表MySQL数据表:导入、转JSON与查询优化

简介:这是一份面向后端开发、数据分析与地图应用开发者的中国省市区城市列表MySQL数据表,以SQL脚本形式提供,可直接导入数据库,也可转换为JSON数据使用。数据覆盖全国省份、城市与区县,包含行政区划码和经纬度信息&…

作者头像 李华
网站建设 2026/10/12 5:50:43

用Go从零写一个命令行记账工具:SQLite存储与CSV导出

1. 为什么我最终选择用 Go 来写一个记账工具很多人第一次听到“用 Go 写记账工具”都会觉得有点小题大做——记账嘛,Excel 拉个表格,或者手机上下个现成的 App 不就完了?我一开始也是这么想的。但用了几年各种记账软件之后,我发现…

作者头像 李华