PyTorch Binary分类-相同的网络结构,“更简单”的数据,但性能较差?

Phi*_*ien 14 python artificial-intelligence machine-learning deep-learning pytorch

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

n_pts = 500
X, 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))
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

然后使用相当基本的神经网络将其准确分类

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
Run Code Online (Sandbox Code Playgroud)

当我对健康数据感兴趣时,我决定尝试使用相同的网络结构对一些基本的现实世界数据集进行分类。我从这里获取了一名患者的心率数据,并对其进行了更改,以便所有> 91的值都被标记为异常(例如a 1,所有<= 91的值都标记为a 0)。这是完全任意的,但是我只是想看看分类是如何工作的。此示例的完整笔记本在这里

在此处输入图片说明

对我来说不直观的是,为什么第一个示例在1,000个历元后损失0.0016,而第二个示例在10,000个历元后却损失0.4296

示例1的训练损失

心率示例训练损失

也许我天真地认为心率示例更容易分类。任何能帮助我理解为什么这不是我所看到的见解都会很棒!

Sha*_*hai 14

TL; DR

您的输入数据未标准化。

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

你会得到
在此处输入图片说明

仅1000次迭代即可收敛。

更多细节

两个示例之间的主要区别在于x,第一个示例中的数据以(0,0)为中心,并且方差很小。
另一方面,第二示例中的数据以92为中心,并且具有相对较大的方差。

当您随机初始化权重时,不会考虑数据中的初始偏差,该权重是基于假设输入大致呈正态分布的假设进行的。
优化过程几乎不可能补偿该总偏差-因此模型陷入了次优的解决方案。

将输入标准化后,通过减去平均值并除以std,优化过程将再次变得稳定,并迅速收敛为一个好的解决方案。

有关输入归一化和权重初始化的更多详细信息,您可以阅读He等人 深入研究整流器:在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)  # adding batchnorm
        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)))  # batchnorm the input x
        x = torch.sigmoid(self.linear2(x))
        return x
Run Code Online (Sandbox Code Playgroud)

这种修改不会对输入数据进行任何更改,仅在1000个纪元后就会产生类似的收敛:
在此处输入图片说明

轻微评论

为了数值稳定,最好使用nn.BCEWithLogitsLoss代替nn.BCELoss。为此,您需要torch.sigmoidforward()输出中删除,sigmoid它将在损失内计算。
见,例如,这个线程,关于相关乙状结肠+交叉熵二进制预测的损失。