ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于LSTM的自动音乐生成:从MIDI预处理到模型训练与采样实践

基于LSTM的自动音乐生成:从MIDI预处理到模型训练与采样实践 简介这份压缩包是一个基于机器学习LSTM的自动音乐生成软件完整项目包含Python源码、项目说明与详细设计说明书适合作为课程设计或期末大作业直接使用。项目已获导师指导并以97分高分通过代码与文档完整下载后无需修改即可运行。压缩包共6个文件其中具体包含2个Jupyter Notebook分别为主程序与用户界面、1个Python生成脚本、1份PDF详细设计说明书以及许可与说明文档整体大小仅462KB。目前已有296人浏览学习。资源内附完整可运行源码、设计说明和使用文档覆盖古典钢琴音乐自动生成场景可帮助读者系统理解LSTM在序列生成方向的应用包括数据预处理、模型训练、音乐生成与结果展示等核心环节既可直接用于课程设计提交也可作为后续二次开发的起点与参考。1. 基于机器学习LSTM的自动音乐生成这份源码包能直接跑通但你要知道它怎么工作机器学习里的LSTM长短期记忆网络在很多人的印象中是做股票预测、时间序列回归的但拿它来生成音乐同样是一条被验证过的路子。这个项目把一个重要的方向做了落地让模型学习古典钢琴曲的旋律规律然后自己续写新音符。它不是简单调用某个现成模型而是从MIDI数据预处理、LSTM模型搭建、训练到生成完整走了一遍。我拆完这份源码之后的第一感受是作为课程设计它的完成度确实高作为入门LSTM序列生成的学习样例它的代码结构也足够清晰新手跟着跑一遍能同时搞明白“序列数据怎么喂给LSTM”和“音乐生成到底在生成什么”这两件事。适合正在做机器学习课程设计、期末大作业或者想用Python实践一个完整LSTM落地项目的读者。2. 项目结构与MIDI数据预处理把钢琴曲转成LSTM能读懂的序列2.1 这份压缩包里到底有什么拿到压缩包之后第一步不是急着跑模型而是搞清楚文件结构。解压后你会看到这些核心内容文件/目录作用main.ipynb主流程notebook从数据预处理到训练的全过程UI.ipynb带界面交互的演示notebookcreate_music_py.py纯Python脚本版音乐生成入口Classical-piano-composer训练用的古典钢琴MIDI数据集目录详细设计说明书.pdf课程设计配套文档包含系统设计、流程、测试README.md项目说明这里要特别提醒Classical-piano-composer这个数据集目录是项目能不能跑起来的关键。很多课程设计项目会把数据集放在网盘里下载后还要手动改路径这份资源是直接打包好的省了一步。2.2 MIDI文件的读取方式音乐生成的第一步是把MIDI文件解析成模型能理解的数据。MIDI不是音频文件它记录的是音符的演奏指令——哪个时间点、按下哪个音、持续多久、力度多大。项目里用music21这个库来做解析这是音乐信息领域的主流工具。from music21 import converter, instrument, note, chord def parse_midi(file_path): 解析MIDI文件提取音符和和弦序列 midi converter.parse(file_path) notes_to_parse None # 提取所有乐器声部找钢琴部分 parts instrument.partitionByInstrument(midi) if parts: notes_to_parse parts.parts[0].recurse() else: notes_to_parse midi.flat.notes notes [] for element in notes_to_parse: if isinstance(element, note.Note): # 单音用音名表示如 C4、E5 notes.append(str(element.pitch)) elif isinstance(element, chord.Chord): # 和弦用.连接多个音名如 C4.E4.G4 notes.append(..join(str(n) for n in element.pitches)) return notes这段代码的逻辑很直接先读MIDI文件尝试按乐器分组优先取钢琴声部如果没有分组信息就把所有音符拉平处理。需要留意的是这个项目处理的曲子是单声部旋律为主和弦会以.连接的方式压缩成一个符号这样LSTM能把和弦当作一个token来学习而不是把一个和弦拆成多个独立音高。2.3 序列化与映射把音乐变成数字解析出来的是一串字符串列表但神经网络只能吃数值。项目里会把所有出现过的音符符号收集起来建立一张“符号到整数”的映射表然后按固定长度切窗生成训练样本。from collections import Counter def create_sequences(notes, sequence_length100): 把音符序列切成长度为sequence_length的输入-输出对 # 建立音符合表到整数的映射 symbol_to_int {sym: idx for idx, sym in enumerate(sorted(set(notes)))} sequences [] # 滑动窗口切分用前100个音符预测第101个 for i in range(0, len(notes) - sequence_length, 1): seq_in notes[i:i sequence_length] seq_out notes[i sequence_length] sequences.append((seq_in, seq_out)) return sequences, symbol_to_int这里的sequence_length100是一个关键超参数。100个音符的上下文够LSTM捕捉到乐句级别的重复结构但又不至于太长导致训练缓慢。如果数据集比较小可以适当减小到50或者64我之前在类似项目上测试过短序列会让生成结果偏向碎片化长序列会让模型更容易过拟合到某几首曲子的固定模式。2.4 数据集规模与训练前的准备工作项目自带的Classical-piano-composer目录里大多是古典钢琴曲目的MIDI文件。从实际跑通的经验来看这个数据集的量级在几十到上百首曲目之间预处理后得到的音符总数通常在数万级别。这个规模对于LSTM来说不算大训练时要注意过拟合问题后面我会专门讲。训练之前还需要把输入序列做one-hot编码或者Embedding映射。在notebook里作者用的是one-hot方式也就是每个时间步输入一个向量维度等于音符合表总数。这种方式在小词表场景下比Embedding更直接虽然稀疏但效果足够。import numpy as np def prepare_training_data(sequences, symbol_to_int, sequence_length100): 把字符串序列转成one-hot编码的numpy数组 vocab_size len(symbol_to_int) x np.zeros((len(sequences), sequence_length, vocab_size), dtypebool) y np.zeros((len(sequences), vocab_size), dtypebool) for i, (seq_in, seq_out) in enumerate(sequences): for j, symbol in enumerate(seq_in): x[i, j, symbol_to_int[symbol]] 1 y[i, symbol_to_int[seq_out]] 1 return x, ydtypebool在这里是合理的——One-hot向量非0即1用bool类型比float省4倍内存。这个细节在序列长、词表大的情况下能明显降低内存占用。3. LSTM模型架构与训练细节从Keras搭建到loss收敛3.1 为什么音乐生成选择LSTM而不是CNN或Transformer音乐是时序数据一个音符出现的概率强烈依赖前面的音符序列。LSTM的门控机制允许信息在跨时间步传播时进行选择性遗忘和记忆这对学习音乐中的主题重复、乐句呼应特别关键。CNN在音乐生成里也有应用但它在短窗口特征提取上有优势在处理长距离依赖时感受野有限。Transformer理论上更强大但它需要大量数据和计算资源对课程设计场景来说LSTM在“数据量中等、单机CPU可训练、效果可感知”这几个条件之间是最佳平衡点。项目选择Keras构建LSTM实验成本低调整参数也很方便。from keras.models import Sequential from keras.layers import LSTM, Dense, Dropout, Activation def build_model(input_shape, vocab_size): 构建LSTM音乐生成模型 model Sequential([ LSTM(256, input_shapeinput_shape, return_sequencesTrue), Dropout(0.3), LSTM(256), Dropout(0.3), Dense(vocab_size), Activation(softmax) ]) model.compile( losscategorical_crossentropy, optimizerrmsprop, metrics[accuracy] ) return model这里用了两层LSTM第一层设置了return_sequencesTrue意思是把每个时间步的隐藏状态都输出给下一层这样第二层LSTM能看到完整的中间表示。Dropout(0.3)用来缓解过拟合但在音乐生成这类任务上dropout比例不宜太高0.2到0.3是常见区间太高的dropout会让模型学不稳。rmsprop优化器是这个任务里非常经典的选择——它自适应调整学习率在RNN上通常比Adam更稳尤其是在后期收敛阶段。3.2 训练过程的参数选择与收敛判断训练时用到的关键参数会直接影响结果质量。我拆阅项目后整理了最核心的几个参数值说明batch_size128序列样本数较大时用分批训练128是CPU训练的舒适区epochs50~100看loss曲线收敛情况一般50轮左右开始稳定sequence_length100输入音符序列长度LSTM units256隐藏层神经元数越大表达力越强但越容易过拟合dropout0.3防止过拟合训练过程的目标是让categorical_crossentropy多分类交叉熵不断下降。每个epoch结束时模型会学习“根据前100个音符下一个音符的概率分布”。这里的分类数等于音符合表大小通常几百到上千取决于数据集里出现过的不同音符和和弦的种类数。from keras.callbacks import ModelCheckpoint checkpoint ModelCheckpoint( music_weights.hdf5, monitorloss, verbose1, save_best_onlyTrue, modemin ) history model.fit( x, y, batch_size128, epochs80, callbacks[checkpoint] )ModelCheckpoint在这里的作用是保留loss最低的那一次权重。训练中途如果过拟合了你可以回头加载这个checkpoint而不是用最后一步的权重。monitorloss而不是val_loss是因为这个项目没有单独切验证集全部数据都用于训练这样在小数据集上能充分利用样本。3.3 训练过程踩到的一个关键点loss下降但生成结果不进步这是一个非常典型的LSTM音乐生成现象loss从2.5降到1.2左右时生成结果开始有“像样”的趋势但如果继续训练到loss降到0.2以下生成结果反而变成同一段旋律的无限循环。原因很简单——模型把训练集中的某些曲子“背”下来了它找到了最小化loss的捷径是精确复制片段。我在跑这个项目时遇到过loss已经很低但输出音符全是学过曲子的连续片段的情况。解决方式是训练到loss下降趋势变缓时就停止不要追求极限收敛。课程设计里展示生成结果时选取中间epoch的权重往往比最后epoch的权重效果更好。3.4 训练速度与硬件选择的实际体验如果在笔记本电脑CPU上训练数据量在数万序列、词表几百的条件下每个epoch大约需要一到三分钟。80个epoch就是一到四小时这个时间成本对课程设计场景是可以接受的。如果手头有支持CUDA的显卡把同样的代码放到GPU上一个epoch能压缩到十秒级别。训练过程中的损失曲线我一般会画出来放到说明文档里这比说一百句“模型收敛良好”都有说服力。4. 从训练到生成Temperature采样与音乐续写实现4.1 生成的核心逻辑不是取最大值而是按概率抽样模型训练完成后生成阶段要做的事情是给定一段种子序列比如训练集里随机截取的100个音符预测下一个音符的概率分布然后从分布中抽样。这里有一个极其关键的控制参数——temperature。def sample_with_temperature(preds, temperature1.0): 从预测概率分布中采样temperature控制随机性 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) probas np.random.multinomial(1, preds, 1) return np.argmax(probas)temperature的作用是重塑概率分布的尖锐程度。temperature越小比如0.5分布越尖锐模型倾向选择高概率音符结果更保守、更稳定但也容易重复temperature越大比如1.5分布越平坦低概率音符也有机会被选中结果更有“创造性”但噪音也更多。这个项目默认值取1.0实际调参时建议在0.6到1.2之间尝试。np.random.multinomial按概率分布抽取一个样本这样既不会每次都选最高概率造成呆板重复也不会完全随机。注意在计算时加一个1e-8的极小值做平滑防止log(0)报错——这是字符级生成任务里的经典细节。4.2 生成歌曲的完整流程生成阶段在主脚本create_music_py.py里被封装成了函数加载训练好的权重输入种子序列逐音符预测并采样每生成一个音符就把它接在序列末尾同时丢掉最前面的一个音符保持输入窗口长度恒定。def generate_music(model, seed_sequence, num_notes500, temperature1.0): 从种子序列出发生成指定数量的新音符 int_to_symbol {idx: sym for sym, idx in symbol_to_int.items()} current_sequence seed_sequence.copy() generated [] for _ in range(num_notes): # 把当前序列转为one-hot x_input np.zeros((1, len(current_sequence), vocab_size)) for i, idx in enumerate(current_sequence): x_input[0, i, idx] 1 preds model.predict(x_input, verbose0)[0] next_idx sample_with_temperature(preds, temperature) generated.append(int_to_symbol[next_idx]) current_sequence.append(next_idx) current_sequence current_sequence[1:] return generated逐音符生成是序列生成模型的标准推理方式。这里有一个性能点每生成一个音符就调用一次model.predict500个音符就是500次前向传播CPU上可能耗时十几秒到一分钟这是正常现象。如果想加速可以用model.predict_on_batch减少调用开销。4.3 把生成的音符序列转成MIDI可播放文件生成的音符列表还是字符串需要把它转回MIDI格式才能听到声音。项目里用music21的stream和note对象重建乐谱然后输出为.mid文件。from music21 import stream, note, chord, duration def notes_to_midi(generated_notes, output_fileoutput.mid): 把音符字符串列表转成MIDI文件 offset 0 output_notes [] for pattern in generated_notes: if . in pattern: # 和弦拆开并同时播放 chord_notes [note.Note(n) for n in pattern.split(.)] for n in chord_notes: n.duration duration.Duration(0.5) chord_obj chord.Chord(chord_notes) chord_obj.offset offset output_notes.append(chord_obj) else: # 单音 n note.Note(pattern) n.duration duration.Duration(0.5) n.offset offset output_notes.append(n) offset 0.5 midi_stream stream.Stream(output_notes) midi_stream.write(midi, fpoutput_file)每个音符的时值统一设为0.5秒这是简化处理。真实音乐中音符时值变化是情感表达的重要部分但作为课程设计保持统一时值可以降低生成结果的杂乱程度。如果你希望更丰富的节奏可以进一步训练一个同时预测音符和时值的模型但这会让项目复杂度上升一个量级。项目的UI.ipynb里把生成功能包装成了带交互界面的版本运行后可以直接在Notebook里输入参数、点击生成、播放结果演示效果比纯命令行好很多。5. LSTM音乐生成的常见问题与避坑记录从loss不降到MIDI无法播放5.1 训练时loss不降或下降极慢现象训练了好几个epochloss一直停留在2.5以上基本没有下降趋势。原因最常见的是数据预处理环节出了问题音符序列里混入了大量噪声符号比如力度标记、时值标记被当作独立音符提取出来导致词表非常大且分布极其稀疏。另一个可能原因是学习率不合适rmsprop默认学习率在0.001左右但如果特征分布异常收敛会非常慢。解决先检查symbol_to_int映射表的大小如果词表超过2000甚至3000大概率是解析时把非音符元素也收进去了。回到parse_midi函数过滤掉note.Note和chord.Chord之外的元素。另外可以打印训练集里前100个音符看看是否有异常符号。5.2 生成的音乐反复重复同一个小片段现象生成的500个音符听上去像是某一段旋律在无限循环变化极少。原因这是过拟合的典型表现模型把训练数据背下来了。在数据集只有几十首曲子的情况下训练轮数过多、dropout设置过低都会导致这个问题。解决把训练轮数减少到loss曲线刚开始走平的位置或者增大dropout到0.4试试。另外可以调高temperature到1.2让采样分布更平坦减轻重复感。我通常会在生成阶段用0.8、1.0、1.2三个temperature各生成一段对比后选效果最好的。5.3 生成结果格式正确但MIDI文件无法播放现象程序正常输出output.mid但用播放器打开后没有声音或者提示文件损坏。原因midi_stream.write(midi, fp...)这一步在music21版本较老时有时会写出格式不兼容的文件。另一个可能是生成的音符中有不存在的音名比如解析阶段产生的残留字符串在生成阶段被原样输出。解决在notes_to_midi里加一个校验遇到无法被note.Note()解析的字符串就跳过并打印警告。同时确认music21版本如果用的是老版本升级到最新版通常能解决文件格式问题。5.4 数据集太大导致预处理内存溢出现象处理多首MIDI时内存占用飙升进程卡死或直接被系统杀掉。原因create_sequences函数用双重循环把所有序列一次性生成并保存在内存里。数据集音符总量达到十万级别时one-hot编码后的矩阵会非常庞大。以100个音符序列长度、500个音符合表为例十万个训练样本就是10万×100×500的矩阵如果用float类型内存占用会达到几十GB。解决切分序列时使用生成器而不是一次性构建全部数组或者把数据分批写入HDF5文件。我在处理类似项目时的做法是先用一个小数据集验证流程全量训练时把create_sequences改成逐步yield配合model.fit的steps_per_epoch参数。5.5 运行notebook时第三方库版本冲突现象import music21或import keras时直接报错常见的是Aborted (core dumped)或者缺少DLL文件。原因music21依赖matplotlib和numpyKeras 2.x和TensorFlow 2.x之间的版本匹配关系比较敏感。Python 3.7到3.9之间某些组合会存在二进制不兼容问题。解决建议先创建虚拟环境然后用pip install music21 keras tensorflow numpy一次性安装最新版本。如果你用的是Python 3.10及以上优先选择TensorFlow 2.10以上版本。这个项目是在Keras 2.x时代写的如果你的环境默认装了Keras 3可以考虑把from keras...改为from tensorflow.keras...兼容性更好。6. 从生成到验证最后一公里怎么判断自动生成的音乐真的“有效”生成只是一个中间节点作为课程设计你还需要向老师或者读者证明“这是有效的、有依据的”。我的做法是做一个三层验证。第一层把生成的MIDI转成音频听一遍记录整体印象——它是否有旋律感、节奏是否稳定、有没有明显的突兀跳变。第二层把训练集里的真实曲子和生成曲子的音符分布做一个对比看音高分布的直方图是否接近这说明模型学到了训练集的音高偏好。第三层用音程分析做定量评估统计生成序列中相邻音符之间的音程跨度音乐理论认为二度到五度的级进和跳进是旋律的主要构成如果生成结果频繁出现超过八度的跳跃说明采样参数太激进了。在参数层面我会固定随机种子后对比不同temperature的输出长度分布和重复率这个指标比主观听感更容易写进说明文档。我当时做完这个项目后的一个习惯是每次训练完一定用训练集里没有出现过的一首MIDI的前50个音符作为种子去生成而不是自己手工构造种子序列——这样生成结果的差异性更能反映模型的泛化能力而不是对特定片段的记忆。如果你想要给答辩老师现场演示最稳的路线是用UI.ipynb里的交互界面先生成一小段100到200个音符当场播放再切换temperature调出不同风格的两段对比。整个过程控制在三分钟以内既不冷场也不会暴露生成质量不稳定的短板。我最开始做这个项目时犯过一个错把temperature调成2.0生成了一段“天马行空”的音乐答辩时播放出来效果非常杂乱后来每次演示我都强制把temperature锁在0.8到1.2之间生成前先跑一遍验证。希望这个教训能帮你少走一次弯路。另外提醒一句详细设计说明书PDF里包含的数据流图、模块划分和测试用例是答辩时最有力的支撑材料。不要只展示代码把预处理流程、模型结构图、loss曲线、生成示例这些内容按章节组织好你的课程设计就完整了。希望这个项目能帮你顺利拿到高分也希望你通过亲手复现真正理解LSTM在序列生成任务上为什么有效、边界在哪里。本文还有配套的精品资源点击获取
返回列表