Простая нейронная сеть с прямой связью в JavaScript

1

Я новичок в этом сайте, поэтому заранее извиняюсь, если я делаю что-то не так в этом посте.

В настоящее время я тестирую компьютерное обучение, и я изучаю нейронные сети. В настоящее время я использую http://neuralnetworksanddeeplearning.com/. Тем не менее, я не полностью понимаю все, и весь код написан на Python (мне больше нравится JavaScript).

Я создал программу, которая работает для простых данных. Тем не менее, для более сложных данных (распознавание рукописных цифр с данными MNIST) уровень точности не так высок, как на веб-сайте выше сказано, что с помощью нейронной сети из 784 входных нейронов 10-400 скрытых нейронов в скрытый слой (только один скрытый слой и попробовал несколько возможных нейронов) и 10 выходных нейронов с сотнями итераций. Я думаю, что есть ошибка с моим шагом распространения (например, шаг поезда, я включаю в себя другие функции здесь как ссылку), что мешает ему учиться достаточно быстро (BTW, я использую кросс-энтропию в качестве моей стоимости функция). Я был бы очень признателен, если кто-нибудь может помочь мне найти ошибку. Заранее спасибо.

Ниже приведен код. Веса располагаются в массиве массивов массивов (weight[i][j][k] - это вес между j-м нейронами в i-м слое и k-м нейроном в (i + 1) -м слое). Аналогично, 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
  • 1
    @desertnaut Спасибо за ваш отзыв. Я удалил из своего вопроса часть кода, который не нужно проверять. Это лучше сейчас?
Теги:
machine-learning
neural-network

1 ответ

0
Лучший ответ

Хорошо, я думаю, я собираюсь ответить на собственный вопрос. Таким образом, я не думаю, что в моем коде есть ошибка, и он отлично подходит для использования (хотя и действительно, действительно неэффективного), если кто-то хочет.

Причина, по которой мои пробежки по данным MNIST не дают точных ответов, связана с тем, что я сначала не обрабатывал данные. Необработанные данные дали темноту 28 * 28 пикселей в диапазоне [0, 255], которые я использовал непосредственно в качестве входных данных для каждой из данных обучения. Правильная процедура здесь заключалась бы в том, чтобы преобразовать это в диапазон [0, 1] или [-1, 1].

Причина, по которой диапазон [0, 255] не работает, объясняется тем, что второй скрытый слой нейронов будет получать действительно положительные или отрицательные входы.

Когда алгоритм backpropagation вычисляет градиент, изменение, рассчитанное для каждого веса, будет действительно маленьким, поскольку оно пропорционально наклону функции активации на входе в нейрон (производная логистической функции равна exp (-x)/(1 + exp (-x)), который близок к 0 для действительно положительных и отрицательных значений x). Таким образом, нейронная сеть будет очень долго тренироваться и, в моем случае, не сможет хорошо изучить данные.

С помощью правильного метода я могу достичь около 90% -ной точности для нейронной сети 784 * 200 * 10 за довольно короткое время, хотя она по-прежнему не так точна, как то, что автор говорит, что он может достичь с помощью четного более простой алгоритм в ссылке, упомянутой в вопросе.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню