资讯动态

基于LSTM的歌词生成:从字符序列到风格化创作

发布时间:2026/9/17 2:45:49 来源:尧图企业网站定制
简介基于TensorFlow的LSTM歌词自动生成项目面向对自然语言处理、文本生成感兴趣的Python开发者与音乐科技爱好者可帮助你快速上手用深度神经网络复现特定艺术家的歌词风格。其核心思路是下载某艺术家全部歌词经清洗后训练长短期记忆网络模型学会单词搭配、押韵和换行习惯输入种子词即可续写出风格统一的新歌词。压缩包共22个文件以14个Python脚本为主涵盖歌词抓取、数据清洗、词频统计、模型定义、训练与采样生成等完整流程另含2个设计原图、2个图片、许可证、README说明及测试文本整体仅3.33MB目录模块划分明确。目前已有323人学习下载。通过阅读代码和README读者能掌握LSTM文本生成的完整链路包括数据预处理、构建模型、损失函数与采样策略并可直接迁移到古诗、台词、标题生成等类似场景中。1. 用 LSTM 生成歌词这个任务到底在解决什么问题“使用 LSTM 和 TensorFlow 以任何艺术家的风格生成新歌词”这个标题把文本生成里最常踩的坑都串到了一起语料清洗、序列建模、采样策略、风格控制。它的核心假设是一位艺术家的歌词在词汇习惯、句长分布、换行位置和语气词使用上存在可学习的统计规律而 LSTM 恰好能通过这些规律做最朴素的“下一个字符预测”。只要拿到足够多的该艺术家纯文本歌词不需要任何人工标注就能训练出一个按字符不断续写的模型生成阶段用带温度的随机采样控制“像”和“新”之间的比例。先给期望降温字符级 LSTM 能学到“像不像”但学不到“是不是”。它不理解歌词主题也没法真正掌握隐喻和叙事结构所谓风格更多体现为常用字、句子长度、标点习惯和押韵偏好。所以这套方案适合快速做歌词灵感草稿、demo 生成、文本增强以及理解 RNN 生成原理想要直接产出可发布的完整作品还需要在结构层面继续加约束。2. LSTM 歌词生成的数据准备与序列构造2.1 为什么选字符级 LSTM 而不是词级模型歌词生成先要决定建模粒度。词级模型词表通常有几万个词训练慢遇到语料里没出现过的词只能输出UNK字符级模型的词表只有几十到一百多个字符模型规模小训练快还能自然拼出带拼写变化的新词。英文说唱里的“yeah”“oh”“em”以及歌手自造的拟声词都不会因为词表限制被丢掉。LSTM 在这种任务里解决的是“长期依赖”问题一句歌词开头的内容可能决定了句尾的押韵和情绪走向。普通 RNN 的反向传播会把梯度逐时间步连乘下去导致长距离信息被冲淡LSTM 通过遗忘门、输入门和输出门让信息可以选择性地跨过几十个时间步。在 TensorFlow 的 Keras 接口里LSTM层默认只返回最后一个时间步的输出而训练歌词生成必须设置return_sequencesTrue让每个时间步都产生一个预测。另外字符级方案和 Python 的数据处理生态衔接得很顺。歌词保存成 UTF-8 的.txt文件用open()直接读入再通过collections.Counter统计字符分布不需要引入大型分词工具。对个人项目和几万字级别的语料一个 LSTM 层就够用并不需要马上转到 Transformer 那种更重的位置编码和注意力结构。2.2 数据准备清洗歌词、构建词表与训练序列我处理歌词文本时不会把所有标点清光因为换行、逗号和句号对歌词的节奏有直接影响。统一做四件事全角标点转半角、连续空行压缩成一个、去掉[Chorus]这类段落标签、按歌手分别保存为独立文件夹里的纯文本文件。[Chorus]这种标记虽然能告诉模型段落结构但字符级模型很难把它的语义学好容易在生成时反复输出方括号。下面这段 Python 代码把多个歌词文本合并成一个长字符串并统计词表import os from collections import Counter def load_lyrics(folder_path): text [] for fname in os.listdir(folder_path): if not fname.endswith(.txt): continue with open(os.path.join(folder_path, fname), r, encodingutf-8) as f: raw f.read().strip() lines [l.strip() for l in raw.splitlines() if l.strip()] text.append(\n.join(lines)) return \n\n.join(text) lyrics load_lyrics(./lyrics) chars sorted(set(lyrics)) char2idx {c: i for i, c in enumerate(chars)} idx2char {i: c for c, i in char2idx.items()} print(总字符数:, len(lyrics), 词表大小:, len(chars))清洗时按行处理而不是整首歌词直接处理是为了保留原歌词的换行格式。Counter在这里先不直接用但可以用它检查字符表里是否混入了不可见字符如果词表大小超过 300多半是清洗不彻底比如混入了 HTML 转义、Windows 换行符\r或 UTF-8 BOM。字符到 ID 的映射后续同时用于输入序列和模型输出层。2.3 训练样本切分与一步预测的数据构造LSTM 歌词生成训练的是“给定前 N 个字符预测第 N1 个字符”。先把整个歌词文本转成 ID 数组然后按固定窗口切样本。窗口长度要根据歌词平均句长来定中文流行歌 40 到 50 个字符比较合适英文说唱 60 到 80 更常见。窗口太短学不到押韵关系太长训练成本上升而且字符级模型不一定能有效利用超长上下文。import numpy as np seq_len 60 ids np.array([char2idx[c] for c in lyrics], dtypenp.int32) def make_sequences(ids, seq_len): x, y [], [] for i in range(0, len(ids) - seq_len, 3): x.append(ids[i:i seq_len]) y.append(ids[i 1:i seq_len 1]) return np.array(x), np.array(y) x_train, y_train make_sequences(ids, seq_len) print(样本数:, x_train.shape[0], 输入形状:, x_train.shape, 标签形状:, y_train.shape)标签y不是单个字符而是和输入等长的字符序列。这样配合 LSTM 的return_sequencesTrue模型在每一个时间步都有监督信号反向传播时梯度能覆盖所有位置的输出。窗口步长取 3 是为了让相邻训练样本不会完全重复同时又能覆盖更多起始位置如果语料很小可以把步长降到 1相当于数据增强。还有一个常见误用是把y只保留最后一个字符让模型只在最后一个时间步做预测这会大大浪费 LSTM 的中间状态生成时也容易出现“开头有信息、结尾没信息”的失衡感。3. TensorFlow 模型构建与训练参数设置3.1 Keras LSTM 模型的最小实现一个能跑通的歌词生成模型通常由三层组成Embedding把字符 ID 映射成稠密向量LSTM学习时序依赖Dense输出每个字符的概率分布。字符级词表只有几十到一百多Embedding 维度设在 64 到 128 就足够更大会明显增加参数量但对效果帮助有限。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Embedding, LSTM, Dense vocab_size len(chars) embedding_dim 128 units 256 model Sequential([ Embedding(vocab_size, embedding_dim, input_lengthseq_len), LSTM(units, return_sequencesTrue), Dense(vocab_size, activationsoftmax) ]) model.compile(optimizeradam, losssparse_categorical_crossentropy) model.summary()sparse_categorical_crossentropy这个损失函数可以直接接受整数标签不需要把y_train做 one-hot能省下大量内存。Embedding的input_length要等于seq_len否则后面 LSTM 拿到的时间步数会和预期不一致。LSTM的return_sequences必须显式打开否则输出形状从(batch, seq_len, units)变成(batch, units)接到Dense上时时间步维度丢失训练时会导致数据维度的不匹配。3.2 关键超参与调参方向LSTM 歌词生成没有一组固定的最优参数但有一个比较容易上手的调整思路先用小模型跑通再根据 loss 和生成样本来加容量。下表是我常用的参数范围超参数常见范围调整方向LSTM units128~512语料大、风格差异明显时可调大生成重复严重时调小embedding_dim64~256字符级任务不敏感优先保持 128batch_size64~256显存够用就加大小数据集 64 更稳定dropout / recurrent_dropout0~0.3语料不够大时建议开启能减少重复输出learning_rate1e-4~1e-2Adam 下先用 1e-3loss 震荡就降到 1e-4字符级模型的参数量增长很快。以词表 100、units256为例最后的Dense层已经有 25,700 个参数LSTM 内部更是有几十万参数。如果语料只有几万字符模型很容易在几十个 epoch 内过拟合表现为训练 loss 很低但生成时反复输出同一句话。此时优先把units降到 128并给LSTM层加dropout0.2。还有一种更硬的手段是减少训练轮数配合早停直接取训练过程里生成效果最好的检查点。3.3 训练循环、检查点与 loss 观察歌词生成任务一般不需要划分验证集因为同一歌手的歌词合集并不存在“分布外”的自然分割。我习惯让模型只看训练集 loss并配合早停防止过拟合。关键是盯着 loss 曲线的下降形态前 10 个 epoch 内loss 通常会从词表大小的对数附近快速下降如果 20 个 epoch 后 loss 还留在一个高平台就要检查语料是不是太小或者窗口长度是否不适配歌词节奏。from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping checkpoint ModelCheckpoint( lyrics_lstm.keras, monitorloss, save_best_onlyTrue, verbose1 ) early_stop EarlyStopping( monitorloss, patience10, restore_best_weightsTrue ) history model.fit( x_train, y_train, epochs100, batch_size128, callbacks[checkpoint, early_stop], verbose2 ) print(最低 loss:, min(history.history[loss]))ModelCheckpoint监控的是训练集losssave_best_onlyTrue表示只保留最小 loss 对应的权重避免训练后期过拟合权重覆盖掉中期效果更好的模型。EarlyStopping的patience10给模型 10 个 epoch 的缓冲期loss 连续不下降才停止。训练完成后lyrics_lstm.keras文件就是生成阶段要重新加载的模型。3.4 CPU 与 GPU 环境下的适配训练这个模型不需要 GPU但需要管理好等待时间。约 3 万字符的语料、seq_len60、batch_size128在普通 CPU 上跑一个 epoch 大约几十秒到几分钟换到 GPU 后数据读取反而容易成为瓶颈。常见做法是用tf.data.Dataset把数据包一层训练时自动做乱序和预取import tensorflow as tf dataset tf.data.Dataset.from_tensor_slices((x_train, y_train)) dataset dataset.shuffle(10000).batch(128).prefetch(tf.data.AUTOTUNE) model.fit(dataset, epochs100, callbacks[checkpoint, early_stop])from_tensor_slices直接把 NumPy 数组转成数据集shuffle(10000)设置一个足够大的乱序缓存避免模型按原文本顺序硬背prefetch(tf.data.AUTOTUNE)让数据加载和模型训练并行。有些人会在训练时看到 TensorFlow 输出一堆 Build 日志那不是报错。要确认 GPU 是否被使用用tf.config.list_physical_devices(GPU)查看返回列表即可为空就说明当前 TensorFlow 进程没有拿到 GPU。4. 按“艺术家风格”生成歌词的采样策略与代码实现4.1 从训练好的模型生成歌词状态恢复与循环预测训练完成后推理输入是(1, seq_len)的 ID 序列输出是(1, seq_len, vocab_size)的概率。生成时取最后一个时间步的概率分布从中选一个字符拼到序列末尾再丢掉序列最前面的字符把窗口整体右移一位。def generate_lyrics(model, seed_text, gen_len300, temperature0.8): seed_ids [char2idx.get(c, 0) for c in seed_text[-seq_len:]] if len(seed_ids) seq_len: seed_ids [char2idx.get( , 0)] * (seq_len - len(seed_ids)) seed_ids current np.array(seed_ids, dtypenp.int32).reshape(1, -1) result list(seed_text[-seq_len:]) for _ in range(gen_len): preds model.predict(current, verbose0)[0, -1, :] 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) next_idx np.random.choice(range(len(preds)), ppreds) result.append(idx2char[next_idx]) current np.roll(current, -1, axis1) current[0, -1] next_idx return .join(result)生成时先给种子文本补足长度char2idx.get(c, 0)遇到词表外的字符会回退到 ID 0这样不会因为一个未知符号中断整个生成。np.roll沿着时间轴整体左移一位再把新字符写到窗口末尾。每次只调用一次model.predict生成一个字符虽然速度不快但逻辑清晰如果要加速可以把多个候选序列打包成一个 batch 同时推理。temperature的作用是在取概率对数后缩放分布值越小概率高的字符被进一步放大文本更保守值越大概率分布越平随机性越强。4.2 temperature 温度采样与风格控制的量化效果采样时温度对文本风格的控制非常直接。常用取值和效果如下温度实际效果适合场景0.2~0.4输出稳定句子重复率高检查模型是否记住语料或生成押韵骨架0.6~0.8流畅度和创造性比较平衡大多数歌词 demo 生成1.0~1.2词汇跳跃大常出现新组合找灵感、试探词表边界不要用np.argmax直接取最大概率字符。贪心采样在确定模式下会不断放大模型最熟练的 n-gram 片段两三次迭代后就开始原地重复。np.random.choice(..., ppreds)相当于引入了一个随机探索项这也是文本生成模型和普通分类模型在使用方式上的核心差异。4.3 让模型模仿特定艺术家分歌手训练与多风格条件生成“以任何艺术家的风格”在工程上通常有两条路。第一条最直白每个歌手准备 1 万到 5 万字符的歌词各自训练一个独立模型生成时加载对应的.keras文件。问题是多数独立歌手的公开语料根本不够而且每加一个歌手就多一份维护成本。第二条是训练一个多风格条件模型给模型额外输入一个歌手 ID让同一个模型学会所有歌手的共用语言规律同时通过风格向量产生差异。实现方法是在字符 Embedding 后把歌手向量拼接到特征维度from tensorflow.keras.layers import Input, Concatenate, Lambda, Embedding, LSTM, Dense from tensorflow.keras.models import Model char_input Input(shape(seq_len,), namechar) style_input Input(shape(1,), namestyle) char_embed Embedding(vocab_size, 128)(char_input) style_embed Embedding(num_artists, 32)(style_input) style_embed Lambda(lambda x: tf.tile(x, [1, seq_len, 1]))(style_embed) merged Concatenate(axis-1)([char_embed, style_embed]) x LSTM(256, return_sequencesTrue)(merged) out Dense(vocab_size, activationsoftmax)(x) model Model([char_input, style_input], out)style_input的形状是(batch, 1)通过Embedding后变成(batch, 1, 32)tf.tile把风格向量沿seq_len方向复制让它能和字符 Embedding 按最后一个维度拼接。训练时给每个样本额外传入歌手 ID生成时把某个歌手 ID 固定模型就会偏向该歌手的词汇和句长习惯。这种做法的代价是数据量要更大否则风格向量被语言规律淹没所有歌手生成结果趋于一致。实际项目里还可以把两条路结合先用全体语料训练一个基础生成模型再用目标歌手的少量文本在基础模型上微调 10 到 30 个 epoch。迁移学习让冷门歌手只需几千字也能得到可用的风格输出同时避免了为每个歌手维护完整训练流程。5. 用困惑度与重复率验证生成效果再落到小技巧生成歌词没有唯一答案常规验证看三个维度困惑度、重复率、人工盲评。先用model.evaluate(x_train, y_train)拿到交叉熵损失计算exp(loss)得到困惑度数字越低说明模型对字符序列的预测越有把握但困惑度低只代表“更确定”不代表“更有创意”。重复率可以写一个简单统计把生成文本按每 50 个字符切段统计段内出现超过一次的三字符片段数如果温度 0.8 时重复率还很高说明模型过拟合或训练步数太多。更靠得住的是人工盲评用同一个种子文本输入不同模型让几个人判断哪一段更像某个歌手这个结果比任何数值都有说服力。还有一个容易被忽略的细节是随机种子。np.random.seed(42)只能保证主进程里第一次采样路径一致后续model.predict的数值计算如果放在 GPU 上结果会有微小浮动。想精确复现某段生成需要把当前窗口状态、温度、采样序列完整保存下来而不是只记住一个随机种子。实际调生成效果时我很少把所有控制交给 LSTM。“约束押韵”可以通过候选集过滤实现生成阶段先按温度采样出 10 个候选字符再用一个简单的韵脚表过滤不匹配的候选最后从剩余字符里随机选一个。这样模型负责句法和词汇流畅度人工规则负责押韵和主题词约束两者互补。把温度、韵脚表和种子参数写进同一个配置文件每次生成前读一遍调整成本是最低的。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价