Skip to content

Commit ede9886

Browse files
ooplesclaude
andcommitted
fix(networks): resnet/vgg train adds batch dim for 3d input
ResNet/VGG's Forward() explicitly accepts 3D [C,H,W] input and expands it to 4D [1,C,H,W] before running the layer stack. Their Train() overrides, however, called TrainWithTape directly — which delegates to NeuralNetworkBase.ForwardForTraining, which does NOT add a batch dim and just runs the raw tensor through every layer. For a 3D input [3, 32, 32], the conv/pool chain preserves the rank-3 shape and the classifier's AdaptiveAveragePool + Flatten ends up producing [512, 1] (the 512 final-block channel count gets treated as a batch dim by FlattenLayer.Forward's "preserve first dim" rule). The final DenseLayer with inputSize=512 sees actualInputSize=1 via input.Shape[^1], calls EnsureWeightShapeForInput(1) which resizes weights to [1, 10], and produces [512, 10] — which then fails the loss shape check in EnsureTargetMatchesPredicted because the target is [10]. Fix: mirror Forward()'s expansion in Train() — when input is 3D, add a leading batch dim to BOTH input and target before dispatching to TrainWithTape. Any 4D input is passed through untouched. The target expansion is guarded so a caller that already provided a batched target is not double-expanded. Verified locally, all 4 of the previously-failing tests now pass: - ResNetNetwork_Train_CompletesWithoutError - ResNetNetwork_Train_LossDecreases - VGGNetwork_Train_CompletesWithoutError - VGGNetwork_Train_LossDecreases Closes the 08a NN-Classic (ResNet/VGG/DenseNet) CI shard failure from the PR #1154 triage. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent b187e31 commit ede9886

2 files changed

Lines changed: 30 additions & 2 deletions

File tree

src/NeuralNetworks/ResNetNetwork.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -521,12 +521,29 @@ public override Tensor<T> Predict(Tensor<T> input)
521521
/// </summary>
522522
/// <param name="input">The input tensor for training.</param>
523523
/// <param name="expectedOutput">The expected output tensor (one-hot encoded class labels).</param>
524+
/// <remarks>
525+
/// Accepts both 3D <c>[C, H, W]</c> (single unbatched example) and 4D
526+
/// <c>[B, C, H, W]</c> inputs — matches <see cref="Forward"/>'s contract.
527+
/// When a 3D input arrives, a leading batch dimension is added so every
528+
/// downstream layer sees the conv-standard 4D tensor. The corresponding
529+
/// target is also expanded to <c>[1, numClasses]</c> so the loss sees
530+
/// matching batch dims on both operands. Previously
531+
/// <see cref="NeuralNetworkBase{T}.ForwardForTraining"/> fed the raw 3D
532+
/// tensor straight to the layer stack, which caused the FlattenLayer to
533+
/// treat the 512-channel dimension as a batch and produce a
534+
/// <c>[512, 10]</c> prediction — failing the loss shape check in
535+
/// <c>EnsureTargetMatchesPredicted</c>.
536+
/// </remarks>
524537
public override void Train(Tensor<T> input, Tensor<T> expectedOutput)
525538
{
526539
SetTrainingMode(true);
527540
try
528541
{
529-
TrainWithTape(input, expectedOutput, _optimizer);
542+
Tensor<T> processedInput = input.Rank == 3 ? AddBatchDimension(input) : input;
543+
Tensor<T> processedTarget = input.Rank == 3 && expectedOutput.Rank < processedInput.Rank - 2
544+
? AddBatchDimension(expectedOutput)
545+
: expectedOutput;
546+
TrainWithTape(processedInput, processedTarget, _optimizer);
530547
}
531548
finally
532549
{

src/NeuralNetworks/VGGNetwork.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,18 @@ public override void Train(Tensor<T> input, Tensor<T> expectedOutput)
388388
SetTrainingMode(true);
389389
try
390390
{
391-
TrainWithTape(input, expectedOutput, _optimizer);
391+
// Match Forward's behavior: accept 3D [C,H,W] single example and
392+
// expand to 4D [1,C,H,W] so the layer stack (conv / pool / flatten /
393+
// dense) receives the batched shape it expects. Without the
394+
// expansion the FlattenLayer in the classifier head treats the
395+
// channel dimension as a batch and produces an output with the
396+
// wrong leading dim — fails the loss-shape check on every training
397+
// call with a single 3D input.
398+
Tensor<T> processedInput = input.Rank == 3 ? AddBatchDimension(input) : input;
399+
Tensor<T> processedTarget = input.Rank == 3 && expectedOutput.Rank < processedInput.Rank - 2
400+
? AddBatchDimension(expectedOutput)
401+
: expectedOutput;
402+
TrainWithTape(processedInput, processedTarget, _optimizer);
392403
}
393404
finally
394405
{

0 commit comments

Comments
 (0)