vaisagan commited on
Commit
7694c24
·
verified ·
1 Parent(s): c5383d0

Upload src/models/face_model.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. src/models/face_model.py +85 -0
src/models/face_model.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Multi-task face model: MobileNetV2 backbone → gender head + age head.
3
+
4
+ gender : CrossEntropyLoss (2-class)
5
+ age : SmoothL1Loss (regression, label normalised 0-1)
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Tuple
11
+ import torch
12
+ import torch.nn as nn
13
+ from torchvision import models
14
+ from torchvision.models import MobileNet_V2_Weights
15
+
16
+
17
+ class FaceModel(nn.Module):
18
+ def __init__(self, pretrained: bool = True, dropout: float = 0.3) -> None:
19
+ super().__init__()
20
+
21
+ weights = MobileNet_V2_Weights.IMAGENET1K_V1 if pretrained else None
22
+ backbone = models.mobilenet_v2(weights=weights)
23
+
24
+ # Feature extractor (all layers except the final classifier)
25
+ self.features = backbone.features
26
+
27
+ # Global average pooling + flatten → 1280-dim vector
28
+ self.pool = nn.AdaptiveAvgPool2d(1)
29
+
30
+ hidden = 512
31
+ self.shared = nn.Sequential(
32
+ nn.Flatten(),
33
+ nn.Linear(1280, hidden),
34
+ nn.BatchNorm1d(hidden),
35
+ nn.ReLU(inplace=True),
36
+ nn.Dropout(dropout),
37
+ )
38
+
39
+ # Gender head: binary
40
+ self.gender_head = nn.Sequential(
41
+ nn.Linear(hidden, 128),
42
+ nn.ReLU(inplace=True),
43
+ nn.Linear(128, 2),
44
+ )
45
+
46
+ # Age head: scalar regression
47
+ self.age_head = nn.Sequential(
48
+ nn.Linear(hidden, 128),
49
+ nn.ReLU(inplace=True),
50
+ nn.Linear(128, 1),
51
+ nn.Sigmoid(), # output in [0, 1] matching normalised labels
52
+ )
53
+
54
+ def forward(
55
+ self, x: torch.Tensor
56
+ ) -> "Tuple[torch.Tensor, torch.Tensor]":
57
+ x = self.features(x)
58
+ x = self.pool(x)
59
+ x = self.shared(x)
60
+ gender_logits = self.gender_head(x)
61
+ age_pred = self.age_head(x).squeeze(1)
62
+ return gender_logits, age_pred
63
+
64
+ def freeze_backbone(self) -> None:
65
+ for p in self.features.parameters():
66
+ p.requires_grad = False
67
+
68
+ def unfreeze_backbone(self) -> None:
69
+ for p in self.features.parameters():
70
+ p.requires_grad = True
71
+
72
+
73
+ def build_model(cfg, device: torch.device) -> FaceModel:
74
+ model = FaceModel(pretrained=True, dropout=0.3)
75
+ model.freeze_backbone() # warm-up phase: train heads only
76
+ return model.to(device)
77
+
78
+
79
+ def load_model(path: str, device: torch.device) -> FaceModel:
80
+ model = FaceModel(pretrained=False)
81
+ state = torch.load(path, map_location=device)
82
+ model.load_state_dict(state["model_state_dict"])
83
+ model.to(device)
84
+ model.eval()
85
+ return model