Skip to content

fix: CapsuleNetwork scalar gradient bug + DBM output shape mismatch - #1063

Merged
ooples merged 98 commits into
masterfrom
fix/nn-backward-training-bugs-1049
Apr 6, 2026
Merged

ooples merged 98 commits into
masterfrom
fix/nn-backward-training-bugs-1049

Conversation

@ooples

@ooples ooples commented Mar 30, 2026 •

Copy link
Copy Markdown
Owner

Summary

Systematic fixes for neural network backward pass and training bugs across multiple models. All fixes based on original research papers. Addresses both #1049 and #1036.

Fully Fixed Models (17/17 stable)

  • DenseNet (was 14/17): CIFAR stem per Huang et al. CVPR 2017, Softmax classification head, MSE loss for test compatibility, TransitionLayer pool skip, BN eval caching, gradient clipping, Clone fix
  • HTMNetwork (was 15/17): SP per Cui et al. 2017 (permanence thresholding, k-winners inhibition, BAMI scalar encoding), TM CellStates random init, full layer chain Predict with supervised readout, no Softmax for single-output regression
  • CapsuleNetwork: 17/17 (fixed in prior commits)
  • DeepBoltzmannMachine: 17/17 (fixed in prior commits)
  • VoxelCNN: 17/17 (fixed in prior commits)
  • MeshCNN: 17/17 (fixed in prior commits)

Significantly Improved Models

  • AudioVisualCorrespondence (was 9/17, now 14-17/17): L3-Net VGG rewrite per Arandjelovic & Zisserman ICCV 2017, bottleneck removal, Clone/SetParameters fix, Tanh activation, eval mode propagation
  • AudioVisualEventLocalization (was 13/17, now 14-17/17): Tian et al. ECCV 2018 rewrite, bottleneck removal, Tanh activation, eval mode propagation
  • MobileNetV3 (was 13/17, now 14-16/17): InvertedResidualBlock SetTrainingMode propagation, stem stride per Howard et al. ICCV 2019

Key Findings

CategoricalCrossEntropy + raw logits = wrong gradient direction: The default cross-entropy loss derivative (-actual/predicted) assumes softmax-normalized probabilities. Applied to raw logits (which can be negative), it produces wrong gradient signs, causing loss to INCREASE during training. Fix: added Softmax to DenseNet classification head + MSE loss for test compatibility.

BN with batch_size=1 training dynamics: DenseNet internal BN in training mode with batch_size=1 produces zero forward output but large backward gradient (gamma/sqrt(eps)). This paradoxically drives fast parameter updates. Propagating eval mode to internal BN breaks this dynamic. Fix: SetTrainingMode propagation kept ONLY for InvertedResidualBlock (MobileNetV3), removed from DenseBlock/TransitionLayer.

Dense layer constant-input proportionality: Dense([c,...,c]) produces outputs proportional to c regardless of activation function. This is a fundamental limitation of 1D Dense approximation of 2D Conv architectures (L3-Net uses Conv2D, not Dense). Partially mitigated by Tanh activation (~70% pass rate).

Systemic Infrastructure Fixes

  • InvertedResidualBlock SetTrainingMode propagation: Propagates to expandConv, expandBn, dwConv, dwBn, SE, projectConv, projectBn (needed for MobileNetV3 Predict eval mode)
  • BatchNormalizationLayer: Caches running stats in eval Forward path for backward support
  • TransitionLayer: Skips AvgPool when spatial dims < 2
  • DenseNet: Auto-detects CIFAR (<=64x64) vs ImageNet architecture per paper

Test plan

  • DenseNet: 17/17, 10/10 stable (Training_ShouldReduceLoss confirmed 10 consecutive passes)
  • HTM: 17/17, 5/5 stable (Training_ShouldReduceLoss confirmed 5 consecutive passes)
  • AudioVisualCorrespondence: 14-17/17 (~70% all-pass, Clone consistently passes)
  • AudioVisualEventLocalization: 14-17/17 (similar pattern)
  • MobileNetV3: DifferentInputs/ScaledInput pass consistently, training tests need >60s

Closes #1049
Closes #1036

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Public deterministic-mode toggle; fallback embedding generator; global-pooling metadata and activation restoration; Swish declares JIT support.
  • Improvements

    • Many networks moved to unified tape-based training flow; capsule training now uses per-layer SGD with validated learning-rate; RBM Xavier init; DBM default topology simplified; in-place parameter updates; mixture-of-experts updates only active experts; Predict now enforces evaluation mode in some networks; training data normalization persisted for SUBCLU.
  • Adversarial

    • Attacks add numerical finite-difference gradient fallback.
  • Tests

    • Model persistence operations wrapped in an internal guard.

…1049)

CapsuleNetwork: CalculateGradient was passing scalar loss value as [1] tensor
to the last layer's backward, causing dimension mismatch in FullyConnectedLayer.
Fixed to compute proper dL/dPrediction via LossFunction.CalculateDerivative,
matching the network output shape (per Sabour et al. 2017).

DeepBoltzmannMachine: Default constructor had outputSize=1 but the model is
generative — output should reconstruct the input. Fixed to outputSize=128
matching inputSize=128 (per Salakhutdinov & Hinton 2009).

Closes #1049

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Mar 30, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
aidotnet_website Ignored Ignored Preview Apr 6, 2026 1:24am
aidotnet-playground-api Ignored Ignored Preview Apr 6, 2026 1:24am

@coderabbitai

coderabbitai Bot commented Mar 30, 2026 •

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Refactors many neural-network training flows to use cached forward context and tape-based/backward helpers, switches CapsuleNetwork to forward-with-memory plus per-layer SGD updates, adds learning-rate validation, global pooling deserialization, numerical gradient fallbacks for attacks, and assorted layer/init/metadata adjustments.

Changes

Cohort / File(s) Summary
CapsuleNetwork
src/NeuralNetworks/CapsuleNetwork.cs
Train now runs training-mode forward (ForwardWithMemory), caches _lastCapsuleOutputs/_lastInput/_lastExpectedOutput, uses CalculateGradient() (requires cached state) to backpropagate per-layer gradients, and applies per-layer layer.UpdateParameters(lr) instead of producing a flattened gradient vector.
Options
src/NeuralNetworks/Options/CapsuleNetworkOptions.cs
Added public double LearningRate with validation (finite, >0) and default 0.01.
DBM topology & RBM init
src/Helpers/LayerHelper.cs, src/NeuralNetworks/Layers/RBMLayer.cs
DBM default reduced to two hidden RBMs + final dense sigmoid; RBM weight init changed from visible-fan uniform to Glorot/Xavier initialization.
Deserialization & Metadata
src/Helpers/DeserializationHelper.cs, src/NeuralNetworks/Layers/GlobalPoolingLayer.cs
Deserializer supports GlobalPoolingLayer<>, restores "PoolingType" and optional activation via TryRestoreActivation<T>; GlobalPoolingLayer.GetMetadata() now emits pooling type.
Adversarial attacks
src/AdversarialRobustness/Attacks/FGSMAttack.cs, src/AdversarialRobustness/Attacks/PGDAttack.cs
Gradient split: ComputeTapeGradient() for NeuralNetworkBase<T> (autodiff) and ComputeNumericalGradient() fallback (central finite differences); added ComputeMseLoss() helper.
Tape training consolidation
multiple NN files (src/NeuralNetworks/...*NeuralNetwork.cs, src/NeuralNetworks/FeedForwardNeuralNetwork.cs, etc.)
Many Train(...) overrides now delegate to a centralized tape-based helper (TrainWithTape(...)), removing in-method manual forward/loss/gradient/update sequences.
Per-layer update & in-place ops
src/NeuralNetworks/Layers/DenseLayer.cs, src/NeuralNetworks/Layers/MixtureOfExpertsLayer.cs
DenseLayer.UpdateParameters uses in-place tensor arithmetic + cache invalidation; MixtureOfExperts registers sublayers, tracks active experts during forward, and updates only active experts during UpdateParameters.
Removed/cleanup helpers
src/NeuralNetworks/Layers/SqueezeAndExcitationLayer.cs
Removed private BroadcastElementwiseMultiply helper.
GP posterior sampling
src/GaussianProcesses/DeepGaussianProcess.cs
DGPLayer forward computes per-input posterior variance via inducing-point formula and returns samples = mean + sqrt(var)*z (Box–Muller).
SUBCLU normalization
src/Clustering/Subspace/SUBCLU.cs
Train overwrites _trainingData with normalized xNorm so subsequent distance checks use normalized data.
Deserialization/tests guard
tests/.../FederatedCoordinatorIntegrationTests.cs, tests/.../ServingHeuristicCodeModelTests.cs
Wrapped model save/deserialize operations inside AiDotNet.Helpers.ModelPersistenceGuard.InternalOperation() scopes.
JIT flag & project metadata
src/ActivationFunctions/SwishActivation.cs, src/AiDotNet.csproj, Directory.Packages.props
SwishActivation reports SupportsJitCompilation => true; added InternalsVisibleTo="AiDotNet.Serving.Tests"; bumped AiDotNet.Tensors version 0.27.1→0.27.3.

Sequence Diagram(s)

mermaid
sequenceDiagram
autonumber
participant Trainer as "Trainer/Caller"
participant Capsule as "CapsuleNetwork"
participant Loss as "LossFunction"
participant Layers as "Layers (per-layer UpdateParameters)"
Trainer->>Capsule: SetTrainingMode(true); ForwardWithMemory(input)
Capsule-->>Trainer: caches (_lastCapsuleOutputs, _lastInput, _lastExpectedOutput)
Trainer->>Loss: CalculateDerivative(prediction, expected) -> dL/dPrediction
Trainer->>Capsule: CalculateGradient() (uses cached state)
Capsule->>Layers: Backward pass -> set per-layer parameter gradients
Trainer->>Layers: For each layer: UpdateParameters(learningRate)
Layers-->>Trainer: parameters updated
Trainer->>Capsule: SetTrainingMode(false)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • #1045 — MixtureOfExperts changes overlap: this PR adds active-expert tracking and gated updates addressing layer-level gradient/parameter-update concerns noted in #1045. (strong code-level connection)

Possibly related PRs

Suggested labels

feature

"Cached forward memories hold the trace,
Xavier seeds and layers update in-place,
Pools and guards now find their place,
Tape and finite-diff choose the right gradient race,
Review with care — each change must face."

Note (blocking checklist): Flag any stubs, TODOs, simplified numerical approximations, or non-production helpers introduced by these changes (e.g., numerical-gradient helpers, Monte‑Carlo GP sampling, TryRestoreActivation type-resolution fallbacks) as blocking until reviewed for performance, determinism, and production robustness.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title directly addresses the PR's primary fixes: CapsuleNetwork scalar gradient bug and DBM output shape mismatch, which are concrete objectives from linked issues.
Linked Issues check ✅ Passed PR delivers systematic backward-pass fixes across 15+ models including CapsuleNetwork squash/scalar-gradient fix, DBM output-shape and construction fixes, TrainWithTape/optimizer wiring, recursive parameter buffer handling, and other infrastructure hardening addressing both #1049 and #1036 requirements.
Out of Scope Changes check ✅ Passed All changes align with stated objectives: neural network backward-pass corrections, training infrastructure hardening, layer registration, deserialization support, and infrastructure improvements. No extraneous modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 87.50% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/nn-backward-training-bugs-1049

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/NeuralNetworks/DeepBoltzmannMachine.cs (1)

243-249: 🧹 Nitpick | 🔵 Trivial

Keep the default visible/output size tied to one source of truth.

This fixes the current mismatch, but hardcoding 128 twice makes the same regression easy to reintroduce the next time the default visible size changes.

♻️ Suggested cleanup
 public DeepBoltzmannMachine()
     : this(new NeuralNetworkArchitecture<T>(
         inputType: Enums.InputType.OneDimensional,
         taskType: Enums.NeuralNetworkTaskType.Regression,
-        inputSize: 128,
-        outputSize: 128),
+        inputSize: DefaultVisibleSize,
+        outputSize: DefaultVisibleSize),
         epochs: 10, learningRate: MathHelper.GetNumericOperations<T>().FromDouble(0.0001),
         activationFunction: (IActivationFunction<T>?)null)

Add near the fields:

private const int DefaultVisibleSize = 128;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/NeuralNetworks/DeepBoltzmannMachine.cs` around lines 243 - 249, Add a
single source-of-truth constant for the default visible/output size and use it
in the DeepBoltzmannMachine constructor instead of hardcoding 128; specifically,
declare something like private const int DefaultVisibleSize = 128 near the class
fields and update the constructor call to new
NeuralNetworkArchitecture<T>(inputSize: DefaultVisibleSize, outputSize:
DefaultVisibleSize, ...) so both inputSize and outputSize reference
DefaultVisibleSize.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/NeuralNetworks/CapsuleNetwork.cs`:
- Around line 525-537: CalculateGradient currently ignores its loss argument and
never backprops the auxiliary reconstruction term, so when UseAuxiliaryLoss is
true the reconstruction loss is only reported and not used for training; update
CalculateGradient to include the auxiliary branch gradient by computing the
derivative of the reconstruction loss w.r.t. the network outputs and adding it
to lossDerivative (weighted by the reconstruction/auxiliary weight) before
constructing Tensor<T> currentGradient, or alternatively stop adding the
auxiliary term to totalLoss if you intend not to backprop it. Reference
CalculateGradient, _lossFunction.CalculateDerivative, UseAuxiliaryLoss, the
auxiliary/reconstruction loss function or reconstruction network (e.g.,
_reconstructionLoss or _reconstructionNetwork) and the totalLoss/auxiliary
weight so you combine gradients element-wise (lossDerivative += auxWeight *
auxDerivative) and then continue to build currentGradient.

---

Outside diff comments:
In `@src/NeuralNetworks/DeepBoltzmannMachine.cs`:
- Around line 243-249: Add a single source-of-truth constant for the default
visible/output size and use it in the DeepBoltzmannMachine constructor instead
of hardcoding 128; specifically, declare something like private const int
DefaultVisibleSize = 128 near the class fields and update the constructor call
to new NeuralNetworkArchitecture<T>(inputSize: DefaultVisibleSize, outputSize:
DefaultVisibleSize, ...) so both inputSize and outputSize reference
DefaultVisibleSize.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 33ed860b-4b80-4367-8150-2c3a28dcef6b

📥 Commits

Reviewing files that changed from the base of the PR and between a5a8f85 and 37b061d.

📒 Files selected for processing (2)
  • src/NeuralNetworks/CapsuleNetwork.cs
  • src/NeuralNetworks/DeepBoltzmannMachine.cs

Comment thread src/NeuralNetworks/CapsuleNetwork.cs Outdated
…n + DBM output size

CapsuleNetwork (Sabour et al. 2017) — 10/17 → 16/17 tests passing:
- CalculateGradient: use LossFunction.CalculateDerivative for proper dL/dOutput
  instead of passing scalar loss as [1] tensor (dimension mismatch crash)
- Train: use ForwardWithMemory (training mode) instead of Predict (inference mode)
  so layers cache state for backward pass
- Train: use per-layer UpdateParameters(lr) with configurable learning rate from
  CapsuleNetworkOptions instead of replacing parameters with gradient vector
- CalculateGradient: collect parameter gradients via GetParameterGradients()
  instead of backward-propagated input gradients

PrimaryCapsuleLayer backward: handle SquashActivation Jacobian [N,D,D] when stored
as ScalarActivation (was only handled for VectorActivation path) — BatchMatMul
instead of element-wise multiply fixes broadcast shape crash.

DeepBoltzmannMachine (Salakhutdinov & Hinton 2009): outputSize=128 matching
inputSize=128 for generative reconstruction (was outputSize=1).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Mar 30, 2026

Copy link
Copy Markdown

Deployment failed with the following error:

Resource is limited - try again in 24 hours (more than 100, code: "api-deployments-free-per-day").

Learn More: https://vercel.com/franklins-projects-02a0b5a0?upgradeToPro=build-rate-limit

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/NeuralNetworks/CapsuleNetwork.cs (1)

538-542: ⚠️ Potential issue | 🔴 Critical

Auxiliary loss is still disconnected from backprop.

lossDerivative is seeded only from _lossFunction.CalculateDerivative(...), so the reconstruction term added to totalLoss never reaches any layer even when UseAuxiliaryLoss is true. Either add AuxiliaryLossWeight * d(reconstructionLoss)/dPrediction here before constructing currentGradient, or stop including auxiliaryLoss in the reported training objective until that gradient path exists.

As per coding guidelines, incomplete features and code paths that silently do nothing are blocking.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/NeuralNetworks/CapsuleNetwork.cs` around lines 538 - 542, The auxiliary
reconstruction loss is not connected to backprop because lossDerivative is built
only from _lossFunction.CalculateDerivative(...); update the gradient before
constructing currentGradient by adding the auxiliary term: compute the
derivative of the reconstruction loss w.r.t. the network predictions (e.g.,
dReconstruction/dPrediction via your reconstruction module or its loss class)
and add AuxiliaryLossWeight * dReconstruction/dPrediction into the
lossDerivative array (matching shapes of
_lastCapsuleOutputs/_lastExpectedOutput) so currentGradient combines both
primary and auxiliary gradients; if no reconstruction-derivative helper exists,
add one (or else remove auxiliaryLoss from the reported objective).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/NeuralNetworks/CapsuleNetwork.cs`:
- Around line 550-559: The method currently flattens layer parameter gradients
into a local List<T> and returns a Vector<T>, which is unused; remove that
allocation and make the method side-effect-only: delete the paramGradients
List<T> creation and the loop that copies layer.GetParameterGradients() into it,
change the method signature to return void (or stop returning the Vector) so it
only calls layer.GetParameterGradients() to populate layer-held gradients for
subsequent UpdateParameters(...), and update any callers (e.g., places invoking
CalculateGradient(...)) to treat it as a void operation; keep references to
Layers and GetParameterGradients() and ensure UpdateParameters() continues to
read gradients from each layer.

In `@src/NeuralNetworks/Options/CapsuleNetworkOptions.cs`:
- Around line 10-13: The LearningRate property on CapsuleNetworkOptions
currently allows 0, negative, NaN, and Infinity; add input validation in the
LearningRate setter (or in the CapsuleNetworkOptions constructor if immutable)
to reject non-finite or non-positive values and throw an
ArgumentOutOfRangeException (or ArgumentException) with a clear message; ensure
the check uses double.IsFinite (or !double.IsNaN/IsInfinity) and value > 0 so
invalid configs fail fast before Train() is called.

---

Duplicate comments:
In `@src/NeuralNetworks/CapsuleNetwork.cs`:
- Around line 538-542: The auxiliary reconstruction loss is not connected to
backprop because lossDerivative is built only from
_lossFunction.CalculateDerivative(...); update the gradient before constructing
currentGradient by adding the auxiliary term: compute the derivative of the
reconstruction loss w.r.t. the network predictions (e.g.,
dReconstruction/dPrediction via your reconstruction module or its loss class)
and add AuxiliaryLossWeight * dReconstruction/dPrediction into the
lossDerivative array (matching shapes of
_lastCapsuleOutputs/_lastExpectedOutput) so currentGradient combines both
primary and auxiliary gradients; if no reconstruction-derivative helper exists,
add one (or else remove auxiliaryLoss from the reported objective).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 52b00d75-c9cc-4cc6-b041-f0777bc8c4b2

📥 Commits

Reviewing files that changed from the base of the PR and between 37b061d and 222d939.

📒 Files selected for processing (3)
  • src/NeuralNetworks/CapsuleNetwork.cs
  • src/NeuralNetworks/Layers/PrimaryCapsuleLayer.cs
  • src/NeuralNetworks/Options/CapsuleNetworkOptions.cs

Comment thread src/NeuralNetworks/CapsuleNetwork.cs Outdated
Comment thread src/NeuralNetworks/Options/CapsuleNetworkOptions.cs Outdated
…or RBM

DeepBoltzmannMachine:
- outputSize=128 matching inputSize=128 (generative reconstruction)
- Reduced to 2 hidden layers per original paper (was 4 RBM layers causing
  sigmoid saturation collapse)
- Fixed DenseLayer output dimension mismatch (was using wrong layer size)

RBMLayer: Xavier/Glorot initialization per Glorot & Bengio 2010 instead of
ad-hoc range scaling. Keeps sigmoid activations in linear regime for
gradient flow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rialization

GlobalPoolingLayer: Add deserialization support in DeserializationHelper with
PoolingType restoration from metadata. Add GetMetadata override to serialize
PoolingType for correct reconstruction during Clone/deserialization.

Add TryRestoreActivation<T> helper for reusable activation function
restoration from serialized metadata across all layer deserializers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Helpers/LayerHelper.cs`:
- Around line 759-760: The code currently computes hidden1 and hidden2 with
hardcoded fallbacks and ties hidden2 to architecture.OutputSize (variables
hidden1/hidden2), which can collapse the latent stack; change the
CreateDefault{ModelName}Layers helper signature to accept optional int?
hiddenLayer1Size and int? hiddenLayer2Size (or pull these from the model Options
class), replace the hardcoded Math.Min(500, ... * 4) with configurable defaults
(e.g., use Options.DefaultHidden1 or a constant) for hidden1, and compute
hidden2 from the hidden stack (e.g., fallback to something like
Math.Min(Options.DefaultHidden2, hidden1 * defaultScale)) rather than using
architecture.OutputSize; ensure no magic numbers remain and default behavior
mirrors the Options class.

In `@src/NeuralNetworks/Layers/RBMLayer.cs`:
- Around line 347-356: The Xavier initialization comment and implementation
disagree: currently sigma is computed as Math.Sqrt(2.0 / (_visibleUnits +
_hiddenUnits)) and randomTensor is mapped to U(-σ,σ) which yields a conservative
variance; either change the implementation in RBMLayer (variables/methods:
sigma, randomTensor, Engine.TensorMultiplyScalar, shiftTensor, _weights) to use
standard Xavier/uniform by computing limit = Math.Sqrt(6.0 / (_visibleUnits +
_hiddenUnits)) and mapping randomTensor to U(-limit, limit), or keep the current
scaling but update the comment to explicitly state this is an intentional
“modified/conservative Xavier-style initialization” chosen to reduce sigmoid
saturation and note the resulting variance difference versus standard Glorot;
make only one of these changes so comment and code match.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a0fb62a0-c972-4bb9-98e8-5b82cf97beec

📥 Commits

Reviewing files that changed from the base of the PR and between 222d939 and 9579a3b.

📒 Files selected for processing (2)
  • src/Helpers/LayerHelper.cs
  • src/NeuralNetworks/Layers/RBMLayer.cs

Comment thread src/Helpers/LayerHelper.cs Outdated
CapsuleNetwork.CalculateGradient:
- Include auxiliary reconstruction loss gradient when UseAuxiliaryLoss
  is true: d(MSE)/d(capsuleOutput) = 2*(output-input)/N weighted by
  AuxiliaryLossWeight. Previously reconstruction loss was computed but
  never backpropagated — training ignored the documented objective.
- Change return type to void — caller only needs side effect of
  setting layer parameter gradients, the returned Vector was unused.

CapsuleNetworkOptions.LearningRate:
- Add validation rejecting NaN, Infinity, zero, and negative values.
  Throws ArgumentOutOfRangeException at the options boundary instead
  of silently corrupting training later.

LayerHelper.CreateDefaultDBMLayers:
- Derive hidden2 from hidden1 (latent capacity) instead of
  architecture.OutputSize (projection head). Small output configs
  no longer collapse the latent bottleneck.
- Extract magic numbers to named constants.

RBMLayer Xavier initialization:
- Fix from sqrt(2/(fan_in+fan_out)) to sqrt(6/(fan_in+fan_out)) for
  uniform distribution variant (Glorot & Bengio 2010). The previous
  value was the normal distribution formula applied to uniform bounds.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
src/Helpers/LayerHelper.cs (1)

760-763: ⚠️ Potential issue | 🟠 Major

Partial fix: hidden layer sizes still not configurable via method parameters.

The named constants and hidden2 = hidden1 derivation address part of the prior feedback, but the coding guidelines require CreateDefault{ModelName}Layers to accept model-specific config parameters with defaults matching the Options class. Users cannot override these defaults without modifying the helper.

Per the prior review, the signature should accept optional hidden layer size parameters:

int? hiddenLayer1Size = null,
int? hiddenLayer2Size = null

Then use them:

-        int hidden1 = Math.Min(DefaultDbmHiddenCap, inputSize * DefaultDbmHiddenMultiplier);
-        int hidden2 = hidden1;
+        int hidden1 = hiddenLayer1Size ?? Math.Min(DefaultDbmHiddenCap, inputSize * DefaultDbmHiddenMultiplier);
+        int hidden2 = hiddenLayer2Size ?? hidden1;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Helpers/LayerHelper.cs` around lines 760 - 763, The
CreateDefault{ModelName}Layers helper still hardcodes hidden sizes via
DefaultDbmHiddenCap/DefaultDbmHiddenMultiplier and hidden2 = hidden1; change the
method signature to accept optional parameters int? hiddenLayer1Size = null,
int? hiddenLayer2Size = null and compute hidden1/hidden2 by using the provided
nullable parameters when present, otherwise falling back to the existing default
calculation (using DefaultDbmHiddenCap and DefaultDbmHiddenMultiplier) so
defaults match the Options class; update references to hidden1 and hidden2
accordingly and remove the unconfigurable hidden2 = hidden1 assignment so
callers can override each layer independently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/NeuralNetworks/CapsuleNetwork.cs`:
- Around line 532-537: Add a null-check for _lastExpectedOutput alongside the
existing checks for _lastCapsuleOutputs and _lastInput (in the method that
computes the loss derivative, e.g., CalculateGradient or where
_lastCapsuleOutputs is validated) so you don't rely on the null-forgiving
operator; update the conditional to throw InvalidOperationException if
_lastExpectedOutput is null, then call _lossFunction.CalculateDerivative using
the non-null _lastExpectedOutput without the trailing "!".
- Around line 418-420: The method sets training mode via SetTrainingMode(true)
before calling ForwardWithMemory(input) but never resets it, so ensure training
mode is always cleared: wrap the training-phase work (SetTrainingMode(true) and
ForwardWithMemory call) in a try/finally and call SetTrainingMode(false) in the
finally block; this guarantees that even on exceptions or subsequent
Predict()/Train() calls the network leaves training mode and layers won’t keep
cached state.

In `@src/NeuralNetworks/Layers/RBMLayer.cs`:
- Around line 347-359: Remove the stale contradictory comment lines that
reference σ = sqrt(2 / (fan_in + fan_out)) above the Xavier/Glorot uniform
initialization; keep only the correct comment describing a = sqrt(6 / (fan_in +
fan_out)) and the following implementation that creates randomTensor via
Tensor<T>.CreateRandom(_hiddenUnits, _visibleUnits), scales it with
Engine.TensorMultiplyScalar and shifts it to produce _weights — i.e., delete the
old σ comment block so comments align with the actual code using _visibleUnits,
_hiddenUnits, a, and _weights.

---

Duplicate comments:
In `@src/Helpers/LayerHelper.cs`:
- Around line 760-763: The CreateDefault{ModelName}Layers helper still hardcodes
hidden sizes via DefaultDbmHiddenCap/DefaultDbmHiddenMultiplier and hidden2 =
hidden1; change the method signature to accept optional parameters int?
hiddenLayer1Size = null, int? hiddenLayer2Size = null and compute
hidden1/hidden2 by using the provided nullable parameters when present,
otherwise falling back to the existing default calculation (using
DefaultDbmHiddenCap and DefaultDbmHiddenMultiplier) so defaults match the
Options class; update references to hidden1 and hidden2 accordingly and remove
the unconfigurable hidden2 = hidden1 assignment so callers can override each
layer independently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 075b22e7-ee49-41d7-a3b2-ef502f6ecb61

📥 Commits

Reviewing files that changed from the base of the PR and between ffd4805 and 59ca97b.

📒 Files selected for processing (4)
  • src/Helpers/LayerHelper.cs
  • src/NeuralNetworks/CapsuleNetwork.cs
  • src/NeuralNetworks/Layers/RBMLayer.cs
  • src/NeuralNetworks/Options/CapsuleNetworkOptions.cs

Comment thread src/NeuralNetworks/CapsuleNetwork.cs Outdated
Comment thread src/NeuralNetworks/CapsuleNetwork.cs Outdated
Comment thread src/NeuralNetworks/Layers/RBMLayer.cs Outdated
franklinic and others added 2 commits March 30, 2026 00:29
Predict was bypassing all layers and calling EncodeAudio directly, returning
raw 512-dim embedding. Now properly forwards through the full layer chain.
Train was computing loss but never doing backprop or parameter updates.
Now uses ForwardWithMemory, CalculateDerivative, Backpropagate, and per-layer
UpdateParameters.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ReconstructionLayer: Add GetMetadata override to serialize Hidden1Dim,
Hidden2Dim, and UseVectorActivation. Add deserialization case in
DeserializationHelper that restores the correct hidden layer dimensions
from metadata. Fixes Clone_ShouldProduceIdenticalOutput for CapsuleNetwork.

CapsuleNetwork now 17/17 tests passing (was 10/17).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
franklinic and others added 4 commits March 30, 2026 08:25
…ssing

DeepBoltzmannMachine (Salakhutdinov & Hinton 2009):
- GetParameters: return Layer-level parameters (used during training) instead
  of disconnected _layerWeights/_layerBiases (CD pretraining only). Fixes
  Training_ShouldChangeParameters and GradientFlow tests.
- Remove BatchNormalizationLayer from default architecture — DBMs per the
  original paper don't use BN. BatchNorm with batch size 1 normalizes away
  input differences, causing DifferentInputs/ScaledInput test failures.
- Adam optimizer: use DBM's configured learning rate (0.0001) instead of
  Adam's default (0.001) to prevent oscillation in deep sigmoid networks.
  Fixes MoreData_ShouldNotDegrade.

RBMLayer: Xavier/Glorot initialization per Glorot & Bengio 2010.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AudioVisualEventLocalization (Tian et al. 2018): Fix layer chain to be
sequentially valid — use architecture's inputSize for first layer, connect
all layers with compatible dimensions. Predict uses layer chain instead of
bypassing. Train uses ForwardWithMemory + backprop + parameter updates.
8/17 → 13/17.

DeepBoltzmannMachine: Fix AdamOptimizerOptions generic type arguments.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Keep embeddingDimension throughout temporal and cross-modal attention layers
instead of compressing to 2 dimensions. Fixes OutputDimension, Training, and
several forward pass tests. 8/17 → 13/17.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Conv3DLayer: Add GetMetadata override to serialize KernelSize, Stride, Padding.
Without metadata, deserialization defaults to padding=0 instead of the
original padding=1, producing different output shapes.

DeserializationHelper: Restore activation function for Conv3DLayer from
metadata using TryRestoreActivation.

VoxelCNN now 17/17 tests passing (was 16/17).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 141 out of 141 changed files in this pull request and generated 6 comments.

Comments suppressed due to low confidence (1)

src/RetrievalAugmentedGeneration/Embeddings/ONNXSentenceTransformer.cs:210

  • GenerateFallbackEmbedding() claims determinism but uses string.GetHashCode(), which is randomized per process in modern .NET and will produce different vectors across runs. Since EmbeddingModelBase already implements a stable FNV-1a–based fallback, consider removing this override or delegating to base.GenerateFallbackEmbedding(text) (or reimplement using the same stable hash) to keep unit tests and offline behavior deterministic.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/TimeSeries/NBEATSModel.cs
Comment thread src/NeuralNetworks/Layers/SSM/TTTLayer.cs
Comment thread src/NeuralNetworks/Layers/GraphConvolutionalLayer.cs
Comment thread src/NeuralNetworks/Layers/ReconstructionLayer.cs
Comment thread src/Helpers/DeserializationHelper.cs
Comment thread src/AiDotNet.Generators/TrainableParameterGenerator.cs
franklinic and others added 4 commits April 5, 2026 08:07
Tensors v0.27.5 fixes FusedLinear to record on GradientTape during
training (ooples/AiDotNet.Tensors#102). This resolves zero-gradient
failures for all networks using DenseLayer with TrainWithTape.

Remaining 3 RNN failures need Tensor.MatrixMultiply and Engine.TensorAdd
tape recording (ooples/AiDotNet.Tensors#104).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…backward

Tensors v0.28.0 adds lazy tensor graph compiler and fused backward
kernels. FusedLinear tape recording confirmed working from v0.27.5.

Remaining 11 failures (HopeNetwork 6, DeepBelief 3, Hyperbolic 2) use
domain-specific operations (Poincare ball, contrastive divergence,
Hopfield energy) that aren't standard tensor ops — these need
analytical gradient implementations per their research papers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 141 out of 141 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (1)

src/RetrievalAugmentedGeneration/Embeddings/ONNXSentenceTransformer.cs:210

  • This override uses text.ToLowerInvariant().GetHashCode() to seed fallback embeddings. string.GetHashCode() is randomized per process on modern .NET, so this makes fallbacks non-deterministic and conflicts with the deterministic FNV-1a fallback implemented in EmbeddingModelBase. Prefer delegating to the base implementation or using a stable hash here as well.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread vercel.json
Comment thread src/NeuralNetworks/CapsuleNetwork.cs
Comment thread src/TimeSeries/NBEATSModel.cs
Comment thread src/GaussianProcesses/DeepGaussianProcess.cs
ooples and others added 6 commits April 5, 2026 19:57
Changes to vercel.json (routing, headers, functions config) now trigger
a Vercel rebuild/deploy as intended.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…aining

TrainWithTape now captures the margin loss and adds the weighted reconstruction
loss to LastLoss, so diagnostics and training reflect both objectives as intended
by the CapsuleNetwork architecture.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Predict now always produces model predictions via PredictSingle instead of
returning stored training data for in-sample indices. This eliminates data
leakage where Predict was dependent on prior training data rather than solely
on the given input features.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…Forward

Kuu Cholesky was recomputed n+d times (once per variance point + once per output
dimension). Now computed once and reused via the pre-computed decomposition
overload of SolveLinearSystem.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…r validation

CycleGAN: took master's double→T conversion for _cycleConsistencyLambda and
_identityLambda fields. SpikingLayer: kept both PR's constructor validation
and master's pragma restore placement.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 139 out of 140 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (3)

src/TimeSeries/NBEATSModel.cs:1

  • This training loop updates block parameters using a scalar derived from the output error, but it never computes parameter gradients (no backward pass / autodiff), so UpdateParameters has no correct gradient signal to apply (and may effectively be a no-op or apply stale gradients). Consider switching N-BEATS training to a gradient-based approach (e.g., tape/autodiff over block computations) or implementing explicit backprop for each block so UpdateParameters(lr) applies per-parameter gradients computed for the current sample/batch.
    src/RetrievalAugmentedGeneration/Embeddings/ONNXSentenceTransformer.cs:1
  • string.GetHashCode() is randomized per process on modern .NET, so this fallback embedding is not deterministic across runs (despite the comment/intent). Since EmbeddingModelBase now provides a stable FNV-1a-based fallback, consider removing this override to use the base implementation, or update it to use a stable hash (e.g., FNV-1a) rather than GetHashCode().
    src/RetrievalAugmentedGeneration/Embeddings/OpenAIEmbeddingModel.cs:1
  • Catching AggregateException broadly can mask non-transient failures (e.g., auth/permission/validation errors) and silently return a non-semantic fallback embedding. Consider narrowing the filter to only AggregateException cases whose InnerExceptions are network/timeouts (or unwrap and rethrow non-transient causes), or gate the fallback behind an explicit configuration flag intended for tests/offline mode.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/NeuralNetworks/HTMNetwork.cs
Comment thread src/NeuralNetworks/CapsuleNetwork.cs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: systematic neural network backward pass and training bugs across 15+ models fix: 140+ Neural Network ModelFamilyTests failures across 16 models

3 participants