# Copyright 2024 NVIDIA CORPORATION & AFFILIATES # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # SPDX-License-Identifier: Apache-2.0 from typing import Callable import numpy as np import torch import torch.nn as nn from torch.nn.utils.spectral_norm import SpectralNorm class ResidualBlock(nn.Module): def __init__(self, fn: Callable): super().__init__() self.fn = fn def forward(self, x: torch.Tensor) -> torch.Tensor: return (self.fn(x) + x) / np.sqrt(2) class SpectralConv1d(nn.Conv1d): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) SpectralNorm.apply(self, name="weight", n_power_iterations=1, dim=0, eps=1e-12) class BatchNormLocal(nn.Module): def __init__(self, num_features: int, affine: bool = True, virtual_bs: int = 8, eps: float = 1e-5): super().__init__() self.virtual_bs = virtual_bs self.eps = eps self.affine = affine if self.affine: self.weight = nn.Parameter(torch.ones(num_features)) self.bias = nn.Parameter(torch.zeros(num_features)) def forward(self, x: torch.Tensor) -> torch.Tensor: shape = x.size() # Reshape batch into groups. G = np.ceil(x.size(0) / self.virtual_bs).astype(int) x = x.view(G, -1, x.size(-2), x.size(-1)) # Calculate stats. mean = x.mean([1, 3], keepdim=True) var = x.var([1, 3], keepdim=True, unbiased=False) x = (x - mean) / (torch.sqrt(var + self.eps)) if self.affine: x = x * self.weight[None, :, None] + self.bias[None, :, None] return x.view(shape) def make_block(channels: int, kernel_size: int) -> nn.Module: return nn.Sequential( SpectralConv1d( channels, channels, kernel_size=kernel_size, padding=kernel_size // 2, padding_mode="circular", ), BatchNormLocal(channels), nn.LeakyReLU(0.2, True), ) # Adapted from https://github.com/autonomousvision/stylegan-t/blob/main/networks/discriminator.py class DiscHead(nn.Module): def __init__(self, channels: int, c_dim: int, cmap_dim: int = 64): super().__init__() self.channels = channels self.c_dim = c_dim self.cmap_dim = cmap_dim self.main = nn.Sequential( make_block(channels, kernel_size=1), ResidualBlock(make_block(channels, kernel_size=9)) ) if self.c_dim > 0: self.cmapper = nn.Linear(self.c_dim, cmap_dim) self.cls = SpectralConv1d(channels, cmap_dim, kernel_size=1, padding=0) else: self.cls = SpectralConv1d(channels, 1, kernel_size=1, padding=0) def forward(self, x: torch.Tensor, c: torch.Tensor) -> torch.Tensor: h = self.main(x) out = self.cls(h) if self.c_dim > 0: cmap = self.cmapper(c).unsqueeze(-1) out = (out * cmap).sum(1, keepdim=True) * (1 / np.sqrt(self.cmap_dim)) return out