feat!(fastsiam): more faithful FastSiam implementation - #110
feat!(fastsiam): more faithful FastSiam implementation#110giuliano-macedo wants to merge 1 commit into
Conversation
📝 WalkthroughWalkthrough
ChangesFastSiam Single-Branch Refactor
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@minerva/models/ssl/fastsiam.py`:
- Around line 105-106: The `avg_pooling` docstring in `FastSiam` does not match
the behavior in `forward`, where pooling happens before flattening and is not
conditional on `flatten=True`. Update the parameter description near
`avg_pooling` to reflect the actual control flow in `FastSiam.forward`, making
it clear that average pooling is applied independently of the flatten option.
- Around line 176-180: The loss normalization in the FastSiam loss computation
is incorrect because the accumulated terms from the loop are being divided by
self.k and then shifted by 1, which mis-scales losses for arbitrary k. Update
the return in the loss calculation inside the FastSiam method that builds loss
from ps and zs so it averages over all k+1 accumulated terms instead of applying
a constant offset, using the existing loss accumulation logic and the
self.k/self.k + 1 loop bounds as the guide.
- Around line 135-143: The SimSiam projector in the FastSiam model is still
hard-coded to use 512 instead of the constructor’s hid_dim, so only the
prediction head respects user configuration. Update the projection_head
construction in FastSiam to use hid_dim as the hidden layer size, matching how
prediction_head already wires hid_dim through, so both heads follow the
configured dimensions consistently.
In `@tests/models/ssl/test_fastsiam.py`:
- Around line 26-35: The current FastSiam forward test only checks output
shapes, so it can miss regressions where the stop-gradient behavior is removed.
Update test_forward in the FastSiam test to also verify the contract of
FastSiam.forward by asserting the returned z is detached (for example, that it
does not require gradients) while p still remains part of autograd. Use the
existing self.model(view) call and the z, p outputs to place these assertions
alongside the current shape checks.
- Around line 120-140: The test for FastSiam flatten behavior is not actually
validating the flatten=False path because the mock backbone already returns a 2D
tensor, so the model can still pass even if it always flattens. Update
test_flatten_flag in test_fastsiam.py to use a non-flattened backbone output
shape from mock_backbone (for example a 3D tensor with last dimension 2048),
then assert that FastSiam preserves that extra dimension when flatten is
disabled while still producing the expected z and p outputs. Keep the check
focused on the flatten attribute and the forward path in FastSiam.
- Around line 104-119: The FastSiam pooling test currently uses a backbone
output with 1x1 spatial size, so the avg_pooling=False path is not actually
exercised. Update test_avg_pooling_flag in test_fastsiam.py to make
self.mock_backbone return a larger spatial feature map and adjust in_dim
accordingly, then keep the assertions on model.global_avg_pool and output shapes
so the forward path without pooling is truly validated.
- Around line 37-48: The current loss tests only assert that
`_single_step_arbitrary_k` and the `k == 3` path return a scalar tensor, so they
can miss arithmetic regressions like an incorrect extra offset or scaling.
Update `test_single_step_arbitrary_k` and the corresponding
`test_single_step_k3` (the other loss-path test) to use a numeric oracle by
either computing the expected loss manually or stubbing `forward`/`loss_fn`
deterministically, then assert the returned value matches that expected number
for both paths.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 182d823d-61ca-4572-84f1-ee2511e4fddc
📒 Files selected for processing (2)
minerva/models/ssl/fastsiam.pytests/models/ssl/test_fastsiam.py
Checklist before requesting a review
Hi, first of all thanks for the implementation.
Motivation
While reading the FastSiam implementation, I've noticed some divergences when comparing it with the original paper.
Although I have seem good results with the current implementation, I still think it is different from the original paper authors intent.
So this PR is more of a faithful implementation of the paper, than a de-facto "fix".
The main divergences that I observed are:
The paper explicitly states:
Since SimSiam is an implementation that shares weights between branches, this doesn't seem to be the same algorithm described in the paper.
In fact, this implementation seems to decay into the BYOL architecture.
And also, a minor issue that doesn't really impact these points is that the
update_target_branchmethod was defined, but not called anywhere.If the user simply just pass the model to
trainer.fit(), it would equate to dead code and the target network will never be updated.Summary of the changes
For$k=3$ I've inlined the loss function because that will probaly be the most common value for $k$ that the user will take, therefore inlining it would be faster than the fallback for arbitrary $k$ that I
used Lightly's implementation as a base.
I've made some breaking changes to the arguments of this class because some of them weren't used (like
test_metricandnum_classes), others were EMA-related (momentum), and I've changed the case of theKargument to follow the style-guide of other classes in Minerva that uses lower case instead.
Also I've the
flattenandavg_pooling:parameter that is useful when not using a CNN-based backend, a similar setup that theSimCLRclass uses.And finally, I've added a simple check to garantee the user passes$k+1$ views in his Dataset, because this algorithm assumes that the user will use k views for the target branch, and 1 view for the prediction branch, therefore $k+1$ .
Reference total loss derived from the paper
To help with the review, here are the total loss for specific$k$ that I derived from the paper, and the general formula:
Final Notes
Please let me know if my assertions are incorrect, also, if there is any specific context behind the original design choices that I might have missed, I am more than happy to discuss this further and make any necessary adjustments based on your feedback!