Repository: pytorch/ignite

Issue:
WeightsHistHandler should plot all weights (incl those without grad)
## 🚀 Feature

Currently, `WeightsHistHandler` do not log weights without grad:
https://github.com/pytorch/ignite/blob/a168476762061f74af64793bce1b2d5b6e9b3f15/ignite/contrib/handlers/tensorboard_logger.py#L434-L435

Let's put an option to enable logging all weights. Maybe, we can make it even without option and just log everything ?


Meanwhile, here is a workaround code for TensorboardLogger:
```python
from ignite.contrib.handlers import TensorboardLogger
from ignite.contrib.handlers.tensorboard_logger import WeightsHistHandler

class FixedWeightsHistHandler(WeightsHistHandler):
    
    def __call__(self, engine, logger, event_name):
        if not isinstance(logger, TensorboardLogger):
            raise RuntimeError("Handler 'WeightsHistHandler' works only with TensorboardLogger")

        global_step = engine.state.get_event_attrib_value(event_name)
        tag_prefix = f"{self.tag}/" if self.tag else ""
        for name, p in self.model.named_parameters():            
            name = name.replace(".", "/")
            logger.writer.add_histogram(
                tag=f"{tag_prefix}weights/{name}", values=p.data.detach().cpu().numpy(), global_step=global_step,
            )

```

WeightsHistHandler should plot all weights (incl those without grad)
## 🚀 Feature

Currently, `WeightsHistHandler` do not log weights without grad:
https://github.com/pytorch/ignite/blob/a168476762061f74af64793bce1b2d5b6e9b3f15/ignite/contrib/handlers/tensorboard_logger.py#L434-L435

Let's put an option to enable logging all weights. Maybe, we can make it even without option and just log everything ?


Meanwhile, here is a workaround code for TensorboardLogger:
```python
from ignite.contrib.handlers import TensorboardLogger
from ignite.contrib.handlers.tensorboard_logger import WeightsHistHandler

class FixedWeightsHistHandler(WeightsHistHandler):
    
    def __call__(self, engine, logger, event_name):
        if not isinstance(logger, TensorboardLogger):
            raise RuntimeError("Handler 'WeightsHistHandler' works only with TensorboardLogger")

        global_step = engine.state.get_event_attrib_value(event_name)
        tag_prefix = f"{self.tag}/" if self.tag else ""
        for name, p in self.model.named_parameters():            
            name = name.replace(".", "/")
            logger.writer.add_histogram(
                tag=f"{tag_prefix}weights/{name}", values=p.data.detach().cpu().numpy(), global_step=global_step,
            )

```



Discussion:
Assume you have a partial frozen model, it makes sense to only trace the evolution of the weight which are updated.
The question is does checking if p.grad is not None is what we want ?
What about weights modification with other computation ? like pca_low_rank ?

I see what you mean. Ultimately, we would like to have an optional list of weights to plot ? 
Something like:

```python
from ignite.contrib.handlers import TensorboardLogger
from ignite.contrib.handlers.tensorboard_logger import WeightsHistHandler


class FixedWeightsHistHandler(WeightsHistHandler):

    def __init__(self, model, tag=None, names_whitelist=None):
        super(WeightsHistHandler, self).__init__(model, tag=tag)
        self.names_whitelist = names_whitelist
    
    def __call__(self, engine, logger, event_name):
        if not isinstance(logger, TensorboardLogger):
            raise RuntimeError("Handler 'WeightsHistHandler' works only with TensorboardLogger")

        global_step = engine.state.get_event_attrib_value(event_name)
        tag_prefix = f"{self.tag}/" if self.tag else ""
        for name, p in self.model.named_parameters():
            
            if self.names_whitelist is not None and name not in self.names_whitelist:
                continue

            name = name.replace(".", "/")
            logger.writer.add_histogram(
                tag=f"{tag_prefix}weights/{name}", values=p.data.detach().cpu().numpy(), global_step=global_step,
            )


tb_logger = TensorboardLogger(log_dir="experiments/tb_logs")
# ...
tb_logger.attach(
    trainer,
    event_name=Events.ITERATION_COMPLETED,
    log_handler=FixedWeightsHistHandler(model, names_whitelist=[n for n, _ in model.named_parameters() if "conv" in n])
)
```
@DhDeepLIT I thought you wanted to avoid the case of inexisting grads and log weights in any case ?
```python
if p.grad is None:
    continue
```
Or you are now talking about `GradsHistHandler` ?
Yes that is my need, but on your side, what was THE thing you wanted to do ?
Consider that the zero_grad() function can also set grad to None.
Thus checking if the grad attribute is None or Not may not be the right option.
BTW your proposed patch is fine for my need, the whitelist is a great idea too.
FYI: zero_grad does not set grads to None by default but just zeros them :)

https://github.com/pytorch/pytorch/blob/33e9a0b5f6674bd429bda689534e1c987b38cf6e/torch/nn/modules/module.py#L1785-L1794

Maybe it should be the other way around though :
it is easier to filter the weights/grad we want if a key is present in the module full name say you want to check ALL fc layer. It would be better to check if 'fc' is in the named parameter of the module, right ?
I agree that non-exact match for names_whitelist could be helpful...
https://pytorch.org/docs/stable/generated/torch.optim.Optimizer.zero_grad.html

There is a option with 'set_to_none=True'
Assume you have a partial frozen model, it makes sense to only trace the evolution of the weight which are updated.
The question is does checking if p.grad is not None is what we want ?
What about weights modification with other computation ? like pca_low_rank ?

I see what you mean. Ultimately, we would like to have an optional list of weights to plot ? 
Something like:

```python
from ignite.contrib.handlers import TensorboardLogger
from ignite.contrib.handlers.tensorboard_logger import WeightsHistHandler


class FixedWeightsHistHandler(WeightsHistHandler):

    def __init__(self, model, tag=None, names_whitelist=None):
        super(WeightsHistHandler, self).__init__(model, tag=tag)
        self.names_whitelist = names_whitelist
    
    def __call__(self, engine, logger, event_name):
        if not isinstance(logger, TensorboardLogger):
            raise RuntimeError("Handler 'WeightsHistHandler' works only with TensorboardLogger")

        global_step = engine.state.get_event_attrib_value(event_name)
        tag_prefix = f"{self.tag}/" if self.tag else ""
        for name, p in self.model.named_parameters():
            
            if self.names_whitelist is not None and name not in self.names_whitelist:
                continue

            name = name.replace(".", "/")
            logger.writer.add_histogram(
                tag=f"{tag_prefix}weights/{name}", values=p.data.detach().cpu().numpy(), global_step=global_step,
            )


tb_logger = TensorboardLogger(log_dir="experiments/tb_logs")
# ...
tb_logger.attach(
    trainer,
    event_name=Events.ITERATION_COMPLETED,
    log_handler=FixedWeightsHistHandler(model, names_whitelist=[n for n, _ in model.named_parameters() if "conv" in n])
)
```
@DhDeepLIT I thought you wanted to avoid the case of inexisting grads and log weights in any case ?
```python
if p.grad is None:
    continue
```
Or you are now talking about `GradsHistHandler` ?
Yes that is my need, but on your side, what was THE thing you wanted to do ?
Consider that the zero_grad() function can also set grad to None.
Thus checking if the grad attribute is None or Not may not be the right option.
BTW your proposed patch is fine for my need, the whitelist is a great idea too.
FYI: zero_grad does not set grads to None by default but just zeros them :)

https://github.com/pytorch/pytorch/blob/33e9a0b5f6674bd429bda689534e1c987b38cf6e/torch/nn/modules/module.py#L1785-L1794

Maybe it should be the other way around though :
it is easier to filter the weights/grad we want if a key is present in the module full name say you want to check ALL fc layer. It would be better to check if 'fc' is in the named parameter of the module, right ?
I agree that non-exact match for names_whitelist could be helpful...
https://pytorch.org/docs/stable/generated/torch.optim.Optimizer.zero_grad.html

There is a option with 'set_to_none=True'

Resolving patch:
diff --git a/ignite/contrib/handlers/tensorboard_logger.py b/ignite/contrib/handlers/tensorboard_logger.py
--- a/ignite/contrib/handlers/tensorboard_logger.py
+++ b/ignite/contrib/handlers/tensorboard_logger.py
@@ -459,6 +459,9 @@ class WeightsHistHandler(BaseWeightsHistHandler):
                 event_name=Events.ITERATION_COMPLETED,
                 log_handler=WeightsHistHandler(model, whitelist=weight_selector)
             )
+
+    ..  versionchanged:: 0.5.0
+        optional argument `whitelist` added.
     """
 
     def __init__(


Test patch: