according to best practices in coding and numerical computing, create Convolutional Layer similar to this Dense layer
 public class DenseLayerParameters<T> : LayerParameters<T> where T : INumber<T>, IExponentialFunctions<T>, ITrigonometricFunctions<T>, ILogarithmicFunctions<T>, IFloatingPointIeee754<T>, IRootFunctions<T> { public int InputSize { get; set; } public int OutputSize { get; set; } public DenseLayerParameters() : base(LayerType.Dense) { } } public class DenseLayer<T> : ILayer<T>, IDisposable where T : INumber<T>, IExponentialFunctions<T>, ITrigonometricFunctions<T>, ILogarithmicFunctions<T>, IFloatingPointIeee754<T>, IRootFunctions<T> { public Tensor<T> Weights { get; private set; } public Tensor<T> Biases { get; private set; } public Tensor<T> Input { get; private set; } public Tensor<T> Output { get; private set; } public Tensor<T> Gradients { get; private set; } public Tensor<T> GradBiases { get; private set; } public IInitializer<T> WeightInitializer { get; private set; } public IInitializer<T> BiasInitializer { get; private set; } public IActivationFunction<T> Activation { get; private set; } public string Name { get; private set; } public int NumInputs { get; private set; } public int NumOutputs { get; private set; } public int TotalParameters { get; private set; } public string ActivationFunctionName => Activation.GetType().Name; public ILogger Logger { get; private set; } public DenseLayer( int inputSize, int outputSize, IActivationFunction<T> activation, IInitializer<T>? weightInitializer = null, IInitializer<T>? biasInitializer = null, string? name = null, ILogger<DenseLayer<T>>? logger = null) { NumInputs = inputSize; NumOutputs = outputSize; Activation = activation; WeightInitializer = weightInitializer ?? new XavierInitializer<T>(); BiasInitializer = biasInitializer ?? new RandomUniformInitializer<T>(T.Zero, T.Zero); Name = name ?? $"DenseLayer_{Guid.NewGuid()}"; Logger = logger ?? NullLogger<DenseLayer<T>>.Instance; Weights = new Tensor<T>(inputSize, outputSize); Biases = new Tensor<T>(1, outputSize); TotalParameters = inputSize * outputSize + outputSize; Gradients = new Tensor<T>(Weights.Shape); Gradients.Fill(T.Zero); GradBiases = new Tensor<T>(Biases.Shape); GradBiases.Fill(T.Zero); } public void Dispose() { Weights?.Dispose(); Weights = null; Biases?.Dispose(); Biases = null; Gradients?.Dispose(); Gradients = null; GradBiases?.Dispose(); GradBiases = null; Input?.Dispose(); Input = null; Output?.Dispose(); Output = null; Logger.LogInformation("Disposed resources for layer '{Name}'.", Name); } public void Initialize() { WeightInitializer.Initialize(Weights); BiasInitializer.Initialize(Biases); Logger.LogInformation("Initialized DenseLayer '{Name}' with {TotalParameters} parameters.", Name, TotalParameters); } public Tensor<T> Forward(Tensor<T> input) { Input = input; var preActivation = input.MatMul(Weights).Add(Biases); Output?.Dispose(); Output = Activation.Forward(preActivation); return Output; } public Tensor<T> Backward(Tensor<T> outputGradient) { var gradPreActivation = Activation.Backward(Output, outputGradient); var inputTranspose = Input.Transpose(0, 1); var weightsTranspose = Weights.Transpose(0, 1); var gradWeights = inputTranspose.MatMul(gradPreActivation); var gradBiases = gradPreActivation.Sum(axis: 0).ExpandDims(0); var gradInput = gradPreActivation.MatMul(weightsTranspose); Gradients.AddInPlace(gradWeights); GradBiases.AddInPlace(gradBiases); return gradInput; } public void UpdateWeights(IOptimizer<T> optimizer) { optimizer.Update(Weights, Gradients); optimizer.Update(Biases, GradBiases); } public void ClearGradients() { Gradients.Fill(T.Zero); GradBiases.Fill(T.Zero); } public void Visualize() { Logger.LogInformation("Visualizing layer '{Name}'.", Name); } public IEnumerable<Tensor<T>> GetParameters() { yield return Weights; yield return Biases; } public IEnumerable<Tensor<T>> GetGradients() { yield return Gradients; yield return GradBiases; } public string GetSummary() { var sb = new StringBuilder(); sb.AppendLine($"\tName: {Name}"); sb.AppendLine($"\tType: Dense Layer"); sb.AppendLine($"\tInput Shape: [{NumInputs}]"); sb.AppendLine($"\tOutput Shape: [{NumOutputs}]"); sb.AppendLine($"\tActivation: {Activation.GetType().Name}"); sb.AppendLine($"\tParameters: {TotalParameters}"); return sb.ToString(); } public LayerData<T> GetLayerData() { return new DenseLayerData<T> { Type = GetType().AssemblyQualifiedName, Name = Name, NumInputs = NumInputs, NumOutputs = NumOutputs, Weights = Weights.ToArray(), Biases = Biases.ToArray(), WeightShape = Weights.Shape, BiasShape = Biases.Shape }; } public void LoadLayerData(LayerData<T> data) { if (data is DenseLayerData<T> denseData) { Name = denseData.Name; NumInputs = denseData.NumInputs; NumOutputs = denseData.NumOutputs; Weights?.Dispose(); Biases?.Dispose(); Gradients?.Dispose(); GradBiases?.Dispose(); Weights = new Tensor<T>(denseData.Weights, denseData.WeightShape); Biases = new Tensor<T>(denseData.Biases, denseData.BiasShape); var activationType = Type.GetType(denseData.ActivationFunctionType.GetType().ToString()); if (activationType == null) throw new InvalidOperationException($"Could not find type '{denseData.ActivationFunctionType}' for ActivationFunction."); Activation = ActivationFunctionFactory<T>.Create(denseData.ActivationFunctionType); Gradients = new Tensor<T>(Weights.Shape); Gradients.Fill(T.Zero); GradBiases = new Tensor<T>(Biases.Shape); GradBiases.Fill(T.Zero); Logger.LogInformation("Layer '{Name}' loaded successfully.", Name); } else { throw new InvalidOperationException($"Invalid layer data provided for layer '{Name}'."); } } }