Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
jantic
GitHub Repository: jantic/deoldify
Path: blob/master/fastai/callbacks/hooks.py
781 views
1
"Hooks provide extensibility at the model level."
2
from ..torch_core import *
3
from ..callback import *
4
from ..basic_train import *
5
from ..basic_data import *
6
7
__all__ = ['ActivationStats', 'Hook', 'HookCallback', 'Hooks', 'hook_output', 'hook_outputs',
8
'model_sizes', 'num_features_model', 'model_summary', 'dummy_eval', 'dummy_batch']
9
10
class Hook():
11
"Create a hook on `m` with `hook_func`."
12
def __init__(self, m:nn.Module, hook_func:HookFunc, is_forward:bool=True, detach:bool=True):
13
self.hook_func,self.detach,self.stored = hook_func,detach,None
14
f = m.register_forward_hook if is_forward else m.register_backward_hook
15
self.hook = f(self.hook_fn)
16
self.removed = False
17
18
def hook_fn(self, module:nn.Module, input:Tensors, output:Tensors):
19
"Applies `hook_func` to `module`, `input`, `output`."
20
if self.detach:
21
input = (o.detach() for o in input ) if is_listy(input ) else input.detach()
22
output = (o.detach() for o in output) if is_listy(output) else output.detach()
23
self.stored = self.hook_func(module, input, output)
24
25
def remove(self):
26
"Remove the hook from the model."
27
if not self.removed:
28
self.hook.remove()
29
self.removed=True
30
31
def __enter__(self, *args): return self
32
def __exit__(self, *args): self.remove()
33
34
class Hooks():
35
"Create several hooks on the modules in `ms` with `hook_func`."
36
def __init__(self, ms:Collection[nn.Module], hook_func:HookFunc, is_forward:bool=True, detach:bool=True):
37
self.hooks = [Hook(m, hook_func, is_forward, detach) for m in ms]
38
39
def __getitem__(self,i:int)->Hook: return self.hooks[i]
40
def __len__(self)->int: return len(self.hooks)
41
def __iter__(self): return iter(self.hooks)
42
@property
43
def stored(self): return [o.stored for o in self]
44
45
def remove(self):
46
"Remove the hooks from the model."
47
for h in self.hooks: h.remove()
48
49
def __enter__(self, *args): return self
50
def __exit__ (self, *args): self.remove()
51
52
def _hook_inner(m,i,o): return o if isinstance(o,Tensor) else o if is_listy(o) else list(o)
53
54
def hook_output (module:nn.Module, detach:bool=True, grad:bool=False)->Hook:
55
"Return a `Hook` that stores activations of `module` in `self.stored`"
56
return Hook(module, _hook_inner, detach=detach, is_forward=not grad)
57
58
def hook_outputs(modules:Collection[nn.Module], detach:bool=True, grad:bool=False)->Hooks:
59
"Return `Hooks` that store activations of all `modules` in `self.stored`"
60
return Hooks(modules, _hook_inner, detach=detach, is_forward=not grad)
61
62
class HookCallback(LearnerCallback):
63
"Callback that can be used to register hooks on `modules`. Implement the corresponding function in `self.hook`."
64
def __init__(self, learn:Learner, modules:Sequence[nn.Module]=None, do_remove:bool=True):
65
super().__init__(learn)
66
self.modules,self.do_remove = modules,do_remove
67
68
def on_train_begin(self, **kwargs):
69
"Register the `Hooks` on `self.modules`."
70
if not self.modules:
71
self.modules = [m for m in flatten_model(self.learn.model)
72
if hasattr(m, 'weight')]
73
self.hooks = Hooks(self.modules, self.hook)
74
75
def on_train_end(self, **kwargs):
76
"Remove the `Hooks`."
77
if self.do_remove: self.remove()
78
79
def remove(self):
80
if getattr(self, 'hooks', None): self.hooks.remove()
81
def __del__(self): self.remove()
82
83
class ActivationStats(HookCallback):
84
"Callback that record the mean and std of activations."
85
86
def on_train_begin(self, **kwargs):
87
"Initialize stats."
88
super().on_train_begin(**kwargs)
89
self.stats = []
90
91
def hook(self, m:nn.Module, i:Tensors, o:Tensors)->Tuple[Rank0Tensor,Rank0Tensor]:
92
"Take the mean and std of `o`."
93
return o.mean().item(),o.std().item()
94
def on_batch_end(self, train, **kwargs):
95
"Take the stored results and puts it in `self.stats`"
96
if train: self.stats.append(self.hooks.stored)
97
def on_train_end(self, **kwargs):
98
"Polish the final result."
99
super().on_train_end(**kwargs)
100
self.stats = tensor(self.stats).permute(2,1,0)
101
102
def dummy_batch(m: nn.Module, size:tuple=(64,64))->Tensor:
103
"Create a dummy batch to go through `m` with `size`."
104
ch_in = in_channels(m)
105
return one_param(m).new(1, ch_in, *size).requires_grad_(False).uniform_(-1.,1.)
106
107
def dummy_eval(m:nn.Module, size:tuple=(64,64)):
108
"Pass a `dummy_batch` in evaluation mode in `m` with `size`."
109
m.eval()
110
return m(dummy_batch(m, size))
111
#return m.eval()(dummy_batch(m, size))
112
113
def model_sizes(m:nn.Module, size:tuple=(64,64))->Tuple[Sizes,Tensor,Hooks]:
114
"Pass a dummy input through the model `m` to get the various sizes of activations."
115
with hook_outputs(m) as hooks:
116
x = dummy_eval(m, size)
117
return [o.stored.shape for o in hooks]
118
119
def num_features_model(m:nn.Module)->int:
120
"Return the number of output features for `model`."
121
sz = 64
122
while True:
123
try: return model_sizes(m, size=(sz,sz))[-1][1]
124
except Exception as e:
125
sz *= 2
126
if sz > 2048: raise
127
128
def total_params(m:nn.Module)->int:
129
params, trainable = 0, False
130
if hasattr(m, "weight") and hasattr(m.weight, "size"):
131
params += m.weight.numel()
132
trainable = m.weight.requires_grad
133
if hasattr(m, "bias") and hasattr(m.bias, "size"): params += m.bias.numel()
134
return params, trainable
135
136
def hook_params(modules:Collection[nn.Module])->Hooks:
137
return Hooks(modules, lambda m, i, o: total_params(m))
138
139
def params_size(m: Union[nn.Module,Learner], size: tuple = (3, 64, 64))->Tuple[Sizes, Tensor, Hooks]:
140
"Pass a dummy input through the model to get the various sizes. Returns (res,x,hooks) if `full`"
141
if isinstance(m, Learner):
142
if m.data.is_empty:
143
raise Exception("This is an empty `Learner` and `Learner.summary` requires some data to pass through the model.")
144
ds_type = DatasetType.Train if m.data.train_dl else (DatasetType.Valid if m.data.valid_dl else DatasetType.Test)
145
x = m.data.one_batch(ds_type=ds_type, detach=False, denorm=False)[0]
146
x = [o[:1] for o in x] if is_listy(x) else x[:1]
147
m = m.model
148
elif isinstance(m, nn.Module): x = next(m.parameters()).new(1, *size)
149
else: raise TypeError('You should either pass in a Learner or nn.Module')
150
with hook_outputs(flatten_model(m)) as hook_o:
151
with hook_params(flatten_model(m))as hook_p:
152
x = m.eval()(*x) if is_listy(x) else m.eval()(x)
153
output_size = [((o.stored.shape[1:]) if o.stored is not None else None) for o in hook_o]
154
params = [(o.stored if o.stored is not None else (None,None)) for o in hook_p]
155
params, trainables = map(list,zip(*params))
156
return output_size, params, trainables
157
158
def get_layer_name(layer:nn.Module)->str:
159
return str(layer.__class__).split(".")[-1].split("'")[0]
160
161
def layers_info(m:Collection[nn.Module]) -> Collection[namedtuple]:
162
func = lambda m:list(map(get_layer_name, flatten_model(m)))
163
layers_names = func(m.model) if isinstance(m, Learner) else func(m)
164
layers_sizes, layers_params, layers_trainable = params_size(m)
165
layer_info = namedtuple('Layer_Information', ['Layer', 'OutputSize', 'Params', 'Trainable'])
166
return list(map(layer_info, layers_names, layers_sizes, layers_params, layers_trainable))
167
168
def model_summary(m:Learner, n:int=70):
169
"Print a summary of `m` using a output text width of `n` chars"
170
info = layers_info(m)
171
header = ["Layer (type)", "Output Shape", "Param #", "Trainable"]
172
res = m.model.__class__.__name__ + "\n"
173
res += "=" * n + "\n"
174
res += f"{header[0]:<20} {header[1]:<20} {header[2]:<10} {header[3]:<10}\n"
175
res += "=" * n + "\n"
176
total_params = 0
177
total_trainable_params = 0
178
for layer, size, params, trainable in info:
179
if size is None: continue
180
total_params += int(params)
181
total_trainable_params += int(params) * trainable
182
size, trainable = str(list(size)), str(trainable)
183
res += f"{layer:<20} {size:<20} {int(params):<10,} {trainable:<10}\n"
184
res += "_" * n + "\n"
185
res += f"\nTotal params: {total_params:,}\n"
186
res += f"Total trainable params: {total_trainable_params:,}\n"
187
res += f"Total non-trainable params: {total_params - total_trainable_params:,}\n"
188
189
res += f"Optimized with {str(m.opt_func)[25:-1].replace('>', '')}\n"
190
if m.true_wd: res += f"Using true weight decay as discussed in https://www.fast.ai/2018/07/02/adam-weight-decay/ \n"
191
if "wd" in str(m.opt_func) or "weight_decay" in str(m.opt_func): res += f"\x1b[1;31m Specifying weight decay in the optimizer has no effect, Learner will overwrite \x1b[0m \n"
192
if "lr" in str(m.opt_func) or "learning_rate" in str(m.opt_func): res += f"\x1b[1;31m Specifying lr in the optimizer has no effect, pass it to fit or the defaults.lr will apply \x1b[0m \n"
193
res += f"Loss function : {m.loss_func.__class__.__name__}\n"
194
res += "=" * n + "\n"
195
res += "Callbacks functions applied \n"
196
res += "\n".join([f" {cbs.__class__.__name__}" for cbs in m.callbacks])
197
198
return PrettyString(res)
199
200
Learner.summary = model_summary
201
202