/// <summary>
        /// Configure the pipeline settings.
        /// </summary>
        /// <param name="dataLoader">The data loader.</param>
        private void ConfigurePipeline(ILearningPipelineDataLoader dataLoader)
        {
            var provider = (BatchDataProvider)dataLoader;

            this.SetBatchSize(provider.BatchSize);

            int datasetSize = DetermineDatasetSize(provider.LabelFilename);

            this.SetDatasetSize(datasetSize);
        }
        /// <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));
        }