【问题标题】:neural network trained with PyTorch outputs the mean value for every input用 PyTorch 训练的神经网络输出每个输入的平均值
【发布时间】:2021-10-10 19:51:44
【问题描述】:

我正在使用PyTorch 来让我的神经网络识别来自MNIST database 的数字。

import torch
import torchvision

我想实现一个类似于3Blue1Brown's video series about neural networks 中所示的非常简单的设计。以下设计特别实现了1.6%的错误率。

class Net(torch.nn.Module):

    def __init__(self):
        super(Net, self).__init__()
        self.layer1 = torch.nn.Linear(784, 800)
        self.layer2 = torch.nn.Linear(800, 10)

    def forward(self, x):
        x = torch.sigmoid(self.layer1(x))
        x = torch.sigmoid(self.layer2(x))
        return x

数据是使用 torchvision 收集的,并以包含 32 张图像的小批量进行组织。

batch_size = 32
training_set = torchvision.datasets.MNIST("./", download=True, transform=torchvision.transforms.ToTensor())
training_loader = torch.utils.data.DataLoader(training_set, batch_size=32)

我使用均方误差作为损失函数,使用学习率为 0.001 的随机梯度下降作为我的优化算法。

net = Net()
loss_function = torch.nn.MSELoss()
optimizer = torch.optim.SGD(net.parameters(), lr=0.001)

最后使用以下代码训练并保存网络:

for images, labels in training_loader:
    optimizer.zero_grad()
    for i in range(batch_size):
        output = net(torch.flatten(images[i]))
        desired_output = torch.tensor([float(j == labels[i]) for j in range(10)])
        loss = loss_function(output, desired_output)
        loss.backward()
    optimizer.step()
torch.save(net.state_dict(), "./trained_net.pth")

但是,这里是一些测试图像的输出:

tensor([0.0978, 0.1225, 0.1018, 0.0961, 0.1022, 0.0885, 0.1007, 0.1077, 0.0994,
        0.1081], grad_fn=<SigmoidBackward>)
tensor([0.0978, 0.1180, 0.1001, 0.0929, 0.1006, 0.0893, 0.1010, 0.1051, 0.0978,
        0.1067], grad_fn=<SigmoidBackward>)
tensor([0.0981, 0.1227, 0.1018, 0.0970, 0.0979, 0.0908, 0.1001, 0.1092, 0.1011,
        0.1088], grad_fn=<SigmoidBackward>)
tensor([0.1061, 0.1149, 0.1037, 0.1001, 0.0957, 0.0919, 0.1044, 0.1022, 0.0997,
        0.1052], grad_fn=<SigmoidBackward>)
tensor([0.0996, 0.1137, 0.1005, 0.0947, 0.0977, 0.0916, 0.1048, 0.1109, 0.1013,
        0.1085], grad_fn=<SigmoidBackward>)
tensor([0.1008, 0.1154, 0.0986, 0.0996, 0.1031, 0.0952, 0.0995, 0.1063, 0.0982,
        0.1094], grad_fn=<SigmoidBackward>)
tensor([0.0972, 0.1235, 0.1013, 0.0984, 0.0974, 0.0907, 0.1032, 0.1075, 0.1001,
        0.1080], grad_fn=<SigmoidBackward>)
tensor([0.0929, 0.1258, 0.1016, 0.0978, 0.1006, 0.0889, 0.1001, 0.1068, 0.0986,
        0.1024], grad_fn=<SigmoidBackward>)
tensor([0.0982, 0.1207, 0.1040, 0.0990, 0.0999, 0.0910, 0.0980, 0.1051, 0.1039,
        0.1078], grad_fn=<SigmoidBackward>)

如您所见,网络似乎接近了一种状态,即每个输入的答案都是:

[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]

这个神经网络并不比仅仅猜测更好。我的设计或代码哪里出了问题?

【问题讨论】:

  • 在你的训练代码中,添加一些代码来判断训练时损失是否真的在减少。 Example

标签: python machine-learning neural-network pytorch mnist


【解决方案1】:

以下几点对您有用:

  • 乍一看,您的模型没有学习,因为您的预测与随机猜测一样好。第一个举措是监控你的损失,这里你只有一个时期。至少你可以根据看不见的数据评估你的模型:

    validation_set = torchvision.datasets.MNIST('./', 
        download=True, train=False, transform=T.ToTensor())
    
    validation_loader = DataLoader(validation_set, batch_size=32)
    
  • 您正在使用 MSE 损失(L2 范数)来训练分类任务 not the right tool for this kind of task。您可以改为使用负对数似然。 PyTorch 提供nn.CrossEntropyLoss,它在一个模块中包含一个 log-softmax 和负对数似然损失。可以通过添加以下内容来实现此更改:

    loss_function = nn.CrossEntropyLoss()
    

    并在应用loss_function 时使用正确的目标形状(见下文)。由于损失函数将应用 log-softmax,因此您不应该在模型的输出上使用激活函数。

  • 您正在使用 sigmoid 作为激活函数,中间非线性将更好地用作ReLU (see related post)。 sigmoid 更适合二进制分类任务。同样,由于我们使用的是nn.CrossEntropyLoss,我们必须在layer2 之后删除激活。

    class Net(torch.nn.Module):
        def __init__(self):
            super(Net, self).__init__()
            self.flatten = nn.Flatten()
            self.layer1 = torch.nn.Linear(784, 800)
            self.layer2 = torch.nn.Linear(800, 10)
    
        def forward(self, x):
            x = self.flatten(x)
            x = torch.relu(self.layer1(x))
            x = self.layer2(x)
            return x
    
  • 不太重要的一点是,您可以推断整个批次的估计值,而不是一次遍历每个批次一个元素。一个 epoch 的典型训练循环如下所示:

    for images, labels in training_loader:
        optimizer.zero_grad()
        output = net(images)
        loss = loss_function(output, labels)
        loss.backward()
        optimizer.step()
    

通过这些类型的修改,您可以期望在单个 epoch 后获得大约 80% 的验证。

【讨论】:

  • 明确提到“我想实现一个非常简单的设计,类似于 3Blue1Brown 的视频系列中显示的内容”,而更改激活将与此背道而驰。我认为这不是问题所在。
  • @SamarthBhatia 如果你想使用 sigmoid 和 MSE,你必须训练更长的时间才能达到相同的性能,这由你决定。
  • 如果问题告诉你它需要一个固定的架构,那么你就不能改变架构,即使 ReLU 已被证明比 sigmoid 好得多。这正是问题所要求的——不是你的选择
  • 封闭式问题的答案不一定是封闭式的。我提供了一种替代方法,可以证明对阅读这篇文章的用户很有价值。你不是 OP,所以我不太关心我的答案的框架。既然你似乎把这个放在心上,你应该试着自己提供一个答案;)
  • 确实,您的回答对我来说非常有价值。您提出的修改不是太失控且易于理解。 @pu239 当我说“简单设计”时,我应该更具体一些。感谢您的关心。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-30
  • 1970-01-01
  • 2013-12-27
  • 1970-01-01
  • 1970-01-01
  • 2013-03-19
相关资源
最近更新 更多