kricko commited on
Commit
c8a49c7
·
verified ·
1 Parent(s): 0683a2b

Add standalone auditor_inference.py helper script

Browse files
Files changed (1) hide show
  1. auditor_inference.py +295 -0
auditor_inference.py ADDED
@@ -0,0 +1,295 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/env/bin python3
2
+ """
3
+ Standalone Adversarial Image Auditor Inference Module
4
+ Provides the necessary classes to load the pre-trained CompleteMultiTaskAuditor
5
+ and evaluate images.
6
+ """
7
+
8
+ import torch
9
+ import torch.nn as nn
10
+ from torchvision import models, transforms
11
+ from PIL import Image
12
+ import torch.nn.functional as F
13
+
14
+ # =============================================================================
15
+ # CONSTANTS & CONFIGURATION
16
+ # =============================================================================
17
+
18
+ class SafetyCategories:
19
+ CATEGORIES = ['safe', 'violence', 'sexual', 'hate', 'illegal_activity', 'sensitive_ip']
20
+ NUM_CLASSES = 6
21
+ IDX = {cat: i for i, cat in enumerate(CATEGORIES)}
22
+
23
+ CLASS_NAMES = ['Safe', 'Violence', 'Sexual', 'Hate', 'Illegal Activity', 'Sensitive IP']
24
+ MAX_PROMPT_LENGTH = 77
25
+
26
+ DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
27
+
28
+ # =============================================================================
29
+ # TEXT TOKENIZATION
30
+ # =============================================================================
31
+
32
+ class SimpleTokenizer:
33
+ """Simple word-level tokenizer"""
34
+ def __init__(self, vocab_size=50000, max_length=77):
35
+ self.vocab_size = vocab_size
36
+ self.max_length = max_length
37
+ self.word_to_idx = {'<PAD>': 0, '<UNK>': 1, '<SOS>': 2, '<EOS>': 3}
38
+ self.idx = 4
39
+
40
+ def build_vocab(self, texts):
41
+ """Build vocabulary from texts"""
42
+ word_freq = {}
43
+ for text in texts:
44
+ if not text:
45
+ continue
46
+ for word in text.lower().split():
47
+ word_freq[word] = word_freq.get(word, 0) + 1
48
+
49
+ # Add most frequent words
50
+ sorted_words = sorted(word_freq.items(), key=lambda x: x[1], reverse=True)
51
+ for word, _ in sorted_words[:self.vocab_size - 4]:
52
+ if word not in self.word_to_idx:
53
+ self.word_to_idx[word] = self.idx
54
+ self.idx += 1
55
+
56
+ def encode(self, text):
57
+ """Tokenize text to indices"""
58
+ if not text:
59
+ return torch.zeros(self.max_length, dtype=torch.long)
60
+
61
+ words = text.lower().split()
62
+ indices = [self.word_to_idx.get('<SOS>', 2)]
63
+
64
+ for word in words[:self.max_length-2]:
65
+ idx = self.word_to_idx.get(word, 1) # UNK token
66
+ indices.append(idx)
67
+
68
+ indices.append(self.word_to_idx.get('<EOS>', 3))
69
+
70
+ # Pad to max_length
71
+ while len(indices) < self.max_length:
72
+ indices.append(0)
73
+
74
+ return torch.tensor(indices[:self.max_length], dtype=torch.long)
75
+
76
+ # =============================================================================
77
+ # TEXT ENCODER FOR PROMPT CONDITIONING
78
+ # =============================================================================
79
+
80
+ class SimpleTextEncoder(nn.Module):
81
+ """Simple text encoder using learned embeddings + LSTM"""
82
+ def __init__(self, vocab_size=50000, embed_dim=512, hidden_dim=256):
83
+ super(SimpleTextEncoder, self).__init__()
84
+ self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0)
85
+ self.lstm = nn.LSTM(embed_dim, hidden_dim, batch_first=True, bidirectional=True)
86
+ self.fc = nn.Linear(hidden_dim * 2, 512)
87
+ self.dropout = nn.Dropout(0.1)
88
+
89
+ def forward(self, text_tokens):
90
+ if text_tokens is None or text_tokens.size(0) == 0:
91
+ return torch.zeros(1, 512, device=next(self.parameters()).device)
92
+
93
+ embedded = self.embedding(text_tokens)
94
+ embedded = self.dropout(embedded)
95
+
96
+ _, (hidden, _) = self.lstm(embedded)
97
+ hidden = torch.cat([hidden[0], hidden[1]], dim=1)
98
+ text_features = self.fc(hidden)
99
+ return text_features
100
+
101
+ # =============================================================================
102
+ # COMPLETE MULTI-TASK RESNET101 MODEL
103
+ # =============================================================================
104
+
105
+ class CompleteMultiTaskAuditor(nn.Module):
106
+ """
107
+ ResNet101 Backbone Auditor incorporating safety classes and metrics
108
+ """
109
+ def __init__(self, num_classes=6, vocab_size=50000):
110
+ super(CompleteMultiTaskAuditor, self).__init__()
111
+ resnet = models.resnet101(weights=None)
112
+ self.features = nn.Sequential(*list(resnet.children())[:-2])
113
+
114
+ self.text_encoder = SimpleTextEncoder(vocab_size=vocab_size, embed_dim=512, hidden_dim=256)
115
+
116
+ self.adv_head = nn.Conv2d(2048, 1, kernel_size=1)
117
+ self.class_head = nn.Conv2d(2048, num_classes, kernel_size=1)
118
+ self.quality_head = nn.Conv2d(2048, 1, kernel_size=1)
119
+
120
+ self.object_detection_head = nn.Sequential(
121
+ nn.Conv2d(2048, 512, kernel_size=3, padding=1),
122
+ nn.ReLU(),
123
+ nn.Conv2d(512, num_classes, kernel_size=1)
124
+ )
125
+
126
+ self.faithfulness_head = nn.Sequential(
127
+ nn.Linear(2048 + 512, 512),
128
+ nn.ReLU(),
129
+ nn.Dropout(0.3),
130
+ nn.Linear(512, 256),
131
+ nn.ReLU(),
132
+ nn.Linear(256, 1)
133
+ )
134
+
135
+ self.seam_quality_head = nn.Sequential(
136
+ nn.Conv2d(2048, 512, kernel_size=3, padding=1),
137
+ nn.ReLU(),
138
+ nn.BatchNorm2d(512),
139
+ nn.Conv2d(512, 256, kernel_size=3, padding=1),
140
+ nn.ReLU(),
141
+ nn.BatchNorm2d(256),
142
+ nn.Conv2d(256, 1, kernel_size=1)
143
+ )
144
+
145
+ self.relative_adv_head = nn.Sequential(
146
+ nn.Linear(2048, 512),
147
+ nn.ReLU(),
148
+ nn.Dropout(0.2),
149
+ nn.Linear(512, 256),
150
+ nn.ReLU(),
151
+ nn.Linear(256, 1),
152
+ )
153
+
154
+ self.timestep_embed = nn.Sequential(
155
+ nn.Linear(1, 128),
156
+ nn.ReLU(),
157
+ nn.Linear(128, 256),
158
+ nn.ReLU(),
159
+ nn.Linear(256, 512)
160
+ )
161
+
162
+ def forward(self, x, text_tokens=None, timestep=None, return_features=False):
163
+ feats = self.features(x)
164
+
165
+ global_feats = F.adaptive_avg_pool2d(feats, (1, 1)).flatten(1)
166
+
167
+ adv_map = self.adv_head(feats)
168
+ adv_logits = F.adaptive_avg_pool2d(adv_map, (1, 1)).flatten(1)
169
+
170
+ class_map = self.class_head(feats)
171
+ class_logits = F.adaptive_avg_pool2d(class_map, (1, 1)).flatten(1)
172
+
173
+ qual_map = self.quality_head(feats)
174
+ qual_logits = F.adaptive_avg_pool2d(qual_map, (1, 1)).flatten(1)
175
+
176
+ object_heatmaps = self.object_detection_head(feats)
177
+
178
+ if text_tokens is not None and text_tokens.size(0) > 0:
179
+ text_features = self.text_encoder(text_tokens)
180
+ else:
181
+ text_features = torch.zeros(x.size(0), 512, device=x.device)
182
+
183
+ combined_features = torch.cat([global_feats, text_features], dim=1)
184
+ faithfulness_logits = self.faithfulness_head(combined_features)
185
+
186
+ seam_quality_map = self.seam_quality_head(feats)
187
+ seam_quality_score = torch.sigmoid(
188
+ F.adaptive_avg_pool2d(seam_quality_map, (1, 1)).flatten(1)
189
+ )
190
+
191
+ relative_adv_score = torch.sigmoid(self.relative_adv_head(global_feats))
192
+
193
+ timestep_features = None
194
+ if timestep is not None and timestep.size(0) > 0:
195
+ timestep_features = self.timestep_embed(timestep)
196
+
197
+ outputs = {
198
+ 'binary_logits': adv_logits,
199
+ 'class_logits': class_logits,
200
+ 'quality_logits': qual_logits,
201
+ 'object_heatmaps': object_heatmaps,
202
+ 'faithfulness_logits': faithfulness_logits,
203
+ 'adversarial_map': adv_map,
204
+ 'class_map': class_map,
205
+ 'seam_quality_map': seam_quality_map,
206
+ 'seam_quality_score': seam_quality_score,
207
+ 'relative_adv_score': relative_adv_score,
208
+ 'text_features': text_features,
209
+ 'timestep_features': timestep_features,
210
+ }
211
+
212
+ if return_features:
213
+ outputs['features'] = feats
214
+ outputs['global_features'] = global_feats
215
+
216
+ return outputs
217
+
218
+ # =============================================================================
219
+ # INFERENCE HELPER
220
+ # =============================================================================
221
+
222
+ def inference(model, tokenizer, image, prompt=""):
223
+ """
224
+ Independent plug-and-play function for auditing an image
225
+
226
+ Args:
227
+ model: Trained CompleteMultiTaskAuditor model
228
+ tokenizer: Initialized SimpleTokenizer used during training
229
+ image: PIL Image
230
+ prompt: String text prompt condition
231
+
232
+ Returns:
233
+ dict:
234
+ - global_safety_score: float (1.0 - adversary probability)
235
+ - category_probabilities: dict of class name -> probability
236
+ - faithfulness_score: float (0 to 1)
237
+ - seam_quality: float (0 to 1)
238
+ - binary_map: 2D numpy array
239
+ """
240
+ was_training = model.training
241
+ model.eval()
242
+
243
+ transform = transforms.Compose([
244
+ transforms.Resize((224, 224)),
245
+ transforms.ToTensor(),
246
+ transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
247
+ ])
248
+
249
+ if isinstance(image, Image.Image):
250
+ if image.mode != 'RGB':
251
+ image = image.convert('RGB')
252
+
253
+ image_tensor = transform(image).unsqueeze(0).to(DEVICE)
254
+ text_tokens = tokenizer.encode(prompt).unsqueeze(0).to(DEVICE)
255
+ timestep = torch.tensor([[0.0]], dtype=torch.float32).to(DEVICE)
256
+
257
+ with torch.no_grad():
258
+ outputs = model(image_tensor, text_tokens=text_tokens, timestep=timestep)
259
+
260
+ binary_prob = torch.sigmoid(outputs['binary_logits']).item()
261
+ global_safety_score = 1.0 - binary_prob
262
+
263
+ class_probs = F.softmax(outputs['class_logits'], dim=1)[0].cpu().numpy()
264
+ category_probabilities = {CLASS_NAMES[i]: float(class_probs[i]) for i in range(len(CLASS_NAMES))}
265
+
266
+ faithfulness_score = torch.sigmoid(outputs['faithfulness_logits']).item()
267
+ seam_quality = outputs['seam_quality_score'].item()
268
+ binary_map = outputs['adversarial_map'][0, 0].cpu().numpy()
269
+
270
+ if was_training:
271
+ model.train()
272
+
273
+ return {
274
+ "global_safety_score": global_safety_score,
275
+ "category_probabilities": category_probabilities,
276
+ "faithfulness_score": faithfulness_score,
277
+ "seam_quality": seam_quality,
278
+ "binary_map": binary_map
279
+ }
280
+
281
+ # =============================================================================
282
+ # EXAMPLE USAGE (Stand-alone test)
283
+ # =============================================================================
284
+ if __name__ == "__main__":
285
+ print("[+] Loading Standalone Modular Auditor Architecture...")
286
+ # Initialize tokenizer (Usually, you'd load the state dict or recreate it with vocab)
287
+ tokenizer = SimpleTokenizer(vocab_size=50000, max_length=MAX_PROMPT_LENGTH)
288
+
289
+ # Initialize the model instance
290
+ model = CompleteMultiTaskAuditor(num_classes=6, vocab_size=50000)
291
+ model.to(DEVICE)
292
+ model.eval()
293
+
294
+ print("[+] Architecture Initialized successfully!")
295
+ print(f"[+] Loaded {SafetyCategories.NUM_CLASSES} classes: {CLASS_NAMES}")