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

训练问答深度学习模型效果不佳的问题排查

训练问答模型生成质量差的问题排查与解决

问题描述

训练一个简单深度学习问答模型时,验证损失较低但生成序列质量极差。已知条件:

  • 训练数据量充足,模型规模足够捕捉数据模式,已尝试调整多种超参数,损失曲线无过拟合迹象。
  • 输入为(batch_size, max_length)的批量分词语句,仅使用<pad>填充token和<unk>未知词token,未引入<sos>起始符与<eos>结束符,认为模型会生成固定max_length长度序列,无需上述两种token。

模型结构

模型由以下模块组成:

  • 嵌入层:为输入src生成token embedding和positional embedding
  • Transformer编码器
  • 线性层解码器:接收编码器输出生成logits

嵌入层与编码器输出形状为(batch_size, max_length, vocab_size),生成token形状为(batch_size, max_length),采用交叉熵损失训练。

模型代码

import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.nn import TransformerEncoderLayer, TransformerEncoder, LayerNorm

# 假设Embedding是自定义或扩展的实现
class Embedding(nn.Module):
    def __init__(self, vocab_size, hidden_size, max_position_embeddings, pad_token_id):
        super().__init__()
        self.token_emb = nn.Embedding(vocab_size, hidden_size, pad_token_id)
        self.pos_emb = nn.Embedding(max_position_embeddings, hidden_size)
    
    def forward(self, x):
        seq_len = x.size(1)
        positions = torch.arange(seq_len, device=x.device)
        return self.token_emb(x) + self.pos_emb(positions)

class Model(nn.Module):

    def __init__(self,
                 vocab_size,
                 hidden_size,
                 max_length,
                 pad_id,
                 num_encoder_layers: int = 2,
                 layer_norm_eps: float = 1e-5):
        super(Model, self).__init__()

        self.pad_id = pad_id

        self.embeddings = Embedding(
            vocab_size=vocab_size,
            hidden_size=hidden_size,
            max_position_embeddings=max_length,
            pad_token_id=pad_id
        )

        # 修正原代码参数错误:第二个参数是注意力头数nhead,而非编码器层数
        encoder_layer = TransformerEncoderLayer(hidden_size, nhead=hidden_size//64, batch_first=True)
        encoder_norm = LayerNorm(hidden_size, eps=layer_norm_eps)
        self.encoder = TransformerEncoder(encoder_layer, num_encoder_layers, encoder_norm)

        self.fc = nn.Linear(hidden_size, vocab_size)

    def forward(self, src, trg=None):
        src_embeddings = self.embeddings(src)

        output = self.encoder(src_embeddings)
        logits = self.fc(output)

        if trg is not None:
            loss = F.cross_entropy(
                input=logits.view(-1, logits.size(-1)),
                target=trg.view(-1),
            )
        else:
            loss = None

        predicted_ids = torch.argmax(logits, dim=-1)

        return predicted_ids, loss

核心问题分析

1. 模型结构不符合问答生成任务逻辑

当前模型是编码器+线性层的序列标注范式,本质是对输入序列的每个位置做token分类,而非问答生成需要的“从输入问题生成独立回答序列”的Seq2Seq逻辑。训练时强制模型将输入问题的每个位置与回答序列的对应位置绑定,完全违背问答任务中“输入问题到输出回答”的语义映射关系。

2. 缺失/的致命影响

即使要生成固定长度序列,没有起始符和结束符会导致模型无法区分序列的语义边界:

  • 训练时模型无法学习“从起始位置开始生成回答”的逻辑
  • 推理时模型没有停止信号,只能盲目填充到max_length,大概率生成无意义重复或<pad> token

3. 损失计算未忽略填充token

当前损失计算未屏蔽<pad>位置的损失,模型会为了降低损失,倾向于生成大量<pad>或高频无意义token,导致验证损失低但生成内容完全不符合预期。

4. TransformerEncoderLayer参数传递错误

原代码中误将编码器层数num_encoder_layers作为注意力头数nhead传入,会导致注意力机制配置不合理,严重影响模型对语义的捕捉能力。

解决方案建议

1. 重构为Transformer编码器-解码器架构

问答生成属于Seq2Seq任务,必须使用编码器-解码器结构:

  • 编码器负责编码输入问题的语义信息
  • 解码器基于编码器输出+已生成的token,自回归生成回答序列

2. 引入/ token

  • 分词时给每个回答序列添加<sos>(开头)和<eos>(结尾)
  • 训练时解码器输入为带<sos>的序列,目标为带<eos>的序列
  • 推理时从<sos>开始生成,直到出现<eos>或达到max_length

3. 修正损失计算,忽略填充token

在交叉熵损失中添加ignore_index=self.pad_id,避免模型因预测填充位置被惩罚:

loss = F.cross_entropy(
    input=logits.view(-1, logits.size(-1)),
    target=trg.view(-1),
    ignore_index=self.pad_id
)

4. 修正Transformer层参数

确保注意力头数nhead能被hidden_size整除(如hidden_size=512时nhead=8),并开启batch_first=True适配输入形状:

encoder_layer = TransformerEncoderLayer(
    d_model=hidden_size,
    nhead=hidden_size//64,
    batch_first=True
)

5. 改用自回归生成方式

推理阶段不要直接对编码器输出做argmax,而是逐token生成:

  1. 初始输入为<sos> token
  2. 每次将已生成的序列输入解码器,结合编码器输出预测下一个token
  3. 重复直到生成<eos>或达到max_length

内容的提问来源于stack exchange,提问作者Luke Wardford

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 22:22:46