从零开始构建的简单神经网络无法学习。

3
我实现了一个神经网络类,始终只有一个隐藏层,并且没有使用任何库 - 甚至没有numpy。我已经按照我的理解做了所有事情,但它根本没有学习,损失实际上是持续增加的,即使在查看了许多在线示例之后,我也找不到问题出在哪里。
以下是我的MLP类及其尝试学习XOR函数的演示:
import random
from math import exp


class MLP:

    def __init__(self, numInputs, numHidden, numOutputs):
        # MLP architecture sizes
        self.numInputs = numInputs
        self.numHidden = numHidden
        self.numOutputs = numOutputs

        # MLP weights
        self.IH_weights = [[random.random() for i in range(numHidden)] for j in range(numInputs)]
        self.HO_weights = [[random.random() for i in range(numOutputs)] for j in range(numHidden)]

        # Gradients corresponding to weight matrices computed during backprop
        self.IH_gradients = [[0 for i in range(numHidden)] for j in range(numInputs)]
        self.HO_gradients = [[0 for i in range(numOutputs)] for j in range(numHidden)]

        # Input, hidden and output neuron values
        self.I = None
        self.H = [0 for i in range(numHidden)]
        self.O = [0 for i in range(numOutputs)]

        self.H_deltas = [0 for i in range(numHidden)]
        self.O_deltas = [0 for i in range(numOutputs)]

    # Sigmoid
    def activation(self, x):
        return 1 / (1 + exp(-x))

    # Derivative of Sigmoid
    def activationDerivative(self, x):
        return x * (1 - x)

    # Squared Error
    def calculateError(self, prediction, label):
        return (prediction - label) ** 2

    def forward(self, input):
        self.I = input
        for i in range(self.numHidden):
            for j in range(self.numInputs):
                self.H[i] += self.I[j] * self.IH_weights[j][i]
            self.H[i] = self.activation(self.H[i])

        for i in range(self.numOutputs):
            for j in range(self.numHidden):
                self.O[i] += self.activation(self.H[j] * self.HO_weights[j][i])
            self.O[i] = self.activation(self.O[i])

        return self.O

    def backwards(self, label):
        if label != list:
            label = [label]

        error = 0
        for i in range(self.numOutputs):
            neuronError = self.calculateError(self.O[i], label[i])
            error += neuronError
            self.O_deltas[i] = neuronError * self.activationDerivative(self.O[i])
            for j in range(self.numHidden):
                self.HO_gradients[j][i] += self.O_deltas[i] * self.H[j]

        for i in range(self.numHidden):
            neuronError = 0
            for j in range(self.numOutputs):
                neuronError += self.HO_weights[i][j] * self.O_deltas[j]
            self.H_deltas[i] = neuronError * self.activationDerivative(self.H[i])
            for j in range(self.numInputs):
                self.IH_gradients[j][i] += self.H_deltas[i] * self.I[j]

        return error

    def updateWeights(self, learningRate):
        for i in range(self.numInputs):
            for j in range(self.numHidden):
                self.IH_weights[i][j] += learningRate * self.IH_gradients[i][j]

        for i in range(self.numHidden):
            for j in range(self.numOutputs):
                self.HO_weights[i][j] += learningRate * self.HO_gradients[i][j]

        self.IH_gradients = [[0 for i in range(self.numHidden)] for j in range(self.numInputs)]
        self.HO_gradients = [[0 for i in range(self.numOutputs)] for j in range(self.numHidden)]


data = [
    [[0, 0], 0],
    [[0, 1], 1],
    [[1, 0], 1],
    [[1, 1], 0]
]

mlp = MLP(2, 5, 1)

for epoch in range(100):
    epochError = 0
    for i in range(len(data)):
        mlp.forward(data[i][0])
        epochError += mlp.backwards(data[i][1])
    print(epochError / len(data))
    mlp.updateWeights(0.001)

1
为什么不使用numpy?这将使实现矩阵乘法例程变得非常繁琐。我建议将这样的功能抽象出来,以便更容易进行调试。 - cs95
2个回答

1

你对这个有什么想法?我向朋友展示了它 - 我们都发现你的目标是在不使用太多抽象的情况下完成算法是有益的,尽管尝试找到错误是困难的。

他发现的改进是updateWeights需要成为一个负反馈循环,因此在两行中将"+="更改为"-=":

self.IH_weights[i][j] -= learningRate * self.IH_gradients[i][j]

并且

self.HO_weights[i][j] -= learningRate * self.HO_gradients[i][j]

另一个因素是增加学习率。通过这些更改,误差降至约16%(对于我来说,可能有其他我没有看到的更改),然后开始上升渐近到27% - 可能是由于使用过高的学习率进行过度训练。
我使学习率依赖于时期。
mlp.updateWeights(0.1/(0.01 * (epoch+1)))

并且它稳步下降并稳定在0.161490...

但是如果你从“前面”得到预测,它总是预测0.66-输入已被清除。所以...那很糟糕。

 - Input Data: [0, 0] | Prediction: [0.6610834017294481] |Truth: 0
 - Input Data: [0, 1] | Prediction: [0.6616502691118376] |Truth: 1
 - Input Data: [1, 0] | Prediction: [0.6601936411430607] |Truth: 1
 - Input Data: [1, 1] | Prediction: [0.6596122207209283] |Truth: 0

我已经使用numpy实现了它,并使其正常工作 - 我的反向传播实现是错误的。我在反向传播中使用的是输出处的实际误差,而不是它的导数。现在,我正在尝试再次删除numpy依赖,只是为了好玩。 - KOB
这是我的另一个问题,其中numpy实现已经完成:https://stackoverflow.com/questions/49754939/multilayer-perceptron-works-fine-with-sigmoid-activations-but-not-with-hyperbol/49758402?noredirect=1#comment86535759_49758402 - KOB

1
如果我正确理解了您的实现,那么我认为您的问题在于反向函数中权重更新的计算,更新应该是误差(而不是误差平方)乘以sigmoid导数,因此我建议您重新检查/重新计算。

似乎没有改变任何东西-损失仍然持续增加。 - KOB
@KOB,这似乎不是真的。我自己尝试了已经修正的代码,所以看起来你可能还有其他问题。我建议你重新编写反向传播函数,并在代码中加入合理性检查,以确定问题出现的位置。很抱歉我没有更多帮助。 - Zer0k

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接