基于Transformer的Seq2Seq旋律和声模型:数据与训练问题问询
基于Transformer的巴赫众赞歌和声生成模型问题解答
问题场景概述
使用PyTorch开发Transformer Seq2Seq模型,输入巴赫众赞歌的旋律序列,输出等长的和声序列。已解决Embedding索引越界问题,但训练后模型持续预测填充token,且存在序列预测相关疑问。
1. Seq2Seq模型是否仅预测每个样本的第一个时间步?如何调整以预测完整和声序列?
你的模型并非只预测第一个时间步,只是代码中仅提取了第一个样本的第一个时间步预测结果:
# 仅取第一个样本、第一个时间步的预测 pred_argmax_value.append(torch.argmax(pred[0,:,0]).item()) pred_max_value.append(torch.max(pred[0,:,0]).item())
模型的forward输出是完整序列的预测结果:
- 输出
pred在permute前的形状为(seq_len, batch_size, harmony_vocab_size),对应每个时间步、每个样本的所有和声token概率 - 经过
pred.permute(1, 2, 0)后,形状变为(batch_size, harmony_vocab_size, seq_len),此时CrossEntropyLoss会自动计算每个时间步的损失,说明模型在学习预测完整序列
要获取完整和声序列的预测,只需对整个维度取argmax:
# 获取每个样本、每个时间步的预测token索引 full_pred = torch.argmax(pred, dim=1) # shape: (batch_size, seq_len)
2. 推理阶段,是否需要循环输入旋律以生成完整和声?
推理阶段不需要重复输入旋律,但需要采用自回归生成方式(循环传递解码器的输出作为下一次输入),原因是Transformer解码器使用了三角掩码(get_tgt_mask),每个时间步只能看到当前及之前的输出。
推理的大致流程:
- 编码旋律序列(一次编码即可,无需重复输入)
- 初始化解码器输入为
SOStoken(或第一个时间步的初始输入) - 循环生成每个时间步的和声:将上一步的预测结果作为解码器的下一个输入,直到生成完整长度或遇到
EOStoken
示例代码片段:
def generate_harmony(model, melody, sos_token, max_len=640, device='cuda'): model.eval() melody = melody.unsqueeze(0).to(device) # (1, seq_len) # 编码旋律 src_emb = model.source_embedding(melody) * math.sqrt(model.dim_model) src_emb = model.positional_encoder(src_emb) src_emb = src_emb.permute(1, 0, 2) # (seq_len, 1, dim_model) # 初始化解码器输入:SOS token开头 tgt = torch.tensor([[sos_token]], device=device) # (1, 1) with torch.no_grad(): for _ in range(max_len-1): tgt_emb = model.target_embedding(tgt) * math.sqrt(model.dim_model) tgt_emb = model.positional_encoder(tgt_emb) tgt_emb = tgt_emb.permute(1, 0, 2) # (current_len, 1, dim_model) # 生成三角掩码 tgt_mask = model.get_tgt_mask(tgt.size(1)).to(device) output = model.transformer(src_emb, tgt_emb, tgt_mask=tgt_mask) output = model.out(output) # 获取最后一个时间步的预测 next_token = torch.argmax(output[-1, :, :], dim=1) tgt = torch.cat([tgt, next_token.unsqueeze(0)], dim=1) # 如果遇到EOS token可提前终止(若使用) # if next_token.item() == eos_token: # break return tgt.squeeze(0) # (max_len,)
如果任务严格要求等长输出,也可尝试一次性输入与旋律等长的初始序列(比如全SOS),但自回归方式更符合Transformer解码器的设计逻辑,预测效果更稳定。
3. 模型持续预测填充token,是否需要单独的end-of-sequence token?
模型持续预测填充token的核心原因不是缺少EOS,而是损失计算未忽略填充位置,加上填充token在数据中占比可能过高,模型为了最小化损失直接选择预测填充。
解决步骤:
- 设置损失函数忽略填充token:
将CrossEntropyLoss的ignore_index参数设为和声填充token(4960),避免模型为填充位置计算损失:loss_fcn = nn.CrossEntropyLoss(ignore_index=4960) - 按需添加EOS token:
若序列存在明确结束位置(而非单纯用填充补长),可添加EOS token标记序列结束,同时在预处理时将目标序列有效部分结尾替换为EOS,再用填充补长。但对于固定640步的巴赫众赞歌任务,EOS不是必须,明确有效序列长度仍有助于模型学习。 - 检查数据填充占比:
如果训练数据中填充部分占比过高,可考虑使用可变长度序列(去掉不必要的填充),或调整预处理逻辑,仅保留有效序列部分。
4. 输入/目标序列是否需要start-of-sequence token?训练时对目标序列做偏移的作用是什么?
- 目标序列需要SOS token:
训练时使用的target_input = target_harmony[:, :-1]、target_expected = target_harmony[:, 1:]是teacher forcing逻辑,但如果原始目标序列没有SOS token,解码器的第一个输入是目标序列的第一个真实和声token,而推理时解码器的第一个输入是SOS token,训练与推理的输入分布不一致,会导致模型性能下降。正确预处理应该是在目标序列开头添加SOS token,结尾按需添加EOS token,让target_input为[SOS, t1, t2, ..., tn-1],target_expected为[t1, t2, ..., tn, EOS],保证训练与推理逻辑一致。 - 输入(旋律)序列不需要SOS token:
旋律是编码器的完整时序输入,编码器会处理整个序列的信息,无需额外添加SOS。 - 目标序列偏移的作用:
这个操作是teacher forcing的核心:让解码器在每一步预测下一个token时,输入真实的前序token(而非自己的预测),加速模型收敛,训练过程更稳定。如果不偏移,解码器输入与目标序列完全一致,模型会倾向于直接复制输入,无法学习到预测下一个token的能力。
内容的提问来源于stack exchange,提问作者FletcherW
相关产品推荐
相关产品推荐

