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

使用LSTM在Python中预测未来值

这段代码可以预测指定股票的当前日期之前的值,但不能预测…

如何在gensim的word2vec模型中查找双词组的相似性

我有一个word2vec模型,假设我使用的是googl…

dask_xgboost.predict 可以工作但无法显示 – 数据必须是一维的

我试图使用 XGBoost 创建模型。 看起来我成功地…

ML Tuning – Cross Validation in Spark

我在https://spark.apache.org/…

如何在React JS中使用fetch从REST API获取预测

我正在开发一个应用程序,其中Flask REST AP…

如何分析ML.NET中多类分类预测得分数组?

我在ML.NET中创建了一个多类分类项目。该项目可以对…

发表回复

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