【问题标题】:Simple Feedforward Neural Network in JavaScriptJavaScript 中的简单前馈神经网络
【发布时间】:2018-12-23 17:04:55
【问题描述】:

我是这个网站的新手,所以如果我在这篇文章中做错了什么,我提前道歉。

我目前正在尝试机器学习,并且正在学习神经网络。我目前正在使用http://neuralnetworksanddeeplearning.com/。但是,我并没有完全理解所有内容,并且所有代码都是用 Python 编写的(我更喜欢 JavaScript)。

我创建了一个适用于简单数据的程序。然而,对于更复杂的数据(使用 MNIST 数据进行手写数字识别),准确率并没有上面网站所说的那么高,通过使用 784 个输入神经元的神经网络,在隐藏层(只有一个隐藏层并尝试了几种可能数量的神经元),以及 10 个输出神经元,经过数百次迭代。我认为我的反向传播步骤(即训练步骤,我在此处包含其他函数作为参考)存在错误,导致它无法足够快地学习(顺便说一句,我使用交叉熵作为我的成本功能)。如果有人可以帮助我找到错误,我将不胜感激。提前致谢。

下面是代码。权重排列在数组的数组中(weight[i][j][k] 是第 i 层中的第 j 个神经元与第 (i+1) 层中的第 k 个神经元之间的权重)。类似地,bias[i][j] 是第 (i+1) 层对第 j 个神经元的偏差。训练数据被格式化为具有输入和输出键的对象数组(参见下面的示例)。

class NeuralNetwork {
  constructor(layers) {
    // Check if layers is a valid argument
    // Initialize neural network
    if (!Array.isArray(layers) || layers.length < 2) {
      throw Error("Layers must be specified as an array of length at least 2");
    }
    this.weights = [];
    this.biases = [];
    for (let i = 0, l = layers.length; i < l; ++i) {
      let currentLayer = layers[i];
      if (typeof currentLayer === "number" && Number.isInteger(currentLayer) && currentLayer > 0) {
        let numWeights = layers[i + 1];
        if (i < l - 1) {
          this.weights.push([]);
        }
        if (i) {
          this.biases.push([]);
        }

        // Seed weights and biases
        for (let j = 0; j < currentLayer; ++j) {
          if (i < l - 1) {
            let weights = [];
            for (let k = 0; k < numWeights; ++k) {
              weights.push(Math.random() * 2 - 1);
            }
          this.weights[i].push(weights);
          }
          if (i) {
            this.biases[i - 1].push(Math.random() * 2 - 1);
          }
        }
      } else {
        throw Error("Array used to specify NeuralNetwork layers must consist solely of positive integers");
      }
    }
    this.activation = (x) => 1 / (1 + Math.exp(-x));
    this.activationDerivative = (x) => this.activation(x) * (1 - this.activation(x));
    Object.freeze(this);
    console.log("Successfully initialized NeuralNetwork");
    return this;
  }
  run(input, training) {
    // Forward propagation
    let currentInput;
    if (training) {
      currentInput = [input.map((a) => {return {before: a, after: a}})];
    } else {
      currentInput = [...input];
    }
    for (let i = 0, l = this.weights.length; i < l; ++i) {
      let newInput = [];
      for (let j = 0, m = this.weights[i][0].length, n = (training ? currentInput[i] : currentInput).length; j < m; ++j) {
        let sum = this.biases[i][j];
        for (let k = 0; k < n; ++k) {
          sum += (training ? currentInput[i][k].after : currentInput[k]) * this.weights[i][k][j];
        }
        if (training) {
          newInput.push({
            before: sum,
            after: this.activation(sum)
          });
        } else {
          newInput.push(this.activation(sum));
        }
      }
      if (training) {
        currentInput.push(newInput);
      } else {
        currentInput = newInput;
      }
    }
    return currentInput;
  }
  train(data, learningRate = 0.1, batch = 50, iterations = 10000) {
    // Backward propagation
    console.log("Initialized training");
    let length = data.length,
        totalCost = 0,
        learningRateFunction = typeof learningRate === "function",
        batchCount = 0,
        weightChanges = [],
        biasChanges = [];
    for (let i = 0; i < iterations; ++i) {
      let rate = learningRateFunction ? learningRate(i, totalCost) : learningRate;
      totalCost = 0;
      for (let j = 0, l = length; j < l; ++j) {
        let currentData = data[j],
            result = this.run(currentData.input, true),
            outputLayer = result[result.length - 1],
            outputLayerError = [],
            errors = [];
        for (let k = 0, m = outputLayer.length; k < m; ++k) {
          let currentOutputNeuron = outputLayer[k];
          outputLayerError.push(currentOutputNeuron.after - currentData.output[k]);
          totalCost -= Math.log(currentOutputNeuron.after) * currentData.output[k] + Math.log(1 - currentOutputNeuron.after) * (1 - currentData.output[k]);
        }
        errors.push(outputLayerError);
        for (let k = result.length - 1; k > 1; --k) {
          let previousErrors = errors[0],
              newErrors = [],
              currentLayerWeights = this.weights[k - 1],
              previousResult = result[k - 1];
          for (let i = 0, n = currentLayerWeights.length; i < n; ++i) {
            let sum = 0,
                currentNeuronWeights = currentLayerWeights[i];
            for (let j = 0, o = currentNeuronWeights.length; j < o; ++j) {
              sum += currentNeuronWeights[j] * previousErrors[j];
            }
            newErrors.push(sum * this.activationDerivative(previousResult[i].before));
          }
          errors.unshift(newErrors);
        }
        for (let k = 0, n = this.biases.length; k < n; ++k) {
          if (!weightChanges[k]) weightChanges[k] = [];
          if (!biasChanges[k]) biasChanges[k] = [];
          let currentLayerWeights = this.weights[k],
              currentLayerBiases = this.biases[k],
              currentLayerErrors = errors[k],
              currentLayerResults = result[k],
              currentLayerWeightChanges = weightChanges[k],
              currentLayerBiasChanges = biasChanges[k];
          for (let i = 0, o = currentLayerBiases.length; i < o; ++i) {
            let change = rate * currentLayerErrors[i];
            for (let j = 0, p = currentLayerWeights.length; j < p; ++j) {
              if (!currentLayerWeightChanges[j]) currentLayerWeightChanges[j] = [];
              currentLayerWeightChanges[j][i] = (currentLayerWeightChanges[j][i] || 0) - change * currentLayerResults[j].after;
            }
            currentLayerBiasChanges[i] = (currentLayerBiasChanges[i] || 0) - change;
          }
        }
        ++batchCount;
        if (batchCount % batch === 0 || i === iterations - 1 && j === l - 1) {
          for (let k = 0, n = this.weights.length; k < n; ++k) {
            let currentLayerWeights = this.weights[k],
                currentLayerBiases = this.biases[k],
                currentLayerWeightChanges = weightChanges[k],
                currentLayerBiasChanges = biasChanges[k];
            for (let i = 0, o = currentLayerWeights.length; i < o; ++i) {
              let currentNeuronWeights = currentLayerWeights[i],
                  currentNeuronWeightChanges = currentLayerWeightChanges[i];
              for (let j = 0, p = currentNeuronWeights.length; j < p; ++j) {
                currentNeuronWeights[j] += currentNeuronWeightChanges[j] / batch;
              }
              currentLayerBiases[i] += currentLayerBiasChanges[i] / batch;
            }
          }
          weightChanges = [];
          biasChanges = [];
        }
      }
      totalCost /= length;
    }
    console.log(`Training ended due to iterations reached\nIterations: ${iterations} times\nTime spent: ${(new Date).getTime() - startTime} ms`);
    return this;
  }
}

示例

测试一个点是否在一个圆内。对于这个例子,神经网络表现良好。然而,对于更复杂的例子,比如手写识别,神经网络的表现真的很差(我能得到的最好的单个神经网络是 70% 的准确率,而网站上说的准确率是 96%,即使使用类似的参数)。

let trainingData = [];
for (let i = 0; i < 1000; ++i) {
    let [x, y] = [Math.random(), Math.random()];
    trainingData.push({input: [x, y], output: [Number(Math.hypot(x,y) < 1)]});
}
let brain = new NeuralNetwork([2, 5, 5, 1]);
brain.train(trainingData.slice(0,700), 0.1, 10, 500); // Accuracy rate 95.33% on the remaining 300 entries in trainingData

【问题讨论】:

  • @desertnaut 感谢您的反馈。我已经从我的问题中删除了一些不需要检查的代码。现在好点了吗?

标签: javascript machine-learning neural-network


【解决方案1】:

好的,我想我会回答我自己的问题。所以,我认为我的代码中没有错误,如果有人愿意的话,使用它是完全可以的(尽管效率非常非常低)。

我对 MNIST 数据的运行没有给出准确答案的原因是我一开始没有处理数据。原始数据给出了 [0, 255] 范围内 28*28 像素的暗度,我直接将其用作每个训练数据的输入。此处正确的过程是将其转换为 [0, 1] 或 [-1, 1] 的范围。

[0, 255] 范围不能正常工作的原因是第二个隐藏层神经元将接收真正的正或负输入。

当反向传播算法计算梯度时,为每个权重计算的变化将非常小,因为它与神经元输入处激活函数的斜率成正比(逻辑函数的导数是 exp(-x )/(1+exp(-x)),对于 x) 的真正正值和负值,它接近于 0。因此,神经网络需要很长时间才能训练,而且在我的情况下,它无法很好地学习数据。

使用正确的方法,我能够在相当短的时间内实现 784*200*10 神经网络的 90% 左右的准确度,尽管它仍然不如作者所说的准确度能够实现在问题中提到的链接中使用更简单的算法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-06-04
    • 1970-01-01
    • 2017-12-17
    • 1970-01-01
    • 1970-01-01
    • 2016-11-29
    • 2016-08-04
    • 1970-01-01
    相关资源
    最近更新 更多