/// <summary>
        /// Stochastic gradient descent algorithm with lazy data loading.
        /// </summary>
        public static void SgdLazy(DeepNeuralNetwork nn, int trainingDataSetSize, GetNextDataDelegate getTrainingData,
                                   int batchSize, int epochs, double learningRate, UpdateTrainingStatusDelegate statusUpdate,
                                   int testDataSetSize = 0, GetNextDataDelegate getTestingData = null, CheckIfCorrectDelegate checkCorrect = null)
        {
            Random   rng          = new Random();
            DateTime startingTime = DateTime.Now;

            int[] dataSetIndexes = new int[trainingDataSetSize];
            for (int i = 0; i < dataSetIndexes.Length; ++i)
            {
                dataSetIndexes[i] = i;
            }
            int currentBatchOffset;
            int currentBatchSize;

            for (int epoch = 1; epoch <= epochs; ++epoch)
            {
                dataSetIndexes.Shuffle(rng);
                currentBatchOffset = 0;
                int currentBatch = 0;
                for (; currentBatchOffset < trainingDataSetSize; currentBatchOffset += batchSize, ++currentBatch)
                {
                    int remainingDataSize = trainingDataSetSize - currentBatchOffset;
                    currentBatchSize = remainingDataSize < batchSize ? remainingDataSize : batchSize;
                    TrainWithBatchLazy(nn, currentBatchSize, getData, learningRate);
                }
                TrainingStatus status = new TrainingStatus
                {
                    EpochsDone = epoch
                };
                if (testDataSetSize != 0 && getTestingData != null && checkCorrect != null)
                {
                    double errorRate = TestNetwork(nn, testDataSetSize, getTestingData, out int correctCount, checkCorrect);
                    status.Error   = errorRate;
                    status.Correct = correctCount;
                }

                //status update
                TimeSpan elapsed   = DateTime.Now - startingTime;
                TimeSpan remaining = TimeSpan.FromTicks((long)(elapsed.Ticks * ((epochs - epoch) / (double)epoch)));
                status.ElapsedTime = elapsed;
                status.TimeLeft    = remaining;
                statusUpdate(status);
            }

            LabeledData getData(int indexInBatch)
            {
                int actualIndex = dataSetIndexes[currentBatchOffset + indexInBatch];

                return(getTrainingData(actualIndex));
            }
        }
 public LazyBatchEnumerator(GetNextDataDelegate getNext, int size)
 {
     this.getNext = getNext;
     this.size    = size;
     currentIndex = 0;
 }
 public LazyBatch(GetNextDataDelegate getNext, int size)
 {
     this.getNext = getNext;
     this.size    = size;
 }
        /// <summary>
        /// Tests the network over a given test dataset. Returns the error ( sum(|a - y(x)|^2)/n ). The out param will count the data that was correctly categorized using a given function.
        /// </summary>
        private static double TestNetwork(DeepNeuralNetwork nn, int testingDataSetSize, GetNextDataDelegate getNextData, out int correctCount, CheckIfCorrectDelegate checkCorrect)
        {
            correctCount = 0;
            Vector <double> error = new DenseVector(nn.OutputLayer.GetNeuronCount());

            for (int i = 0; i < testingDataSetSize; ++i)
            {
                LabeledData     labeledData = getNextData(i);
                Vector <double> result      = nn.ProcessInput(labeledData.InputValues);
                if (checkCorrect(result.AsArray(), labeledData.OutputValues.AsArray()))
                {
                    ++correctCount;
                }
                Vector <double> diff = labeledData.OutputValues - result;
                error += diff.PointwiseMultiply(diff);
            }
            error = error.Divide(testingDataSetSize);
            return(error.Average());
        }
        /// <summary>
        /// Train the network using the given training set. The starting weights and biasses are the ones present in the network when the call to this function is made.
        /// This "lazy" version means that each training value pair will be read (using the function given as a parameter) only when it has to be processed.
        /// Recomended when the size of one training data is very large and loading the whole batch would need too much memory.
        /// Obs.: The lazy loading of the data should be ensured by the function given.
        /// </summary>
        public static void TrainWithBatchLazy(DeepNeuralNetwork nn, int batchSize, GetNextDataDelegate getNextData, double learningRate)
        {
            LazyBatch batch = new LazyBatch(getNextData, batchSize);

            TrainWithBatch(nn, batch, batchSize, learningRate);
        }