Created a new pull request with current changes.
It seems like converting BN to GroupNorm is enough even without accumulating gradients. You may check the notebooks Iāve shared
Thatās very interesting @kcturgutlu . I have tried ~2 weeks ago Group Norm and Instance Norm on the petās notebook with resnet50 and the acc was not goodā¦
I will try again using snippets from your code and report backā¦
Instance norm didnāt work for me as well. Yeah maybe do groupnorm+acc and groupnorm+no acc
I have repeated your notebook as it is on MNIST dataset, and yes Group Norm worked impressively wellā¦
However, when I changed the data into the Petās dataset, it didnāt workā¦
Here are the results and the modified notebook:
model = vgg16_bn (chosen since itās sequential - easy to manipulate)
data = Petās dataset
Experiment Results
-
No Accumulation
batch_size = 64
acc = 0.90 -
Naive Accumulation
effective_batch_size = 64
step = 32
bs = 2
acc = 0.63 -
Accumulation + BnFreeze
effective_batch_size = 64
step = 32
bs = 2
acc = 0.63 -
Increase BN Momentum (on current batch stat)
effective_batch_size = 64
step = 32
bs = 2
acc = 0.21 -
Decrease BN Momentum (on current batch stat)
effective_batch_size = 64
step = 32
bs = 2
acc = 0.65 -
Replace BN with Instance Norm
effective_batch_size = 64
step = 32
bs = 2
acc = 0.03 -
Replace BN with Group Norm
num_groups=4
effective_batch_size = 64
step = 32
bs = 2
acc = 0.07 -
ResNet18 + Replace BN with Group Norm
num_groups=4
effective_batch_size = 64
step = 32
bs = 2
acc = 0.03 -
ResNet18 + Replace BN with Group Norm no Accumulation
num_groups=4
bs = 2
acc = 0.02
I suspect that the MNIST dataset is too simple. Perhaps it is already normalized, like what the the Human Protein Atlas comp winner did (@pudae), when he normalized each image alone before passing them to the model, which is of course weird, and donāt think can be generalized to other datasets.
I have tried different runs with num_groups=1,2,4,8,16,32,64
seems the best is 64 :
-
Replace BN with Group Norm
num_groups=4
effective_batch_size = 64
step = 32
bs = 2
acc = 0.36 -
ResNet18 + Replace BN with Group Norm
num_groups=4
effective_batch_size = 64
step = 32
bs = 2
acc = 0.12 -
ResNet18 + Replace BN with Group Norm no Accumulation
num_groups=4
bs = 2
acc = 0.44
I wonder whether groupnorm.weight
, groupnorm.bias
, groupnorm.eps
need to be tuned for each dataset and not simply copy it from BN?
I am testing changing them one by one
Why I am getting different accuracy with this change:
accuracy = 0.36:
groupnorm.weight = bn.weight
accuracy = 0.20:
groupnorm.weight = torch.nn.Parameter(bn.weight)
When I divide weight it will be changed from parameter to tensor, so I need to convert it back to parameter. But doing so, seems breaks something even without changing the value of the weight
I must be missing something obvious here, can you help me to understand.
I would think that you could just do the following, waiting to step
and zero_grad
till after N batches. Maybe does not work with reduce=mean
? Were these two points not available when you wrote this a bit ago?
class AccumulateStep(LearnerCallback):
""" Does accumlated step every nth step by accumulating gradients """
def __init__(self, learn:Learner, n_step:int = 5):
super().__init__(learn)
self.n_step = n_step
def on_backward_end(self, num_batch,**kwargs):
if (num_batch % self.n_step) == 0: self.opt.step()
def on_step_end(self, num_batch, **kwargs):
if (num_batch % self.n_step) == 0: self.opt.zero_grad()
Stepping and zeroing grad happens automatically whenever on_backward_end
and on_step_end
returns False. So you would be anyway stepping with this code youāve written. As for zeroing gradients itās only necessary when you do the actual accumulated step, so you should also skip zeroing it in order to accumulate the gradients.
You may check loss_batch
to see how step and zero_grad works by default:
This is the implementation:
We use return {'skip_step':True, 'skip_zero':True}
to skip.
Hope this helps
Thanks for pointing this out. You are right , it was calling the stepper on all steps with my code.
Is there a doc page for this new callback, I canāt seem to find it?
I wrote this helper, which I think we should add to the library to allow people to use this callback:
def accum_grad(learn:Learner, n_step:int=1)->Learner:
"Add accumulation of gradients of `n_step` during training."
learn.callback_fns.append(partial(AccumulateScheduler, n_step=n_step))
return learn
Any feedback on that?
Yes that might be helpful, you may find the callback here: https://github.com/fastai/fastai/blob/fbbc6f91e8e8e91ba0e3cc98ac148f6b26b9e041/fastai/train.py#L99-L134.
But there is no docs I guess. The thing is batchnorm is still a problem with it.
That would be because the person that introduced that feature never followed up with docs
So sorry for that, itās 100% my fault . I was hoping to get stable results and fixing batchnorm issue. I should probably create a doc for it explaining what this callback is for and what is itās limitations.
What do you think is the simplest way to fix the batchnorm issue in a way that doesnāt change too many parts of fastai?
Probably using instance norm or group norm, but in experiments it didnāt work for every dataset. For example, it worked in case of mnist but not for dog breeds.
For BatchNorm, I realize what was wrong with my layer now that we have had to debug a vanilla implementation with Jeremy. I donāt think itās fixable without using the modified version Jeremy will introduce in next course, but Iāll think about some way to do it (basically the update on training mode is done with the statistics of the batch, not the moving average, the moving average is only used at validation, so we need to find a way to trick BatchNorm into using the stats of the accumulated batches).
I understand your comment a bit better after course 10. It may be useful to have a variant that mixes both solutions (running batchnorm and accumulating batchnorm). You might already have it worked out; I will have to sleep over it.
When I watched lesson 10, I said to myself, YESā¦ For proper statistics we should accumulate sums and sums of squares and not the moving averageā¦ How clear it is nowā¦ This is why studying the basics in part2 of the course is so importantā¦ I didnāt quite understood BN, so I didnāt know what was the problem in running BN moving averageā¦
I think my source of confusion is that during validation the BN is working with moving average, right?
But couldnāt we do the validation too, with Jeremyās modified method in the training mode (using the stats of the accumulated batches)? and why validation is different from training?
Thanks Sylvain for your exceptional efforts and thanks to Jeremyā¦ You havenāt let us downā¦ Jeremy replied 1 month ago that
But here is a great developer who couldnāt let it go until he solved it with you Sylvainā¦
Now I am trying to use Jeremyās RunningBatchNorm
class with my pets notebook experimentsā¦ I have created two classes RunningBatchNorm2d
and RunningBatchNorm1d
to replace all BN types in resnet18ā¦
I have tried it for couple of hours and modified the resnet18 to include this class instead of BNā¦
Here is my notebook in nbviewer
I am getting this error when try to run fit or getting the learn.summary()ā¦ I will try more to debug it and let you know how things will goā¦ Just in case anybody have an idea, please let me knowā¦ I think this is something related to different dimensions arrangements with our modified class and the normal BNā¦
RuntimeError Traceback (most recent call last)
<ipython-input-58-bc39e9e85f86> in <module>
----> 1 learn.summary()
~/anaconda3/envs/fastai-v1/lib/python3.7/site-packages/fastai/callbacks/hooks.py in model_summary(m, n)
164 def model_summary(m:Learner, n:int=70):
165 "Print a summary of `m` using a output text width of `n` chars"
--> 166 info = layers_info(m)
167 header = ["Layer (type)", "Output Shape", "Param #", "Trainable"]
168 res = "=" * n + "\n"
~/anaconda3/envs/fastai-v1/lib/python3.7/site-packages/fastai/callbacks/hooks.py in layers_info(m)
158 func = lambda m:list(map(get_layer_name, flatten_model(m)))
159 layers_names = func(m.model) if isinstance(m, Learner) else func(m)
--> 160 layers_sizes, layers_params, layers_trainable = params_size(m)
161 layer_info = namedtuple('Layer_Information', ['Layer', 'OutputSize', 'Params', 'Trainable'])
162 return list(map(layer_info, layers_names, layers_sizes, layers_params, layers_trainable))
~/anaconda3/envs/fastai-v1/lib/python3.7/site-packages/fastai/callbacks/hooks.py in params_size(m, size)
146 with hook_outputs(flatten_model(m)) as hook_o:
147 with hook_params(flatten_model(m))as hook_p:
--> 148 x = m.eval()(*x) if is_listy(x) else m.eval()(x)
149 output_size = [((o.stored.shape[1:]) if o.stored is not None else None) for o in hook_o]
150 params = [(o.stored if o.stored is not None else (None,None)) for o in hook_p]
~/anaconda3/envs/fastai-v1/lib/python3.7/site-packages/torch/nn/modules/module.py in __call__(self, *input, **kwargs)
487 result = self._slow_forward(*input, **kwargs)
488 else:
--> 489 result = self.forward(*input, **kwargs)
490 for hook in self._forward_hooks.values():
491 hook_result = hook(self, input, result)
~/anaconda3/envs/fastai-v1/lib/python3.7/site-packages/torch/nn/modules/container.py in forward(self, input)
90 def forward(self, input):
91 for module in self._modules.values():
---> 92 input = module(input)
93 return input
94
~/anaconda3/envs/fastai-v1/lib/python3.7/site-packages/torch/nn/modules/module.py in __call__(self, *input, **kwargs)
487 result = self._slow_forward(*input, **kwargs)
488 else:
--> 489 result = self.forward(*input, **kwargs)
490 for hook in self._forward_hooks.values():
491 hook_result = hook(self, input, result)
~/anaconda3/envs/fastai-v1/lib/python3.7/site-packages/torch/nn/modules/container.py in forward(self, input)
90 def forward(self, input):
91 for module in self._modules.values():
---> 92 input = module(input)
93 return input
94
~/anaconda3/envs/fastai-v1/lib/python3.7/site-packages/torch/nn/modules/module.py in __call__(self, *input, **kwargs)
487 result = self._slow_forward(*input, **kwargs)
488 else:
--> 489 result = self.forward(*input, **kwargs)
490 for hook in self._forward_hooks.values():
491 hook_result = hook(self, input, result)
~/anaconda3/envs/fastai-v1/lib/python3.7/site-packages/torch/nn/modules/container.py in forward(self, input)
90 def forward(self, input):
91 for module in self._modules.values():
---> 92 input = module(input)
93 return input
94
~/anaconda3/envs/fastai-v1/lib/python3.7/site-packages/torch/nn/modules/module.py in __call__(self, *input, **kwargs)
487 result = self._slow_forward(*input, **kwargs)
488 else:
--> 489 result = self.forward(*input, **kwargs)
490 for hook in self._forward_hooks.values():
491 hook_result = hook(self, input, result)
~/anaconda3/envs/fastai-v1/lib/python3.7/site-packages/torch/nn/modules/container.py in forward(self, input)
90 def forward(self, input):
91 for module in self._modules.values():
---> 92 input = module(input)
93 return input
94
~/anaconda3/envs/fastai-v1/lib/python3.7/site-packages/torch/nn/modules/module.py in __call__(self, *input, **kwargs)
487 result = self._slow_forward(*input, **kwargs)
488 else:
--> 489 result = self.forward(*input, **kwargs)
490 for hook in self._forward_hooks.values():
491 hook_result = hook(self, input, result)
~/anaconda3/envs/fastai-v1/lib/python3.7/site-packages/torch/nn/modules/container.py in forward(self, input)
90 def forward(self, input):
91 for module in self._modules.values():
---> 92 input = module(input)
93 return input
94
~/anaconda3/envs/fastai-v1/lib/python3.7/site-packages/torch/nn/modules/module.py in __call__(self, *input, **kwargs)
487 result = self._slow_forward(*input, **kwargs)
488 else:
--> 489 result = self.forward(*input, **kwargs)
490 for hook in self._forward_hooks.values():
491 hook_result = hook(self, input, result)
~/anaconda3/envs/fastai-v1/lib/python3.7/site-packages/torch/nn/modules/conv.py in forward(self, input)
318 def forward(self, input):
319 return F.conv2d(input, self.weight, self.bias, self.stride,
--> 320 self.padding, self.dilation, self.groups)
321
322
RuntimeError: Given groups=1, weight of size [128, 64, 1, 1], expected input[1, 128, 28, 28] to have 64 channels, but got 128 channels instead
Did you tried to run some dummy tensor with the right shape through your adapted BN layer?
Something like this but with the BN layer:
conv_layer = nn.Conv2d(1,16,3)
x = torch.randn(10, 28, 28)
conv_layer(x).shape
# this works too without creating a nn.Conv2d instance: nn.Conv2d(1,16,3)(x)
The problem is with errors in models that donāt have a āline-by-lineā forward method that the stack trace is quite confusingā¦
Another way to debug this is to insert a debugger layer (however the option from able should be easier).
Running BatchNorm is not a good idea as I understand from the BatchReNorm paper. I also tried it on a recent kaggle competition and it behaved exactly the way they described in the paper. Please, let me know if anyone got it to work though
This section: