Download model/w_mae.py from OneScience-Group/W-MAE: direct link, hf CLI and curl.
- Browser
- Download file 11.2 kB
-
https://huggingface.co/OneScience-Group/W-MAE/resolve/main/model/w_mae.py
- Command line
-
hf download hf://OneScience-Group/W-MAE/model/w_mae.py
-
curl -L -o w_mae.py https://huggingface.co/OneScience-Group/W-MAE/resolve/main/model/w_mae.py
11.2 kB
| from __future__ import annotations | |
| from functools import partial | |
| from typing import NamedTuple | |
| import numpy as np | |
| import torch | |
| from torch import nn | |
| from .afno import Block | |
| class WMAEOutput(NamedTuple): | |
| loss: torch.Tensor | |
| prediction: torch.Tensor | |
| mask: torch.Tensor | |
| def _sincos_1d(embed_dim: int, positions: np.ndarray) -> np.ndarray: | |
| if embed_dim % 2 != 0: | |
| raise ValueError("The 1D sine-cosine embedding dimension must be even.") | |
| omega = np.arange(embed_dim // 2, dtype=np.float64) | |
| omega = 1.0 / (10000 ** (omega / (embed_dim / 2.0))) | |
| values = np.einsum("m,d->md", positions.reshape(-1), omega) | |
| return np.concatenate((np.sin(values), np.cos(values)), axis=1) | |
| def build_2d_sincos_position_embedding( | |
| embed_dim: int, grid_size: tuple[int, int], include_cls_token: bool | |
| ) -> torch.Tensor: | |
| if embed_dim % 4 != 0: | |
| raise ValueError("The 2D sine-cosine embedding dimension must be divisible by four.") | |
| grid_h = np.arange(grid_size[0], dtype=np.float32) | |
| grid_w = np.arange(grid_size[1], dtype=np.float32) | |
| grid = np.meshgrid(grid_w, grid_h) | |
| embedding = np.concatenate( | |
| (_sincos_1d(embed_dim // 2, grid[0]), _sincos_1d(embed_dim // 2, grid[1])), axis=1 | |
| ) | |
| if include_cls_token: | |
| embedding = np.concatenate((np.zeros((1, embed_dim)), embedding), axis=0) | |
| return torch.from_numpy(embedding).float().unsqueeze(0) | |
| class PatchEmbed(nn.Module): | |
| def __init__( | |
| self, | |
| img_size: tuple[int, int], | |
| patch_size: tuple[int, int], | |
| in_chans: int, | |
| embed_dim: int, | |
| ) -> None: | |
| super().__init__() | |
| if img_size[0] % patch_size[0] or img_size[1] % patch_size[1]: | |
| raise ValueError(f"img_size={img_size} must be divisible by patch_size={patch_size}.") | |
| self.img_size = img_size | |
| self.patch_size = patch_size | |
| self.grid_size = (img_size[0] // patch_size[0], img_size[1] // patch_size[1]) | |
| self.num_patches = self.grid_size[0] * self.grid_size[1] | |
| self.proj = nn.Conv2d(in_chans, embed_dim, kernel_size=patch_size, stride=patch_size) | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| if x.ndim != 4 or tuple(x.shape[-2:]) != self.img_size: | |
| raise ValueError(f"PatchEmbed expects [B,C,{self.img_size[0]},{self.img_size[1]}], got {tuple(x.shape)}.") | |
| return self.proj(x).flatten(2).transpose(1, 2) | |
| class MaskedAutoencoderAFNO(nn.Module): | |
| """W-MAE pretraining model reconstructed from the official AFNO source.""" | |
| def __init__( | |
| self, | |
| img_size: tuple[int, int] = (720, 1440), | |
| patch_size: tuple[int, int] = (8, 8), | |
| in_chans: int = 20, | |
| embed_dim: int = 768, | |
| depth: int = 12, | |
| decoder_embed_dim: int = 512, | |
| decoder_depth: int = 6, | |
| mlp_ratio: float = 4.0, | |
| norm_layer: type[nn.Module] = nn.LayerNorm, | |
| norm_pix_loss: bool = False, | |
| num_blocks: int = 8, | |
| sparsity_threshold: float = 0.01, | |
| hard_thresholding_fraction: float = 1.0, | |
| ) -> None: | |
| super().__init__() | |
| self.img_size = tuple(img_size) | |
| self.patch_size = tuple(patch_size) | |
| self.in_chans = in_chans | |
| self.embed_dim = embed_dim | |
| self.decoder_embed_dim = decoder_embed_dim | |
| self.norm_pix_loss = norm_pix_loss | |
| self.patch_embed = PatchEmbed(self.img_size, self.patch_size, in_chans, embed_dim) | |
| num_patches = self.patch_embed.num_patches | |
| # The official AFNO path keeps this checkpoint key but does not prepend | |
| # a class token in forward_encoder, so it must not enter DDP reduction. | |
| self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dim), requires_grad=False) | |
| self.pos_embed = nn.Parameter(torch.zeros(1, num_patches + 1, embed_dim), requires_grad=False) | |
| block_args = dict( | |
| mlp_ratio=mlp_ratio, | |
| norm_layer=norm_layer, | |
| num_blocks=num_blocks, | |
| sparsity_threshold=sparsity_threshold, | |
| hard_thresholding_fraction=hard_thresholding_fraction, | |
| ) | |
| self.blocks = nn.ModuleList([Block(dim=embed_dim, **block_args) for _ in range(depth)]) | |
| self.norm = norm_layer(embed_dim) | |
| self.decoder_embed = nn.Linear(embed_dim, decoder_embed_dim) | |
| self.mask_token = nn.Parameter(torch.zeros(1, 1, decoder_embed_dim)) | |
| self.decoder_pos_embed = nn.Parameter( | |
| torch.zeros(1, num_patches, decoder_embed_dim), requires_grad=False | |
| ) | |
| decoder_args = dict(block_args) | |
| decoder_args["norm_layer"] = norm_layer | |
| self.decoder_blocks = nn.ModuleList( | |
| [Block(dim=decoder_embed_dim, **decoder_args) for _ in range(decoder_depth)] | |
| ) | |
| self.decoder_norm = norm_layer(decoder_embed_dim) | |
| patch_area = self.patch_size[0] * self.patch_size[1] | |
| self.decoder_pred = nn.Linear(decoder_embed_dim, in_chans * patch_area) | |
| self.initialize_weights() | |
| def initialize_weights(self) -> None: | |
| grid_size = self.patch_embed.grid_size | |
| self.pos_embed.data.copy_(build_2d_sincos_position_embedding(self.embed_dim, grid_size, True)) | |
| self.decoder_pos_embed.data.copy_( | |
| build_2d_sincos_position_embedding(self.decoder_embed_dim, grid_size, False) | |
| ) | |
| nn.init.xavier_uniform_(self.patch_embed.proj.weight.data.flatten(1)) | |
| nn.init.normal_(self.cls_token, std=0.02) | |
| nn.init.normal_(self.mask_token, std=0.02) | |
| self.apply(self._init_weights) | |
| def _init_weights(module: nn.Module) -> None: | |
| if isinstance(module, nn.Linear): | |
| nn.init.xavier_uniform_(module.weight) | |
| if module.bias is not None: | |
| nn.init.zeros_(module.bias) | |
| elif isinstance(module, nn.LayerNorm): | |
| nn.init.zeros_(module.bias) | |
| nn.init.ones_(module.weight) | |
| def patchify(self, images: torch.Tensor) -> torch.Tensor: | |
| if images.ndim != 4 or images.shape[1] != self.in_chans or tuple(images.shape[-2:]) != self.img_size: | |
| raise ValueError( | |
| f"patchify expects [B,{self.in_chans},{self.img_size[0]},{self.img_size[1]}], " | |
| f"got {tuple(images.shape)}." | |
| ) | |
| ph, pw = self.patch_size | |
| gh, gw = self.patch_embed.grid_size | |
| x = images.reshape(images.shape[0], self.in_chans, gh, ph, gw, pw) | |
| x = torch.einsum("nchpwq->nhwpqc", x) | |
| return x.reshape(images.shape[0], gh * gw, ph * pw * self.in_chans) | |
| def unpatchify(self, patches: torch.Tensor) -> torch.Tensor: | |
| ph, pw = self.patch_size | |
| gh, gw = self.patch_embed.grid_size | |
| expected_dim = ph * pw * self.in_chans | |
| if patches.ndim != 3 or patches.shape[1:] != (gh * gw, expected_dim): | |
| raise ValueError(f"unpatchify expects [B,{gh * gw},{expected_dim}], got {tuple(patches.shape)}.") | |
| x = patches.reshape(patches.shape[0], gh, gw, ph, pw, self.in_chans) | |
| x = torch.einsum("nhwpqc->nchpwq", x) | |
| return x.reshape(patches.shape[0], self.in_chans, gh * ph, gw * pw) | |
| def random_masking( | |
| x: torch.Tensor, mask_ratio: float | |
| ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: | |
| if mask_ratio not in {0.0, 0.75}: | |
| raise ValueError("Official W-MAE AFNO grid reshaping supports mask_ratio 0.0 or 0.75 only.") | |
| batch, length, channels = x.shape | |
| len_keep = int(length * (1.0 - mask_ratio)) | |
| noise = torch.rand(batch, length, device=x.device) | |
| ids_shuffle = torch.argsort(noise, dim=1) | |
| ids_restore = torch.argsort(ids_shuffle, dim=1) | |
| ids_keep = ids_shuffle[:, :len_keep] | |
| x_masked = torch.gather(x, 1, ids_keep.unsqueeze(-1).expand(-1, -1, channels)) | |
| mask = torch.ones(batch, length, device=x.device) | |
| mask[:, :len_keep] = 0 | |
| return x_masked, torch.gather(mask, 1, ids_restore), ids_restore | |
| def forward_encoder( | |
| self, images: torch.Tensor, mask_ratio: float | |
| ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: | |
| x = self.patch_embed(images) + self.pos_embed[:, 1:] | |
| x, mask, ids_restore = self.random_masking(x, mask_ratio) | |
| grid_h, grid_w = self.patch_embed.grid_size | |
| divisor = 2 if mask_ratio == 0.75 else 1 | |
| expected_tokens = (grid_h // divisor) * (grid_w // divisor) | |
| if x.shape[1] != expected_tokens: | |
| raise ValueError("The selected mask ratio does not form the rectangular AFNO grid expected by W-MAE.") | |
| x = x.reshape(x.shape[0], grid_h // divisor, grid_w // divisor, self.embed_dim) | |
| for block in self.blocks: | |
| x = block(x) | |
| x = self.norm(x) | |
| return x.flatten(1, 2), mask, ids_restore | |
| def forward_decoder(self, latent: torch.Tensor, ids_restore: torch.Tensor) -> torch.Tensor: | |
| x = self.decoder_embed(latent) | |
| missing_tokens = ids_restore.shape[1] - x.shape[1] | |
| if missing_tokens < 0: | |
| raise ValueError("Latent token count exceeds the decoder target token count.") | |
| mask_tokens = self.mask_token.expand(x.shape[0], missing_tokens, -1) | |
| x = torch.cat((x, mask_tokens), dim=1) | |
| x = torch.gather(x, 1, ids_restore.unsqueeze(-1).expand(-1, -1, x.shape[-1])) | |
| x = x + self.decoder_pos_embed | |
| grid_h, grid_w = self.patch_embed.grid_size | |
| x = x.reshape(x.shape[0], grid_h, grid_w, self.decoder_embed_dim) | |
| for block in self.decoder_blocks: | |
| x = block(x) | |
| x = self.decoder_pred(self.decoder_norm(x)) | |
| return x.flatten(1, 2) | |
| def forward_loss( | |
| self, images: torch.Tensor, prediction: torch.Tensor, mask: torch.Tensor, mask_ratio: float | |
| ) -> torch.Tensor: | |
| target = self.patchify(images) | |
| if self.norm_pix_loss: | |
| mean = target.mean(dim=-1, keepdim=True) | |
| variance = target.var(dim=-1, keepdim=True) | |
| target = (target - mean) / torch.sqrt(variance + 1e-6) | |
| loss = (prediction - target).pow(2).mean(dim=-1) | |
| if mask_ratio == 0.0: | |
| return loss.mean() | |
| masked_count = mask.sum() | |
| if masked_count.item() == 0: | |
| raise ValueError("Masked reconstruction loss requires at least one masked patch.") | |
| return (loss * mask).sum() / masked_count | |
| def forward(self, images: torch.Tensor, mask_ratio: float = 0.75) -> WMAEOutput: | |
| latent, mask, ids_restore = self.forward_encoder(images, mask_ratio) | |
| prediction = self.forward_decoder(latent, ids_restore) | |
| loss = self.forward_loss(images, prediction, mask, mask_ratio) | |
| return WMAEOutput(loss, prediction, mask) | |
| def w_mae_base( | |
| embed_dim: int = 768, | |
| depth: int = 12, | |
| decoder_embed_dim: int = 512, | |
| decoder_depth: int = 6, | |
| mlp_ratio: float = 4.0, | |
| norm_layer: type[nn.Module] = partial(nn.LayerNorm, eps=1e-6), | |
| **kwargs: object, | |
| ) -> MaskedAutoencoderAFNO: | |
| return MaskedAutoencoderAFNO( | |
| embed_dim=embed_dim, | |
| depth=depth, | |
| decoder_embed_dim=decoder_embed_dim, | |
| decoder_depth=decoder_depth, | |
| mlp_ratio=mlp_ratio, | |
| norm_layer=norm_layer, | |
| **kwargs, | |
| ) | |
| mae_vit_base_patch16 = w_mae_base | |