我正在尝试编写一个MLP来将输入分类为三个对象。我用一个数字来表示每个对象。
1-10 : 香蕉11-20 : 苹果21-30 : 胡萝卜
MLP只有两层:一个隐藏层(2个单元)和一个输出层(3个单元)。
每个单元具有以下属性:
- inputs[](传递给该单元的输入)
- weights[]
- delta
- sum(输入与权重的加权和)
- output(激活后的和)
每个单元还有一个激活函数:
double activate(double[] inputs) { this.inputs = inputs; sum = 0; for (int i = 0; i < inputs.length; i++) sum += weights[i] * inputs[i]; output = 1.0 / (1.0 + (Math.exp(-sum))); // 激活 return output; }
还有一个用于修正权重的函数:
void correctWeights(double momentum, double learningRate) { for (int i = 0; i < weights.length; i++) { weights[i] = weights[i] * momentum + learningRate * delta * (output * (1 - output)) * inputs[i]; } }
其中 (output * (1 - output))
是导数。
为了训练网络,我有一个函数,它会循环N次,在循环中我会生成与对象相关的输入,然后将其传播到网络中并使用反向传播。
private void train() { for (int i = 0; i < 10000; i++) { int[] expectedOutput = new int[3]; double[] inputs = {ThreadLocalRandom.current().nextInt(1, 30 + 1)}; if (inputs[0] <= 10) { expectedOutput[0] = 1; expectedOutput[1] = 0; expectedOutput[2] = 0; } if (inputs[0] <= 20 && inputs[0] > 10) { expectedOutput[0] = 0; expectedOutput[1] = 1; expectedOutput[2] = 0; } if (inputs[0] <= 30 && inputs[0] > 20) { expectedOutput[0] = 0; expectedOutput[1] = 0; expectedOutput[2] = 1; } double[] outputs = propagate(inputs); backPropagate(expectedOutput, outputs); } }
传播函数只是通过整个网络并激活单元。
private double[] propagate(double[] inputs) { double[] hiddenOutputs = new double[hiddenLayer.length]; for (int i = 0; i < hiddenLayer.length; i++) hiddenOutputs[i] = hiddenLayer[i].activate(inputs); double[] outputs = new double[outputLayer.length]; for (int i = 0; i < outputs.length; i++) outputs[i] = outputLayer[i].activate(hiddenOutputs); return outputs; }
反向传播算法取自 http://home.agh.edu.pl/~vlsi/AI/backp_t_en/backprop.html
private void backPropagate(int[] expectedOutput, double[] output) { for (int i = 0; i < outputLayer.length; i++) { outputLayer[i].setDelta(expectedOutput[i] - output[i]); } for (int i = 0; i < hiddenLayer.length; i++) { double delta = 0; for (int j = 0; j < outputLayer.length; j++) { delta += outputLayer[j].getDelta() * outputLayer[j].getWeight(i); } hiddenLayer[i].setDelta(delta); } for (int i = 0; i < hiddenLayer.length; i++) hiddenLayer[i].correctWeights(momentum, learningRate); for (int i = 0; i < outputLayer.length; i++) outputLayer[i].correctWeights(momentum, learningRate); }
它还具有在训练后识别对象的功能
private void recognize(String number) { double[] inputs = {Double.parseDouble(number)}; double[] outputs = propagate(inputs); System.out.println("香蕉: " + outputs[0]); System.out.println("苹果: " + outputs[1]); System.out.println("胡萝卜: " + outputs[2]); }
所以问题是,当我将任何数字传递给recognize函数时,我得到的输出类似于这样:
香蕉: 0.49984367018594233苹果: 0.49984367018594233胡萝卜: 0.5001563298140577
每次都选择胡萝卜(胡萝卜也是网络最后训练的对象)。所以如果我输入5,它会输出这是胡萝卜。如果我输入15,它会输出这是胡萝卜。如果我在train函数中更改学习对象的顺序,使香蕉成为最后学习的对象,那么网络将始终选择香蕉作为答案。
我已经为此工作了几天了,我找不到任何解决方案,请帮助我,我做错了什么?
回答:
我注意到您选择了一个0到30之间的随机数,然后为它确定一个输出,但是您忘记了对输入进行归一化。神经网络在输入范围在0-1
之间时(取决于您使用的激活函数)表现最佳。
所以您需要做的就是对输入进行归一化。这意味着,将输入等比例转换为0
到1
之间的数字。
您的输入是一个数值,所以您所要做的就是选择一个最大值,并用它来除以所有值。在您的案例中,这可以是30
,因为没有输入高于30
。所以每个数字的转换如下:
10 -> 10 / 30 -> 0.3315 -> 15 / 30 -> 0.50etc.
关于归一化的更多信息,请阅读这里。