Skip to content

Commit ee44996

Browse files
committed
fix: handle 1D tensors and default DenseLayer to identity
1 parent 9e1b7a2 commit ee44996

1 file changed

Lines changed: 64 additions & 22 deletions

File tree

src/NeuralNetworks/Layers/DenseLayer.cs

Lines changed: 64 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -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 2828 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

Comments
 (0)