@@ -151,12 +151,12 @@ public class DenseLayer<T> : LayerBase<T>
151151 /// <para><b>For Beginners:</b> This tells you how many individual numbers the layer can adjust during training.
152152 ///
153153 /// The parameter count:
154- /// - Equals (number of inputs × number of outputs) + number of outputs
154+ /// - Equals (number of inputs � number of outputs) + number of outputs
155155 /// - First part counts the weights, second part counts the biases
156156 /// - Higher numbers mean more flexibility but also more risk of overfitting
157157 ///
158158 /// For example, a dense layer with 100 inputs and 50 outputs would have
159- /// 100 × 50 = 5,000 weights plus 50 biases, for a total of 5,050 parameters.
159+ /// 100 � 50 = 5,000 weights plus 50 biases, for a total of 5,050 parameters.
160160 /// </para>
161161 /// </remarks>
162162 public override int ParameterCount => ( _weights . Rows * _weights . Columns ) + _biases . Length ;
@@ -190,7 +190,7 @@ public class DenseLayer<T> : LayerBase<T>
190190 /// </summary>
191191 /// <param name="inputSize">The number of input neurons.</param>
192192 /// <param name="outputSize">The number of output neurons.</param>
193- /// <param name="activationFunction">The activation function to apply. Defaults to ReLU if not specified.</param>
193+ /// <param name="activationFunction">The activation function to apply. Defaults to Identity (linear) if not specified.</param>
194194 /// <remarks>
195195 /// <para>
196196 /// This constructor creates a dense layer with the specified number of input and output neurons.
@@ -205,14 +205,14 @@ public class DenseLayer<T> : LayerBase<T>
205205 /// - What mathematical function to apply to the results (activation)
206206 ///
207207 /// For example, a layer with inputSize=784 and outputSize=10 could connect the flattened
208- /// pixels of a 28× 28 image to 10 output neurons (one for each digit 0-9).
208+ /// pixels of a 28� 28 image to 10 output neurons (one for each digit 0-9).
209209 ///
210210 /// The layer automatically initializes all the weights and biases with carefully chosen
211211 /// starting values that help with training.
212212 /// </para>
213213 /// </remarks>
214214 public DenseLayer ( int inputSize , int outputSize , IActivationFunction < T > ? activationFunction = null )
215- : base ( [ inputSize ] , [ outputSize ] , activationFunction ?? new ReLUActivation < T > ( ) )
215+ : base ( [ inputSize ] , [ outputSize ] , activationFunction ?? new IdentityActivation < T > ( ) )
216216 {
217217 _weights = new Matrix < T > ( outputSize , inputSize ) ;
218218 _biases = new Vector < T > ( outputSize ) ;
@@ -226,7 +226,7 @@ public DenseLayer(int inputSize, int outputSize, IActivationFunction<T>? activat
226226 /// </summary>
227227 /// <param name="inputSize">The number of input neurons.</param>
228228 /// <param name="outputSize">The number of output neurons.</param>
229- /// <param name="vectorActivation">The vector activation function to apply. Defaults to ReLU if not specified.</param>
229+ /// <param name="vectorActivation">The vector activation function to apply. Defaults to Identity (linear) if not specified.</param>
230230 /// <remarks>
231231 /// <para>
232232 /// This constructor creates a dense layer with the specified number of input and output neurons
@@ -247,7 +247,7 @@ public DenseLayer(int inputSize, int outputSize, IActivationFunction<T>? activat
247247 /// </para>
248248 /// </remarks>
249249 public DenseLayer ( int inputSize , int outputSize , IVectorActivationFunction < T > ? vectorActivation = null )
250- : base ( [ inputSize ] , [ outputSize ] , vectorActivation ?? new ReLUActivation < T > ( ) )
250+ : base ( [ inputSize ] , [ outputSize ] , vectorActivation ?? new IdentityActivation < T > ( ) )
251251 {
252252 _weights = new Matrix < T > ( outputSize , inputSize ) ;
253253 _biases = new Vector < T > ( outputSize ) ;
@@ -368,18 +368,19 @@ public void SetWeights(Matrix<T> weights)
368368 public override Tensor < T > Forward ( Tensor < T > input )
369369 {
370370 _lastInput = input ;
371- int batchSize = input . Shape [ 0 ] ;
372371
373- var flattenedInput = input . Reshape ( batchSize , input . Shape [ 1 ] ) ;
374- var output = flattenedInput . Multiply ( _weights . Transpose ( ) ) . Add ( _biases ) ;
372+ var ( input2D , squeezeOutput ) = EnsureRank2BatchFirst ( input ) ;
373+ var output = input2D . Multiply ( _weights . Transpose ( ) ) . Add ( _biases ) ;
375374
376375 if ( UsingVectorActivation )
377376 {
378- return VectorActivation ! . Activate ( output ) ;
377+ var activated = VectorActivation ! . Activate ( output ) ;
378+ return squeezeOutput ? activated . Reshape ( [ activated . Shape [ 1 ] ] ) : activated ;
379379 }
380380 else
381381 {
382- return ApplyActivation ( output ) ;
382+ var activated = ApplyActivation ( output ) ;
383+ return squeezeOutput ? activated . Reshape ( [ activated . Shape [ 1 ] ] ) : activated ;
383384 }
384385 }
385386
@@ -413,33 +414,74 @@ public override Tensor<T> Backward(Tensor<T> outputGradient)
413414 if ( _lastInput == null )
414415 throw new InvalidOperationException ( "Forward pass must be called before backward pass." ) ;
415416
416- int batchSize = _lastInput . Shape [ 0 ] ;
417+ var ( lastInput2D , _) = EnsureRank2BatchFirst ( _lastInput ) ;
418+ int batchSize = lastInput2D . Shape [ 0 ] ;
419+
420+ var ( outputGradient2D , _) = EnsureRank2BatchFirst ( outputGradient ) ;
421+ if ( outputGradient2D . Shape [ 0 ] != batchSize )
422+ {
423+ if ( outputGradient . Length % batchSize != 0 )
424+ {
425+ throw new ArgumentException (
426+ $ "Output gradient length ({ outputGradient . Length } ) is not divisible by batch size ({ batchSize } ).",
427+ nameof ( outputGradient ) ) ;
428+ }
429+
430+ outputGradient2D = outputGradient . Reshape ( [ batchSize , outputGradient . Length / batchSize ] ) ;
431+ }
417432
418433 Tensor < T > activationGradient ;
419434 if ( UsingVectorActivation )
420435 {
421- activationGradient = VectorActivation ! . Derivative ( outputGradient ) ;
436+ activationGradient = VectorActivation ! . Derivative ( outputGradient2D ) ;
422437 }
423438 else
424439 {
425440 // Apply scalar activation derivative element-wise
426- activationGradient = new Tensor < T > ( outputGradient . Shape ) ;
427- for ( int i = 0 ; i < outputGradient . Length ; i ++ )
441+ activationGradient = new Tensor < T > ( outputGradient2D . Shape ) ;
442+ for ( int i = 0 ; i < outputGradient2D . Length ; i ++ )
428443 {
429- activationGradient [ i ] = ScalarActivation ! . Derivative ( outputGradient [ i ] ) ;
444+ activationGradient [ i ] = ScalarActivation ! . Derivative ( outputGradient2D [ i ] ) ;
430445 }
431446 }
432447
433- var flattenedInput = _lastInput . Reshape ( batchSize , _lastInput . Shape [ 1 ] ) ;
434-
435- _weightsGradient = activationGradient . Transpose ( [ 1 , 0 ] ) . ToMatrix ( ) . Multiply ( flattenedInput . ToMatrix ( ) ) ;
436- _biasesGradient = activationGradient . Sum ( [ 0 ] ) . ToMatrix ( ) . ToColumnVector ( ) ;
448+ _weightsGradient = activationGradient . Transpose ( [ 1 , 0 ] ) . ToMatrix ( ) . Multiply ( lastInput2D . ToMatrix ( ) ) ;
449+ _biasesGradient = activationGradient . Sum ( [ 0 ] ) . ToVector ( ) ;
437450
438451 var inputGradient = activationGradient . Multiply ( _weights ) ;
439452
440453 return inputGradient . Reshape ( _lastInput . Shape ) ;
441454 }
442455
456+ private static ( Tensor < T > Tensor2D , bool SqueezeOutput ) EnsureRank2BatchFirst ( Tensor < T > tensor )
457+ {
458+ if ( tensor . Shape . Length == 1 )
459+ {
460+ return ( tensor . Reshape ( [ 1 , tensor . Shape [ 0 ] ] ) , true ) ;
461+ }
462+
463+ int batchSize = tensor . Shape [ 0 ] ;
464+ if ( batchSize <= 0 )
465+ {
466+ throw new ArgumentException ( $ "Invalid batch size ({ batchSize } ).", nameof ( tensor ) ) ;
467+ }
468+
469+ int features = tensor . Length / batchSize ;
470+ if ( tensor . Length != batchSize * features )
471+ {
472+ throw new ArgumentException (
473+ $ "Tensor length ({ tensor . Length } ) is not compatible with batch size ({ batchSize } ).",
474+ nameof ( tensor ) ) ;
475+ }
476+
477+ if ( tensor . Shape . Length == 2 && tensor . Shape [ 1 ] == features )
478+ {
479+ return ( tensor , false ) ;
480+ }
481+
482+ return ( tensor . Reshape ( [ batchSize , features ] ) , false ) ;
483+ }
484+
443485 /// <summary>
444486 /// Updates the layer's parameters (weights and biases) using the calculated gradients.
445487 /// </summary>
@@ -641,4 +683,4 @@ public override LayerBase<T> Clone()
641683 copy . SetParameters ( GetParameters ( ) ) ;
642684 return copy ;
643685 }
644- }
686+ }
0 commit comments