为何自定义范数损失函数下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,所以损失始终不变。
简单修正方向(供参考)
- 全程使用PyTorch张量操作,去掉
.detach()和.numpy(),避免转换为numpy数组。 - 在模型中添加方法直接返回第一层的输出,比如:
def get_first_layer_output(self, x): return F.relu(self.conv1(x)) - 损失函数直接接收PyTorch张量,用
torch.norm计算范数:def my_loss(tensor): return torch.norm(tensor) - 训练循环中直接使用第一层输出计算损失,无需手动构建
view2(除非是可视化需求,可视化可以在计算损失后再转numpy)。
内容的提问来源于stack exchange,提问作者josf
相关产品推荐
相关产品推荐

