Aloukik21 commited on
Commit
72b9f85
·
verified ·
1 Parent(s): c738563

Upload image/Bombek1-siglip-dinov2/model.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. image/Bombek1-siglip-dinov2/model.py +227 -0
image/Bombek1-siglip-dinov2/model.py ADDED
@@ -0,0 +1,227 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ AI Image Detector - SigLIP2 + DINOv2 Ensemble with LoRA
3
+
4
+ This model detects AI-generated images using an ensemble of:
5
+ - SigLIP2-SO400M (semantic features)
6
+ - DINOv2-Large (self-supervised visual features)
7
+
8
+ Both backbones use LoRA adapters for efficient fine-tuning.
9
+ """
10
+
11
+ import torch
12
+ import torch.nn as nn
13
+ import math
14
+ from torch.amp import autocast
15
+
16
+ import timm
17
+ from transformers import AutoProcessor, SiglipVisionModel
18
+ from peft import LoraConfig, get_peft_model
19
+ from torchvision import transforms
20
+ from PIL import Image
21
+
22
+
23
+ class LoRALinear(nn.Module):
24
+ """Custom LoRA implementation for DINOv2 QKV layers."""
25
+ def __init__(self, original: nn.Linear, rank: int, alpha: float, dropout: float = 0.1):
26
+ super().__init__()
27
+ self.original = original
28
+ self.scaling = alpha / rank
29
+
30
+ for p in self.original.parameters():
31
+ p.requires_grad = False
32
+
33
+ self.lora_A = nn.Linear(original.in_features, rank, bias=False)
34
+ self.lora_B = nn.Linear(rank, original.out_features, bias=False)
35
+ self.dropout = nn.Dropout(dropout)
36
+
37
+ nn.init.kaiming_uniform_(self.lora_A.weight, a=math.sqrt(5))
38
+ nn.init.zeros_(self.lora_B.weight)
39
+
40
+ def forward(self, x):
41
+ return self.original(x) + self.lora_B(self.lora_A(self.dropout(x))) * self.scaling
42
+
43
+
44
+ class ClassificationHead(nn.Module):
45
+ """MLP classification head with LayerNorm and dropout."""
46
+ def __init__(self, input_dim: int, hidden_dim: int = 512, dropout: float = 0.3):
47
+ super().__init__()
48
+ self.head = nn.Sequential(
49
+ nn.LayerNorm(input_dim),
50
+ nn.Linear(input_dim, hidden_dim),
51
+ nn.GELU(),
52
+ nn.Dropout(dropout),
53
+ nn.Linear(hidden_dim, hidden_dim // 2),
54
+ nn.GELU(),
55
+ nn.Dropout(dropout),
56
+ nn.Linear(hidden_dim // 2, 1),
57
+ )
58
+
59
+ def forward(self, x):
60
+ return self.head(x).squeeze(-1)
61
+
62
+
63
+ class EnsembleAIDetector(nn.Module):
64
+ """Ensemble model combining SigLIP2 and DINOv2 for AI image detection."""
65
+
66
+ def __init__(self, siglip_model_name: str, dinov2_model_name: str, image_size: int = 392):
67
+ super().__init__()
68
+
69
+ # SigLIP2 backbone
70
+ self.siglip = SiglipVisionModel.from_pretrained(
71
+ siglip_model_name,
72
+ torch_dtype=torch.bfloat16
73
+ )
74
+ self.siglip_dim = self.siglip.config.hidden_size
75
+
76
+ # DINOv2 backbone
77
+ self.dinov2 = timm.create_model(
78
+ dinov2_model_name,
79
+ pretrained=True,
80
+ num_classes=0,
81
+ img_size=image_size
82
+ )
83
+ self.dinov2_dim = self.dinov2.num_features
84
+
85
+ # Classification head
86
+ self.classifier = ClassificationHead(self.siglip_dim + self.dinov2_dim)
87
+
88
+ def forward(self, siglip_pixels, dinov2_pixels):
89
+ # Extract features
90
+ siglip_features = self.siglip(pixel_values=siglip_pixels).pooler_output
91
+ dinov2_features = self.dinov2(dinov2_pixels)
92
+
93
+ # Combine and classify
94
+ combined = torch.cat([siglip_features.float(), dinov2_features], dim=-1)
95
+ logits = self.classifier(combined)
96
+
97
+ return logits, siglip_features, dinov2_features
98
+
99
+
100
+ def create_model_with_lora(
101
+ siglip_model_name: str = "google/siglip2-so400m-patch14-384",
102
+ dinov2_model_name: str = "vit_large_patch14_dinov2.lvd142m",
103
+ image_size: int = 392,
104
+ lora_rank: int = 32,
105
+ lora_alpha: int = 64,
106
+ lora_dropout: float = 0.1
107
+ ) -> EnsembleAIDetector:
108
+ """Create the model with LoRA adapters applied."""
109
+
110
+ model = EnsembleAIDetector(siglip_model_name, dinov2_model_name, image_size)
111
+
112
+ # Apply LoRA to SigLIP using PEFT
113
+ lora_config = LoraConfig(
114
+ r=lora_rank,
115
+ lora_alpha=lora_alpha,
116
+ target_modules=["q_proj", "v_proj"],
117
+ lora_dropout=lora_dropout,
118
+ bias="none"
119
+ )
120
+ model.siglip = get_peft_model(model.siglip, lora_config)
121
+
122
+ # Apply LoRA to DINOv2 (custom implementation for QKV layers)
123
+ for name, module in model.dinov2.named_modules():
124
+ if hasattr(module, 'qkv') and isinstance(module.qkv, nn.Linear):
125
+ module.qkv = LoRALinear(module.qkv, lora_rank, lora_alpha, lora_dropout)
126
+
127
+ return model
128
+
129
+
130
+ def create_transforms(image_size: int = 392):
131
+ """Create preprocessing transforms for DINOv2."""
132
+ return transforms.Compose([
133
+ transforms.Resize((image_size, image_size), interpolation=transforms.InterpolationMode.BICUBIC),
134
+ transforms.ToTensor(),
135
+ transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
136
+ ])
137
+
138
+
139
+ class AIImageDetector:
140
+ """High-level API for AI image detection."""
141
+
142
+ def __init__(self, model_path: str, device: str = None):
143
+ """
144
+ Initialize the detector.
145
+
146
+ Args:
147
+ model_path: Path to pytorch_model.pt
148
+ device: Device to use ("cuda", "cpu", or None for auto)
149
+ """
150
+ if device is None:
151
+ device = "cuda" if torch.cuda.is_available() else "cpu"
152
+ self.device = torch.device(device)
153
+
154
+ # Load checkpoint
155
+ checkpoint = torch.load(model_path, map_location=self.device, weights_only=False)
156
+ config = checkpoint.get('config', {})
157
+
158
+ # Create model
159
+ self.model = create_model_with_lora(
160
+ siglip_model_name=config.get('siglip_model', 'google/siglip2-so400m-patch14-384'),
161
+ dinov2_model_name=config.get('dinov2_model', 'vit_large_patch14_dinov2.lvd142m'),
162
+ image_size=config.get('image_size', 392),
163
+ lora_rank=config.get('lora_rank', 32),
164
+ lora_alpha=config.get('lora_alpha', 64),
165
+ lora_dropout=config.get('lora_dropout', 0.1),
166
+ )
167
+
168
+ # Load weights
169
+ self.model.load_state_dict(checkpoint['model_state_dict'])
170
+ self.model.to(self.device)
171
+ self.model.eval()
172
+
173
+ # Create processors
174
+ self.siglip_processor = AutoProcessor.from_pretrained('google/siglip2-so400m-patch14-384')
175
+ self.dinov2_transform = create_transforms(config.get('image_size', 392))
176
+
177
+ print(f"Model loaded on {self.device}")
178
+
179
+ @torch.no_grad()
180
+ def predict(self, image) -> dict:
181
+ """
182
+ Predict whether an image is AI-generated.
183
+
184
+ Args:
185
+ image: PIL Image, path to image, or URL
186
+
187
+ Returns:
188
+ dict with keys:
189
+ - probability: float, P(AI-generated)
190
+ - prediction: str, "ai-generated" or "real"
191
+ - confidence: float, confidence score
192
+ """
193
+ # Load image if needed
194
+ if isinstance(image, str):
195
+ if image.startswith('http'):
196
+ import requests
197
+ from io import BytesIO
198
+ response = requests.get(image)
199
+ image = Image.open(BytesIO(response.content))
200
+ else:
201
+ image = Image.open(image)
202
+
203
+ if image.mode != 'RGB':
204
+ image = image.convert('RGB')
205
+
206
+ # Preprocess
207
+ siglip_inputs = self.siglip_processor(images=image, return_tensors="pt")
208
+ siglip_pixels = siglip_inputs["pixel_values"].to(self.device)
209
+ dinov2_pixels = self.dinov2_transform(image).unsqueeze(0).to(self.device)
210
+
211
+ # Inference
212
+ with autocast('cuda', enabled=self.device.type == 'cuda'):
213
+ logits, _, _ = self.model(siglip_pixels, dinov2_pixels)
214
+
215
+ probability = torch.sigmoid(logits).item()
216
+ prediction = "ai-generated" if probability > 0.5 else "real"
217
+ confidence = probability if probability > 0.5 else 1 - probability
218
+
219
+ return {
220
+ "probability": probability,
221
+ "prediction": prediction,
222
+ "confidence": confidence
223
+ }
224
+
225
+ def __call__(self, image):
226
+ """Shorthand for predict()."""
227
+ return self.predict(image)