You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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),每个时间步只能看到当前及之前的输出。

推理的大致流程:

  1. 编码旋律序列(一次编码即可,无需重复输入)
  2. 初始化解码器输入为SOS token(或第一个时间步的初始输入)
  3. 循环生成每个时间步的和声:将上一步的预测结果作为解码器的下一个输入,直到生成完整长度或遇到EOS token

示例代码片段:

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在数据中占比可能过高,模型为了最小化损失直接选择预测填充。

解决步骤:

  1. 设置损失函数忽略填充token:
    将CrossEntropyLoss的ignore_index参数设为和声填充token(4960),避免模型为填充位置计算损失:
    loss_fcn = nn.CrossEntropyLoss(ignore_index=4960)
    
  2. 按需添加EOS token:
    若序列存在明确结束位置(而非单纯用填充补长),可添加EOS token标记序列结束,同时在预处理时将目标序列有效部分结尾替换为EOS,再用填充补长。但对于固定640步的巴赫众赞歌任务,EOS不是必须,明确有效序列长度仍有助于模型学习。
  3. 检查数据填充占比:
    如果训练数据中填充部分占比过高,可考虑使用可变长度序列(去掉不必要的填充),或调整预处理逻辑,仅保留有效序列部分。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 16:37:04