Gabriele Campanella commited on
Commit
efb420c
·
1 Parent(s): ca8858c

first commit

Browse files
Files changed (2) hide show
  1. README.md +13 -0
  2. vision_transformer.py +390 -0
README.md CHANGED
@@ -1,3 +1,16 @@
1
  ---
2
  license: cc-by-nc-sa-4.0
 
 
 
 
 
 
 
 
3
  ---
 
 
 
 
 
 
1
  ---
2
  license: cc-by-nc-sa-4.0
3
+ language:
4
+ - en
5
+ pipeline_tag: image-feature-extraction
6
+ tags:
7
+ - pathology
8
+ - neuropathology
9
+ - foundation_model
10
+ - vit
11
  ---
12
+
13
+ # neuroFM_HE20x
14
+
15
+ ViT-large (300M parameters) trained on a diverse neuropathology dataset.
16
+
vision_transformer.py ADDED
@@ -0,0 +1,390 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ #
3
+ # This source code is licensed under the Apache License, Version 2.0
4
+ # found in the LICENSE file in the root directory of this source tree.
5
+
6
+ # References:
7
+ # https://github.com/facebookresearch/dino/blob/main/vision_transformer.py
8
+ # https://github.com/rwightman/pytorch-image-models/tree/master/timm/models/vision_transformer.py
9
+ import os
10
+ import sys
11
+ sys.path.insert(0,'/sc/arion/projects/comppath_utils/envs/dinov2')
12
+ from functools import partial
13
+ import math
14
+ from typing import Sequence, Tuple, Union, Callable
15
+
16
+ import torch
17
+ import torch.nn as nn
18
+ import torch.utils.checkpoint
19
+ from torch.nn.init import trunc_normal_
20
+
21
+ from dinov2.layers import Mlp, PatchEmbed, SwiGLUFFNFused, MemEffAttention, NestedTensorBlock as Block
22
+
23
+
24
+ def named_apply(fn: Callable, module: nn.Module, name="", depth_first=True, include_root=False) -> nn.Module:
25
+ if not depth_first and include_root:
26
+ fn(module=module, name=name)
27
+ for child_name, child_module in module.named_children():
28
+ child_name = ".".join((name, child_name)) if name else child_name
29
+ named_apply(fn=fn, module=child_module, name=child_name, depth_first=depth_first, include_root=True)
30
+ if depth_first and include_root:
31
+ fn(module=module, name=name)
32
+ return module
33
+
34
+
35
+ class BlockChunk(nn.ModuleList):
36
+ def forward(self, x):
37
+ for b in self:
38
+ x = b(x)
39
+ return x
40
+
41
+
42
+ class DinoVisionTransformer(nn.Module):
43
+ def __init__(
44
+ self,
45
+ img_size=224,
46
+ patch_size=16,
47
+ in_chans=3,
48
+ embed_dim=768,
49
+ depth=12,
50
+ num_heads=12,
51
+ mlp_ratio=4.0,
52
+ qkv_bias=True,
53
+ ffn_bias=True,
54
+ proj_bias=True,
55
+ drop_path_rate=0.0,
56
+ drop_path_uniform=False,
57
+ init_values=None, # for layerscale: None or 0 => no layerscale
58
+ embed_layer=PatchEmbed,
59
+ act_layer=nn.GELU,
60
+ block_fn=Block,
61
+ ffn_layer="mlp",
62
+ block_chunks=1,
63
+ num_register_tokens=0,
64
+ interpolate_antialias=False,
65
+ interpolate_offset=0.1,
66
+ ):
67
+ """
68
+ Args:
69
+ img_size (int, tuple): input image size
70
+ patch_size (int, tuple): patch size
71
+ in_chans (int): number of input channels
72
+ embed_dim (int): embedding dimension
73
+ depth (int): depth of transformer
74
+ num_heads (int): number of attention heads
75
+ mlp_ratio (int): ratio of mlp hidden dim to embedding dim
76
+ qkv_bias (bool): enable bias for qkv if True
77
+ proj_bias (bool): enable bias for proj in attn if True
78
+ ffn_bias (bool): enable bias for ffn if True
79
+ drop_path_rate (float): stochastic depth rate
80
+ drop_path_uniform (bool): apply uniform drop rate across blocks
81
+ weight_init (str): weight init scheme
82
+ init_values (float): layer-scale init values
83
+ embed_layer (nn.Module): patch embedding layer
84
+ act_layer (nn.Module): MLP activation layer
85
+ block_fn (nn.Module): transformer block class
86
+ ffn_layer (str): "mlp", "swiglu", "swiglufused" or "identity"
87
+ block_chunks: (int) split block sequence into block_chunks units for FSDP wrap
88
+ num_register_tokens: (int) number of extra cls tokens (so-called "registers")
89
+ interpolate_antialias: (str) flag to apply anti-aliasing when interpolating positional embeddings
90
+ interpolate_offset: (float) work-around offset to apply when interpolating positional embeddings
91
+ """
92
+ super().__init__()
93
+ norm_layer = partial(nn.LayerNorm, eps=1e-6)
94
+
95
+ self.num_features = self.embed_dim = embed_dim # num_features for consistency with other models
96
+ self.num_tokens = 1
97
+ self.n_blocks = depth
98
+ self.num_heads = num_heads
99
+ self.patch_size = patch_size
100
+ self.num_register_tokens = num_register_tokens
101
+ self.interpolate_antialias = interpolate_antialias
102
+ self.interpolate_offset = interpolate_offset
103
+
104
+ self.patch_embed = embed_layer(img_size=img_size, patch_size=patch_size, in_chans=in_chans, embed_dim=embed_dim)
105
+ num_patches = self.patch_embed.num_patches
106
+
107
+ self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dim))
108
+ self.pos_embed = nn.Parameter(torch.zeros(1, num_patches + self.num_tokens, embed_dim))
109
+ assert num_register_tokens >= 0
110
+ self.register_tokens = (
111
+ nn.Parameter(torch.zeros(1, num_register_tokens, embed_dim)) if num_register_tokens else None
112
+ )
113
+
114
+ if drop_path_uniform is True:
115
+ dpr = [drop_path_rate] * depth
116
+ else:
117
+ dpr = [x.item() for x in torch.linspace(0, drop_path_rate, depth)] # stochastic depth decay rule
118
+
119
+ if ffn_layer == "mlp":
120
+ ffn_layer = Mlp
121
+ elif ffn_layer == "swiglufused" or ffn_layer == "swiglu":
122
+ ffn_layer = SwiGLUFFNFused
123
+ elif ffn_layer == "identity":
124
+ def f(*args, **kwargs):
125
+ return nn.Identity()
126
+
127
+ ffn_layer = f
128
+ else:
129
+ raise NotImplementedError
130
+
131
+ blocks_list = [
132
+ block_fn(
133
+ dim=embed_dim,
134
+ num_heads=num_heads,
135
+ mlp_ratio=mlp_ratio,
136
+ qkv_bias=qkv_bias,
137
+ proj_bias=proj_bias,
138
+ ffn_bias=ffn_bias,
139
+ drop_path=dpr[i],
140
+ norm_layer=norm_layer,
141
+ act_layer=act_layer,
142
+ ffn_layer=ffn_layer,
143
+ init_values=init_values,
144
+ )
145
+ for i in range(depth)
146
+ ]
147
+ if block_chunks > 0:
148
+ self.chunked_blocks = True
149
+ chunked_blocks = []
150
+ chunksize = depth // block_chunks
151
+ for i in range(0, depth, chunksize):
152
+ # this is to keep the block index consistent if we chunk the block list
153
+ chunked_blocks.append([nn.Identity()] * i + blocks_list[i : i + chunksize])
154
+ self.blocks = nn.ModuleList([BlockChunk(p) for p in chunked_blocks])
155
+ else:
156
+ self.chunked_blocks = False
157
+ self.blocks = nn.ModuleList(blocks_list)
158
+
159
+ self.norm = norm_layer(embed_dim)
160
+ self.head = nn.Identity()
161
+
162
+ self.mask_token = nn.Parameter(torch.zeros(1, embed_dim))
163
+
164
+ self.init_weights()
165
+
166
+ def init_weights(self):
167
+ trunc_normal_(self.pos_embed, std=0.02)
168
+ nn.init.normal_(self.cls_token, std=1e-6)
169
+ if self.register_tokens is not None:
170
+ nn.init.normal_(self.register_tokens, std=1e-6)
171
+ named_apply(init_weights_vit_timm, self)
172
+
173
+ def interpolate_pos_encoding(self, x, w, h):
174
+ previous_dtype = x.dtype
175
+ npatch = x.shape[1] - 1
176
+ N = self.pos_embed.shape[1] - 1
177
+ if npatch == N and w == h:
178
+ return self.pos_embed
179
+ pos_embed = self.pos_embed.float()
180
+ class_pos_embed = pos_embed[:, 0]
181
+ patch_pos_embed = pos_embed[:, 1:]
182
+ dim = x.shape[-1]
183
+ w0 = w // self.patch_size
184
+ h0 = h // self.patch_size
185
+ M = int(math.sqrt(N)) # Recover the number of patches in each dimension
186
+ assert N == M * M
187
+ kwargs = {}
188
+ if self.interpolate_offset:
189
+ # Historical kludge: add a small number to avoid floating point error in the interpolation, see https://github.com/facebookresearch/dino/issues/8
190
+ # Note: still needed for backward-compatibility, the underlying operators are using both output size and scale factors
191
+ sx = float(w0 + self.interpolate_offset) / M
192
+ sy = float(h0 + self.interpolate_offset) / M
193
+ kwargs["scale_factor"] = (sx, sy)
194
+ else:
195
+ # Simply specify an output size instead of a scale factor
196
+ kwargs["size"] = (w0, h0)
197
+ patch_pos_embed = nn.functional.interpolate(
198
+ patch_pos_embed.reshape(1, M, M, dim).permute(0, 3, 1, 2),
199
+ mode="bicubic",
200
+ antialias=self.interpolate_antialias,
201
+ **kwargs,
202
+ )
203
+ assert (w0, h0) == patch_pos_embed.shape[-2:]
204
+ patch_pos_embed = patch_pos_embed.permute(0, 2, 3, 1).view(1, -1, dim)
205
+ return torch.cat((class_pos_embed.unsqueeze(0), patch_pos_embed), dim=1).to(previous_dtype)
206
+
207
+ def prepare_tokens_with_masks(self, x, masks=None):
208
+ B, nc, w, h = x.shape
209
+ x = self.patch_embed(x)
210
+ if masks is not None:
211
+ x = torch.where(masks.unsqueeze(-1), self.mask_token.to(x.dtype).unsqueeze(0), x)
212
+
213
+ x = torch.cat((self.cls_token.expand(x.shape[0], -1, -1), x), dim=1)
214
+ x = x + self.interpolate_pos_encoding(x, w, h)
215
+
216
+ if self.register_tokens is not None:
217
+ x = torch.cat(
218
+ (
219
+ x[:, :1],
220
+ self.register_tokens.expand(x.shape[0], -1, -1),
221
+ x[:, 1:],
222
+ ),
223
+ dim=1,
224
+ )
225
+
226
+ return x
227
+
228
+ def forward_features_list(self, x_list, masks_list):
229
+ x = [self.prepare_tokens_with_masks(x, masks) for x, masks in zip(x_list, masks_list)]
230
+ for blk in self.blocks:
231
+ x = blk(x)
232
+
233
+ all_x = x
234
+ output = []
235
+ for x, masks in zip(all_x, masks_list):
236
+ x_norm = self.norm(x)
237
+ output.append(
238
+ {
239
+ "x_norm_clstoken": x_norm[:, 0],
240
+ "x_norm_regtokens": x_norm[:, 1 : self.num_register_tokens + 1],
241
+ "x_norm_patchtokens": x_norm[:, self.num_register_tokens + 1 :],
242
+ "x_prenorm": x,
243
+ "masks": masks,
244
+ }
245
+ )
246
+ return output
247
+
248
+ def forward_features(self, x, masks=None):
249
+ if isinstance(x, list):
250
+ return self.forward_features_list(x, masks)
251
+
252
+ x = self.prepare_tokens_with_masks(x, masks)
253
+
254
+ for blk in self.blocks:
255
+ x = blk(x)
256
+
257
+ x_norm = self.norm(x)
258
+ return {
259
+ "x_norm_clstoken": x_norm[:, 0],
260
+ "x_norm_regtokens": x_norm[:, 1 : self.num_register_tokens + 1],
261
+ "x_norm_patchtokens": x_norm[:, self.num_register_tokens + 1 :],
262
+ "x_prenorm": x,
263
+ "masks": masks,
264
+ }
265
+
266
+ def _get_intermediate_layers_not_chunked(self, x, n=1):
267
+ x = self.prepare_tokens_with_masks(x)
268
+ # If n is an int, take the n last blocks. If it's a list, take them
269
+ output, total_block_len = [], len(self.blocks)
270
+ blocks_to_take = range(total_block_len - n, total_block_len) if isinstance(n, int) else n
271
+ for i, blk in enumerate(self.blocks):
272
+ x = blk(x)
273
+ if i in blocks_to_take:
274
+ output.append(x)
275
+ assert len(output) == len(blocks_to_take), f"only {len(output)} / {len(blocks_to_take)} blocks found"
276
+ return output
277
+
278
+ def _get_intermediate_layers_chunked(self, x, n=1):
279
+ x = self.prepare_tokens_with_masks(x)
280
+ output, i, total_block_len = [], 0, len(self.blocks[-1])
281
+ # If n is an int, take the n last blocks. If it's a list, take them
282
+ blocks_to_take = range(total_block_len - n, total_block_len) if isinstance(n, int) else n
283
+ for block_chunk in self.blocks:
284
+ for blk in block_chunk[i:]: # Passing the nn.Identity()
285
+ x = blk(x)
286
+ if i in blocks_to_take:
287
+ output.append(x)
288
+ i += 1
289
+ assert len(output) == len(blocks_to_take), f"only {len(output)} / {len(blocks_to_take)} blocks found"
290
+ return output
291
+
292
+ def get_intermediate_layers(
293
+ self,
294
+ x: torch.Tensor,
295
+ n: Union[int, Sequence] = 1, # Layers or n last layers to take
296
+ reshape: bool = False,
297
+ return_class_token: bool = False,
298
+ norm=True,
299
+ ) -> Tuple[Union[torch.Tensor, Tuple[torch.Tensor]]]:
300
+ if self.chunked_blocks:
301
+ outputs = self._get_intermediate_layers_chunked(x, n)
302
+ else:
303
+ outputs = self._get_intermediate_layers_not_chunked(x, n)
304
+ if norm:
305
+ outputs = [self.norm(out) for out in outputs]
306
+ class_tokens = [out[:, 0] for out in outputs]
307
+ outputs = [out[:, 1 + self.num_register_tokens :] for out in outputs]
308
+ if reshape:
309
+ B, _, w, h = x.shape
310
+ outputs = [
311
+ out.reshape(B, w // self.patch_size, h // self.patch_size, -1).permute(0, 3, 1, 2).contiguous()
312
+ for out in outputs
313
+ ]
314
+ if return_class_token:
315
+ return tuple(zip(outputs, class_tokens))
316
+ return tuple(outputs)
317
+
318
+ def forward(self, *args, is_training=False, **kwargs):
319
+ ret = self.forward_features(*args, **kwargs)
320
+ if is_training:
321
+ return ret
322
+ else:
323
+ return self.head(ret["x_norm_clstoken"])
324
+
325
+
326
+ def init_weights_vit_timm(module: nn.Module, name: str = ""):
327
+ """ViT weight initialization, original timm impl (for reproducibility)"""
328
+ if isinstance(module, nn.Linear):
329
+ trunc_normal_(module.weight, std=0.02)
330
+ if module.bias is not None:
331
+ nn.init.zeros_(module.bias)
332
+
333
+
334
+ def vit_small(patch_size=16, num_register_tokens=0, **kwargs):
335
+ model = DinoVisionTransformer(
336
+ patch_size=patch_size,
337
+ embed_dim=384,
338
+ depth=12,
339
+ num_heads=6,
340
+ mlp_ratio=4,
341
+ block_fn=partial(Block, attn_class=MemEffAttention),
342
+ num_register_tokens=num_register_tokens,
343
+ **kwargs,
344
+ )
345
+ return model
346
+
347
+
348
+ def vit_base(patch_size=16, num_register_tokens=0, **kwargs):
349
+ model = DinoVisionTransformer(
350
+ patch_size=patch_size,
351
+ embed_dim=768,
352
+ depth=12,
353
+ num_heads=12,
354
+ mlp_ratio=4,
355
+ block_fn=partial(Block, attn_class=MemEffAttention),
356
+ num_register_tokens=num_register_tokens,
357
+ **kwargs,
358
+ )
359
+ return model
360
+
361
+
362
+ def vit_large(patch_size=16, num_register_tokens=0, **kwargs):
363
+ model = DinoVisionTransformer(
364
+ patch_size=patch_size,
365
+ embed_dim=1024,
366
+ depth=24,
367
+ num_heads=16,
368
+ mlp_ratio=4,
369
+ block_fn=partial(Block, attn_class=MemEffAttention),
370
+ num_register_tokens=num_register_tokens,
371
+ **kwargs,
372
+ )
373
+ return model
374
+
375
+
376
+ def vit_giant2(patch_size=16, num_register_tokens=0, **kwargs):
377
+ """
378
+ Close to ViT-giant, with embed-dim 1536 and 24 heads => embed-dim per head 64
379
+ """
380
+ model = DinoVisionTransformer(
381
+ patch_size=patch_size,
382
+ embed_dim=1536,
383
+ depth=40,
384
+ num_heads=24,
385
+ mlp_ratio=4,
386
+ block_fn=partial(Block, attn_class=MemEffAttention),
387
+ num_register_tokens=num_register_tokens,
388
+ **kwargs,
389
+ )
390
+ return model