avalanche icon indicating copy to clipboard operation
avalanche copied to clipboard

Changes in Dynamic Modules

Open AlbinSou opened this issue 1 year ago • 8 comments

I tried to implement the changes that would fix bugs described in Issue #1591 and also allow users to use Dynamic modules more easily outside of avalanche strategies.

To do so, I made several major changes.

  • Remove old avalanche_model_adaptation method and replace it with a new version which does things differently. Instead of iterating on all modules, it iterates only on childrens, and does so recursively in order to explore the whole hierarchy. It still iterates on the hierarchy of nn.Modules and catches DynamicModules to adapt them just like before.
  • Added a method adapt() to dynamicmodule that will call the new "avalanche_model_adaptation" on itself and recursively on it's submodules
  • Added an _auto_adapt attribute to all DynamicModules that triggers or not the adaptation inside the avalanche_model_adaptation recursion (defaults to True). In particular, this attribute does not prevent the user to call directly module.adapt() if he wants, however if the module needs to be adapted as a second level or more module inside this recursion, it will not get adapted. This is the part that fixes the bug described in #1591, since that way we can instantiate IncrementalClassifiers inside the MultiHeadClassifier that will not be automatically adapted but rather adapted manually by the MultiHeadClassifier only if the experience used for adaptation contains the task id related to this IncrementalClassifier.
  • Made a few clarification changes in IncrementalClassifier and Multiheadclassifier by replacing occurences of adaptation() implementation by train_adaptation and eval_adaptation, where if the eval_adaptation needs to be the same than train_adaptation I just call train_adaptation inside eval_adaptation (nested adaptation() override were a bit complex to understand and could lead to confusion)

For now tests do not pass, but some of them I am not sure what behaviour they were checking and if we really want this behaviour. I will also add some more tests to check that the behaviour in #1591 is solved (my local test says it is fixed for now).

AlbinSou avatar Feb 16 '24 23:02 AlbinSou

Pull Request Test Coverage Report for Build 8038239946

Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

Details

  • 0 of 115 (100.0%) changed or added relevant lines in 6 files are covered.
  • 60 unchanged lines in 1 file lost coverage.
  • Overall coverage increased (+0.1%) to 64.167%

Files with Coverage Reduction New Missed Lines %
avalanche/training/supervised/lamaml_v2.py 60 0.0%
<!-- Total: 60
Totals Coverage Status
Change from base Build 7933962330: 0.1%
Covered Lines: 20155
Relevant Lines: 31410

💛 - Coveralls

coveralls avatar Feb 19 '24 12:02 coveralls

Made a few clarification changes in IncrementalClassifier and Multiheadclassifier by replacing occurences of adaptation() implementation by train_adaptation and eval_adaptation, where if the eval_adaptation needs to be the same than train_adaptation I just call train_adaptation inside eval_adaptation (nested adaptation() override were a bit complex to understand and could lead to confusion)

I think we could also remove train_adaptation and eval_adaptation and have a single method. After, you can detect train/eval mode with a single if when necessary, so it seems to be adding unnecessary complexity in the common case.

AntonioCarta avatar Feb 19 '24 15:02 AntonioCarta

@AntonioCarta How would you like to manage the adapt() and adaptation() function ? I wanted to keep the name of the "adaptation" also to keep backward compatibility but now it's a bit confusing to have two methods adapt() and adaptation()

AlbinSou avatar Feb 19 '24 15:02 AlbinSou

@AntonioCarta How would you like to manage the adapt() and adaptation() function ? I wanted to keep the name of the "adaptation" also to keep backward compatibility but now it's a bit confusing to have two methods adapt() and adaptation()

The advantage of the current solution is that it's backward compatible. If we don't want to rename the old method we could rename the new one as rec_adapt for recursive adaptation?

I was also thinking about a test case to check that the recursive calls don't break the #1591 fix:

model = ABasicDynamicModule(ABasicModule(MultiTaskClassifier(...)))
model.adapt(exp)  # here it's calling rec_adapt from the "base" layer, not the MultiTaskClassifier. It should still work.
# assert that wrong heads are not adapted

AntonioCarta avatar Feb 20 '24 09:02 AntonioCarta

@AntonioCarta How would you like to manage the adapt() and adaptation() function ? I wanted to keep the name of the "adaptation" also to keep backward compatibility but now it's a bit confusing to have two methods adapt() and adaptation()

The advantage of the current solution is that it's backward compatible. If we don't want to rename the old method we could rename the new one as rec_adapt for recursive adaptation?

I was also thinking about a test case to check that the recursive calls don't break the #1591 fix:

model = ABasicDynamicModule(ABasicModule(MultiTaskClassifier(...)))
model.adapt(exp)  # here it's calling rec_adapt from the "base" layer, not the MultiTaskClassifier. It should still work.
# assert that wrong heads are not adapted

I added a test for that at the end of test_models.DynamicModulesTest. Yes, maybe I will rename to make it clearer. Do you know how I can fix these failed Coveralls?

AlbinSou avatar Feb 20 '24 09:02 AlbinSou

Made a few clarification changes in IncrementalClassifier and Multiheadclassifier by replacing occurences of adaptation() implementation by train_adaptation and eval_adaptation, where if the eval_adaptation needs to be the same than train_adaptation I just call train_adaptation inside eval_adaptation (nested adaptation() override were a bit complex to understand and could lead to confusion)

I think we could also remove train_adaptation and eval_adaptation and have a single method. After, you can detect train/eval mode with a single if when necessary, so it seems to be adding unnecessary complexity in the common case.

Yes I agree, I don't remember why we included them in the first place

AlbinSou avatar Feb 22 '24 10:02 AlbinSou

@AntonioCarta On my side everything is ok. I was thinking maybe to include an example on how to use dynamic modules outside and inside of a strategy (call to recursive_adaptation and explanation on how avalanche_model_adaptation works). Another thing is that I changed the location of avalanche_model_adaptation for now it's inside dynamic_modules.py before it was in models/utils.py. For now I import it in utils so that ppl don't have to change their imports, but it's a bit weird. The reason I put it inside dynamic_modules is that this function is needed in recursive_adaptation() method so putting it to utils was leading to annoying recursive imports.

I also just noticed that NCM is still using eval_adaptation so I will need to change that. I don't know how this has not been caught by some tests.

AlbinSou avatar Feb 23 '24 13:02 AlbinSou

Ok. I will wait to merge this after release to be on the safe side.

I was thinking maybe to include an example on how to use dynamic modules outside and inside of a strategy

we already have examples of that in the notebooks. Maybe you can just update them?

AntonioCarta avatar Feb 23 '24 13:02 AntonioCarta