基于LSTM的机器学习音乐生成毕设源码:从MIDI数据处理到旋律生成全流程
简介这份资源是面向计算机、人工智能、自动化等专业学生与教师的高分毕业设计项目包主题为基于机器学习的音乐自动生成软件设计与实现答辩评审分达95分代码经过调试测试可稳定运行适合作为课程设计、期末大作业或毕设参考也便于基础较好的学习者在此基础上修改扩展功能。压缩包共7个文件约461KB包含2个ipynb交互式笔记本、1个py脚本、1个pdf详细设计说明书、1个md项目说明、1个txt介绍及1个license许可文件覆盖模型训练、界面交互与音乐生成等核心模块结构清晰便于按流程阅读。目前已有224人学习下载读者可从中获取完整项目源码、设计说明书与运行说明理解机器学习生成音乐的实现思路与工程组织方式并借鉴其代码结构与排错经验快速完成自己的课程或毕设任务。1. 从一段能跑通的旋律说起这份毕设源码到底交付了什么如果你正在为课程设计或毕业设计找一个能跑通、能讲清、还能在答辩时扛住追问的机器学习项目这份「基于机器学习的音乐自动生成软件」源码包值得先看一眼。它不是那种只丢一个 notebook、跑完就报错的半成品而是把数据、模型、生成逻辑和可视化界面都串起来的完整工程。解压后能看到main.ipynb、UI.ipynb、create_music_py.py三个核心文件外加Classical-piano-composer数据集目录、项目说明、介绍文档和一份详细设计说明书 PDF。换句话说它同时给了你「能跑的代码」和「能写进论文的材料」。这个项目解决的核心问题是用机器学习模型学习古典钢琴曲的音符序列规律然后生成新的 MIDI 旋律。适合三类人——想快速搭出毕设原型的计算机/AI 专业学生、需要一份可讲解的课程大作业的通信或自动化方向同学、以及想拿一个完整小项目练手 LSTM 和音乐数据处理的开发者。它不追求生成专业级编曲但胜在流程完整、门槛可控、每一环都能拆开讲。2. 拆开压缩包先看结构文件分工与运行环境怎么定2.1 目录里每个文件负责什么拿到一个毕设压缩包最忌讳上来就双击 notebook 从头跑到尾。先花五分钟把文件分工理清楚后面能省掉大量「为什么这里报错」的时间。这个包的结构大致是这样文件/目录作用是否必须main.ipynb数据处理、模型定义、训练主流程是UI.ipynb生成结果的界面展示与交互是create_music_py.py音符序列转 MIDI 的生成脚本是Classical-piano-composer训练用古典钢琴 MIDI 数据集是项目说明.md环境依赖、运行步骤说明建议先读详细设计说明书.pdf论文/答辩用的设计文档写论文时用介绍.txt项目背景与功能概述可选main.ipynb是主心骨训练和预测逻辑都在里面UI.ipynb负责把生成结果可视化答辩演示时用它比较直观create_music_py.py是把模型输出的音符序列落成.mid文件的关键一步很多人卡在「模型跑完了但没有声音」问题往往出在这一环。2.2 环境依赖与版本选择这类音乐生成项目对版本比较敏感尤其是深度学习框架和音乐处理库。常见做法是建一个独立虚拟环境避免和你机器上已有的包打架。我一般会这样起环境# 创建独立环境Python 版本建议 3.8~3.10 conda create -n music_gen python3.9 conda activate music_gen # 核心依赖深度学习框架 音乐处理 可视化 pip install tensorflow2.10.0 pip install music218.3.0 pip install numpy pandas matplotlib pip install jupyter这里有几个参数要说明。tensorflow选 2.10 是因为它同时兼容 CPU 和较老的 CUDA 环境毕设机器往往没有高端显卡用 CPU 也能跑通小规模训练。music21是音乐信息处理的核心库负责解析 MIDI、提取音符和和弦版本差异会影响 API8.x 相对稳定。如果你机器上已经有 TensorFlow 2.15 以上先别急着降级可以先试跑报module not found或 API 不兼容再回退。提示不要用系统自带的 Python 直接装包。毕设项目依赖多污染全局环境后后面调其他项目会互相干扰。2.3 数据集格式与读取方式Classical-piano-composer目录里是一批 MIDI 文件这是音乐生成任务最常用的输入格式。MIDI 不存音频波形只存音符的音高、时值、力度等事件信息体积小、易解析非常适合做序列建模。读取逻辑通常长这样from music21 import converter, instrument, note, chord import glob notes [] # 遍历数据集目录下所有 mid 文件 for file in glob.glob(Classical-piano-composer/*.mid): midi converter.parse(file) # 把乐谱拆成音符和和弦两类事件 notes_to_parse midi.flat.notes for element in notes_to_parse: if isinstance(element, note.Note): notes.append(str(element.pitch)) elif isinstance(element, chord.Chord): # 和弦用各音高拼接表示 notes.append(..join(str(n) for n in element.normalOrder))这段代码的逻辑是把每个 MIDI 文件解析成音符对象单音取音高字符串和弦取音高集合的拼接串统一存进notes列表。这样一段旋律就变成了一个字符串序列后面才能喂给模型。参数上midi.flat.notes会把多声部压平成单序列简化处理如果你想让模型学多声部就得改成按声部遍历但毕设规模下压平足够用。3. 模型怎么搭、怎么训LSTM 序列建模的关键参数3.1 为什么用 LSTM 而不是普通神经网络音乐本质是时间序列当前音符和前面若干音符强相关。普通全连接网络没有记忆喂进去一个音符就只根据这一个音符预测下一个生成出来会像随机点音符。LSTM 的循环结构和门控机制能保留上下文适合这种「根据前 N 个音符预测第 N1 个」的任务。这也是这类毕设最主流的选择讲起来清楚实现也不复杂。模型结构一般是输入层接收 one-hot 或 embedding 后的音符序列中间一到两层 LSTM最后接全连接层输出下一个音符的概率分布。常见做法是两层 LSTM每层 256 或 512 个单元dropout 0.2~0.3 防过拟合。3.2 数据预处理把音符变成模型能吃的张量模型不认识字符串得先把音符序列编码成数字。标准流程是统计所有出现过的音符建一个「音符→整数」的映射表再把序列切成固定长度的训练样本。import numpy as np from tensorflow.keras.utils import to_categorical # 建立音符到整数的映射 pitchnames sorted(set(notes)) note_to_int {n: i for i, n in enumerate(pitchnames)} sequence_length 100 # 用前100个音符预测第101个 network_input [] network_output [] for i in range(len(notes) - sequence_length): seq_in notes[i:i sequence_length] seq_out notes[i sequence_length] network_input.append([note_to_int[c] for c in seq_in]) network_output.append(note_to_int[seq_out]) n_vocab len(pitchnames) # 输入归一化 one-hot输出 one-hot network_input np.reshape(network_input, (len(network_input), sequence_length, 1)) network_input network_input / float(n_vocab) network_output to_categorical(network_output)关键参数是sequence_length它决定模型看多长的上下文。设太小比如 20生成的旋律缺乏连贯性设太大比如 200训练慢且容易过拟合毕设数据量下 50~100 比较稳妥。n_vocab是音符种类总数决定了输出层维度。输入除以n_vocab是简单归一化让数值落在 0~1训练更稳。3.3 训练过程与收敛判断模型定义和训练部分通常这样写from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dropout, Dense, Activation model Sequential() model.add(LSTM(256, input_shape(sequence_length, 1), return_sequencesTrue)) model.add(Dropout(0.3)) model.add(LSTM(256)) model.add(Dropout(0.3)) model.add(Dense(n_vocab)) model.add(Activation(softmax)) model.compile(losscategorical_crossentropy, optimizerrmsprop) model.fit(network_input, network_output, epochs100, batch_size64)第一层 LSTM 的return_sequencesTrue是为了把序列传给第二层第二层不需要所以默认 False。损失用交叉熵因为本质是多分类问题。优化器rmsprop是这类序列任务的常用选择比 SGD 收敛快。epochs和batch_size要看机器CPU 训练建议 batch 小一点、epoch 先跑 50 看 loss 曲线loss 不再明显下降就可以停。训练时留意 loss 是否震荡震荡厉害就把学习率调低或加大 dropout。4. 从模型输出到能听的 MIDI生成与落盘全流程4.1 采样生成音符序列训练完只是得到一组权重真正生成旋律要写采样逻辑。思路是从训练序列里随机取一段作为种子让模型预测下一个音符把预测结果接到序列尾部再预测下一个循环若干步。import random # 随机选一个种子序列 start np.random.randint(0, len(network_input) - 1) pattern list(network_input[start]) prediction_output [] for _ in range(200): # 生成200个音符 prediction_input np.reshape(pattern, (1, len(pattern), 1)) prediction_input prediction_input / float(n_vocab) # 取概率最大的音符作为预测 index np.argmax(model.predict(prediction_input, verbose0)) result pitchnames[index] prediction_output.append(result) # 滑动窗口去掉第一个接上新预测的 pattern.append(index) pattern pattern[1:]这里用argmax取概率最大的音符生成结果稳定但偏保守。想更有变化可以改成按概率分布随机采样代价是可能出现不和谐音。pattern pattern[1:]是滑动窗口保证每次输入长度固定。生成步数 200 只是示例想要更长的曲子就加大但太长容易重复。4.2 用 music21 把序列写回 MIDI拿到音符字符串序列后要转成 MIDI 文件才能播放。这一步在create_music_py.py里from music21 import stream, note, chord offset 0 output_notes [] for pattern in prediction_output: # 和弦多个音高用点分隔 if (. in pattern) or pattern.isdigit(): notes_in_chord pattern.split(.) chord_notes [note.Note(int(n)) for n in notes_in_chord] new_chord chord.Chord(chord_notes) new_chord.offset offset output_notes.append(new_chord) else: new_note note.Note(pattern) new_note.offset offset output_notes.append(new_note) # 每个音符间隔0.5拍 offset 0.5 midi_stream stream.Stream(output_notes) midi_stream.write(midi, fpoutput.mid)offset控制音符在时间轴上的位置每次加 0.5 表示每个音符占半拍节奏均匀。想做出长短音变化可以按音符类型给不同增量。write(midi, fp...)落盘成文件用任意 MIDI 播放器就能听。很多人跑完模型发现没声音八成是这一步没执行或路径写错。4.3 UI 界面怎么接进来UI.ipynb一般用 ipywidgets 或简单的 matplotlib 做交互提供「生成」「播放」「保存」按钮。答辩演示时评委更愿意看点击按钮就出旋律的效果而不是你现场跑训练。建议提前把模型权重存成.h5UI 里直接加载权重做推理省去等待时间。# 保存训练好的权重UI 里直接加载 model.save(music_model.h5) # UI 中加载 from tensorflow.keras.models import load_model model load_model(music_model.h5)5. 避坑与排查跑不通时先查这几处5.1 现象notebook 一跑就报找不到数据集原因通常是相对路径不对。notebook 的工作目录和你解压的目录不一致glob.glob(Classical-piano-composer/*.mid)自然找不到文件。解决先import os; print(os.getcwd())确认当前目录再用绝对路径或os.path.join拼路径别硬编码相对路径。5.2 现象训练 loss 一直不降或变成 nan原因可能是输入没归一化、学习率过高、或音符映射表为空。先检查n_vocab是否大于 0再确认输入除以了n_vocab。如果 loss 变 nan把优化器学习率调小或换回adam试。数据量太小时也会不收敛可以适当减小模型规模。5.3 现象生成的 MIDI 能播放但全是单音、没有和弦原因在采样阶段用了argmax且训练数据里和弦占比低模型倾向输出单音。解决改用按概率采样或在预处理时对和弦样本做加权。也可以手动在生成后处理里插入和弦但那样就不算模型生成的了答辩时容易被问住。5.4 现象music21 写入 MIDI 报错或文件为空常见原因是音符字符串格式不对比如出现了music21不认识的符号。检查prediction_output里每个元素是否是合法音高名或数字。另外offset如果一直是 0所有音符会叠在同一时刻听起来像一坨。确认每次循环都执行了offset 0.5。5.5 现象换台机器就装不上依赖原因多是 Python 版本或 TensorFlow 版本不匹配。建议把pip freeze requirements.txt导出换机器时按文件装。如果目标机器没有 GPU装tensorflow而非tensorflow-gpu避免 CUDA 相关报错。6. 进阶玩法与验证让生成结果更耐听、答辩更稳6.1 用温度参数控制生成的「大胆程度」argmax太死板纯随机又太乱折中方案是引入温度temperature做采样。温度高概率分布被拉平生成更多样但可能跑调温度低接近 argmax稳定但重复。实现上对 logits 做缩放再 softmaxdef sample_with_temperature(preds, temperature1.0): preds np.asarray(preds).astype(float64) preds np.log(preds 1e-8) / temperature exp_preds np.exp(preds) preds exp_preds / np.sum(exp_preds) return np.random.choice(len(preds), ppreds)温度设 0.8~1.0 之间通常比较耐听低于 0.5 会明显重复高于 1.2 容易出怪音。答辩时可以现场演示不同温度下的生成效果说明你理解模型输出的概率本质比只跑一个固定结果更有说服力。6.2 验证生成质量的两个土办法没有专业评测指标时可以用两个可操作的验证方式。一是重复率统计把生成的音符序列和训练集比对看有多少片段是直接抄的重复率过高说明模型没学到规律只是记忆。二是人耳盲听生成 5 段混入 2 段真实古典曲让同学听哪段是机器生成的如果大部分能听出来说明还有提升空间。这两个方法写进论文的「结果分析」章节比空谈「效果良好」扎实得多。6.3 答辩前必做的一次完整走查我踩过的坑是训练时用了一个sequence_length生成时忘了同步结果输入维度对不上现场报错很尴尬。从那以后我每次改完参数都强制走一遍「预处理→训练→生成→落盘→播放」全流程确认端到端通。建议你也把模型权重、映射表、sequence_length一起存下来生成脚本从文件读别在 notebook 里靠变量传递。这样换机器、重开内核都不怕。希望这份拆解能帮你少走弯路顺利把项目跑起来、讲清楚。本文还有配套的精品资源点击获取