【发布时间】:2017-10-18 07:52:30
【问题描述】:
我正在尝试编写一个将输入分类为三个对象的 MLP。 我有一个代表每个对象的数字。
1-10 : Banana
11-20 : Apple
21:30 : Carrot
MLP 中只有两层:一层隐藏层(2 个单元)和一层输出层(3 个单元)。
每个单元有:
- inputs[](传递给此单元的输入)
- 权重[]
- 三角洲
- sum(输入权重的总和)
- 输出(激活总和)
每个单元也有一个激活函数:
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))); // activation
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("Banana: " + outputs[0]);
System.out.println("Apple: " + outputs[1]);
System.out.println("Carrot: " + outputs[2]);
}
所以问题是,当我将任何数字传递给 recognize 函数时,我得到的输出类似于:
Banana: 0.49984367018594233
Apple: 0.49984367018594233
Carrot: 0.5001563298140577
每次都选择胡萝卜(胡萝卜也是网络最后训练的对象)。因此,如果我输入 5,它将输出它是胡萝卜。如果我输入 15,它将输出它是胡萝卜。如果我在 train 函数中更改正在学习的对象的顺序并使香蕉成为最后学习的对象,那么网络将始终选择香蕉作为其答案。
我已经研究了几天了,但我找不到任何解决方案,请帮助我,我做错了什么?
【问题讨论】:
-
打乱数据非常重要,即随机选择 1 到 30 之间的数字作为输入,并在 每个 迭代中创建相应的标签。
-
你不应该 1) 遍历所有的香蕉,2) 遍历所有的苹果,3) 遍历所有的胡萝卜。如果您有一个输入数组,请将行打乱,或者在您动态生成数据时,在每次迭代中随机创建香蕉/苹果/胡萝卜。
-
是的,我想这应该会提高学习效果。
-
@EthanGills 更常见的是,每次迭代,你用一个洗牌函数洗牌训练数组,然后你一个一个地反向传播它们。
-
@EthanGills 抱歉,毕竟这是对的。但是还有其他问题(我认为):您没有对输入值进行规范化。您正在输入像
10和20这样的值,但您的值应该在0和1范围内。所以输入应该看起来像这样10 -> 10/30 = 0.33。所以在你选择了一个输入之后,你应该首先将它标准化为一个介于0和1之间的数字。
标签: machine-learning neural-network deep-learning perceptron