1

我试图用 PyTorch 和 skorch 训练一个非常简单(我认为)的 NN 模型,但糟糕的性能真的让我感到困惑,所以如果你对此有任何见解,那就太好了。

问题是这样的:有五个对象,A、B、C、D、E(由它们的指纹标记,例如(0, 0)是 A,(0.2, 0.5)是 B,等等)每个对应于一个数字,问题是试图找出每个数字对应的数字。训练数据是“集合”列表和相应的总和。例如: [A, A, A, B, B] == [(0,0), (0,0), (0,0), (0.2,0.5), (0.2, 0.5)] --> 15, [B, C, D, E] == [(0.2,0.5), (0.5,0.8), (0.3,0.9), (1,1)] --> 30 .... 请注意,一个集合中的对象不是恒定的

没有噪音什么的,所以它只是一个可以直接求解的线性系统。所以我认为这对于 NN 来说很容易找到。我实际上使用这个例子作为一个更复杂问题的健全性检查,但很惊讶 NN 甚至无法解决这个问题。

现在我只是想准确指出哪里出了问题。模型定义似乎是对的,数据输入是对的,是不是训练不好导致性能不好?还是NN在这些事情上不擅长?

这是模型定义:

class NN(nn.Module):
    def __init__(
        self,
        input_dim,
        num_nodes,
        num_layers,
        batchnorm=False,
        activation=Tanh,
    ):
        super(SingleNN, self).__init__()
        self.get_forces = get_forces
        self.activation_fn = activation

        self.model = MLP(
            n_input_nodes=input_dim,
            n_layers=num_layers,
            n_hidden_size=num_nodes,
            activation=activation,
            batchnorm=batchnorm,
        )

    def forward(self, batch):
        if isinstance(batch, list):
            batch = batch[0]
        with torch.enable_grad():
            fingerprints = batch.fingerprint.float()
            fingerprints.requires_grad = True
            #index of the current "collection" in the training list
            idx = batch.idx
            sorted_idx = torch.unique_consecutive(idx)
            o = self.model(fingerprints)
            total = scatter(o, idx, dim=0)[sorted_idx]

            return total

    @property
    def num_params(self):
        return sum(p.numel() for p in self.parameters())

class MLP(nn.Module):
    def __init__(
        self,
        n_input_nodes,
        n_layers,
        n_hidden_size,
        activation,
        batchnorm,
        n_output_nodes=1,
    ):
        super(MLP, self).__init__()
        if isinstance(n_hidden_size, int):
            n_hidden_size = [n_hidden_size] * (n_layers)
        self.n_neurons = [n_input_nodes] + n_hidden_size + [n_output_nodes]
        self.activation = activation
        layers = []
        for _ in range(n_layers - 1):
            layers.append(nn.Linear(self.n_neurons[_], self.n_neurons[_ + 1]))
            layers.append(activation())
            if batchnorm:
                layers.append(nn.BatchNorm1d(self.n_neurons[_ + 1]))
        layers.append(nn.Linear(self.n_neurons[-2], self.n_neurons[-1]))
        self.model_net = nn.Sequential(*layers)

    def forward(self, inputs):
        return self.model_net(inputs)

skorch部分很简单

model = NN(2, 100, 2)
net = NeuralNetRegressor(
        module=model,
        ...
    )
net.fit(train_dataset, None)

对于测试运行,数据集如下所示(总共 16 个集合):

[[0.7484336 0.5656401]
 [0.        0.       ]
 [0.        0.       ]
 [0.        0.       ]]
[[1. 1.]
 [0. 0.]
 [0. 0.]]
[[0.51311415 0.67012525]
 [0.51311415 0.67012525]
 [0.         0.        ]
 [0.         0.        ]]
[[0.51311415 0.67012525]
 [0.7484336  0.5656401 ]
 [0.         0.        ]]
[[0.51311415 0.67012525]
 [1.         1.        ]
 [0.         0.        ]
 [0.         0.        ]]
[[0.51311415 0.67012525]
 [0.51311415 0.67012525]
 [0.         0.        ]
 [0.         0.        ]
 [0.         0.        ]
 [0.         0.        ]
 [0.         0.        ]
 [0.         0.        ]]
[[0.51311415 0.67012525]
 [1.         1.        ]
 [0.         0.        ]
 [0.         0.        ]
 [0.         0.        ]
 [0.         0.        ]]
....

对应的总数:[10, 11, 14, 14, 17, 18, ...]

只需通过目测就可以很容易地分辨出对象是什么/其中有多少在一个集合中,训练过程如下所示:

 epoch    train_energy_mae    train_loss    cp     dur
-------  ------------------  ------------  ----  ------
      1              4.9852        0.5425     +  0.1486
      2             16.3659        4.2273        0.0382
      3              6.6945        0.7403        0.0025
      4              7.9199        1.2694        0.0024
      5             12.0389        2.4982        0.0024
      6              9.9942        1.8391        0.0024
      7              5.6733        0.7528        0.0024
      8              5.7007        0.5166        0.0024
      9              7.8929        1.0641        0.0024
     10              9.2560        1.4663        0.0024
     11              8.5545        1.2562        0.0024
     12              6.7690        0.7589        0.0024
     13              5.3769        0.4806        0.0024
     14              5.1117        0.6009        0.0024
     15              6.2685        0.8831        0.0024
....
    290              5.1899        0.4750        0.0024
    291              5.1899        0.4750        0.0024
    292              5.1899        0.4750        0.0024
    293              5.1899        0.4750        0.0024
    294              5.1899        0.4750        0.0025
    295              5.1899        0.4750        0.0025
    296              5.1899        0.4750        0.0025
    297              5.1899        0.4750        0.0025
    298              5.1899        0.4750        0.0025
    299              5.1899        0.4750        0.0025
    300              5.1899        0.4750        0.0025
    301              5.1899        0.4750        0.0024
    302              5.1899        0.4750        0.0025
    303              5.1899        0.4750        0.0024
    304              5.1899        0.4750        0.0024
    305              5.1899        0.4750        0.0025
    306              5.1899        0.4750        0.0024
    307              5.1899        0.4750        0.0025

你可以看到它只是在一段时间后停止训练。我可以确认 NN 确实为不同的指纹给出了不同的结果,但不知何故,最终的预测值永远不够好。

我尝试了不同的 NN 大小、学习率、批量大小、激活函数(tanh、relu 等),但似乎都没有帮助。你对此有什么见解吗?有什么我做错了/可以尝试,还是 NN 在这种任务上做得不好?

4

1 回答 1

1

我注意到的第一件事:super(SingleNN, self).__init__()应该是super(NN, self).__init__()。更改它,如果您仍然遇到任何错误,请告诉我。

于 2020-11-15T20:29:58.720 回答