Image Classification
PEFT
Transformers
English
document-classification
industrial-documents
clip
lora
computer-vision
technical-documents
manufacturing
compliance
ecommerce
Instructions to use ssheroz/industrial-document-classifier-clip-lora with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use ssheroz/industrial-document-classifier-clip-lora with PEFT:
Task type is invalid.
- Transformers
How to use ssheroz/industrial-document-classifier-clip-lora with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-classification", model="ssheroz/industrial-document-classifier-clip-lora") pipe("https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/hub/parrots.png")# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("ssheroz/industrial-document-classifier-clip-lora", device_map="auto") - Notebooks
- Google Colab
- Kaggle
Download pipeline.py from ssheroz/industrial-document-classifier-clip-lora: direct link, hf CLI and curl.
- Browser
- Download file 8.02 kB
-
https://huggingface.co/ssheroz/industrial-document-classifier-clip-lora/resolve/95e00bdc0ff9288ae10c562b1a0dcd775e01fbbb/pipeline.py
- Command line
-
hf download hf://ssheroz/industrial-document-classifier-clip-lora@95e00bdc0ff9288ae10c562b1a0dcd775e01fbbb/pipeline.py
-
curl -L -o pipeline.py https://huggingface.co/ssheroz/industrial-document-classifier-clip-lora/resolve/95e00bdc0ff9288ae10c562b1a0dcd775e01fbbb/pipeline.py
8.02 kB
| import gc | |
| import os | |
| from pathlib import Path | |
| from typing import Dict, List, Union | |
| from concurrent.futures import ThreadPoolExecutor, as_completed | |
| import torch | |
| import torch.nn as nn | |
| from PIL import Image | |
| from transformers import CLIPVisionModel, CLIPProcessor | |
| from peft import LoraConfig, get_peft_model | |
| import warnings | |
| warnings.filterwarnings(action="ignore") | |
| class CONSTANTS: | |
| BASE_MODEL_NAME = "openai/clip-vit-base-patch16" | |
| TUNED_MODEL_NAME = "industrial-document-classifier-clip-lora.pt" | |
| EMBEDDING_DIM = 768 | |
| NUM_PARENT_CLASSES = 6 | |
| NUM_CHILD_CLASSES = 13 | |
| DEVICE = "cuda" if torch.cuda.is_available() else "cpu" | |
| LORA_R = 32 | |
| LORA_ALPHA = 64 | |
| LORA_DROPOUT = 0.1 | |
| LORA_TARGET_MODULES = ["q_proj", "k_proj", "v_proj", "out_proj", "fc1", "fc2"] | |
| BATCH_SIZE = 16 | |
| MAX_WORKERS = os.cpu_count() | |
| VALID_IMAGE_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.bmp', '.gif', '.tiff', '.tif', '.webp', '.ico', '.heic', '.heif'} | |
| PARENT_CLASS_NAMES = {0: "product_information", 1: "engineering_drawings", 2: "instructional_guides", 3: "compliance_certificates", 4: "energy_ratings", 5: "warranty_documents"} | |
| class HierarchicalDocumentClassifier(nn.Module): | |
| def __init__(self, model_name: str, num_parent_classes: int, num_child_classes: int, embedding_dim: int): | |
| super().__init__() | |
| self.vision_model = CLIPVisionModel.from_pretrained(model_name, use_safetensors=False) | |
| self.parent_classifier = nn.Linear(embedding_dim, num_parent_classes) | |
| self.child_classifier = nn.Linear(embedding_dim, num_child_classes) | |
| def forward(self, pixel_values): | |
| outputs = self.vision_model(pixel_values=pixel_values) | |
| embeddings = outputs.pooler_output | |
| parent_logits = self.parent_classifier(embeddings) | |
| child_logits = self.child_classifier(embeddings) | |
| return parent_logits, child_logits, embeddings | |
| class DocumentClassifier: | |
| def __init__(self): | |
| self.device = torch.device(CONSTANTS.DEVICE) | |
| self.processor = CLIPProcessor.from_pretrained(CONSTANTS.BASE_MODEL_NAME, use_fast=True) | |
| self.model = self._load_model() | |
| self.model.eval() | |
| self._clear_cache() | |
| def _load_model(self) -> nn.Module: | |
| model = HierarchicalDocumentClassifier( | |
| model_name=CONSTANTS.BASE_MODEL_NAME, | |
| num_parent_classes=CONSTANTS.NUM_PARENT_CLASSES, | |
| num_child_classes=CONSTANTS.NUM_CHILD_CLASSES, | |
| embedding_dim=CONSTANTS.EMBEDDING_DIM) | |
| lora_config = LoraConfig( | |
| r=CONSTANTS.LORA_R, | |
| lora_alpha=CONSTANTS.LORA_ALPHA, | |
| target_modules=CONSTANTS.LORA_TARGET_MODULES, | |
| lora_dropout=CONSTANTS.LORA_DROPOUT, | |
| bias="none") | |
| model.vision_model = get_peft_model(model.vision_model, lora_config) | |
| checkpoint = torch.load(CONSTANTS.TUNED_MODEL_NAME, map_location=self.device) | |
| model.load_state_dict(checkpoint['model_state_dict']) | |
| del checkpoint | |
| self._clear_cache() | |
| return model.to(self.device) | |
| def _clear_cache(self): | |
| if torch.cuda.is_available(): | |
| torch.cuda.empty_cache() | |
| gc.collect() | |
| def _is_valid_image(self, image_path: str) -> bool: | |
| path = Path(image_path) | |
| if not path.exists(): | |
| return False | |
| if not path.is_file(): | |
| return False | |
| if path.suffix.lower() not in CONSTANTS.VALID_IMAGE_EXTENSIONS: | |
| return False | |
| return True | |
| def _load_image(self, image_path: str) -> Union[Image.Image, None]: | |
| try: | |
| img = Image.open(image_path).convert('RGB') | |
| return img | |
| except Exception: | |
| return None | |
| def _process_batch(self, images: List[Image.Image]): | |
| try: | |
| inputs = self.processor(images=images, return_tensors="pt") | |
| pixel_values = inputs['pixel_values'].to(self.device) | |
| with torch.no_grad(): | |
| parent_logits, _, _ = self.model(pixel_values) | |
| probabilities = torch.softmax(parent_logits, dim=1) | |
| return probabilities.cpu() | |
| except Exception: | |
| return None | |
| def _process_single_image(self, image_path: str) -> Dict: | |
| result = { | |
| "image_path": image_path, | |
| "predictions": {}, | |
| "error_response": "" | |
| } | |
| if not self._is_valid_image(image_path): | |
| result["error_response"] = "Invalid image path or unsupported format" | |
| return result | |
| img = self._load_image(image_path) | |
| if img is None: | |
| result["error_response"] = "Failed to load image" | |
| return result | |
| probabilities = self._process_batch([img]) | |
| if probabilities is None: | |
| result["error_response"] = "Model inference failed" | |
| return result | |
| probs = probabilities[0] | |
| predictions_dict = {} | |
| for class_id, class_name in CONSTANTS.PARENT_CLASS_NAMES.items(): | |
| predictions_dict[class_name] = float(probs[class_id]) | |
| result["predictions"] = predictions_dict | |
| return result | |
| def _process_batch_images(self, image_paths: List[str]) -> List[Dict]: | |
| batch_results = [] | |
| batch_images = [] | |
| batch_valid_paths = [] | |
| for img_path in image_paths: | |
| result = { | |
| "image_path": img_path, | |
| "predictions": {}, | |
| "error_response": "" | |
| } | |
| if not self._is_valid_image(img_path): | |
| result["error_response"] = "Invalid image path or unsupported format" | |
| batch_results.append(result) | |
| continue | |
| img = self._load_image(img_path) | |
| if img is None: | |
| result["error_response"] = "Failed to load image" | |
| batch_results.append(result) | |
| continue | |
| batch_images.append(img) | |
| batch_valid_paths.append(img_path) | |
| batch_results.append(result) | |
| if len(batch_images) > 0: | |
| probabilities = self._process_batch(batch_images) | |
| if probabilities is None: | |
| for result in batch_results: | |
| if result["error_response"] == "": | |
| result["error_response"] = "Model inference failed" | |
| else: | |
| valid_idx = 0 | |
| for result in batch_results: | |
| if result["error_response"] == "": | |
| probs = probabilities[valid_idx] | |
| predictions_dict = {} | |
| for class_id, class_name in CONSTANTS.PARENT_CLASS_NAMES.items(): | |
| predictions_dict[class_name] = float(probs[class_id]) | |
| result["predictions"] = predictions_dict | |
| valid_idx += 1 | |
| return batch_results | |
| def predict(self, image_paths: List[str]) -> List[Dict]: | |
| if len(image_paths) == 0: | |
| return [] | |
| batches = [] | |
| for i in range(0, len(image_paths), CONSTANTS.BATCH_SIZE): | |
| batches.append(image_paths[i:i + CONSTANTS.BATCH_SIZE]) | |
| results_map = {} | |
| with ThreadPoolExecutor(max_workers=CONSTANTS.MAX_WORKERS) as executor: | |
| future_to_batch = {executor.submit(self._process_batch_images, batch): batch for batch in batches} | |
| for future in as_completed(future_to_batch): | |
| batch = future_to_batch[future] | |
| batch_results = future.result() | |
| for result in batch_results: | |
| results_map[result["image_path"]] = result | |
| ordered_results = [] | |
| for img_path in image_paths: | |
| ordered_results.append(results_map[img_path]) | |
| return ordered_results | |
| def unload(self): | |
| if hasattr(self, 'model') and self.model is not None: | |
| del self.model | |
| if hasattr(self, 'processor') and self.processor is not None: | |
| del self.processor | |
| self._clear_cache() | |