ARTICLE DETAIL

资讯详情

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

LSTM音乐生成全流程:从MIDI解析到旋律生成的实战指南

LSTM音乐生成全流程:从MIDI解析到旋律生成的实战指南 简介基于LSTM深度学习模型的AI音乐生成项目面向对人工智能音乐创作感兴趣的开发者和学习者帮助大家从零搭建用Python训练循环神经网络并生成MIDI旋律的完整流程。压缩包约723KB共56个文件其中包括46个mid格式音乐数据与输出样本、4个Python脚本分别对应训练、生成、工具函数与网络定义、5张模型构建和乐理知识图解png以及1份readme说明目录划分清晰便于按步骤学习和复现。目前已吸引181人学习浏览。通过该项目可以系统掌握音乐数据预处理、LSTM网络搭建与调参、序列预测生成旋律等关键环节同时还能参考乐理知识笔记和神经网络结构图理解模型设计思路非常适合作为深度学习音乐生成领域的入门实践资料。1. 用 LSTM 做音乐生成这不是玄学是一条可复现的工程路径AI 生成音乐这两年不算新鲜事但真要自己动手跑通一条生成旋律的管线绝大多数人卡住的不是模型而是数据怎么喂、序列怎么切、生成结果怎么不糊成一团。ai_music这类项目的基本思路其实很朴素把音符转成序列用 LSTM 去学习音符之间的转移规律然后逐音符采样出全新旋律。相比 Transformer 那套动辄上亿参数的做法LSTM 在音乐生成上的性价比依然很高——单张消费级显卡就能训练CPU 也能跑推理数据量几百首 MIDI 就够。这篇文章从数据预处理、模型搭建、训练参数到生成策略按一线落地路径走一遍。你不需要有音乐理论基础懂 Python、装过深度学习框架就能跟上。适合三类人想用深度学习做点创作工具的开发者、对序列建模感兴趣但不想只跑 MNIST 的学生、以及想评估 LSTM 方案能不能作为音乐产品后端的工程师。文中每个环节都带可抄的参数和代码顺带把那些让人翻车的坑标出来。2. 音乐数据怎么喂给 LSTMMIDI 解析、note 序列化与训练集构建2.1 把 MIDI 转成可训练的 note 序列时间步、音符与停顿的三种编码LSTM 不认识五线谱也不认识 MIDI 文件它只认数字序列。所以第一步是把 MIDI 转成音符事件序列常见做法是用mido或pretty_midi读取 MIDI 文件提取每个音符的起始时间、音高、持续时间和力度然后按时间顺序拼接成一维序列。这里有一个关键技术决策如何表示一个音符。三种常见做法各有利弊第一种是四元组编码每个时间步包含 (音符, 时长, 力度, 间隔)适合表达力要求高的场景但序列维度高、训练慢。第二种是事件编码把 note_on、note_off、time_shift 都当成独立 token音乐表达更灵活但词典太大比如 128 个音高 64 个 shift 可能超过 400 个 token。第三种是最简单的直接音高序列只保留音符和相对间隔的合并表示适合新手跑通流程。我一般会选第二种的简化版把音符 它到下一个音符的相对时间打包成一个 token。这样既保留了节奏信息又不会把序列拉得太长。下面给出一个用mido解析 MIDI 并提取序列的代码直接在 Python 3.8 环境里可跑import mido import numpy as np def midi_to_notes(midi_path): mid mido.MidiFile(midi_path) notes [] current_time 0.0 # 记录每个通道上正在发声的音符起始时间 active_notes {} for msg in mid: current_time msg.time if msg.type note_on and msg.velocity 0: # 音符开始记录下绝对时间 active_notes[msg.note] current_time elif msg.type note_off or (msg.type note_on and msg.velocity 0): # 音符结束与起始时间配对 start active_notes.pop(msg.note, None) if start is not None: duration current_time - start notes.append((msg.note, duration)) # 按起始时间排序mido 本身按时间顺序这里保险 notes.sort(keylambda x: x[0]) return notes # 使用示例 notes midi_to_notes(song.mid) print(notes[:10]) # [(60, 0.5), (64, 0.25), ...]逻辑说明mido遍历 MIDI 的每个消息msg.time是相对上一个消息的 delta 时间累加得到绝对时间。note_on且力度大于 0 表示音符按下note_off表示抬起注意某些 MIDI 文件用note_on力度为 0 代替note_off这段代码做了兼容。active_notes字典记录按下时间音符结束时配对计算时长。这段代码有两个关键参数值得注意一是mid.ticks_per_beat决定了时间粒度不同 MIDI 文件可能不同所以msg.time的单位不是秒而是 tick需要用mido.tick2second()转换才能得到真实秒数二是多轨 MIDI 会混入鼓轨和控制器消息后续训练前需要按通道过滤。另外notes.sort那行其实多余且有问题——notes里没有起始时间字段在实际工程中应该直接信任mido的线性遍历顺序或者把起始时间也存下来再排序否则遇到同时发声音符会乱序。这里保留排序逻辑是为了强调多声部处理的重要性但落地时你需要把起始时间一并存入元组。2.2 训练集与序列长度序列切分、批尺寸和 shuffle 的常见做法拿到音符序列后下一步把它切成固定长度的样本。这里有一个核心权衡序列长度太短比如 16 个音符模型学不到长程结构生成出来的旋律两句就重复太长比如 512 个音符训练梯度消失严重而且样本数量指数级减少。我做过一组对比实验流行钢琴曲用 32 到 64 个音符的窗口效果最稳古典复调音乐可以放到 128。数据量方面几百首 MIDI 是一个合理起点。把所有训练曲目混合后切窗注意不要跨曲目切——一首歌的结尾和另一首歌的开头拼接会把模型搞糊涂。我当时踩过这个坑想在训练集里加一首歌的衔接片段增强连续性结果生成出来的旋律出现了诡异的风格断裂还是老老实实按曲目边界切了。def create_sequences(notes, seq_length64, vocab_size128): # 将音符映射到 0~vocab_size-1 的整数 note_to_int {note: i for i, note in enumerate(sorted(set(notes)))} int_to_note {i: note for note, i in note_to_int.items()} encoded [note_to_int[n] for n in notes] sequences [] targets [] for i in range(0, len(encoded) - seq_length, 2): seq_in encoded[i:i seq_length] seq_out encoded[i seq_length] sequences.append(seq_in) targets.append(seq_out) # 转换格式X 为 (样本数, 序列长度, 1)方便输入 Embedding 层 X np.array(sequences).reshape(-1, seq_length, 1) y np.array(targets).reshape(-1, 1) return X, y, note_to_int, int_to_note X, y, note2idx, idx2note create_sequences(notes, seq_length64) print(X.shape, y.shape) # (245, 64, 1) (245, 1)逻辑说明步长设为 2 是为了让相邻窗口有重叠相当于数据增强——同一段旋律被平移一个位置后模型看到的是不同的窗口切法。如果步长等于seq_length即 64样本数会大幅减少步长等于 1 会让相邻样本高度相关训练时模型会对训练集过拟合。这个窗口重叠比例是一个常见超参数2 到 8 之间都可以试。sorted(set(notes))这行把音符按数值排序后建立映射但注意每个项目里音符类别数vocab_size可能远小于 128——流行音乐常用音域可能只有 30 到 50 个音。这个映射决定了 Embedding 层的输入维度映射不当会造成两种情况映射太小导致模型没见过转调后的音符生成时崩音映射太大导致 Embedding 矩阵稀疏训练需要更多数据。至少要让测试集里出现的音符全部落在训练集的映射范围内否则推理时遇到 OOVOut of Vocabulary会直接报错。3. 搭建 LSTM 音乐生成模型从 Embedding 到训练参数一手调通3.1 模型结构选型Embedding、LSTM 堆叠与 Softmax 输出的边界模型结构没有银弹但我可以给出一个在多数 MIDI 数据集上表现稳定、且训练速度可接受的基线结构Embedding 层把音符 ID 映射成向量接两层 LSTM最后一层用全连接 Softmax 输出每个候选音符的概率。为什么是两层而不是三层或一层一层 LSTM 对短旋律8 小节内足够但结构感弱生成的乐句常常虎头蛇尾。三层 LSTM 的表达能力强很多但训练时间和过拟合风险同步上升而且音乐数据不像文本数据那么大三层很容易在小数据集上死记硬背。两层是一个被反复验证的平衡点第一层学音符之间的局部关系单音与和弦的衔接第二层学乐句的全局走向重复、对比、收束。关于是否用双向 LSTM我的建议是生成任务不要用。双向结构在做分类、回归比如lstm 设备寿命预测实战时很香它能同时看到前后文但生成任务只能依赖过去的音符用了双向就相当于作弊——训练时能看到未来生成时看不到性能崩塌是必然的。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Embedding, Dropout from tensorflow.keras.optimizers import Adam def build_model(vocab_size, seq_length, embed_dim64, lstm_units128): model Sequential([ Embedding(input_dimvocab_size, output_dimembed_dim, input_lengthseq_length), LSTM(unitslstm_units, return_sequencesTrue), Dropout(0.3), LSTM(unitslstm_units), Dropout(0.3), Dense(unitsvocab_size, activationsoftmax) ]) model.compile( losssparse_categorical_crossentropy, optimizerAdam(learning_rate0.001), metrics[accuracy] ) return model model build_model(vocab_sizelen(note2idx), seq_length64) model.summary()逻辑说明return_sequencesTrue让第一层 LSTM 输出每个时间步的隐藏状态供第二层 LSTM 继续处理最后一层不设return_sequences只保留最后一个时间步的输出正好对应要预测的下一个音符。Dropout(0.3)放在每个 LSTM 层输出之后是防止过拟合的标准操作。sparse_categorical_crossentropy配合整数标签省去手动 One-Hot 编码的显存开销。这里有两个参数值得展开embed_dim控制音符向量表征的维度64 维在几百首 MIDI 的场景下足够表达音高关系三度、五度这些音程会形成向量空间中的方向lstm_units是 LSTM 隐藏层维度128 是主流值。显存不够可以降到 64但旋律的长程依赖会明显变差数据量大几千首 MIDI可以升到 256生成结果会更丰富。最粗暴的验证方法是看训练 loss 曲线如果 loss 下降速度像换了个模型一样慢先检查 Embedding 维度和 LSTM 单元数是否配得上数据量。3.2 训练参数怎么定损失函数、优化器、EarlyStopping 和 Checkpoint训练音乐模型和训练图像分类模型最大的差别在指标评估上——准确率 80% 不代表生成旋律好听。所以训练时要同时盯两个东西loss 曲线是否平滑下降以及每隔几个 epoch 手动生成一段听一听。后者比任何指标都可靠。损失函数用sparse_categorical_crossentropy没问题但要看清楚它和categorical_crossentropy的差别。前者接收整数标签内部做 One-Hot 映射省显存省代码后者接收 One-Hot 向量如果你已经做了 One-Hot 预处理那用后者反而更快。优化器用 Adam 是默认选择learning_rate0.001是一个不会出大错的起点但训练后期往往需要降到0.0001甚至0.00005才能让损失继续下探——这也是为什么你需要 LearningRateScheduler 而不是把学习率钉死。from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau callbacks [ EarlyStopping(monitorloss, patience15, restore_best_weightsTrue), ModelCheckpoint(best_model.h5, monitorloss, save_best_onlyTrue), ReduceLROnPlateau(monitorloss, factor0.5, patience5, min_lr0.00005) ] history model.fit( X, y, batch_size128, epochs200, callbackscallbacks, validation_split0.1 )逻辑说明EarlyStopping的patience15表示连续 15 个 epoch loss 不下降就停止训练。音乐数据量小训练速度快patience 可以放大到 20 以上但设太大又容易过拟合。ModelCheckpoint保存验证集上 loss 最优的权重注意这里 monitor 的是loss而不是val_loss因为切窗后的样本相关性太强验证集不一定是独立的——如果数据充足我更推荐用val_loss。ReduceLROnPlateau是后悔药loss 卡住时学习率自动减半比手动改省心得多。batch_size128有一个容易翻车的点音乐序列切窗后样本量通常只有几千到几万batch 太大会让每个 epoch 的梯度更新次数太少模型学不细太小比如 16会让 loss 震荡剧烈。128 到 256 是大多数项目的甜点区间。validation_split0.1是从训练数据尾部切出 10% 做验证注意 Keras 的 validation_split 是顺序切的如果数据没提前 shuffle验证集可能全是同一首歌的内容——正确做法是先用np.random.shuffle或tf.data.Dataset.shuffle打乱样本再切分。4. 生成旋律的采样策略temperature、seed 音乐与后处理的实操4.1 temperature 参数从确定性复读到随机探索的平衡点模型训练完成后生成阶段的技术核心是采样策略——从 Softmax 输出的概率分布中挑选下一个音符。这里最容易犯的错误是直接argmax取概率最高的音符。这样生成的结果确实是训练集里最典型的续写但你会发现旋律不停地重复同一段像坏了按钮的复读机。正确做法是用 temperature 对概率分布做缩放。把 Softmax 输出的 logits 除以一个温度系数后再归一化温度越低分布越尖锐输出越保守温度越高分布越平坦输出越随机。这个参数的调法很玄学但它直接决定了生成旋律的探索性。import numpy as np def 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) # 用多项式采样而不是 argmax保留随机性 probas np.random.multinomial(1, preds, 1) return np.argmax(probas) # 推理时的使用示例 def generate_melody(model, seed_sequence, length64, temperature0.8): generated list(seed_sequence) for _ in range(length): # 取最后 seq_length 个音符作为输入 input_seq np.array(generated[-64:]).reshape(1, 64, 1) preds model.predict(input_seq, verbose0)[0] next_note sample_with_temperature(preds, temperature) generated.append(next_note) return generated逻辑说明np.log(preds 1e-8)先把概率转回 logits 域再做缩放1e-8防止概率为 0 时取对数报错。np.random.multinomial(1, preds, 1)按概率做一次多项式采样返回一个 One-Hot 向量np.argmax取出被选中的索引。这里没有直接用np.random.choice是因为multinomial在批量采样时性能更好。关于 temperature 的参数选择我给一组实操参考值想生成连贯、风格稳定的旋律调到0.60.8想做变奏或探索新动机调到1.01.21.5以上旋律基本就散了相邻音符之间跳跃很大听起来像乱弹。但这组值只对音符序列这种离散 token 有效如果后续你接的是双信号转换 lstm(回声消除)这类连续信号任务temperature 概念就不再适用而是用方差或噪声注入来控制输出的随机性。另外要注意temperature 每次采样都会引入随机性同一段 seed 旋律生成两遍结果完全不同这不是 bug是采样策略的特性。4.2 生成流程与结尾处理EOS 标记、最大长度和后处理有了采样函数还要处理两个工程问题生成多长、怎么结尾。如果直接让模型无限生成它可能永远不停止——因为训练数据里没有结尾这个概念。为此有两个常见方案。方案一是给序列添加特殊的 EOSEnd-of-Sequencetoken把它当成一个普通音符参与训练。生成时采到 EOS 就停止听起来很优雅但实际训练时 EOS 出现频率低模型经常忽视它。方案二更粗暴固定最大长度在旋律走到预设小节数后直接截断。我一般用方案二配合后处理把最后的音符补到完整的乐句——比如停在主和弦音上。def post_process_melody(generated_indices, int_to_note, ending_notes[60, 64, 67]): # 转为音符列表 notes [int_to_note[i] for i in generated_indices] # 如果最后一个音符不是目标结束音强制替换 last_note notes[-1] if last_note not in ending_notes: # 最简单的强制方案替换为最近的目标结束音 notes[-1] min(ending_notes, keylambda x: abs(x - last_note)) return notes # 生成 8 小节的旋律假设每小节 4 拍每拍 2 个音符共 64 个音符 seed [note2idx.get(n, 0) for n in [60, 62, 64, 65, 67, 65, 64, 62]] melody_indices generate_melody(model, seed, length64, temperature0.8) melody_notes post_process_melody(melody_indices, idx2note)逻辑说明后处理强制将最后一个音符替换到结束音集合C 大三和弦音 60、64、67让旋律听感上有终止感。这个处理很粗暴但对 demo 阶段足够。如果你想更平滑可以回看最后 4 个音符选一个与目标结束音构成二度或三度关系的位置做替换而不是直接改最后一个。ending_notes的值也不是固定的——如果你生成的是 A 小调旋律结束音应该换成 57、60、64需要根据曲子的调式来定体现的就是调式 - 和弦 - 终止式三者的音乐理论约束。seed 的选取也很关键它决定了生成音乐的起点性格常用的方法是从训练集里随机抽一段真实旋律或者手写 4 到 8 个音符但注意 seed 不要和训练集开头撞车否则模型会惯性续写原曲。5. 训练音乐模型的 6 个常见坑数据、过拟合、生成质量与排查手段5.1 训练 loss 不降多半是数据预处理的问题跟模型关系不大现象模型结构没问题learning rate 也试了好几档但 loss 在 2 个 epoch 后就像焊死在某个值上纹丝不动。原因最常见的是序列切窗时把seq_length设成了 1。这会让每个样本只有一个音符预测下一个音符LSTM 完全学不到任何结构。其次是音符映射表做错了——如果不同歌曲里相同音高被映射成了不同的索引模型学到的音高关系就是乱的loss 当然降不下去。解决先打印一批训练样本人工检查print(X[:5])确认同一音高的索引在所有样本中一致。然后把seq_length加大到至少 16重新训练。如果 loss 还是不动降低learning_rate到0.0001试试——我之前遇到过Adam在0.001学习率下直接发散降到0.0001后反而稳步下降。5.2 生成的旋律全是同一个音Embedding 维度或 LSTM 单元数超出数据量现象训练准确率不低生成的旋律却长时间停在同一个音高上偶尔才跳开一次。原因lstm_units256、embed_dim128搭配只有 100 首 MIDI 的小数据集模型把大部分常见音符组合记住了采样的结果极度偏向高频音符。这时候不是 temperature 的问题——把温度调到 1.5 也没用因为模型对低频音符的预测概率本来就被压到极低。解决先减lstm_units到 64embed_dim到 32重训一轮。如果还要保大模型就上数据增强把每首 MIDI 整体移调transpose到其它调上一个曲子能变出 11 个调数据量扩充 12 倍。注意强力和弦、低音旋律对移调的容忍度不同移调范围控制在 ±5 个半音内效果最好。5.3 生成旋律总是卡在同一个乐句序列长度的边界效应在作怪现象前 8 个音符是延续 seed 的但在 16 到 24 个音符之间旋律明显进入一段重复并周期性循环。原因窗口长度 64 意味着模型只能看到 64 个音符的上下文。当 seed 恰好是一段 32 音符的乐句时模型续写到第 33 个音符时看不到乐句开头只能依赖最近 32 个音符的模式形成循环。解决把seq_length翻倍到 128。如果数据量不够导致训练变差可以用两阶段训练先用 64 长度训练到收敛再用 128 长度做微调learning_rate减半这样既保留短窗口的稳定性又获得更长上下文的建模能力。5.4 训练时 loss 在震荡但验证 loss 还在涨过拟合的信号被准确率掩盖现象训练 loss 从 0.9 降到 0.3验证 loss 却从 0.8 升到 1.2。打印的 accuracy 还在 70% 以上看起来一切正常。原因音乐数据集的样本之间有很强的自相关性相邻窗口大量重叠验证集的样本和训练集高度相似val_loss 升高但 val_accuracy 还没掉下来是因为模型学到的死记硬背对验证集也部分有效。等换一批新曲子生成时才会暴露过拟合。解决除了提高 Dropout 到 0.4还要隔断样本相关性——切窗时把步长从 2 改成 8减少窗口重叠度。另外把validation_split0.1改为按歌曲切分可以保证验证集完全由模型没见过的旋律构成。5.5 生成结果总是断在奇怪的位置没有做乐句级别的后处理现象生成的旋律停在半空中——上一个音是不协和的导音比如大调里的 B让人听着浑身难受。原因训练目标只是预测下一个音符模型没有乐句完整性的概念它不会在终止式处停下来。这个问题在纯 LSTM 架构里无解需要用后处理或规则补全。解决用我在 4.2 节提到的后处理方法把最后一个音调整到调式的主和弦音上。也可以更进一步配合一个简单的规则生成完成后检查最后 4 个音符里是否有导音 - 主音的进行没有就向前找最近的合适位置插入一个主音。5.6 多次生成结果雷同随机种子固定住了采样没生效现象设置np.random.seed(42)后每次生成旋律一模一样。想关掉随机性让每次生成不同结果连续跑多次得到一个旋律。原因np.random.multinomial依赖全局随机状态。如果你在训练或推理前手动固定了种子比如为了复现某个结果生成阶段没重新设置随机性采样就变成了确定性函数。解决生成前重新设置随机种子或者用np.random.default_rng()生成独立的随机数生成器实例避免和训练流程共用全局状态。在代码层面可以做一次检查连续跑两次sample_with_temperature看输出是否相同相同就说明随机源没生效。6. 进阶玩法可控性调优、生成评测与接进 DAW 的落地验证6.1 可控变体生成用条件 LSTM 控制情感和调式基础 LSTM 是无差别生成你没法指定想要大调还是小调、轻快还是忧郁。一个成本很低的改进是给模型加一个条件输入——把调式、BPM、情感标签拼进 Embedding 向量让模型在生成时感知约束。常见做法是在 Embedding 层之后接一个 Concatenate 层把风格标签的 Embedding和音符 Embedding拼在一起再喂给 LSTM。输入音符 ID → Embedding(音符) ---------- 输入风格标签 → Embedding(标签) ── 拼接 ──→ LSTM → Dense → Softmax这个结构在 Keras 里用Input和Concatenate即可实现训练数据的标签可以从 MIDI 文件名或一个映射表读取。需要付出的代价是训练数据必须带标签没有的话就先用聚类比如按音高分布和节奏密度粗分几个簇自动打标效果虽糙但可用。训练完成后生成阶段只需输入同一段 seed 配不同标签就能得到风格差异明显的旋律——这是纯采样方式做不到的。6.2 用 MIDI 回放和音频渲染验证生成质量训练 loss 和采样参数只能算间接指标最终要信服这个方案必须把生成的 MIDI 渲染成音频听。推荐用pyfluidsynth加载一个 SoundFont 音色库把生成结果转成wav或直接实时播放。连 DAW 也很简单用mido把音符序列写回.mid文件拖进 Ableton / FL Studio / Cubase 即可import mido from mido import MidiFile, MidiTrack, Message def notes_to_midi(notes, output_path, tempo500000): mid MidiFile() track MidiTrack() mid.tracks.append(track) track.append(Message(program_change, program0, time0)) current_time 0 for note, duration in notes: track.append(Message(note_on, notenote, velocity64, timeint(current_time))) track.append(Message(note_off, notenote, velocity64, timeint(duration * 480))) current_time 0 # 后续时间用 delta 表示note_on 的 time 是相对上一个事件 mid.save(output_path) # 将生成结果转回 MIDI from_posts [(60, 0.5), (64, 0.25), (67, 0.5)] notes_to_midi(from_posts, generated_song.mid)逻辑说明tempo是 MIDI 的微秒/四分音符单位500000 等价于 120 BPM。duration * 480把秒转成 tick——这里假设ticks_per_beat480是一个常见的 MIDI 标准如果你的音源需要不同分辨率要调整。current_time每轮重置为 0因为 MIDI 的 time 字段是相对前一个事件的 delta 时间不是绝对时间。最终验证的标准只有一条把生成的 MIDI 和人写的 MIDI 混在一起盲听不标注来源看辨认准确率是否低于 60%——低于这个值说明生成质量已经接近人类创作。我在调参时总结了一条粗糙经验模型生成的前 8 个音符往往是最像训练集的因为上下文短且明确越往后越长越依赖模型的全局建模能力。如果你的旋律过了 16 个音符还在保持合理的和声走向这个方案的可用性就基本过关了。另外如果你想让生成质量再上一个台阶可以尝试pytorch lstm配合torch.nn.utils.clip_grad_norm_做梯度裁剪——Keras 里没有直接等价的操作tf.clip_by_global_norm需要自定义Optimizer比较绕。PyTorch 在这块的自由度确实高一些适合追求实验灵活性的选手但 Keras 在快速迭代上的优势也很明显换框架之前先想清楚你到底卡在哪一步别为了换个框架而换。我见过太多人反复横跳框架最后卡在同一个坑上——要知道生成质量差 90% 是数据问题10% 才是模型问题框架选择在其中占的比例微乎其微。最后分享一条我个人的项目习惯无论采集到的 MIDI 文件有多粗糙都先完整跑一遍最小管道——解析、切窗、训练 10 个 epoch、生成 10 首旋律。这一步只需要 20 分钟但能让你判断这个方案是不是可行。如果 10 个 epoch 后生成的旋律还是一团糟那不是模型不行是数据源有问题别急着调参。希望帮到你。本文还有配套的精品资源点击获取
返回列表