DiTo97 / DiTo97/PIQA

Hugging Face compatibility

Open
#4 0 comments 0 reactions 0 assignees View on GitHub
help wanted
Dominant language
Python
Stars
1
Forks
0
PR merge metrics
No merged PRs in 30d

Description

The student PIQA model must be HF-compatible:
- https://huggingface.co/docs/transformers/en/custom_models

---

Below is a complete implementation of a custom Hugging Face–compatible student model for panorama IQA. This implementation includes a custom configuration, image processor, and model class that wraps a Swin‑v2 backbone. The model supports two types of heads (“point” and “piven”) and registers itself with the Hugging Face auto classes so that it can be loaded via, for example, `AutoModel.from_pretrained`. You can adjust the default parameters (such as the backbone model name and head type) as needed.

```python
import torch
import torch.nn as nn
from transformers import PretrainedConfig, PreTrainedModel, AutoModel
from transformers import ImageProcessingMixin

#############################
# 1. Custom Configuration
#############################

class PanoramaStudentConfig(PretrainedConfig):
model_type = "panorama_student"

def __init__(
self,
backbone_model_name: str = "microsoft/swinv2-base-patch4-window8-256",
head_type: str = "point", # options: "point" or "piven"
**kwargs,
):
"""
Args:
backbone_model_name (str): The Hugging Face model id for the panorama backbone.
head_type (str): Type of head to use; "point" returns a single score, "piven" returns prediction intervals.
"""
super().__init__(**kwargs)
self.backbone_model_name = backbone_model_name
self.head_type = head_type
# You can add other hyperparameters as needed

#############################
# 2. Custom Image Processor
#############################

class PanoramaImageProcessor(ImageProcessingMixin):
model_input_names = ["pixel_values"]

def __init__(self, size: int = 256, mean=None, std=None):
"""
Simple image processor that resizes images, converts them to tensors,
and normalizes them. Adjust as necessary for your training regime.
"""
self.size = size
# Default normalization values (example for ImageNet-like statistics)
self.mean = mean if mean is not None else [0.485, 0.456, 0.406]
self.std = std if std is not None else [0.229, 0.224, 0.225]

def __call__(self, images, return_tensors="pt", **kwargs):
"""
Args:
images (list[PIL.Image.Image] or a single PIL.Image.Image): Input images.
return_tensors (str): "pt" for PyTorch tensors.
Returns:
A dict with the key "pixel_values".
"""
if not isinstance(images, (list, tuple)):
images = [images]

processed = [self.preprocess(image) for image in images]
pixel_values = torch.stack(processed)
if return_tensors == "pt":
return {"pixel_values": pixel_values}
else:
return {"pixel_values": pixel_values.cpu().numpy()}

def preprocess(self, image):
# Import here to avoid dependency issues if not used elsewhere
from PIL import Image
import torchvision.transforms as T

transform = T.Compose([
T.Resize((self.size, self.size)),
T.ToTensor(),
T.Normalize(mean=self.mean, std=self.std),
])
return transform(image)

#############################
# 3. Custom Student Model
#############################

class PanoramaStudentModel(PreTrainedModel):
config_class = PanoramaStudentConfig

def __init__(self, config: PanoramaStudentConfig):
"""
The student model uses only the panorama backbone. The overall feature map
is taken from the backbone and fed to a head that can be a point estimator
or a prediction intervals head (PIVEN).
"""
super().__init__(config)

# Load the panorama backbone (e.g., Swin-v2) from HF.
self.backbone = AutoModel.from_pretrained(config.backbone_model_name)
hidden_size = self.backbone.config.hidden_size

# Initialize the head depending on the chosen type.
if config.head_type == "point":
# For point estimation, output a single value.
self.head = nn.Sequential(
nn.Linear(hidden_size, 1),
nn.Sigmoid() # scales output to [0,1]; adjust if you prefer a different range.
)
elif config.head_type == "piven":
# For PIVEN, output three numbers:
# - lower bound, upper bound, and a weight (normalized with sigmoid)
self.head = nn.Linear(hidden_size, 3)
else:
raise ValueError(f"Unsupported head type: {config.head_type}")

# Initialize weights and apply any model-specific initialization if needed.
self.post_init()

def forward(self, pixel_values, **kwargs):
"""
Args:
pixel_values (torch.FloatTensor): Batch of preprocessed images, shape (batch_size, channels, height, width).
Returns:
dict: Contains the model output(s). For "point" head: {"score": score}.
For "piven" head: {"lower_bound": lb, "upper_bound": ub, "weight": weight, "point_estimate": point}.
"""
# Forward pass through the backbone.
backbone_outputs = self.backbone(pixel_values=pixel_values, **kwargs)
# Extract the panorama feature vector.
# (Assuming the backbone returns last_hidden_state of shape (batch, seq_len, hidden_size) and using the first token.)
features = backbone_outputs.last_hidden_state[:, 0, :]

if self.config.head_type == "point":
# Compute the quality score.
score = self.head(features)
return {"score": score}
else: # "piven" head
head_output = self.head(features)
# Split the outputs: first two entries for bounds, third for weight.
lower_bound = head_output[:, 0:1]
upper_bound = head_output[:, 1:2]
weight = torch.sigmoid(head_output[:, 2:3])
# Recover point estimate from the interval: point = lower_bound + weight * (upper_bound - lower_bound)
point_estimate = lower_bound + weight * (upper_bound - lower_bound)
return {
"lower_bound": lower_bound,
"upper_bound": upper_bound,
"weight": weight,
"point_estimate": point_estimate,
}

#############################
# 4. Registration for Auto Classes
#############################

# Note: Registration is needed only if you wish to load the model using the Auto classes.
from transformers.models.auto.configuration_auto import CONFIG_MAPPING
from transformers.models.auto.modeling_auto import MODEL_MAPPING
from transformers.models.auto.processing_auto import PROCESSOR_MAPPING

# Register our custom config, model, and image processor.
CONFIG_MAPPING.register(PanoramaStudentConfig.model_type, PanoramaStudentConfig)
MODEL_MAPPING.register(PanoramaStudentConfig, PanoramaStudentModel)
PROCESSOR_MAPPING.register(PanoramaStudentConfig, PanoramaImageProcessor)

#############################
# Example: Saving and Loading
#############################

if __name__ == "__main__":
# Create a configuration for a point-estimate head.
config = PanoramaStudentConfig(
backbone_model_name="microsoft/swinv2-base-patch4-window8-256",
head_type="point",
)
# Instantiate the model.
model = PanoramaStudentModel(config)

# Create an image processor.
processor = PanoramaImageProcessor(size=256)

# Example: process an image (requires PIL)
# from PIL import Image
# image = Image.open("path/to/your/image.jpg")
# inputs = processor(image, return_tensors="pt")
# outputs = model(**inputs)
# print(outputs)

# Save the model and processor for later use.
model.save_pretrained("./panorama_student_model")
processor.save_pretrained("./panorama_student_model")

# Later, you can load them via the Auto classes:
# from transformers import AutoModel, AutoProcessor
# loaded_model = AutoModel.from_pretrained("./panorama_student_model")
# loaded_processor = AutoProcessor.from_pretrained("./panorama_student_model")
```

---

### Explanation

1. **Custom Configuration (`PanoramaStudentConfig`)**
This class inherits from `PretrainedConfig` and defines two key parameters: the backbone model (a Swin‑v2 model from HF) and the type of head ("point" or "piven").

2. **Custom Image Processor (`PanoramaImageProcessor`)**
Inherits from `ImageProcessingMixin` and provides a basic implementation to resize, convert, and normalize images. You may customize the transforms as needed.

3. **Custom Model (`PanoramaStudentModel`)**
Inherits from `PreTrainedModel` and uses the backbone to extract a panorama feature vector. Depending on the configuration, it passes the feature vector to either a point head (a linear layer with sigmoid) or a PIVEN head (a linear layer producing three outputs with a sigmoid applied to the weight).

4. **Registration with Auto Classes**
The config, model, and processor are registered with the HF auto mappings, so users can load your model using `AutoModel.from_pretrained` and `AutoProcessor.from_pretrained`.

This complete implementation makes the student model fully HF-compatible and ready for integration, training, or sharing within the Hugging Face ecosystem.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.