我想实现一个多层感知器(MLP)来解决异或问题。我已经为此绞尽脑汁了很长时间,因为我不是一个出色的科学家,我希望能完全理解这个程序的每一行代码。
我先从几个问题开始:
- 是否有针对神经网络的特定调试方法?(例如预定义的已知权重可以引导到预期的解决方案)
- 下面的反向传播算法(伪代码)是否正确?
- 偏置是否应该包含在这个算法中?如果不包含,偏置是否有必要解决异或问题?如果需要,每个神经元、每层还是整个网络应该有一个偏置?
- 我的Python实现是否正确?如果不正确,是反向传播、前向传播、缺少偏置还是其他原因?
任何回应都非常受欢迎。这不是“作业”,但如果你非常担心这一点,我会很高兴得到一些好的建议。我还没有正确使用numpy,因为我更像是一个开发者而不是科学家,有时在矩阵方程上遇到困难(我目前正在努力解决)。因此,我很难完全理解我找到的不同实现方式。
编辑:算法现在运行良好,我将突出显示伪代码/Python实现中存在的问题:
理论部分:
- 伪代码在权重调整部分是错误的(我编辑了代码,用WRONG标记了该行并进行了修复)。我使用了输出层输出,而应该使用输入值
- 实际上,解决异或问题是不需要偏置的,只需要一个隐藏层。这里有趣的是,只有在隐藏层有4个或更多神经元时才有效,而3个不行。然而,经过基准测试,添加偏置可以显著加速达到最优解的时间(加速5倍或更多)
Python实现:
- 权重被随机初始化在0到1之间,而不是-1到1之间,这样是无法工作的
最后,这个资源对理解“神经元魔法”很有帮助。它没有帮助我调试反向传播,但现在很明显,如果没有更多的理论知识,我永远无法让它工作。如果你和我一样,更多的是开发者而不是科学家,我强烈建议你查看一下。
基于Virginie MATHIVET的《开发者的artificial intelligence》一书,这里是反向传播算法的伪代码(这是一本法语书,抱歉翻译不好):
While stop criteria is not achieved: Initialize d(i) For each example: Compute output value s(i) #1 For each output neuron weight: #2 d(i) = s(i) * (1 - s(i)) * (y(i) - s(i)) EndFor For each hidden neuron weight: sum = 0 For each link towards output neuron k: sum += d(k) * w(i->k) EndFor d(i) = o(i) * (1 - o(i)) * sum EndFor For each weight of the network: If link towards output neuron: w(i) += rate * d(i) * o(i) Else w(i) += rate * d(i) * s(i) # WRONG: s(i) should be input(i) EndIf EndFor EndForEndWhile
在上面,d(x)
是增量,o(x)
是隐藏层输出,s(x)
是输出层输出,y(x)
是预期输出,w(x)
是网络权重。
我理解第#1行是前向传播,而#2行(包括)之后的行是关于反向传播的。因此,正确的算法可以写成:
While stop criteria is not achieved: Initialize d(i) For each example: output = Forward propagation with example inputs #1 Backpropagation of the error between output and expected output #2 EndForEndWhile
我的问题是,算法似乎在反向传播和权重变化中陷入循环,例如,这里是隐藏层的输出:
[Epoch 0, inputs (1.0, 1.0)]: hidden outputs: None[Epoch 0, inputs (0.0, 0.0)]: hidden outputs: [ 0.7755638 0.64556638 0.68163599][Epoch 0, inputs (1.0, 0.0)]: hidden outputs: [ 0.5 0.5 0.5][Epoch 0, inputs (0.0, 1.0)]: hidden outputs: [ 0.60747218 0.58975313 0.55246625][Epoch 1, inputs (1.0, 1.0)]: hidden outputs: [ 0.68911554 0.55079694 0.62718831][Epoch 1, inputs (1.0, 0.0)]: hidden outputs: [ 0.77449528 0.64107552 0.67770194][Epoch 1, inputs (0.0, 0.0)]: hidden outputs: [ 0.60728756 0.58957687 0.55230354][Epoch 1, inputs (0.0, 1.0)]: hidden outputs: [ 0.5 0.5 0.5][Epoch 2, inputs (0.0, 0.0)]: hidden outputs: [ 0.68877278 0.54872848 0.6254074 ][Epoch 2, inputs (1.0, 0.0)]: hidden outputs: [ 0.5 0.5 0.5][Epoch 2, inputs (1.0, 1.0)]: hidden outputs: [ 0.60700878 0.58812487 0.5509695 ][Epoch 2, inputs (0.0, 1.0)]: hidden outputs: [ 0.77344667 0.63591436 0.67311723][Epoch 3, inputs (0.0, 0.0)]: hidden outputs: [ 0.68856723 0.54708942 0.62400827][Epoch 3, inputs (1.0, 0.0)]: hidden outputs: [ 0.5 0.5 0.5]
将示例随机排序不会改变任何事情。此外,我尝试了所有学习率(0.05到0.95),结果相同,所以我认为这不是关于坏的收敛问题。以下是我的Python实现:
def sigmoid(x): return 1 / (1 + np.exp(-x))def dsigmoid(y): return y * (1.0 - y)class NeuralNetwork: def __init__(self, nb_inputs, nb_hidden, nb_outputs, learning_rate): self.nb_inputs = nb_inputs self.nb_hidden = nb_hidden self.nb_outputs = nb_outputs self.learning_rate = learning_rate self.output_deltas = None self.output_weights = np.random.random((nb_outputs, nb_hidden)) # WRONG: should be between -1 and 1, not 0 and 1 self.outputs = None self.hidden_deltas = None self.hidden_weights = np.random.random((nb_hidden, nb_inputs)) # WRONG: should be between -1 and 1, not 0 and 1 self.hidden_outputs = None def forward_propagation(self, inputs): self.hidden_outputs = np.zeros((self.nb_hidden,)) self.outputs = np.zeros((self.nb_outputs,)) # get outputs for hidden layer for i in range(self.nb_hidden): aggregated = sum([inputs[j] * self.hidden_weights[i][j] for j in range(self.nb_inputs)]) self.hidden_outputs[i] = sigmoid(aggregated) # get inputs for output layer for i in range(self.nb_outputs): aggregated = sum([self.hidden_outputs[j] * self.output_weights[i][j] for j in range(self.nb_hidden)]) self.outputs[i] = sigmoid(aggregated) def backpropagation(self, expected_outputs): # find deltas for output layer for i in range(self.nb_outputs): for j in range(self.nb_hidden): self.output_deltas[i][j] = dsigmoid(self.outputs[i]) * (expected_outputs[i] - self.outputs[i]) # find deltas for hidden layer for i in range(self.nb_hidden): for j in range(self.nb_inputs): total = 0.0 for k in range(self.nb_outputs): total += self.output_deltas[k][i] * self.output_weights[k][i] self.hidden_deltas[i][j] = dsigmoid(self.hidden_outputs[i]) * total # change weights for output layer for i in range(self.nb_outputs): for j in range(self.nb_hidden): self.output_weights[i][j] += self.learning_rate * self.output_deltas[i][j] * self.outputs[i] # WRONG: should be self.hidden_outputs[j] # change weights for inputs layer for i in range(self.nb_hidden): for j in range(self.nb_inputs): self.hidden_weights[i][j] += self.learning_rate * self.hidden_deltas[i][j] * self.hidden_outputs[i] # WRONG: should be inputs[j] def train(self, data, nb_iterations): for i in range(nb_iterations): # Init deltas self.output_deltas = np.zeros((self.nb_outputs, self.nb_hidden), dtype=np.float64) self.hidden_deltas = np.zeros((self.nb_hidden, self.nb_inputs), dtype=np.float64) # Train on examples with different orders for inputs, expected_output in sorted(data.items(), key=lambda x: random.random()): expected_outputs = np.array([expected_output]) self.forward_propagation(inputs) self.backpropagation(expected_outputs) # WRONG: need inputs: backpropagation(inputs, expected_outputs) def predict(self, inputs): self.forward_propagation(inputs) return self.outputs
回答:
我已经为此绞尽脑汁了很长时间,因为我不是一个出色的科学家,我希望能完全理解这个程序的每一行代码。
你得到了我的回答。继续这样做,你会做得很棒!
免责声明:部分回答。我可能会随着时间的推移进行改进。
是否有针对神经网络的特定调试方法?(例如预定义的已知权重可以引导到预期的解决方案)
通常没有。由于权重的数量,在大多数实际案例中几乎是不可能的。你仍然可以尝试监控神经元活动(你代码中的aggregated
变量),例如连续两次向网络发送相同的输入,看看它是否在“学习”,即它是否越来越接近正确的值。
下面的反向传播算法(伪代码)是否正确?
我会试着检查。这是你写的吗?
偏置是否应该包含在这个算法中?如果不包含,偏置是否有必要解决异或问题?如果需要,每个神经元、每层还是整个网络应该有一个偏置?
通常每个神经元有一个偏置。如果你只是忽略它也没有问题。
我的Python实现是否正确?如果不正确,是反向传播、前向传播、缺少偏置还是其他原因?
我会检查。缺少偏置是可以的。
作为补充,我推荐这个在线“书籍”:http://neuralnetworksanddeeplearning.com/chap1.html 里面有很多数学,不要在数学上花太多时间,试着理解概念。我觉得它非常教学性。
希望这能帮到你(一点)pltrdy