rewrite this test example to match my class implementation 
   private async Task SpiralDataset()
   {
       var (inputs, targets) = GenerateSpiralDataset(1000);
       int totalSamples = inputs.Shape[0];
       int trainSamples = (int)(totalSamples * 0.8);
       int validationSamples = totalSamples - trainSamples;

       var logger = CreateLogger();

       // instead of creating factory file temporery or using factory/////////////////////
       var networkLogger = logger.CreateLogger<NeuralNetwork<float>>();
       var layerLogger = logger.CreateLogger<DenseLayer<float>>();
       var trainerLogger = logger.CreateLogger<Trainer<float>>();
       var evalLogger = logger.CreateLogger<Evaluator<float>>();
       //var optimizerLogger = logger.CreateLogger<SGDOptimizer<float>>();
       //var weightInitializerLogger = logger.CreateLogger<HeInitializer<float>>();
       //var biasInitializerLogger = logger.CreateLogger<ZeroInitializer<float>>();
       //////////////////////////////////////////////////////////////////


       // Define activation functions
       var reluActivation = ActivationFunctionFactory<float>.Create(ActivationFunctionType.Relu, logger);
       var softActivation = ActivationFunctionFactory<float>.Create(ActivationFunctionType.Softmax, logger);

       // Define initializers
       var weightInitializer = InitializerFactory<float>.Create(InitializerType.He, 0, 0, logger);// new HeInitializer<float>(weightInitializerLogger);
       var biasInitializer = InitializerFactory<float>.Create(InitializerType.Zero, 0, 0, logger);//new ZeroInitializer<float>(biasInitializerLogger);

       // Training data
       var trainingData = inputs.Slice(0, trainSamples);
       var trainingLabels = targets.Slice(0, trainSamples);

       // Validation data
       var validationData = inputs.Slice(trainSamples, validationSamples);
       var validationLabels = targets.Slice(trainSamples, validationSamples);



       StringBuilder result = new StringBuilder();

       var neuralNetwork = new NeuralNetwork<float>(
           logger: networkLogger,
           visualizer: new SimpleVisualizer<float>()
       );

       // Add layers to the network
       neuralNetwork.AddLayer(new DenseLayer<float>(
           inputSize: 2,
           outputSize: 64,
           activation: reluActivation,
           weightInitializer: weightInitializer,
           biasInitializer: biasInitializer,
           name: "HiddenLayer1",
           logger: layerLogger
       ));

       neuralNetwork.AddLayer(new DenseLayer<float>(
           inputSize: 64,
           outputSize: 64,
           activation: reluActivation,
           weightInitializer: weightInitializer,
           biasInitializer: biasInitializer,
           name: "HiddenLayer2",
           logger: layerLogger
       ));

       neuralNetwork.AddLayer(new DenseLayer<float>(
           inputSize: 64,
           outputSize: 3, // Assuming 3 classes in the spiral dataset
           activation: softActivation,
           weightInitializer: weightInitializer,
           biasInitializer: biasInitializer,
           name: "OutputLayer",
           logger: layerLogger
       ));



       var optimizer = OptimizerFactory<float>.CreateOptimizer(OptimizerType.SGD, 0.01f, logger);// new SGDOptimizer<float>(learningRate: 0.01f, optimizerLogger);
       var lossFunction = LossFunctionFactory<float>.CreateLossFunction(LossFunctionType.CrossEntropyLoss, logger); //new CrossEntropyLoss<float>(losslog);
       //  var logger = neuralNetwork.Logger; // Use the same logger

       // Initialize the network
       neuralNetwork.InitializeWeights();


       var batchSize = 32;
       var epochs = 100;

       // Calculate total batches
       int totalBatches = (trainSamples + batchSize - 1) / batchSize;

       //var t = TrainerF
       var trainer = new Trainer<float>(
           network: neuralNetwork,
           lossFunction: lossFunction,
           optimizer: optimizer,
           logger: trainerLogger,
           epochs: epochs,
           batchSize: batchSize//,
                               //  regularization: regularization,
                               //  learningRateScheduler: learningRateScheduler
       );

       trainer.GradientClippingThreshold = 1.0f; // Or another appropriate value

       // Configure early stopping and checkpointing
       trainer.UseEarlyStopping = true;
       trainer.EarlyStoppingPatience = 10;
       trainer.EnableCheckpointing = true;
       trainer.CheckpointDirectory = "checkpoints";
       trainer.CheckpointInterval = 1;

       // Subscribe to events
       // Set maximum to the total number of epochs and batches
       prgEpochs.Maximum = epochs;          // Total number of epochs
       prgBatchs.Maximum = totalBatches;    // Total number of batches per epoch

       // Update progress bars inside event handlers
       trainer.BatchCompleted += (epoch, batch, loss) =>
       {
           this.Invoke((MethodInvoker)(() =>
           {
               prgBatchs.Value = batch;
               // Optionally update other UI elements
           }));
       };

       trainer.EpochCompleted += (epoch, totalEpochs, validationLoss) =>
       {
           this.Invoke((MethodInvoker)(() =>
           {
               prgEpochs.Value = epoch;
               txtLog.AppendText($"Epoch {epoch}/{totalEpochs}: Validation Loss = {validationLoss}{Environment.NewLine}");
               prgBatchs.Value = 0; // Reset batch progress bar for the next epoch
               txtResult.AppendText($"{trainer.Metrics.GetSummary()}{Environment.NewLine}");
           }));
       };

       // Before training
       CheckData(trainingData, "Training Data");
       CheckData(trainingLabels, "Training Labels");
       CheckData(validationData, "Validation Data");
       CheckData(validationLabels, "Validation Labels");


       // Start training
       await trainer.TrainAsync(
           trainingData: trainingData,
           trainingLabels: trainingLabels,
           validationData: validationData,
           validationLabels: validationLabels,
           cancellationToken: CancellationToken.None
       );

       // Evaluate the model on validation data
       var evaluator = new Evaluator<float>(neuralNetwork, batchSize, evalLogger);
       var finalValidationLoss = evaluator.Evaluate(validationData, validationLabels, lossFunction);

       // Optionally, compute accuracy
       int correct = 0;
       var predictions = neuralNetwork.Forward(validationData);

       for (int i = 0; i < validationData.Shape[0]; i++)
       {
           // Extract the ith prediction vector
           var predictedVector = predictions.Slice(i, 1).Reshape(predictions.Shape[1]);

           // Use ArgMax on the predicted vector
           var predictedClass = ArgMax(predictedVector);

           // Extract the true class from one-hot encoded targets
           var targetVector = validationLabels.Slice(i, 1).Reshape(validationLabels.Shape[1]);
           var trueClass = ArgMax(targetVector);

           if (predictedClass == trueClass)
           {
               correct++;
           }
       }

       float accuracy = (float)correct / validationData.Shape[0];
       txtResult.AppendText($"Validation Accuracy: {accuracy * 100:F2}%{Environment.NewLine}");

       //int correct = 0;
       //var predictions = neuralNetwork.Forward(validationData);
       //for (int i = 0; i < predictions.Shape[0]; i++)
       //{
       //    float predictedValue = predictions[i, 0];
       //    int predictedClass = predictedValue >= 0.5f ? 1 : 0;
       //    result.AppendLine($"(validationData[{i}, 0],    validationData[{i}, 1]): Input: {validationData[i, 0]}, {validationData[i, 1]} - Predicted: {predictedClass}, Predicted Value: {predictedValue}, Probability: {predictedValue:F4}");
       //}

       //for (int i = 0; i < validationData.Shape[0]; i++)
       //{
       //    // Extract the ith prediction vector (1D tensor of class probabilities)
       //    var predictedVector = predictions.Slice(i, 1).Squeeze(axis: 0);

       //    // Use ArgMax on the predicted vector
       //    var predictedClass = ArgMax(predictedVector);

       //    // Extract the true class from one-hot encoded targets
       //    var targetVector = validationLabels.Slice(i, 1).Squeeze(axis: 0);
       //    var trueClass = ArgMax(targetVector);

       //    if (predictedClass == trueClass)
       //    {
       //        correct++;
       //    }
       //}



       //float accuracy = (float)correct / validationData.Shape[0];
       //txtResult.AppendText(result.ToString());
       //txtResult.AppendText($"Validation Accuracy: {accuracy * 100:F2}%{Environment.NewLine}");

       string logs = GetLogs();
       txtlogs.AppendText(logs);
   }


   static (Tensor<float> Inputs, Tensor<float> Targets) GenerateSpiralDataset(int samplesPerClass)
   {
       int numClasses = 3; // Changed from 2 to 3
       int dimensions = 2;
       int totalSamples = samplesPerClass * numClasses;

       var inputs = new Tensor<float>(totalSamples, dimensions);
       var targets = new Tensor<float>(totalSamples, numClasses);

       Random rand = new Random();

       for (int classNumber = 0; classNumber < numClasses; classNumber++)
       {
           for (int i = 0; i < samplesPerClass; i++)
           {
               float r = (float)i / samplesPerClass;
               float theta = (float)(classNumber * Math.PI + r * 4 * Math.PI + rand.NextDouble() * 0.2);

               float x = r * (float)Math.Sin(theta);
               float y = r * (float)Math.Cos(theta);

               int index = classNumber * samplesPerClass + i;
               inputs[index, 0] = x;
               inputs[index, 1] = y;

               targets[index, classNumber] = 1.0f; // One-hot encoding
           }
       }

       return (inputs, targets);
   }

   void CheckData(Tensor<float> data, string dataName)
   {
       for (int i = 0; i < data.TotalSize; i++)
       {
           if (float.IsNaN(data.Data[i]) || float.IsInfinity(data.Data[i]))
           {
               throw new InvalidOperationException($"{dataName} contains NaN or Infinity at index {i}");
           }
       }
   }

   static int ArgMax(Tensor<float> tensor)
   {
       int argMax = 0;
       float max = tensor[0];
       for (int i = 1; i < tensor.TotalSize; i++)
       {
           if (tensor[i] > max)
           {
               max = tensor[i];
               argMax = i;
           }
       }
       return argMax;
   }
my implemntation:
public class NeuralNetworkFactory<T> where T : INumber<T>, IExponentialFunctions<T>, ITrigonometricFunctions<T>, ILogarithmicFunctions<T>, IFloatingPointIeee754<T>, IRootFunctions<T> { private readonly List<LayerParameters<T>> layerParametersList = new(); private readonly ILoggerFactory? loggerFactory; private readonly IVisualizer<T> visualizer; public NeuralNetworkFactory(IVisualizer<T> visualizer, ILoggerFactory? loggerFactory = null) { this.loggerFactory = loggerFactory; this.visualizer = visualizer ?? throw new ArgumentNullException(nameof(visualizer)); } public NeuralNetworkFactory<T> AddDenseLayer( int inputSize, int outputSize, ActivationFunctionType activationFunction, T activationAlpha = default, InitializerType weightInitializer = InitializerType.Xavier, T weightInitializerMin = default, T weightInitializerMax = default, InitializerType biasInitializer = InitializerType.Zero, T biasInitializerMin = default, T biasInitializerMax = default, string? name = null) { var parameters = new DenseLayerParameters<T> { InputSize = inputSize, OutputSize = outputSize, ActivationFunction = activationFunction, ActivationAlpha = activationAlpha, WeightInitializer = weightInitializer, WeightInitializerMin = weightInitializerMin, WeightInitializerMax = weightInitializerMax, BiasInitializer = biasInitializer, BiasInitializerMin = biasInitializerMin, BiasInitializerMax = biasInitializerMax, Name = name ?? $"DenseLayer_{Guid.NewGuid()}" }; layerParametersList.Add(parameters); return this; } public NeuralNetwork<T> Build() { var l = loggerFactory?.CreateLogger<NeuralNetwork<T>>(); var neuralNetwork = new NeuralNetwork<T>(visualizer, l); foreach (var layerParams in layerParametersList) { var layer = LayerFactory<T>.CreateLayer(layerParams, loggerFactory); neuralNetwork.AddLayer(layer); } return neuralNetwork; } } public static class LayerFactory<T> where T : INumber<T>, IExponentialFunctions<T>, ITrigonometricFunctions<T>, ILogarithmicFunctions<T>, IFloatingPointIeee754<T>, IRootFunctions<T> { public static ILayer<T> CreateLayer(LayerParameters<T> parameters, ILoggerFactory? loggerFactory = null) { return parameters.LayerType switch { LayerType.Dense => CreateDenseLayer(parameters as DenseLayerParameters<T>, loggerFactory), _ => throw new NotSupportedException($"Layer type {parameters.LayerType} is not supported.") }; } private static ILayer<T> CreateDenseLayer(DenseLayerParameters<T> parameters, ILoggerFactory? loggerFactory = null) { if (parameters == null) throw new ArgumentNullException(nameof(parameters)); var activationFunction = ActivationFunctionFactory<T>.Create( parameters.ActivationFunction, null, parameters.ActivationAlpha); var weightInitializer = InitializerFactory<T>.Create( parameters.WeightInitializer, parameters.WeightInitializerMin, parameters.WeightInitializerMax); var biasInitializer = InitializerFactory<T>.Create( parameters.BiasInitializer, parameters.BiasInitializerMin, parameters.BiasInitializerMax); return new DenseLayer<T>( inputSize: parameters.InputSize, outputSize: parameters.OutputSize, activation: activationFunction, weightInitializer: weightInitializer, biasInitializer: biasInitializer, name: parameters.Name, logger: loggerFactory?.CreateLogger<DenseLayer<T>>() ); } public static ILayer<T> CreateLayerFromData(LayerData<T> layerData, ILoggerFactory? loggerFactory = null) { if (layerData == null) throw new ArgumentNullException(nameof(layerData)); if (layerData is DenseLayerData<T> denseData) { var activationFunctionType = Type.GetType(denseData.ActivationFunctionType.GetType().ToString()); if (activationFunctionType == null) throw new InvalidOperationException($"Could not find type '{denseData.ActivationFunctionType}' for ActivationFunction."); var activationFunction = ActivationFunctionFactory<T>.Create( denseData.ActivationFunctionType, null, denseData.ActivationAlpha); var layer = new DenseLayer<T>( inputSize: denseData.NumInputs, outputSize: denseData.NumOutputs, activation: activationFunction, weightInitializer: new ZeroInitializer<T>(), biasInitializer: new ZeroInitializer<T>(), name: denseData.Name, logger: loggerFactory?.CreateLogger<DenseLayer<T>>() ); layer.LoadLayerData(denseData); return layer; } else { throw new NotSupportedException($"Layer data of type {layerData.GetType()} is not supported."); } } } public class Trainer<T> where T : INumber<T>, IExponentialFunctions<T>, ITrigonometricFunctions<T>, ILogarithmicFunctions<T>, IFloatingPointIeee754<T>, IRootFunctions<T>, IMinMaxValue<T> { public Evaluator<T> Evaluator { get; private set; } public TrainingMetrics<T> Metrics { get; private set; } = new TrainingMetrics<T>(); public IRegularization<T>? Regularization { get; set; } public ILearningRateScheduler<T>? LearningRateScheduler { get; set; } public NeuralNetwork<T> Network { get; private set; } public ILossFunction<T> LossFunction { get; private set; } public IOptimizer<T> Optimizer { get; private set; } public ILogger Logger { get; private set; } public int Epochs { get; set; } public int BatchSize { get; set; } public T GradientClippingThreshold { get; set; } = T.CreateChecked(1.0); public bool UseEarlyStopping { get; set; } = false; public int EarlyStoppingPatience { get; set; } = 5; public T BestValidationLoss { get; private set; } public int NoImprovementEpochs { get; private set; } public bool EnableCheckpointing { get; set; } = false; public string CheckpointDirectory { get; set; } = "checkpoints"; public int CheckpointInterval { get; set; } = 1; public event Action<int, int, T>? EpochCompleted; public event Action<int, int, T>? BatchCompleted; private static readonly Random _rand = new Random(); public Trainer( NeuralNetwork<T> network, ILossFunction<T> lossFunction, IOptimizer<T> optimizer, ILogger<Trainer<T>>? logger = null, int epochs = 100, int batchSize = 32, IRegularization<T>? regularization = null, ILearningRateScheduler<T>? learningRateScheduler = null) { Network = network; LossFunction = lossFunction; Optimizer = optimizer; Logger = logger ?? NullLogger<Trainer<T>>.Instance; Epochs = epochs; BatchSize = batchSize; BestValidationLoss = T.MaxValue; Regularization = regularization; LearningRateScheduler = learningRateScheduler; Evaluator = new Evaluator<T>(Network, BatchSize); } public async ValueTask TrainAsync( Tensor<T> trainingData, Tensor<T> trainingLabels, Tensor<T>? validationData = null, Tensor<T>? validationLabels = null, CancellationToken cancellationToken = default) { try { using (Logger.BeginScope("Trainer")) { int numSamples = trainingData.Shape[0]; int totalBatches = (numSamples + BatchSize - 1) / BatchSize; Network.InitializeWeights(); for (int epoch = 0; epoch < Epochs; epoch++) { using (Logger.BeginScope("Epoch: {Epoch}", epoch + 1)) { if (cancellationToken.IsCancellationRequested) { Logger.LogWarning("Training cancelled."); break; } if (LearningRateScheduler != null) { var newLearningRate = LearningRateScheduler.GetLearningRate(epoch); Optimizer.LearningRate = newLearningRate; Logger.LogInformation($"Adjusted learning rate to {newLearningRate} for epoch {epoch + 1}"); } var epochStartTime = DateTime.UtcNow; ShuffleData(ref trainingData, ref trainingLabels); var epochLoss = T.Zero; var batchIndices = Enumerable.Range(0, totalBatches).ToArray(); var batchDurations = new double[totalBatches]; var batchLosses = new T[totalBatches]; var executionOptions = new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = totalBatches >= Tensor<T>.ParallelizationThreshold ? Environment.ProcessorCount : 1, CancellationToken = cancellationToken }; var actionBlock = new ActionBlock<int>(async batchIndex => { using (Logger.BeginScope("Batch: {Batch}", batchIndex + 1)) { var batchStartTime = DateTime.UtcNow; if (cancellationToken.IsCancellationRequested) { Logger.LogWarning("Training cancelled during batch processing."); cancellationToken.ThrowIfCancellationRequested(); } int batchStart = batchIndex * BatchSize; int currentBatchSize = Math.Min(BatchSize, numSamples - batchStart); var batchData = trainingData.Slice(batchStart, currentBatchSize); var batchLabels = trainingLabels.Slice(batchStart, currentBatchSize); var predictions = Network.Forward(batchData); T loss = LossFunction.ComputeLoss(predictions, batchLabels); if (Regularization != null) { foreach (var layer in Network.Layers) { foreach (var param in layer.GetParameters()) { loss = loss + Regularization.ComputePenalty(param); } } } var gradients = LossFunction.ComputeGradient(predictions, batchLabels); Backward(gradients); if (Regularization != null) { foreach (var layer in Network.Layers) { var parameters = layer.GetParameters().ToList(); var gradientsList = layer.GetGradients().ToList(); for (int i = 0; i < parameters.Count; i++) { var regGradient = Regularization.ComputeGradient(parameters[i]); gradientsList[i].AddInPlace(regGradient); } } } ClipGradients(); UpdateWeights(); ClearGradients(); lock (batchLosses) { batchLosses[batchIndex] = loss; } Metrics.AddBatchError(loss); var batchEndTime = DateTime.UtcNow; batchDurations[batchIndex] = (batchEndTime - batchStartTime).TotalSeconds; Logger.LogDebug($"Epoch {epoch + 1}, Batch {batchIndex + 1}/{totalBatches} completed in {batchDurations[batchIndex]:F2}s. Loss: {loss}"); BatchCompleted?.Invoke(epoch + 1, batchIndex + 1, loss); predictions.Dispose(); gradients.Dispose(); batchData.Dispose(); batchLabels.Dispose(); } }, executionOptions); foreach (var batchIndex in batchIndices) { await actionBlock.SendAsync(batchIndex, cancellationToken); } actionBlock.Complete(); await actionBlock.Completion; epochLoss = batchLosses.Aggregate(T.Zero, (acc, val) => acc + val) / T.CreateChecked(totalBatches); T validationLoss = T.Zero; if (validationData != null && validationLabels != null) { using (Logger.BeginScope("Evaluate}")) { validationLoss = Evaluate(validationData, validationLabels); Logger.LogInformation($"Epoch {epoch + 1}/{Epochs}, Training Loss: {epochLoss}, Validation Loss: {validationLoss}"); if (UseEarlyStopping) { if (validationLoss.CompareTo(BestValidationLoss) < 0) { BestValidationLoss = validationLoss; NoImprovementEpochs = 0; if (EnableCheckpointing) { await SaveCheckpointAsync(epoch + 1, validationLoss); } } else { NoImprovementEpochs++; if (NoImprovementEpochs >= EarlyStoppingPatience) { Logger.LogWarning("Early stopping triggered."); break; } } } } } else { Logger.LogInformation($"Epoch {epoch + 1}/{Epochs}, Training Loss: {epochLoss}"); } if (EnableCheckpointing && (epoch + 1) % CheckpointInterval == 0) { await SaveCheckpointAsync(epoch + 1, validationLoss); } EpochCompleted?.Invoke(epoch + 1, Epochs, validationLoss); var epochEndTime = DateTime.UtcNow; var epochDuration = (epochEndTime - epochStartTime); Metrics.Epoch = epoch + 1; Metrics.EpochDuration = epochDuration; Logger.LogInformation($"Epoch {Metrics.Epoch} completed in {Metrics.EpochDuration.TotalSeconds:F2}s, Average Training Loss: {Metrics.AverageError}, Min Loss: {Metrics.MinError}, Max Loss: {Metrics.MaxError}."); Metrics.Clear(); } } } } catch (OperationCanceledException) { Logger.LogWarning("Training operation was cancelled."); } catch (Exception ex) { Logger.LogError($"An exception occurred during training: {ex.Message}"); throw; } finally { } await Task.CompletedTask; } private async Task SaveCheckpointAsync(int epoch, T validationLoss) { if (!Directory.Exists(CheckpointDirectory)) { Directory.CreateDirectory(CheckpointDirectory); } string checkpointPath = Path.Combine(CheckpointDirectory, $"model_epoch_{epoch}.ckpt"); Logger.LogInformation($"Checkpoint saved at '{checkpointPath}' with Validation Loss: {validationLoss}"); } private void Backward(Tensor<T> gradients) { var grad = gradients; for (int i = Network.Layers.Count - 1; i >= 0; i--) { grad = Network.Layers[i].Backward(grad); } grad.Dispose(); } private void UpdateWeights() { foreach (var layer in Network.Layers) { layer.UpdateWeights(Optimizer); } } private void ClearGradients() { foreach (var layer in Network.Layers) { layer.ClearGradients(); } } private void ClipGradients() { foreach (var layer in Network.Layers) { foreach (var gradient in layer.GetGradients()) { gradient.ClipInPlace(-GradientClippingThreshold, GradientClippingThreshold); } } } public void ShuffleData(ref Tensor<T> data, ref Tensor<T> labels) { int numSamples = data.Shape[0]; int[] indices = Enumerable.Range(0, numSamples).ToArray(); for (int i = numSamples - 1; i > 0; i--) { int j = _rand.Next(i + 1); (indices[i], indices[j]) = (indices[j], indices[i]); } data = data.Gather(indices); labels = labels.Gather(indices); } public T Evaluate(Tensor<T> data, Tensor<T> labels) { return Evaluator.Evaluate(data, labels, LossFunction); } public void LoadCheckpoint(string checkpointPath) { Network.Load(checkpointPath); Logger.LogInformation($"Checkpoint loaded from '{checkpointPath}'"); } } public class Evaluator<T> where T : INumber<T>, IExponentialFunctions<T>, ITrigonometricFunctions<T>, ILogarithmicFunctions<T>, IFloatingPointIeee754<T>, IRootFunctions<T> { public NeuralNetwork<T> Network { get; private set; } public ILogger Logger { get; private set; } public int BatchSize { get; set; } = 32; public Evaluator(NeuralNetwork<T> network, int batchSize = 32, ILogger<Evaluator<T>>? logger = null) { Network = network; Logger = logger ?? NullLogger<Evaluator<T>>.Instance; BatchSize = batchSize; } public T Evaluate(Tensor<T> data, Tensor<T> labels, ILossFunction<T> lossFunction) { int numSamples = data.Shape[0]; int totalBatches = (numSamples + BatchSize - 1) / BatchSize; T totalLoss = T.Zero; var batchIndices = Enumerable.Range(0, totalBatches).ToArray(); ParallelOptions parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = totalBatches >= Tensor<T>.ParallelizationThreshold ? Environment.ProcessorCount : 1 }; object lockObj = new object(); Parallel.ForEach(batchIndices, parallelOptions, (batchIndex) => { using (Logger.BeginScope("Evaluator, Batch: {Batch}", batchIndex)) { int batchStart = batchIndex * BatchSize; int currentBatchSize = Math.Min(BatchSize, numSamples - batchStart); var batchData = data.Slice(batchStart, currentBatchSize); var batchLabels = labels.Slice(batchStart, currentBatchSize); var predictions = Network.Forward(batchData); T batchLoss = lossFunction.ComputeLoss(predictions, batchLabels); lock (lockObj) { totalLoss += batchLoss; } predictions.Dispose(); batchData.Dispose(); batchLabels.Dispose(); } }); T averageLoss = totalLoss / T.CreateChecked(totalBatches); Logger.LogInformation($"Evaluation Loss: {averageLoss}"); return averageLoss; } } public static class InitializerFactory<T> where T : INumber<T>, IExponentialFunctions<T>, ITrigonometricFunctions<T>, ILogarithmicFunctions<T>, IFloatingPointIeee754<T>, IRootFunctions<T> { public static IInitializer<T> Create(InitializerType initializerType, T min = default, T max = default, ILoggerFactory? loggerFactory = null) { return initializerType switch { InitializerType.Xavier => new XavierInitializer<T>(loggerFactory?.CreateLogger<XavierInitializer<T>>()), InitializerType.He => new HeInitializer<T>(loggerFactory?.CreateLogger<HeInitializer<T>>()), InitializerType.RandomUniform => new RandomUniformInitializer<T>(min, max, loggerFactory?.CreateLogger<RandomUniformInitializer<T>>()), InitializerType.Normal => new NormalInitializer<T>(T.Zero, T.Zero, loggerFactory?.CreateLogger<NormalInitializer<T>>()), InitializerType.Zero => new ZeroInitializer<T>(loggerFactory?.CreateLogger<ZeroInitializer<T>>()), _ => throw new NotSupportedException($"Initializer type {initializerType} is not supported.") }; } } public static class LossFunctionFactory<T> where T : INumber<T>, IExponentialFunctions<T>, ITrigonometricFunctions<T>, ILogarithmicFunctions<T>, IFloatingPointIeee754<T>, IRootFunctions<T> { public static ILossFunction<T> CreateLossFunction(LossFunctionType lossFunctionType, ILoggerFactory? loggerFactory = null) { return lossFunctionType switch { LossFunctionType.MeanSquaredError => new MeanSquaredError<T>(loggerFactory?.CreateLogger<MeanSquaredError<T>>()), LossFunctionType.CrossEntropyLoss => new CrossEntropyLoss<T>(loggerFactory?.CreateLogger<CrossEntropyLoss<T>>()), LossFunctionType.BinaryCrossEntropyLoss => new BinaryCrossEntropyLoss<T>(loggerFactory?.CreateLogger<BinaryCrossEntropyLoss<T>>()), _ => throw new NotSupportedException($"Loss function {lossFunctionType} is not supported.") }; } } public static class OptimizerFactory<T> where T : INumber<T>, IExponentialFunctions<T>, ITrigonometricFunctions<T>, ILogarithmicFunctions<T>, IFloatingPointIeee754<T>, IRootFunctions<T> { public static IOptimizer<T> CreateOptimizer(OptimizerType optimizerType, T learningRate, ILoggerFactory? loggerFactory = null) { return optimizerType switch { OptimizerType.SGD => new SGDOptimizer<T>(learningRate, loggerFactory?.CreateLogger<SGDOptimizer<T>>()), OptimizerType.Adam => new AdamOptimizer<T>(learningRate, T.Zero, T.Zero, T.Zero, loggerFactory?.CreateLogger<AdamOptimizer<T>>()), _ => throw new NotSupportedException($"Optimizer type {optimizerType} is not supported.") }; } } public static class RegularizationFactory<T> where T : INumber<T>, IExponentialFunctions<T>, ITrigonometricFunctions<T>, ILogarithmicFunctions<T>, IFloatingPointIeee754<T>, IRootFunctions<T> { public static IRegularization<T>? CreateRegularization(RegularizationType regularizationType, T lambda, ILoggerFactory? loggerFactory = null) { return regularizationType switch { RegularizationType.None => null, RegularizationType.L1 => new L1Regularization<T>(lambda, loggerFactory?.CreateLogger<L1Regularization<T>>()), RegularizationType.L2 => new L2Regularization<T>(lambda, loggerFactory?.CreateLogger<L2Regularization<T>>()), _ => throw new NotSupportedException($"Regularization type {regularizationType} is not supported.") }; } } public static class LearningRateSchedulerFactory<T> where T : unmanaged, INumber<T>, IExponentialFunctions<T>, ITrigonometricFunctions<T>, ILogarithmicFunctions<T>, IFloatingPointIeee754<T>, IRootFunctions<T> { public static ILearningRateScheduler<T>? CreateScheduler(LearningRateSchedulerType schedulerType, T initialLearningRate, int? stepSize = null, T? decayRate = null, ILoggerFactory? loggerFactory = null) { return schedulerType switch { LearningRateSchedulerType.None => null, LearningRateSchedulerType.ExponentialDecay => new ExponentialDecayScheduler<T>(initialLearningRate, decayRate ?? T.One, stepSize ?? 1, loggerFactory?.CreateLogger<ExponentialDecayScheduler<T>>()), _ => throw new NotSupportedException($"Scheduler type {schedulerType} is not supported.") }; } } public class TrainingMetrics<T> where T : INumber<T>, IExponentialFunctions<T>, ITrigonometricFunctions<T>, ILogarithmicFunctions<T>, IFloatingPointIeee754<T>, IRootFunctions<T> { public int Epoch { get; set; } public T AverageError { get; private set; } = T.Zero; public T MinError { get; private set; } public T MaxError { get; private set; } public T StandardDeviation { get; private set; } = T.Zero; public T Variance { get; private set; } = T.Zero; public T SumError { get; private set; } = T.Zero; public TimeSpan EpochDuration { get; set; } private int _batchCount = 0; private T _m2 = T.Zero; public void AddBatchError(T error) { _batchCount++; SumError += error; if (_batchCount == 1) { MinError = error; MaxError = error; } else { if (error.CompareTo(MinError) < 0) MinError = error; if (error.CompareTo(MaxError) > 0) MaxError = error; } var delta = error - AverageError; AverageError += delta / T.CreateTruncating(_batchCount); var delta2 = error - AverageError; _m2 += delta * delta2; Variance = _m2 / T.CreateChecked(_batchCount); StandardDeviation = T.Sqrt(Variance); } public void Clear() { AverageError = T.Zero; MinError = default; MaxError = default; StandardDeviation = T.Zero; Variance = T.Zero; SumError = T.Zero; _batchCount = 0; _m2 = T.Zero; } public string GetSummary() { StringBuilder stm = new StringBuilder(); stm.Append($"Epoch: {Epoch}, AverageError: {AverageError}, MinError: {MinError}, MaxError: {MaxError}, StandardDeviation: {StandardDeviation}, Variance: {Variance}, SumError: {SumError}, EpochDuration: {EpochDuration}."); return stm.ToString(); } }