PyTorch 二元分类 – 相同网络结构,’更简单’的数据,但性能更差?

为了掌握PyTorch(以及一般的深度学习),我开始通过一些基本的分类示例进行学习。其中一个示例是使用sklearn创建的非线性数据集进行分类(完整代码可作为笔记本在此处获取)

n_pts = 500X, y = datasets.make_circles(n_samples=n_pts, random_state=123, noise=0.1, factor=0.2)x_data = torch.FloatTensor(X)y_data = torch.FloatTensor(y.reshape(500, 1))

enter image description here

然后使用一个相当基本的神经网络准确地对其进行分类

class Model(nn.Module):    def __init__(self, input_size, H1, output_size):        super().__init__()        self.linear = nn.Linear(input_size, H1)        self.linear2 = nn.Linear(H1, output_size)    def forward(self, x):        x = torch.sigmoid(self.linear(x))        x = torch.sigmoid(self.linear2(x))        return x    def predict(self, x):        pred = self.forward(x)        if pred >= 0.5:            return 1        else:            return 0

由于我对健康数据感兴趣,我决定尝试使用相同的网络结构来对一些基本的现实世界数据集进行分类。我从这里获取了一位患者的心率数据,并对其进行了修改,使所有大于91的值被标记为异常(例如,标记为1,而所有小于或等于91的值标记为0)。这完全是任意的,但我只是想看看分类效果如何。这个示例的完整笔记本在这里

enter image description here

让我感到不直观的是,为什么第一个示例在1,000个epoch后达到0.0016的损失,而第二个示例在10,000个epoch后仅达到0.4296的损失

Training Loss for Example 1

Training Loss for Heart Rate Example

或许我天真地认为心率示例会更容易分类。任何能帮助我理解为什么我没有看到这种情况的见解都将非常有帮助!


回答:

TL;DR

你的输入数据未经归一化处理。

  1. 使用 x_data = (x_data - x_data.mean()) / x_data.std()
  2. 增加学习率 optimizer = torch.optim.Adam(model.parameters(), lr=0.01)

你将得到
enter image description here

仅需1000次迭代即可收敛。

更多细节

你所提供的两个示例之间的关键区别在于,第一个示例中的数据x围绕(0, 0)居中,并且方差非常低。
而第二个示例中的数据围绕92居中,并且方差相对较大。

在你随机初始化权重时,这种数据的初始偏差并未被考虑到,这是基于输入大致围绕的正态分布的假设进行的。
优化过程几乎不可能补偿这种巨大的偏差 – 因此模型陷入了一个次优解中。

一旦你通过减去均值并除以标准差来归一化输入,优化过程再次变得稳定,并迅速收敛到一个好的解。

关于输入归一化和权重初始化的更多细节,你可以阅读He et al深入研究整流器:在ImageNet分类上超越人类水平表现(ICCV 2015)中的第2.2节。

如果我无法归一化数据怎么办?

如果由于某些原因,你无法提前计算均值和标准差数据,你仍然可以使用nn.BatchNorm1d来估计并在训练过程中归一化数据。例如

class Model(nn.Module):    def __init__(self, input_size, H1, output_size):        super().__init__()        self.bn = nn.BatchNorm1d(input_size)  # 添加批归一化        self.linear = nn.Linear(input_size, H1)        self.linear2 = nn.Linear(H1, output_size)        def forward(self, x):        x = torch.sigmoid(self.linear(self.bn(x)))  # 对输入x进行批归一化        x = torch.sigmoid(self.linear2(x))        return x

这种修改在改变输入数据的情况下,仅在1000个epoch后就达到了类似的收敛:
enter image description here

一个小评论

为了数值稳定性,最好使用nn.BCEWithLogitsLoss而不是nn.BCELoss。为此,你需要从forward()输出中移除torch.sigmoidsigmoid将在损失内部计算。
例如,关于二元预测的相关sigmoid + 交叉熵损失,请参见这个线程

Related Posts

Keras Dense层输入未被展平

这是我的测试代码: from keras import…

无法将分类变量输入随机森林

我有10个分类变量和3个数值变量。我在分割后直接将它们…

如何在Keras中对每个输出应用Sigmoid函数?

这是我代码的一部分。 model = Sequenti…

如何选择类概率的最佳阈值?

我的神经网络输出是一个用于多标签分类的预测类概率表: …

在Keras中使用深度学习得到不同的结果

我按照一个教程使用Keras中的深度神经网络进行文本分…

‘MatMul’操作的输入’b’类型为float32,与参数’a’的类型float64不匹配

我写了一个简单的TensorFlow代码,但不断遇到T…

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注