/// <summary>
        ///   Runs the learning algorithm with the specified input
        ///   training observations and corresponding output labels.
        /// </summary>
        /// 
        /// <param name="observations">The training observations.</param>
        /// <param name="outputs">The observation's labels.</param>
        /// 
        /// <returns>The error in the last iteration.</returns>
        /// 
        public double RunEpoch(T[][] observations, int[] outputs)
        {
            double error = 0;

            if (stochastic)
            {

                // In batch mode, we will use the average of the gradients
                // at each point as a better estimate of the true gradient.
                Array.Clear(gradient, 0, gradient.Length);

                int progress = 0;


#if SERIAL      // For each training point
                for (int i = 0; i < observations.Length; i++)
#else
                Parallel.For(0, observations.Length, i =>
#endif
                {
                    calculator.Inputs = new[] { observations[i] };
                    calculator.Outputs = new[] { outputs[i] };

                    // Compute the estimated gradient
                    double[] estimate = calculator.Gradient();

                    lock (lockObj)
                    {
                        // Accumulate
                        for (int j = 0; j < estimate.Length; j++)
                            gradient[j] += estimate[j];
                        error += calculator.LastError;
                    }

                    int current = Interlocked.Increment(ref progress);
                    double percent = current / (double)observations.Length * 100.0;
                    OnProgressChanged(new ProgressChangedEventArgs((int)percent, i));

                    Accord.Diagnostics.Debug.Assert(!gradient.HasNaN());
                }
#if !SERIAL
);
#endif

                // Compute the average gradient
                for (int i = 0; i < gradient.Length; i++)
                    gradient[i] /= observations.Length;
            }
            else
            {
                calculator.Inputs = observations;
                calculator.Outputs = outputs;

                // Compute the true gradient
                gradient = calculator.Gradient();

                error = calculator.LastError;
            }

            double[] parameters = Model.Function.Weights;

            // Do the Resilient Backpropagation parameter update
            for (int k = 0; k < calculator.Parameters.Length; k++)
            {
                if (Double.IsInfinity(parameters[k])) continue;

                double S = previousGradient[k] * gradient[k];

                if (S > 0.0)
                {
                    weightsUpdates[k] = Math.Min(weightsUpdates[k] * etaPlus, deltaMax);
                    parameters[k] -= Math.Sign(gradient[k]) * weightsUpdates[k];
                    previousGradient[k] = gradient[k];
                }
                else if (S < 0.0)
                {
                    weightsUpdates[k] = Math.Max(weightsUpdates[k] * etaMinus, deltaMin);
                    previousGradient[k] = 0.0;
                }
                else
                {
                    parameters[k] -= Math.Sign(gradient[k]) * weightsUpdates[k];
                    previousGradient[k] = gradient[k];
                }
            }

            Accord.Diagnostics.Debug.Assert(!Model.Function.Weights.HasNaN());

            return convergence.NewValue = error;
        }
        /// <summary>
        ///   Runs the learning algorithm with the specified input
        ///   training observations and corresponding output labels.
        /// </summary>
        /// 
        /// <param name="observations">The training observations.</param>
        /// <param name="outputs">The observation's labels.</param>
        /// 
        /// <returns>The error in the last iteration.</returns>
        /// 
        public double RunEpoch(T[][] observations, int[] outputs)
        {
            double error = 0;

            if (stochastic)
            {

                // In batch mode, we will use the average of the gradients
                // at each point as a better estimate of the true gradient.
                Array.Clear(gradient, 0, gradient.Length);

                int progress = 0;

                // For each training point
#if SERIAL
                for (int i = 0; i < observations.Length; i++)
#else
                Parallel.For(0, observations.Length, i =>
#endif
                {
                    calculator.Inputs = new[] { observations[i] };
                    calculator.Outputs = new[] { outputs[i] };

                    // Compute the estimated gradient
                    double[] estimate = calculator.Gradient();

                    lock (lockObj)
                    {
                        // Accumulate
                        for (int j = 0; j < estimate.Length; j++)
                            gradient[j] += estimate[j];
                        error += calculator.LastError;
                    }

                    int current = Interlocked.Increment(ref progress);
                    double percent = current / (double)observations.Length * 100.0;
                    OnProgressChanged(new ProgressChangedEventArgs((int)percent, i));

                    System.Diagnostics.Debug.Assert(!gradient.HasNaN());
                }
#if !SERIAL
);
#endif

                // Compute the average gradient
                for (int i = 0; i < gradient.Length; i++)
                    gradient[i] /= observations.Length;
            }
            else
            {
                calculator.Inputs = observations;
                calculator.Outputs = outputs;

                // Compute the true gradient
                gradient = calculator.Gradient();

                error = calculator.LastError;
            }

            double[] parameters = Model.Function.Weights;
            stepSize = learningRate / (convergence.CurrentIteration + 1);

            // Update the model using a dynamic step size
            for (int i = 0; i < parameters.Length; i++)
            {
                if (Double.IsInfinity(parameters[i])) continue;

                parameters[i] -= stepSize * gradient[i];

                System.Diagnostics.Debug.Assert(!Double.IsNaN(parameters[i]));
                System.Diagnostics.Debug.Assert(!Double.IsPositiveInfinity(parameters[i]));
            }


            return convergence.NewValue = error;
        }
        /// <summary>
        ///   Runs the learning algorithm with the specified input
        ///   training observations and corresponding output labels.
        /// </summary>
        ///
        /// <param name="observations">The training observations.</param>
        /// <param name="outputs">The observation's labels.</param>
        ///
        /// <returns>The error in the last iteration.</returns>
        ///
        public double RunEpoch(T[][] observations, int[] outputs)
        {
            double error = 0;

            if (stochastic)
            {
                // In batch mode, we will use the average of the gradients
                // at each point as a better estimate of the true gradient.
                Array.Clear(gradient, 0, gradient.Length);

                int progress = 0;

                // For each training point
                if (ParallelOptions.MaxDegreeOfParallelism == 1)
                {
                    for (int i = 0; i < observations.Length; i++)
                    {
                        iterate(observations, outputs, i, ref error, ref progress);
                    }
                }
                else
                {
                    Parallel.For(0, observations.Length, ParallelOptions, i =>
                                 iterate(observations, outputs, i, ref error, ref progress));
                }

                // Compute the average gradient
                for (int i = 0; i < gradient.Length; i++)
                {
                    gradient[i] /= observations.Length;
                }
            }
            else
            {
                calculator.Inputs  = observations;
                calculator.Outputs = outputs;

                // Compute the true gradient
                gradient = calculator.Gradient();

                error = calculator.LastError;
            }

            double[] parameters = Model.Function.Weights;
            stepSize = learningRate / (convergence.CurrentIteration + 1);

            // Update the model using a dynamic step size
            for (int i = 0; i < parameters.Length; i++)
            {
                if (Double.IsInfinity(parameters[i]))
                {
                    continue;
                }

                parameters[i] -= stepSize * gradient[i];

                Accord.Diagnostics.Debug.Assert(!Double.IsNaN(parameters[i]));
                Accord.Diagnostics.Debug.Assert(!Double.IsPositiveInfinity(parameters[i]));
            }


            return(convergence.NewValue = error);
        }