/// <summary>
        /// Applies the learning rate scheduler if the <see cref="LearningPipeline"/> is configured to use learning rate decay.
        /// </summary>
        /// <param name="neuralNetworkModel">The neural network model.</param>
        private void ApplyLearningRateScheduler(ILearningPipelineNeuralNetworkModel neuralNetworkModel)
        {
            Contracts.ValueNotNull(PipelineSettings.LearningRateScheduler, nameof(PipelineSettings.LearningRateScheduler));

            var nerualNetwork = (INeuralNetwork)neuralNetworkModel;

            nerualNetwork.LearningRate = PipelineSettings.LearningRateScheduler.DecayLearningRate(nerualNetwork.LearningRate);
        }
        /// <summary>
        /// Configures the piepline settings.
        /// </summary>
        /// <param name="neuralNetworkModel">The neural network model.</param>
        private void ConfigurePipeline(ILearningPipelineNeuralNetworkModel neuralNetworkModel)
        {
            var network = (INeuralNetwork)neuralNetworkModel;

            var sizes = new int[network.NumberOfLayers - 1];

            List <NnLayer> layers = network.Layers.ToList();

            for (var i = 1; i < network.NumberOfLayers; i++)
            {
                sizes[i - 1] = layers[i].NumberOfInputs;
            }

            this.SetHiddenLayerSizes(sizes);
        }
        /// <summary>
        /// Runs the learning pipeline and generates a <see cref="PredictionModel"/>.
        /// </summary>
        /// <returns>The prediction model.</returns>
        public PredictionModel Run()
        {
            ILearningPipelineDataLoader         dataLoader         = null;
            ILearningPipelineOptimizer          optimizer          = null;
            ILearningPipelineNeuralNetworkModel neuralNetworkModel = null;

            foreach (ILearningPipelineItem item in this)
            {
                switch (item)
                {
                case ILearningPipelineDataLoader loader:
                    dataLoader = loader;
                    break;

                case ILearningPipelineOptimizer opt:
                    optimizer = opt;
                    break;

                case ILearningPipelineNeuralNetworkModel networkModel:
                    neuralNetworkModel = networkModel;
                    break;
                }
            }

            if (dataLoader == null)
            {
                throw new ArgumentNullException(nameof(dataLoader));
            }

            if (optimizer == null)
            {
                throw new ArgumentNullException(nameof(optimizer));
            }

            if (neuralNetworkModel == null)
            {
                throw new ArgumentNullException(nameof(neuralNetworkModel));
            }

            // Indicate the start of training.
            PipelineSettings.IsPipelingRunning = true;
            PipelineSettings.CurrentIteration  = 0;

            for (var epoch = 0; epoch < PipelineSettings.EpochCount; epoch++)
            {
                PipelineSettings.CurrentEpoch = epoch + 1;

                ConsoleUtility.WriteLine($"Current epoch: {PipelineSettings.CurrentEpoch}");

                if (PipelineSettings.UseLearningRateDecay)
                {
                    ApplyLearningRateScheduler(neuralNetworkModel);
                }

                for (var i = 0; i < PipelineSettings.TrainingIterationsCount; i++)
                {
                    PipelineSettings.CurrentIteration++;

                    if (PipelineSettings.CanPerformDropout)
                    {
                        PipelineSettings.DropoutVectors = PipelineSettings.Dropout.GenerateDropoutVectors(PipelineSettings.HiddenLayerSizes);
                    }

                    // A training iteration is constited of three steeps:

                    // 1. Load data
                    var data = (MnistImageBatch)dataLoader.LoadData();

                    // 2. Feedforward step
                    double[][] prediction = neuralNetworkModel.Predict(data.Pixels);

                    // 3. Backpropagation step
                    optimizer.Optimize(prediction, data.Labels);
                }
            }

            // Indicate the end of training.
            PipelineSettings.IsPipelingRunning = false;

            return(new PredictionModel((INeuralNetwork)neuralNetworkModel));
        }