ouzhang57's picture
Upload folder using huggingface_hub (part 10)
2e1a430 verified
Raw
History Blame Contribute Delete
9.6 kB
import os
from typing import Iterable, Union
import torch
import torch.nn as nn
import torch.nn.functional as F
from PIL import Image
from torchvision import transforms
from torchvision.models import inception_v3
from torchvision.models.inception import InceptionA, InceptionC, InceptionE
ImageInput = Union[str, os.PathLike, Image.Image]
IMAGE_EXTENSIONS = {".bmp", ".jpg", ".jpeg", ".pgm", ".png", ".ppm", ".tif", ".tiff", ".webp"}
def _image_files(path: Union[str, os.PathLike]):
path = os.fspath(path)
if os.path.isfile(path):
if os.path.splitext(path)[1].lower() not in IMAGE_EXTENSIONS:
raise ValueError(f"Unsupported image extension for FID: {path}")
return [path]
if not os.path.exists(path):
raise FileNotFoundError(f"FID path does not exist: {path}")
files = []
for root, dirs, names in os.walk(path):
dirs.sort()
for name in sorted(names):
if os.path.splitext(name)[1].lower() in IMAGE_EXTENSIONS:
files.append(os.path.join(root, name))
if not files:
raise ValueError(f"No images found under {path}.")
return files
class _ImageDataset(torch.utils.data.Dataset):
def __init__(self, images: Iterable[ImageInput], transform):
self.images = list(images)
self.transform = transform
def __len__(self):
return len(self.images)
def __getitem__(self, index):
image = self.images[index]
if isinstance(image, (str, os.PathLike)):
image = Image.open(image)
if not isinstance(image, Image.Image):
raise TypeError(f"FID expects PIL images or image paths, but received {type(image)}.")
return self.transform(image.convert("RGB"))
class FIDInceptionModel(nn.Module):
def __init__(self):
super().__init__()
self.model = _fid_inception_v3()
def forward(self, images):
images = 2 * images - 1
return self.model(images)
def _fid_inception_v3(weights_path: str = None):
model = inception_v3(weights=None, aux_logits=False, num_classes=1008, init_weights=False)
model.Mixed_5b = _FIDInceptionA(192, pool_features=32)
model.Mixed_5c = _FIDInceptionA(256, pool_features=64)
model.Mixed_5d = _FIDInceptionA(288, pool_features=64)
model.Mixed_6b = _FIDInceptionC(768, channels_7x7=128)
model.Mixed_6c = _FIDInceptionC(768, channels_7x7=160)
model.Mixed_6d = _FIDInceptionC(768, channels_7x7=160)
model.Mixed_6e = _FIDInceptionC(768, channels_7x7=192)
model.Mixed_7b = _FIDInceptionE1(1280)
model.Mixed_7c = _FIDInceptionE2(2048)
if weights_path is not None:
model.load_state_dict(torch.load(weights_path, map_location="cpu"))
model.fc = nn.Identity()
return model
class _FIDInceptionA(InceptionA):
def forward(self, x):
branch1x1 = self.branch1x1(x)
branch5x5 = self.branch5x5_1(x)
branch5x5 = self.branch5x5_2(branch5x5)
branch3x3dbl = self.branch3x3dbl_1(x)
branch3x3dbl = self.branch3x3dbl_2(branch3x3dbl)
branch3x3dbl = self.branch3x3dbl_3(branch3x3dbl)
branch_pool = F.avg_pool2d(x, kernel_size=3, stride=1, padding=1, count_include_pad=False)
branch_pool = self.branch_pool(branch_pool)
return torch.cat([branch1x1, branch5x5, branch3x3dbl, branch_pool], 1)
class _FIDInceptionC(InceptionC):
def forward(self, x):
branch1x1 = self.branch1x1(x)
branch7x7 = self.branch7x7_1(x)
branch7x7 = self.branch7x7_2(branch7x7)
branch7x7 = self.branch7x7_3(branch7x7)
branch7x7dbl = self.branch7x7dbl_1(x)
branch7x7dbl = self.branch7x7dbl_2(branch7x7dbl)
branch7x7dbl = self.branch7x7dbl_3(branch7x7dbl)
branch7x7dbl = self.branch7x7dbl_4(branch7x7dbl)
branch7x7dbl = self.branch7x7dbl_5(branch7x7dbl)
branch_pool = F.avg_pool2d(x, kernel_size=3, stride=1, padding=1, count_include_pad=False)
branch_pool = self.branch_pool(branch_pool)
return torch.cat([branch1x1, branch7x7, branch7x7dbl, branch_pool], 1)
class _FIDInceptionE1(InceptionE):
def forward(self, x):
branch1x1 = self.branch1x1(x)
branch3x3 = self.branch3x3_1(x)
branch3x3 = torch.cat([self.branch3x3_2a(branch3x3), self.branch3x3_2b(branch3x3)], 1)
branch3x3dbl = self.branch3x3dbl_1(x)
branch3x3dbl = self.branch3x3dbl_2(branch3x3dbl)
branch3x3dbl = torch.cat([self.branch3x3dbl_3a(branch3x3dbl), self.branch3x3dbl_3b(branch3x3dbl)], 1)
branch_pool = F.avg_pool2d(x, kernel_size=3, stride=1, padding=1, count_include_pad=False)
branch_pool = self.branch_pool(branch_pool)
return torch.cat([branch1x1, branch3x3, branch3x3dbl, branch_pool], 1)
class _FIDInceptionE2(InceptionE):
def forward(self, x):
branch1x1 = self.branch1x1(x)
branch3x3 = self.branch3x3_1(x)
branch3x3 = torch.cat([self.branch3x3_2a(branch3x3), self.branch3x3_2b(branch3x3)], 1)
branch3x3dbl = self.branch3x3dbl_1(x)
branch3x3dbl = self.branch3x3dbl_2(branch3x3dbl)
branch3x3dbl = torch.cat([self.branch3x3dbl_3a(branch3x3dbl), self.branch3x3dbl_3b(branch3x3dbl)], 1)
branch_pool = F.max_pool2d(x, kernel_size=3, stride=1, padding=1)
branch_pool = self.branch_pool(branch_pool)
return torch.cat([branch1x1, branch3x3, branch3x3dbl, branch_pool], 1)
class FIDModel(torch.nn.Module):
def __init__(self, model: torch.nn.Module, device: Union[str, torch.device] = "cpu", batch_size: int = 50, num_workers: int = 0):
super().__init__()
self.model = model
self.batch_size = batch_size
self.num_workers = num_workers
self.transform = transforms.Compose(
[
transforms.Resize((299, 299), interpolation=transforms.InterpolationMode.BICUBIC),
transforms.ToTensor(),
]
)
self.to(device)
@property
def device(self):
try:
return next(self.model.parameters()).device
except StopIteration:
return torch.device("cpu")
def _as_images(self, images):
if isinstance(images, (str, os.PathLike)):
return _image_files(images)
if isinstance(images, Image.Image):
return [images]
return list(images)
@torch.no_grad()
def get_activations(self, images, batch_size: int = None, num_workers: int = None):
images = self._as_images(images)
batch_size = self.batch_size if batch_size is None else batch_size
num_workers = self.num_workers if num_workers is None else num_workers
dataset = _ImageDataset(images, transform=self.transform)
dataloader = torch.utils.data.DataLoader(dataset, batch_size=min(batch_size, len(dataset)), shuffle=False, num_workers=num_workers)
activations = []
self.model.eval()
for batch in dataloader:
batch = batch.to(self.device)
features = self.model(batch)
if isinstance(features, tuple):
features = features[0]
if features.ndim == 4:
features = F.adaptive_avg_pool2d(features, output_size=(1, 1)).flatten(1)
activations.append(features.detach().cpu().to(torch.float64))
return torch.cat(activations, dim=0)
def statistics(self, images, batch_size: int = None, num_workers: int = None):
activations = self.get_activations(images, batch_size=batch_size, num_workers=num_workers)
return self.activation_statistics(activations)
@staticmethod
def activation_statistics(activations):
activations = activations.to(torch.float64)
mean = activations.mean(dim=0)
centered = activations - mean
if activations.shape[0] <= 1:
covariance = torch.zeros((activations.shape[1], activations.shape[1]), dtype=torch.float64)
else:
covariance = centered.T @ centered / (activations.shape[0] - 1)
return mean, covariance
@staticmethod
def _sqrtm_psd(matrix, eps: float = 1e-10):
matrix = (matrix + matrix.T) * 0.5
eigenvalues, eigenvectors = torch.linalg.eigh(matrix)
eigenvalues = eigenvalues.clamp_min(eps).sqrt()
return (eigenvectors * eigenvalues.unsqueeze(0)) @ eigenvectors.T
@classmethod
def frechet_distance(cls, mean1, covariance1, mean2, covariance2, eps: float = 1e-6):
mean1 = mean1.to(torch.float64)
covariance1 = covariance1.to(torch.float64)
mean2 = mean2.to(torch.float64)
covariance2 = covariance2.to(torch.float64)
diff = mean1 - mean2
offset = torch.eye(covariance1.shape[0], dtype=torch.float64) * eps
sqrt_cov1 = cls._sqrtm_psd(covariance1 + offset)
covmean = cls._sqrtm_psd(sqrt_cov1 @ (covariance2 + offset) @ sqrt_cov1)
distance = diff.dot(diff) + torch.trace(covariance1) + torch.trace(covariance2) - 2 * torch.trace(covmean)
return distance.clamp_min(0)
def compute(self, reference_images, generated_images, batch_size: int = None, num_workers: int = None):
mean1, covariance1 = self.statistics(reference_images, batch_size=batch_size, num_workers=num_workers)
mean2, covariance2 = self.statistics(generated_images, batch_size=batch_size, num_workers=num_workers)
return self.frechet_distance(mean1, covariance1, mean2, covariance2)
def forward(self, reference_images, generated_images):
return self.compute(reference_images, generated_images)