Transformer多标签分类模型验证测试集输出值异常问题排查
问题:Transformer模型验证/测试集上多次调用同一解码器输出完全一致
我有一个需要输出三个不同标签的Transformer模型,其forward函数代码如下:
def forward(self, src, target): src_mask = self.make_src_mask(src) target_mask = self.make_target_mask(target) enc_src = self.encoder(src, src_mask) out_1 = self.decoder(target, enc_src, src_mask, target_mask) # Plain out_2 = self.decoder(target, enc_src, src_mask, target_mask) # Mid out_3 = self.decoder(target, enc_src, src_mask, target_mask) # Dir out_4 = self.relu(out_3) # Dir flatten = torch.flatten(out_2, start_dim=1, end_dim=2) out_5 = self.fc_out_mid(flatten) # size [num_samples,num_frames] out_4 = self.fc_out_direction(out_4) out_4 = torch.nn.functional.log_softmax(out_4, dim=2) return out_1, out_5, out_4, out_2, out_3
损失函数分别计算out_1、out_5、out_4后合并。我期望out_1、out_2、out_3形状相同但数值不同,训练集上三者数值确实不同,符合预期,但在验证集和测试集上三者数值完全一致。请问这是如何发生的?我哪里操作有误?
核心原因
你三次调用的是同一个解码器实例:
- 训练时模型处于
train()模式,解码器内部的dropout、层归一化等带随机性的组件会生效,每次前向传播的计算会产生微小差异,导致out_1、out_2、out_3数值不同; - 验证/测试时模型切换到
eval()模式,这些随机性组件被禁用(比如dropout停止随机失活,层归一化固定使用训练阶段统计的均值和方差),同一输入下多次调用解码器的输出自然完全一致。
更关键的是,你的代码逻辑本身存在设计错误:想得到三个针对不同任务的输出,却复用同一个解码器,训练时的差异只是随机性带来的“假差异”,并不是模型学习到了三个独立的任务分支,一旦关掉随机性,输出必然重合。
解决办法
要实现三个独立的任务分支,必须为每个分支创建独立的解码器实例,而非复用同一个:
1. 模型初始化阶段定义三个独立解码器
def __init__(self, decoder_config, ...): # 保留原有编码器等组件 self.encoder = Encoder(...) # 为三个任务分别创建独立解码器 self.decoder_plain = Decoder(**decoder_config) # 对应out_1的解码器 self.decoder_mid = Decoder(**decoder_config) # 对应out_2的解码器 self.decoder_dir = Decoder(**decoder_config) # 对应out_3的解码器 # 原有全连接层保持不变 self.fc_out_mid = nn.Linear(...) self.fc_out_direction = nn.Linear(...)
2. Forward函数中调用对应解码器
def forward(self, src, target): src_mask = self.make_src_mask(src) target_mask = self.make_target_mask(target) enc_src = self.encoder(src, src_mask) # 调用不同的解码器得到任务特异性输出 out_1 = self.decoder_plain(target, enc_src, src_mask, target_mask) # Plain out_2 = self.decoder_mid(target, enc_src, src_mask, target_mask) # Mid out_3 = self.decoder_dir(target, enc_src, src_mask, target_mask) # Dir # 后续处理逻辑不变 out_4 = self.relu(out_3) flatten = torch.flatten(out_2, start_dim=1, end_dim=2) out_5 = self.fc_out_mid(flatten) out_4 = self.fc_out_direction(out_4) out_4 = torch.nn.functional.log_softmax(out_4, dim=2) return out_1, out_5, out_4, out_2, out_3
可选优化方案
如果三个任务存在关联性,不想完全独立训练三个完整解码器,可以考虑:
- 共享解码器主体,独立任务头部:让三个分支共享解码器的前N层,只在最后几层或输出层使用独立的参数,既复用共享特征,又保留任务特异性;
- 任务嵌入引导:在解码器输入中加入任务特定的嵌入向量,让同一解码器根据不同嵌入输出对应任务的结果,但这种方式需要修改解码器的前向逻辑,实现复杂度更高。
内容的提问来源于stack exchange,提问作者SecretAgent
相关产品推荐
相关产品推荐

