umeleti commited on
Commit
4d2dbd6
·
verified ·
1 Parent(s): 6a679f0

Delete acevedo_sngp_resnet18/hf_sngp_model.py

Browse files
acevedo_sngp_resnet18/hf_sngp_model.py DELETED
@@ -1,394 +0,0 @@
1
- """
2
- HuggingFace-compatible SNGP (Spectral-normalized Neural Gaussian Process) model wrapper.
3
- Enables model loading without dependency on source code.
4
- """
5
-
6
- import math
7
- from typing import Optional, Tuple
8
-
9
- import torch
10
- import torch.nn as nn
11
- import torch.nn.functional as F
12
- from torch.nn.utils import spectral_norm
13
- from transformers import PreTrainedModel, PretrainedConfig
14
- from transformers.utils import logging
15
- from torchvision.models import (
16
- resnet18, resnet34, resnet50,
17
- ResNet18_Weights, ResNet34_Weights, ResNet50_Weights,
18
- vit_b_16, vit_b_32, vit_l_16, vit_l_32, vit_h_14,
19
- ViT_B_16_Weights, ViT_B_32_Weights, ViT_L_16_Weights,
20
- ViT_L_32_Weights, ViT_H_14_Weights,
21
- )
22
-
23
- logger = logging.get_logger(__name__)
24
-
25
-
26
- class SNGPConfig(PretrainedConfig):
27
- """Configuration class for SNGP model."""
28
- model_type = "sngp_classifier"
29
-
30
- def __init__(
31
- self,
32
- arch: str = "resnet18",
33
- num_classes: int = 2,
34
- rff_dim: int = 1024,
35
- length_scale: float = 1.0,
36
- ridge_penalty: float = 1e-3,
37
- cov_momentum: float = 0.999,
38
- mean_field: bool = True,
39
- n_power_iterations_sn: int = 1,
40
- pretrained: bool = False,
41
- **kwargs
42
- ):
43
- super().__init__(**kwargs)
44
- self.arch = arch
45
- self.num_classes = num_classes
46
- self.rff_dim = rff_dim
47
- self.length_scale = length_scale
48
- self.ridge_penalty = ridge_penalty
49
- self.cov_momentum = cov_momentum
50
- self.mean_field = mean_field
51
- self.n_power_iterations_sn = n_power_iterations_sn
52
- self.pretrained = pretrained
53
-
54
-
55
- def apply_spectral_norm_to_convs(
56
- module: nn.Module,
57
- n_power_iterations: int = 1,
58
- skip_if_has_weight_orig: bool = False
59
- ) -> None:
60
- """
61
- Recursively apply spectral normalization to Conv/Linear layers.
62
-
63
- Args:
64
- module: Module to apply spectral norm to
65
- n_power_iterations: Power iterations for spectral norm
66
- skip_if_has_weight_orig: If True, skip applying SN to layers that already have weight_orig
67
- (useful when loading checkpoints with pre-existing SN)
68
- """
69
- for name, child in module.named_children():
70
- if isinstance(child, (nn.Conv2d, nn.Linear)):
71
- # Skip if already has spectral norm applied
72
- if hasattr(child, 'weight_u'):
73
- continue
74
- # Skip if weight_orig exists (pre-existing spectral norm from checkpoint)
75
- if skip_if_has_weight_orig and hasattr(child, 'weight_orig'):
76
- continue
77
-
78
- sn = spectral_norm(child, n_power_iterations=n_power_iterations)
79
- setattr(module, name, sn)
80
- else:
81
- apply_spectral_norm_to_convs(child, n_power_iterations=n_power_iterations, skip_if_has_weight_orig=skip_if_has_weight_orig)
82
-
83
-
84
- class RandomFeatureGaussianProcess(nn.Module):
85
- """
86
- RFF-GP output layer for uncertainty quantification.
87
-
88
- Uses Random Fourier Features with Gaussian Process posterior to provide:
89
- - Mean-field logits (calibrated predictions)
90
- - Raw logits (unscaled)
91
- - Predictive variance (uncertainty estimates)
92
- """
93
-
94
- def __init__(
95
- self,
96
- in_dim: int,
97
- num_classes: int,
98
- rff_dim: int = 1024,
99
- length_scale: float = 1.0,
100
- ridge_penalty: float = 1e-3,
101
- cov_momentum: float = 0.999,
102
- mean_field: bool = True,
103
- dtype: torch.dtype = torch.float32,
104
- device: Optional[torch.device] = None,
105
- ):
106
- super().__init__()
107
- self.in_dim = in_dim
108
- self.num_classes = num_classes
109
- self.rff_dim = rff_dim
110
- self.length_scale = length_scale
111
- self.ridge = ridge_penalty
112
- self.cov_momentum = cov_momentum
113
- self.mean_field = mean_field
114
-
115
- # Random Fourier feature parameters (fixed)
116
- W = torch.randn(in_dim, rff_dim, dtype=dtype) / length_scale
117
- b = 2 * math.pi * torch.rand(rff_dim, dtype=dtype)
118
- self.register_buffer("W", W)
119
- self.register_buffer("b", b)
120
-
121
- # Linear classifier over RFFs (learned)
122
- self.classifier = nn.Linear(rff_dim, num_classes, bias=True)
123
-
124
- # EMA covariance of features
125
- C = torch.zeros(rff_dim, rff_dim, dtype=dtype)
126
- self.register_buffer("cov_ema", C)
127
- self.register_buffer("num_updates", torch.tensor(0, dtype=torch.long))
128
-
129
- # Identity for covariance computation
130
- eye = torch.eye(rff_dim, dtype=dtype)
131
- self.register_buffer("I", eye)
132
-
133
- self.rff_scale = math.sqrt(2.0 / rff_dim)
134
-
135
- @torch.no_grad()
136
- def _update_cov(self, phi: torch.Tensor) -> None:
137
- """Update exponential moving average of feature covariance."""
138
- B = phi.shape[0]
139
- batch_cov = (phi.T @ phi) / max(1, B)
140
- if self.num_updates == 0:
141
- self.cov_ema.copy_(batch_cov)
142
- else:
143
- self.cov_ema.mul_(self.cov_momentum).add_(
144
- (1.0 - self.cov_momentum) * batch_cov
145
- )
146
- self.num_updates += 1
147
-
148
- def _features(self, x: torch.Tensor) -> torch.Tensor:
149
- """Compute Random Fourier Features: phi(x) = sqrt(2/m) * cos(x W + b)."""
150
- proj = x @ self.W + self.b
151
- phi = torch.cos(proj) * self.rff_scale
152
- return phi
153
-
154
- def forward(
155
- self,
156
- x: torch.Tensor,
157
- update_cov: bool = True
158
- ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
159
- """
160
- Args:
161
- x: Input features [B, in_dim]
162
- update_cov: Update covariance during training
163
-
164
- Returns:
165
- mean_field_logits: Calibrated logits
166
- raw_logits: Uncalibrated logits
167
- pred_var: Predictive variance [B, 1]
168
- """
169
- phi = self._features(x)
170
-
171
- # Update covariance during training
172
- if self.training and update_cov:
173
- with torch.no_grad():
174
- self._update_cov(phi)
175
-
176
- raw_logits = self.classifier(phi)
177
-
178
- # Compute predictive variance via GP approximation
179
- with torch.no_grad():
180
- A = (self.cov_ema + self.ridge * self.I).to(phi.dtype).to(phi.device)
181
- L = torch.linalg.cholesky(A)
182
- phi_T = phi.T
183
- y = torch.linalg.solve_triangular(L, phi_T, upper=False)
184
- z = torch.linalg.solve_triangular(L.T, y, upper=True)
185
- solved = z.T
186
- pred_var = (phi * solved).sum(dim=1, keepdim=True)
187
- pred_var = torch.clamp(pred_var, min=0.0)
188
-
189
- # Apply mean-field logit correction
190
- if self.mean_field:
191
- denom = torch.sqrt(1.0 + pred_var)
192
- mean_field_logits = raw_logits / denom
193
- else:
194
- mean_field_logits = raw_logits
195
-
196
- return mean_field_logits, raw_logits, pred_var
197
-
198
-
199
- class SNGPClassifier(nn.Module):
200
- """
201
- ResNet backbone (torchvision) with spectral normalization + RFF-GP head.
202
- """
203
-
204
- def __init__(
205
- self,
206
- num_classes: int,
207
- arch: str = "resnet18",
208
- pretrained: bool = False,
209
- rff_dim: int = 1024,
210
- length_scale: float = 1.0,
211
- ridge_penalty: float = 1e-3,
212
- cov_momentum: float = 0.999,
213
- mean_field: bool = True,
214
- n_power_iterations_sn: int = 1,
215
- apply_spectral_norm: bool = True,
216
- ):
217
- super().__init__()
218
- self.num_classes = num_classes
219
-
220
- # --- Backbone ---
221
- if arch in {"resnet18", "resnet34", "resnet50"}:
222
- if arch == "resnet18":
223
- weights = ResNet18_Weights.IMAGENET1K_V1 if pretrained else None
224
- base = resnet18(weights=weights)
225
- feat_dim = base.fc.in_features
226
- elif arch == "resnet34":
227
- weights = ResNet34_Weights.IMAGENET1K_V1 if pretrained else None
228
- base = resnet34(weights=weights)
229
- feat_dim = base.fc.in_features
230
- elif arch == "resnet50":
231
- weights = ResNet50_Weights.IMAGENET1K_V1 if pretrained else None
232
- base = resnet50(weights=weights)
233
- feat_dim = base.fc.in_features
234
-
235
- # Remove original classifier
236
- modules = list(base.children())[:-1] # keep up to global avgpool
237
- self.backbone = nn.Sequential(*modules) # outputs [B, feat_dim, 1, 1]
238
-
239
- # Pool + flatten
240
- self.pool = nn.Identity() # resnet already has avgpool at [-2]
241
- self.flatten = nn.Flatten()
242
-
243
- elif arch in {"vit_b_16", "vit_b_32", "vit_l_16", "vit_l_32", "vit_h_14"}:
244
- # Construct ViT with optional ImageNet weights
245
- if arch == "vit_b_16":
246
- weights = ViT_B_16_Weights.IMAGENET1K_V1 if pretrained else None
247
- base = vit_b_16(weights=weights)
248
- elif arch == "vit_b_32":
249
- weights = ViT_B_32_Weights.IMAGENET1K_V1 if pretrained else None
250
- base = vit_b_32(weights=weights)
251
- elif arch == "vit_l_16":
252
- weights = ViT_L_16_Weights.IMAGENET1K_V1 if pretrained else None
253
- base = vit_l_16(weights=weights)
254
- elif arch == "vit_l_32":
255
- weights = ViT_L_32_Weights.IMAGENET1K_V1 if pretrained else None
256
- base = vit_l_32(weights=weights)
257
- elif arch == "vit_h_14":
258
- weights = ViT_H_14_Weights.IMAGENET1K_V1 if pretrained else None
259
- base = vit_h_14(weights=weights)
260
-
261
- # Grab the incoming feature size from the existing head, then strip it
262
- # torchvision ViT uses a Heads block -> final Linear; we read its in_features
263
- feat_dim = None
264
- for m in base.heads.modules():
265
- if isinstance(m, nn.Linear):
266
- feat_dim = m.in_features
267
- break
268
- if feat_dim is None:
269
- # Fallback to hidden_dim if present
270
- feat_dim = getattr(base, "hidden_dim", 768)
271
-
272
- base.heads = nn.Identity() # expose class-token representation [B, feat_dim]
273
-
274
- self.backbone = base # forward now returns [B, feat_dim]
275
- self.pool = nn.Identity() # no pooling for ViT
276
- self.flatten = nn.Identity()
277
-
278
- else:
279
- raise ValueError(f"Unsupported arch: {arch}")
280
-
281
- # Apply spectral norm to all convs/linears in the backbone
282
- # Skip this if loading from checkpoint where weights are already in spectral norm form
283
- if apply_spectral_norm:
284
- apply_spectral_norm_to_convs(self.backbone, n_power_iterations=n_power_iterations_sn)
285
-
286
-
287
- # --- RFF-GP head ---
288
- self.gp_head = RandomFeatureGaussianProcess(
289
- in_dim=feat_dim,
290
- num_classes=num_classes,
291
- rff_dim=rff_dim,
292
- length_scale=length_scale,
293
- ridge_penalty=ridge_penalty,
294
- cov_momentum=cov_momentum,
295
- mean_field=mean_field,
296
- )
297
-
298
- def forward(self, x: torch.Tensor, update_cov: bool = True):
299
- """
300
- Returns:
301
- mean_field_logits, raw_logits, pred_var
302
- """
303
- feats = self.backbone(x)
304
-
305
- # Some backbones may return tuples (e.g., aux outputs). Keep the main tensor.
306
- if isinstance(feats, (tuple, list)):
307
- feats = feats[0]
308
-
309
- # ResNet: [B, C, 1, 1] -> flatten to [B, C]
310
- if feats.dim() == 4:
311
- feats = self.pool(feats) # no-op for your ResNet setup, keeps [B, C, 1, 1]
312
- feats = self.flatten(feats) # -> [B, C]
313
-
314
- # Transformer variants that might return sequences: [B, N, D]
315
- elif feats.dim() == 3:
316
- # Prefer class token if present; otherwise fallback to mean-pool the sequence
317
- feats = feats[:, 0] if getattr(self, "use_cls_token", True) else feats.mean(dim=1)
318
-
319
- # ViT (torchvision with heads=Identity) already returns [B, D]; nothing to do for dim()==2
320
-
321
- return self.gp_head(feats, update_cov=update_cov)
322
-
323
-
324
- class SNGPForImageClassification(PreTrainedModel):
325
- """
326
- HuggingFace-compatible wrapper for SNGP.
327
-
328
- Load with:
329
- from transformers import AutoModel
330
- model = AutoModel.from_pretrained("org/my-model", trust_remote_code=True)
331
- """
332
- config_class = SNGPConfig
333
- base_model_prefix = "model"
334
-
335
- def __init__(self, config: SNGPConfig):
336
- super().__init__(config)
337
- self.model = SNGPClassifier(
338
- num_classes=config.num_classes,
339
- arch=config.arch,
340
- pretrained=config.pretrained,
341
- rff_dim=config.rff_dim,
342
- length_scale=config.length_scale,
343
- ridge_penalty=config.ridge_penalty,
344
- cov_momentum=config.cov_momentum,
345
- mean_field=config.mean_field,
346
- n_power_iterations_sn=config.n_power_iterations_sn,
347
- apply_spectral_norm=False, # No spectral norm - weights are pre-normalized
348
- )
349
-
350
- @classmethod
351
- def from_pretrained(cls, pretrained_model_name_or_path, *args, **kwargs):
352
- """Load model from pretrained checkpoint."""
353
- # Use the standard HuggingFace loading mechanism
354
- # The checkpoint has weight_orig, weight_u, weight_v which match the spectral_norm structure
355
- return super().from_pretrained(pretrained_model_name_or_path, *args, **kwargs)
356
-
357
- def _load_from_state_dict(
358
- self, state_dict, prefix, local_metadata, strict, missing_keys, unexpected_keys, error_msgs
359
- ):
360
- """Standard state dict loading - checkpoint has spectral norm components."""
361
- # Call parent implementation
362
- super()._load_from_state_dict(
363
- state_dict, prefix, local_metadata, strict, missing_keys, unexpected_keys, error_msgs
364
- )
365
-
366
- def forward(
367
- self,
368
- pixel_values: torch.Tensor,
369
- return_dict: bool = True,
370
- update_cov: bool = True,
371
- ):
372
- """
373
- Args:
374
- pixel_values: Input tensor [B, 3, 224, 224]
375
- return_dict: Whether to return dict
376
- update_cov: Update GP covariance during training
377
-
378
- Returns:
379
- Dict with mean_field_logits, raw_logits, pred_var
380
- """
381
- mean_field_logits, raw_logits, pred_var = self.model(
382
- pixel_values,
383
- update_cov=update_cov
384
- )
385
-
386
- if return_dict:
387
- return {
388
- "mean_field_logits": mean_field_logits,
389
- "logits": mean_field_logits, # For compatibility
390
- "raw_logits": raw_logits,
391
- "pred_var": pred_var,
392
- }
393
-
394
- return mean_field_logits, raw_logits, pred_var