Zorrojurro commited on
Commit
49c96b6
Β·
verified Β·
1 Parent(s): a2b8a0b

Upload src/models/feature_extractor.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. src/models/feature_extractor.py +129 -0
src/models/feature_extractor.py ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ CNN Feature Extractor β€” Modified ResNet-18 for grayscale thermal images.
3
+
4
+ Takes single-channel (grayscale) 224Γ—224 images and outputs 256-dim
5
+ feature embeddings suitable for downstream sequence analysis.
6
+ """
7
+
8
+ import torch
9
+ import torch.nn as nn
10
+ import torchvision.models as models
11
+
12
+
13
+ class ThermalFeatureExtractor(nn.Module):
14
+ """
15
+ Modified ResNet-18 that accepts 1-channel grayscale input
16
+ and produces a compact feature embedding.
17
+
18
+ Architecture:
19
+ Input (1, 224, 224)
20
+ β†’ Conv1 (1β†’64, 7Γ—7) (replaces the default 3β†’64)
21
+ β†’ ResNet-18 layers 1-4
22
+ β†’ AdaptiveAvgPool β†’ (512,)
23
+ β†’ FC(512β†’256) + BatchNorm + ReLU + Dropout
24
+ β†’ 256-dim embedding
25
+ """
26
+
27
+ def __init__(
28
+ self,
29
+ embedding_dim: int = 256,
30
+ pretrained: bool = True,
31
+ in_channels: int = 1,
32
+ dropout: float = 0.3,
33
+ ):
34
+ super().__init__()
35
+ self.embedding_dim = embedding_dim
36
+
37
+ # Load pretrained ResNet-18
38
+ weights = models.ResNet18_Weights.DEFAULT if pretrained else None
39
+ resnet = models.resnet18(weights=weights)
40
+
41
+ # Replace the first conv layer: 3-channel β†’ 1-channel
42
+ original_conv = resnet.conv1
43
+ self.conv1 = nn.Conv2d(
44
+ in_channels,
45
+ 64,
46
+ kernel_size=7,
47
+ stride=2,
48
+ padding=3,
49
+ bias=False,
50
+ )
51
+
52
+ # If pretrained, initialise from the mean of the RGB weights
53
+ if pretrained:
54
+ with torch.no_grad():
55
+ self.conv1.weight = nn.Parameter(
56
+ original_conv.weight.mean(dim=1, keepdim=True)
57
+ )
58
+
59
+ # Keep the rest of ResNet-18 up to avgpool
60
+ self.bn1 = resnet.bn1
61
+ self.relu = resnet.relu
62
+ self.maxpool = resnet.maxpool
63
+ self.layer1 = resnet.layer1
64
+ self.layer2 = resnet.layer2
65
+ self.layer3 = resnet.layer3
66
+ self.layer4 = resnet.layer4
67
+ self.avgpool = resnet.avgpool
68
+
69
+ # Projection head: 512 β†’ embedding_dim
70
+ self.projection = nn.Sequential(
71
+ nn.Linear(512, embedding_dim),
72
+ nn.BatchNorm1d(embedding_dim),
73
+ nn.ReLU(inplace=True),
74
+ nn.Dropout(p=dropout),
75
+ )
76
+
77
+ @classmethod
78
+ def from_config(cls, config) -> "ThermalFeatureExtractor":
79
+ """Construct from a Config object."""
80
+ fe = config.model.feature_extractor
81
+ return cls(
82
+ embedding_dim=fe.embedding_dim,
83
+ pretrained=fe.pretrained,
84
+ in_channels=fe.in_channels,
85
+ dropout=config.model.sequence_analyzer.dropout,
86
+ )
87
+
88
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
89
+ """
90
+ Forward pass.
91
+
92
+ Args:
93
+ x: Tensor of shape (B, 1, 224, 224).
94
+
95
+ Returns:
96
+ Embedding tensor of shape (B, embedding_dim).
97
+ """
98
+ x = self.conv1(x)
99
+ x = self.bn1(x)
100
+ x = self.relu(x)
101
+ x = self.maxpool(x)
102
+
103
+ x = self.layer1(x)
104
+ x = self.layer2(x)
105
+ x = self.layer3(x)
106
+ x = self.layer4(x)
107
+
108
+ x = self.avgpool(x)
109
+ x = torch.flatten(x, 1) # (B, 512)
110
+ x = self.projection(x) # (B, embedding_dim)
111
+ return x
112
+
113
+ def extract_features_from_sequence(
114
+ self, sequence: torch.Tensor
115
+ ) -> torch.Tensor:
116
+ """
117
+ Extract features for a batch of sequences.
118
+
119
+ Args:
120
+ sequence: (B, T, 1, H, W) β€” batch of image sequences.
121
+
122
+ Returns:
123
+ (B, T, embedding_dim)
124
+ """
125
+ B, T, C, H, W = sequence.shape
126
+ # Flatten batch and time β†’ (B*T, C, H, W)
127
+ x = sequence.view(B * T, C, H, W)
128
+ features = self.forward(x) # (B*T, D)
129
+ return features.view(B, T, self.embedding_dim)