Spaces:
Running on Zero
Running on Zero
| from typing import Union | |
| import torch | |
| from PIL import Image | |
| from transformers import CLIPModel as HFCLIPModel | |
| ImageInput = Union[Image.Image, list[Image.Image], tuple[Image.Image, ...]] | |
| def _feature_tensor(output, feature_name: str): | |
| if torch.is_tensor(output): | |
| return output | |
| for name in ("image_embeds", "text_embeds", "pooler_output"): | |
| value = getattr(output, name, None) | |
| if torch.is_tensor(value): | |
| return value | |
| if isinstance(output, (list, tuple)): | |
| for value in output: | |
| if torch.is_tensor(value): | |
| return value | |
| raise TypeError(f"{feature_name} must be a tensor or a model output with projected features.") | |
| class ImageMetricsCLIPModel(HFCLIPModel): | |
| def __init__(self, variant: str = "h14"): | |
| super().__init__(self.config(variant)) | |
| def config(variant: str): | |
| from transformers import CLIPConfig | |
| return CLIPConfig( | |
| projection_dim=1024, | |
| logit_scale_init_value=2.6592, | |
| text_config={ | |
| "hidden_size": 1024, | |
| "intermediate_size": 4096, | |
| "num_attention_heads": 16, | |
| "num_hidden_layers": 24, | |
| "max_position_embeddings": 77, | |
| "vocab_size": 49408, | |
| "hidden_act": "quick_gelu", | |
| "layer_norm_eps": 1e-5, | |
| "projection_dim": 1024, | |
| "bos_token_id": 0, | |
| "eos_token_id": 2, | |
| "pad_token_id": 1, | |
| }, | |
| vision_config={ | |
| "hidden_size": 1280, | |
| "intermediate_size": 5120, | |
| "num_attention_heads": 16, | |
| "num_hidden_layers": 32, | |
| "image_size": 224, | |
| "patch_size": 14, | |
| "hidden_act": "quick_gelu", | |
| "layer_norm_eps": 1e-5, | |
| "projection_dim": 1024, | |
| }, | |
| ) | |
| raise ValueError(f"Unsupported ImageMetrics CLIP variant: {variant}") | |
| class CLIPModel(torch.nn.Module): | |
| def __init__(self, model: torch.nn.Module, processor, max_length: int = 77): | |
| super().__init__() | |
| self.model = model | |
| self.processor = processor | |
| self.max_length = max_length | |
| def device(self): | |
| return next(self.parameters(), torch.tensor([])).device | |
| def dtype(self): | |
| return next(self.parameters(), torch.tensor(0.0)).dtype | |
| def _normalize_pairs(self, text, images): | |
| if isinstance(text, str): | |
| text = [text] | |
| else: | |
| text = list(text) | |
| if isinstance(images, Image.Image): | |
| images = [images] | |
| images = [image.convert("RGB") for image in images] | |
| if len(text) == 1 and len(images) > 1: | |
| text = text * len(images) | |
| if len(images) == 1 and len(text) > 1: | |
| images = images * len(text) | |
| if len(text) != len(images): | |
| raise ValueError(f"Expected the same number of prompts and images, got {len(text)} and {len(images)}.") | |
| return text, images | |
| def _processor_call(self, **kwargs): | |
| inputs = self.processor( | |
| padding=True, | |
| truncation=True, | |
| max_length=self.max_length, | |
| return_tensors="pt", | |
| **kwargs, | |
| ).to(self.device) | |
| if self.dtype != torch.float32: | |
| inputs = { | |
| name: ( | |
| value.to(dtype=self.dtype) | |
| if torch.is_tensor(value) and torch.is_floating_point(value) | |
| else value | |
| ) | |
| for name, value in inputs.items() | |
| } | |
| return inputs | |
| def get_image_features(self, images: ImageInput): | |
| if isinstance(images, Image.Image): | |
| images = [images] | |
| images = [image.convert("RGB") for image in images] | |
| image_inputs = self._processor_call(images=images) | |
| image_features = _feature_tensor(self.model.get_image_features(**image_inputs), "image_features") | |
| return torch.nn.functional.normalize(image_features, dim=-1) | |
| def get_text_features(self, text: Union[str, list[str]]): | |
| text_inputs = self._processor_call(text=text) | |
| text_features = _feature_tensor(self.model.get_text_features(**text_inputs), "text_features") | |
| return torch.nn.functional.normalize(text_features, dim=-1) | |
| def similarity_matrix(self, text: Union[str, list[str]], images: ImageInput): | |
| image_features = self.get_image_features(images) | |
| text_features = self.get_text_features(text) | |
| scores = text_features @ image_features.T | |
| if hasattr(self.model, "logit_scale"): | |
| scores = self.model.logit_scale.exp() * scores | |
| return scores | |
| def forward(self, text: Union[str, list[str]], images: ImageInput): | |
| text, images = self._normalize_pairs(text, images) | |
| image_features = self.get_image_features(images) | |
| text_features = self.get_text_features(text) | |
| scores = (text_features * image_features).sum(dim=-1) | |
| if hasattr(self.model, "logit_scale"): | |
| scores = self.model.logit_scale.exp() * scores | |
| return scores |