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

PyTorch 1D-CNN多标签分类训练异常:仅拟合首个标签

1D-CNN多标签分类训练问题解决方案

核心问题分析

你的代码存在以下关键问题,导致模型仅拟合首个标签、其余标签输出一致:

  1. 编码器重复训练:每个batch调用encoder.fit_transform(labels)会重新训练编码器,不同batch的标签编码规则混乱,模型无法学习稳定的标签映射关系。
  2. 模型重复调用无意义:同一batch内连续四次调用model(features),此时模型参数未更新,四个输出output1-output4完全相同,用相同输出拟合不同标签,模型无法针对性学习每个标签的特征。
  3. 损失处理逻辑错误:多次单独执行loss.backward()后才更新参数,梯度累加逻辑混乱;且总损失仅取loss4,导致模型优化方向严重偏离。
  4. 标签处理冗余:过度拆分标签维度,增加了代码复杂度和出错概率。

修正步骤与代码

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 19:21:07