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

为何自定义范数损失函数下PyTorch卷积网络无法学习?

为什么自定义损失的卷积网络无法训练,损失始终不变?

我在PyTorch中搭建了一个卷积网络,自定义了以网络第一层输出范数为目标的损失函数,试图将其最小化,view2用于以矩阵形式展示第一层处理后的数据。但训练过程中误差完全无变化,始终维持为1。我知道这段代码逻辑不合理,但非常想了解它无法实现学习的原因。

代码如下:

data = sio.loadmat('ORL_32x32.mat')
x, y = data['fea'], data['gnd']
x, y = data['fea'].reshape((-1, 1, 32, 32)), data['gnd']
y = np.squeeze(y - 1)  # y in [0, 1, ..., K-1]
class ConvAutoencoder(nn.Module):
    def __init__(self):
        super(ConvAutoencoder, self).__init__()
        ## encoder layers ##
        # conv layer (depth from 3 --> 16), 3x3 kernels
        self.conv1 = nn.Conv2d(1, 3, 3)  
        self.conv2 = nn.Conv2d(3 ,3, 3)  
        self.conv3 = nn.Conv2d(3, 3, 3)
        self.conv4 = nn.Conv2d(3, 3, 3)
        
    def forward(self, x):
        
        x = F.relu(self.conv1(x))
        x = F.relu(self.conv2(x))
        x = F.relu(self.conv3(x))
        x = F.relu(self.conv4(x))
        return x

    def test1(self, x):
      x = F.relu(self.conv1(x))
      x = F.relu(self.conv2(x))
      return x
    
    def test2(self, x):
      x = F.relu(self.conv1(x))
      x = F.relu(self.conv2(x))
      x = F.relu(self.conv3(x))
      x = F.relu(self.conv4(x))
      return x

def my_loss(novi2):
    
    return torch.tensor(LA.norm(novi2)).to(device)


model = ConvAutoencoder().to(device)

epochs = 950; 
lossList = []
view2 = np.zeros((576,400))
view3 = np.zeros((576,400))
losses = torch.tensor(0.).to(device)
optimizer = optim.Adam(model.parameters(), lr=0.001)
if not isinstance(x, torch.Tensor):
  x = torch.tensor(x, dtype=torch.float32, device=device)
x = x.to(device)
if isinstance(y, torch.Tensor):
  y = y.to('cuda').numpy()
K = len(np.unique(y))
for epoch in range(epochs):
        view2 = np.zeros((576,400))
        view3 = np.zeros((576,400))
        output = model.test2(x.to(device)).cpu().detach().numpy()
        output1 = model.test1(x.to(device)).cpu().detach().numpy()
        for i in range(numclass):
            lovro = output[i]
            lovro =lovro[[0]]
            lovro = lovro.squeeze(axis = 0)
            lovro = lovro.flatten()
    
            for j in range(576):
              view2[j][i] = lovro[j]
        for i in range(numclass):
            lovro = output[i]
            
           
        loss = my_loss(view2)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
      
        print('Epoch %02d' %
                  (epoch))

无法学习的核心原因

  • 计算图完全断裂,梯度无法回传
    代码中调用model.test2(x).cpu().detach().numpy()时,.detach()会切断张量与原计算图的关联,.numpy()进一步把张量转换成了numpy数组。后续基于这个numpy数组构建的view2,再转成张量计算损失,这个损失张量和模型的参数之间没有任何梯度依赖关系。loss.backward()时没有梯度可以传播到模型参数,自然无法更新参数,损失也就永远不变。

  • 损失计算的目标完全偏离
    你原本想最小化第一层卷积的输出范数,但实际代码中用的是model.test2(x)的输出(经过4层卷积+ReLU)来构建view2,完全没用到第一层的输出。就算梯度能正常回传,优化的也是第四层输出的范数,和你的目标完全不符。

  • 冗余操作破坏计算图
    把模型输出转成numpy数组后,再手动赋值到view2的操作完全多余,而且彻底断开了和原计算图的联系。PyTorch的自动微分依赖完整的计算图,任何转成numpy的操作都会破坏这个链条。

  • 损失初始值的巧合
    第一次计算时LA.norm(novi2)恰好得到1,而后续因为参数无法更新,这个范数值一直保持1,所以损失始终不变。

简单修正方向(供参考)

  1. 全程使用PyTorch张量操作,去掉.detach()和.numpy(),避免转换为numpy数组。
  2. 在模型中添加方法直接返回第一层的输出,比如:
    def get_first_layer_output(self, x):
        return F.relu(self.conv1(x))
    
  3. 损失函数直接接收PyTorch张量,用torch.norm计算范数:
    def my_loss(tensor):
        return torch.norm(tensor)
    
  4. 训练循环中直接使用第一层输出计算损失,无需手动构建view2(除非是可视化需求,可视化可以在计算损失后再转numpy)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 05:15:40