Нейронная сеть дает странные результаты с разными настройками - почему? - PullRequest
0 голосов
/ 04 августа 2020

Я пытаюсь построить базовую c нейронную сеть с прямой связью с нуля, чтобы понять ее более глубоко.

Следуя моему другому вопросу , у него проблема с обучением партии. Что происходит, когда я передаю все входные данные, веса корректируются правильно, так как я вижу, что прогнозируемые значения становятся все ближе и ближе к ожидаемым значениям. Но тест заканчивается с точностью около 10%. Если я обучаю их пакетами, независимо от количества итераций, я вижу, что предсказанные значения в значительной степени совпадают.

Я проверил алгоритм обратного распространения ошибки, если все в порядке, но не могу найти проблем. Пробовал много вариантов слоев, нейронов внутри них, разную скорость обучения, разные размеры пакетов, нормализации, но ничего не работает. Однажды я запустил его на 10000 итераций, в течение 3 часов (я подумал, может быть, у него не хватит времени на обучение), но те же ужасные результаты. Провел исследование, и многие люди столкнулись с той же проблемой, и попробовали кучу решений для них, ничего не помогло. Я не даю слишком подробную информацию о проблеме, но, по крайней мере, вы можете увидеть, как я «заставил это работать» и какие значения это дает по времени:

const standardizeFeatures = (features, mean, variance) => {
  const standardizedFeatures = features.sub(mean).div(variance.pow(0.5));

  tf.dispose(features);

  return standardizedFeatures;
}

const NN = ({
  trainFeatures = [],
  trainLabels = [],
  layers = [],
  learningRate = 0.1,
  batchSize,
  standardize = false
}) => {
  const { mean, variance } = tf.tidy(() => {
    const { mean, variance } = tf.moments(trainFeatures, 0);
    const filler = variance
      .cast('bool')
      .logicalNot()
      .cast('float32');

    return { mean, variance: variance.add(filler) };
  });

  learningRate = tf.tensor(learningRate);

  trainFeatures = standardize
    ? standardizeFeatures(trainFeatures, mean, variance)
    : trainFeatures;

  let weightsOfLayers = (() => {
    const numberOfNeuronsOfOutput = trainLabels.shape[1];
    const numberOfNeuronsOfLayers = [...layers, numberOfNeuronsOfOutput];

    return numberOfNeuronsOfLayers.map(
      (numberOfNeuronsOfCurrentLayer, i) => {
        const numberOfNeuronsOfPrevLayer = i === 0
          ? trainFeatures.shape[1]
          : numberOfNeuronsOfLayers[i - 1];
          
        const currentLayerWeights = tf.stack(
          [...Array(numberOfNeuronsOfCurrentLayer)].map(() => {
            const bias = 1;
            const neuronWeights = tf.truncatedNormal([bias + numberOfNeuronsOfPrevLayer]);
  
            return neuronWeights;
          }), 1
        );
  
        return currentLayerWeights;
      }
    );
  })();

  const getActivationsOfLayers = featureSet => tf.tidy(() => {
    const layers = [featureSet];

    weightsOfLayers.forEach(currentLayerWeights => {
      const prevLayer = tf
        .ones([layers[layers.length - 1].shape[0], 1])
        .concat(layers[layers.length - 1], 1);
      const currentLayer = prevLayer.matMul(currentLayerWeights);

      layers.push(currentLayer.sigmoid());
    });
    
    return layers;
  });

  const getCost = (predictionLabelSet, labelSet) => (
    predictionLabelSet
      .sub(labelSet)
      .pow(2)
      .sum()
      .div(labelSet.shape[0])
  );

  const updateLearningRate = (prevCost, cost) => tf.tidy(() => {
    let newLearningRate = null;

    if (prevCost) {
      const modifier = tf.stack([cost, prevCost]).argMax().add(1.1).div(2);

      newLearningRate = learningRate.mul(modifier);
      
      tf.dispose(learningRate);
    } else {
      newLearningRate = learningRate;
    }
    return newLearningRate
  })

  const gradientDescent = (activationsOfLayers, labelSet) => tf.tidy(() => {
    activationsOfLayers = activationsOfLayers.reverse();

    let dCda = null;

    const updatedWeightsOfLayers = weightsOfLayers.reverse().map((weights, i) => {
      const prevActivations = activationsOfLayers[i - 1];
      const activations = activationsOfLayers[i];
      const nextActivations = activationsOfLayers[i + 1];

      const prevWeights = weightsOfLayers[i - 1];

      if (dCda) {
        const dadn = prevActivations
          .sub(1)
          .mul(prevActivations)
          .mul(-1);

        dCda = prevWeights
          .slice([1, 0], [-1, -1])
          .matMul(dCda.mul(dadn.transpose()));
      } else {
        dCda = activations
          .sub(labelSet)
          .mul(2)
          .transpose();
      }

      const dndw = tf
        .ones([nextActivations.shape[0], 1])
        .concat(nextActivations, 1)
        .transpose();

      const dadn = activations
        .sub(1)
        .mul(activations)
        .mul(-1);


      const dCdw = dndw
        .matMul(
          dCda.transpose().mul(dadn)
        );
      
      const updatedWeights = weights.sub(dCdw.mul(learningRate));
      return updatedWeights;
    }).reverse();
    return updatedWeightsOfLayers;
  });

  const train = async (iterations = 100) => {
    let prevCost = null;

    const batchQuantity = Math.floor(trainFeatures.shape[0] / batchSize);

    for (let i = 0; i < iterations; i++) {
      for (let j = 0; j < batchQuantity; j++) {
        const startRowIndex = j * batchSize;
        const endRowIndex = batchSize;

        const featureSet = trainFeatures.slice(
          [startRowIndex, 0],
          [endRowIndex, -1]
        );
        const labelSet = trainLabels.slice(
          [startRowIndex, 0],
          [endRowIndex, -1]
        );
        const activationsOfLayers = getActivationsOfLayers(featureSet);

        const predictionLabels = activationsOfLayers[activationsOfLayers.length - 1];

        const cost = getCost(predictionLabels, labelSet);
        
        console.log('Expected:')
        labelSet.print()
        
        console.log('Prediction:')
        predictionLabels.print()

        weightsOfLayers = gradientDescent(activationsOfLayers, labelSet);

        console.log('Cost:')
        cost.print();
        learningRate = updateLearningRate(prevCost, cost);
        prevCost = cost;

        tf.dispose([featureSet, labelSet, activationsOfLayers]);
      }
    }
  };

  const predict = featureSet => {
    featureSet = standardize
      ? standardizeFeatures(featureSet, mean, variance)
      : featureSet;
    const activationsOfLayers = getActivationsOfLayers(featureSet);
    const predictionLabels = activationsOfLayers[activationsOfLayers.length - 1].softmax();

    return predictionLabels;
  }

  const test = ({ testFeatures, testLabels }) => tf.tidy(() => {
    const numberOfObservations = tf.tensor(testLabels.shape[0]);

    testLabels = testLabels.argMax(1);

    const predictionLabels = predict(testFeatures).argMax(1);
    const incorrect = predictionLabels
      .notEqual(testLabels)
      .sum();

    return numberOfObservations.sub(incorrect).div(numberOfObservations);
  });

  return {
    weightsOfLayers,
    train,
    test
  }
}

const train = document.querySelector('#train');
const iterationsInput = document.querySelector('#iterations');
const batchInput = document.querySelector('#batch');
const trainSizeInput = document.querySelector('#trainSize');
const testSizeInput = document.querySelector('#testSize');
const learningRateInput = document.querySelector('#learningRate');
const layersInput = document.querySelector('#layers');
const standardizeInput = document.querySelector('#standardize');
const error = document.querySelector('#error')

let model = null;

train.onclick = () => {
    const iterations = Number(iterationsInput.value);
  const batchSize = Number(batchInput.value);
  const trainSize = Number(trainSizeInput.value);
  const testSize = Number(testSizeInput.value);
  const learningRate = Number(learningRateInput.value);
  const layers = layersInput.value.split(',').map(v => Number(v));
  const standardize = standardizeInput.checked;

    if (
    iterations &&
    batchSize &&
    trainSize &&
    testSize &&
    learningRate &&
    layers.every(v => v)
  ) {
    error.innerHTML = '';
    const { trainFeatures, trainLabels, testFeatures, testLabels } = (() => {
      const { training, test } = mnist.set(trainSize, testSize);

      const trainFeatures = tf.stack(training.map(({ input }) => tf.tensor(input)));
      const trainLabels = tf.stack(training.map(({ output }) => tf.tensor(output)));

      const testFeatures = tf.stack(test.map(({ input }) => tf.tensor(input)));
      const testLabels = tf.stack(test.map(({ output }) => tf.tensor(output)));
      
      return { trainFeatures, trainLabels, testFeatures, testLabels };
    })();
    
    model = NN({
      batchSize,
      learningRate,
      trainFeatures,
      trainLabels,
      layers,
    });
    
    model.train(iterations);
    console.log('Accuracy is:')
    model.test({ testFeatures, testLabels }).print();
  } else {
    error.innerHTML = 'One of the properties are incorrect'
  }
}
body {
  margin: 0;
  height: 100vh;
  width: 100vw;
}

.inputs {
  display: flex;
  flex-direction: column;
  padding: 10px
}
.buttons {
  display: flex;
  padding: 10px;
}

.as-console-wrapper {
  display: none !important;
}
<body>
  <div class='inputs'>
    <label>Iterations:</label>
    <input type='number' id='iterations' value='100'/>
    <label>Batch size:</label>
    <input type='number' id='batch' value='100'/>
    <label>Train observations:</label>
    <input type='number' id='trainSize' value='10000'/>
    <label>Test observations:</label>
    <input type='number' id='testSize' value='2000'/>
    <label>Learning rate:</label>
    <input type='number' id='learningRate' value='0.1'/>
    <label>Hidden layers (number of neurons, layers separated with comma):</label>
    <input type='text' id='layers' value='10, 10'/>
    <label>Standardize:</label>
    <input type='checkbox' id='standardize'/>
    <span id='error'></span>
    <div class='buttons'>
      <button id='train'>
        Start (results will be shown in the console)
      </button>
    </div>
  </div>
  
  <script src='https://cdnjs.cloudflare.com/ajax/libs/tensorflow/2.0.1/tf.min.js' async></script>
  <script src='https://cdnjs.cloudflare.com/ajax/libs/mnist/1.1.0/mnist.js' async></script>
</body>
...