PyTorch 1D-CNN多标签分类训练异常:仅拟合首个标签
1D-CNN多标签分类训练问题解决方案
核心问题分析
你的代码存在以下关键问题,导致模型仅拟合首个标签、其余标签输出一致:
- 编码器重复训练:每个batch调用
encoder.fit_transform(labels)会重新训练编码器,不同batch的标签编码规则混乱,模型无法学习稳定的标签映射关系。 - 模型重复调用无意义:同一batch内连续四次调用
model(features),此时模型参数未更新,四个输出output1-output4完全相同,用相同输出拟合不同标签,模型无法针对性学习每个标签的特征。 - 损失处理逻辑错误:多次单独执行
loss.backward()后才更新参数,梯度累加逻辑混乱;且总损失仅取loss4,导致模型优化方向严重偏离。 - 标签处理冗余:过度拆分标签维度,增加了代码复杂度和出错概率。
修正步骤与代码
1. 提前完成标签编码
训练前仅对编码器拟合一次,后续所有标签转换使用transform,保证编码规则一致:
# 假设train_labels是所有训练集标签的集合 encoder.fit(train_labels) # 转换训练/验证集标签(后续DataLoader传入编码后的标签或原始标签,按需调整) train_labels_encoded = encoder.transform(train_labels) val_labels_encoded = encoder.transform(val_labels)
2. 调整模型结构(可选但推荐)
设计带多输出头的1D-CNN,让模型一次性输出所有标签的预测结果,避免重复forward:
import torch import torch.nn as nn import torch.nn.functional as functional class MultiLabel1DCNN(nn.Module): def __init__(self, input_channels, seq_len, num_classes_list): super().__init__() # 共享特征提取层 self.shared_backbone = nn.Sequential( nn.Conv1d(input_channels, 32, kernel_size=3, padding=1), nn.ReLU(), nn.MaxPool1d(2), nn.Conv1d(32, 64, kernel_size=3, padding=1), nn.ReLU(), nn.MaxPool1d(2), ) # 计算池化后的特征长度 self.feature_len = 64 * (seq_len // 4) # 每个标签独立的分类头 self.head1 = nn.Linear(self.feature_len, num_classes_list[0]) self.head2 = nn.Linear(self.feature_len, num_classes_list[1]) self.head3 = nn.Linear(self.feature_len, num_classes_list[2]) self.head4 = nn.Linear(self.feature_len, num_classes_list[3]) def forward(self, x): x = self.shared_backbone(x) x = x.flatten(1) # 返回四个标签的预测结果 return self.head1(x), self.head2(x), self.head3(x), self.head4(x)
3. 修正训练循环
from tqdm import tqdm # 初始化模型、优化器、损失函数(根据任务选择,如BCEWithLogitsLoss用于二分类,CrossEntropyLoss用于多分类) model = MultiLabel1DCNN(input_channels=1, seq_len=100, num_classes_list=[2,2,3,2]) optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) criterion = nn.CrossEntropyLoss() # 按需替换损失函数 for epoch in tqdm(range(50)): epoch_loss = [] model.train() for batch, (features, labels) in enumerate(train_loader): # 标签编码(仅用transform,不再重新拟合编码器) ordinal_labels = encoder.transform(labels) y = torch.from_numpy(ordinal_labels).to(torch.int64) y_onehot = functional.one_hot(y) # 提取每个标签的目标值(根据任务调整维度和数据类型) label1 = y_onehot[:, 0].to(torch.float32) label2 = y_onehot[:, 1].to(torch.float32) label3 = y_onehot[:, 2].to(torch.float32) label4 = y_onehot[:, 3].to(torch.float32) optimizer.zero_grad() # 一次forward得到所有标签的预测 output1, output2, output3, output4 = model(features) # 计算每个标签的损失 loss1 = criterion(output1, label1) loss2 = criterion(output2, label2) loss3 = criterion(output3, label3) loss4 = criterion(output4, label4) # 总损失求和,一次反向传播 total_loss = loss1 + loss2 + loss3 + loss4 total_loss.backward() optimizer.step() epoch_loss.append(total_loss.item()) # 验证阶段(可选,关闭梯度计算) model.eval() val_epoch_loss = [] with torch.no_grad(): for features, labels in val_loader: ordinal_labels = encoder.transform(labels) y = torch.from_numpy(ordinal_labels).to(torch.int64) y_onehot = functional.one_hot(y) label1 = y_onehot[:, 0].to(torch.float32) label2 = y_onehot[:, 1].to(torch.float32) label3 = y_onehot[:, 2].to(torch.float32) label4 = y_onehot[:, 3].to(torch.float32) output1, output2, output3, output4 = model(features) loss1 = criterion(output1, label1) loss2 = criterion(output2, label2) loss3 = criterion(output3, label3) loss4 = criterion(output4, label4) total_val_loss = loss1 + loss2 + loss3 + loss4 val_epoch_loss.append(total_val_loss.item()) # 打印 epoch 结果 print(f"Epoch {epoch+1} | 训练损失: {sum(epoch_loss)/len(epoch_loss):.4f} | 验证损失: {sum(val_epoch_loss)/len(val_epoch_loss):.4f}")
关键注意事项
- 损失函数需匹配任务类型:如果是二分类多标签,使用
BCEWithLogitsLoss;如果每个标签是多分类,使用CrossEntropyLoss。 - 保证模型输出维度与标签维度一致:比如每个标签是二分类时,分类头输出维度为2;多分类时输出对应类别数。
- 训练过程中注意设备一致性:确保模型、特征、标签都在同一设备(CPU/GPU)上。
内容的提问来源于stack exchange,提问作者Gehad Dahami
相关产品推荐
相关产品推荐

