harpertoken commited on
Commit
cea9f80
·
verified ·
1 Parent(s): 0e86679

Upload modeling_fuse.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. modeling_fuse.py +91 -0
modeling_fuse.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Transformers-compatible wrapper for harpertoken/fuse.
2
+
3
+ The model matches images to captions with a dot product between projected
4
+ CNN and embedding representations, not a transformer. This subclasses
5
+ PreTrainedModel so the weights serialize honestly as config.json plus
6
+ model.safetensors and load through AutoModel. Importing this module registers
7
+ the architecture.
8
+ """
9
+
10
+ import torch
11
+ import torch.nn as nn
12
+ from transformers import AutoConfig, AutoModel, PretrainedConfig, PreTrainedModel
13
+
14
+
15
+ class FuseConfig(PretrainedConfig):
16
+ model_type = "fuse_matcher"
17
+
18
+ def __init__(self, vocab_size=4433, num_labels=1, dropout=0.25, **kwargs):
19
+ super().__init__(**kwargs)
20
+ self.vocab_size = vocab_size
21
+ self.num_labels = num_labels
22
+ self.dropout = dropout
23
+
24
+
25
+ class FuseMatcher(PreTrainedModel):
26
+ config_class = FuseConfig
27
+ base_model_prefix = "fuse"
28
+
29
+ # Transformers 5 reads this mapping while finalising a load. Nothing here
30
+ # is tied, but the attribute has to exist or from_pretrained raises
31
+ # AttributeError before the weights are placed.
32
+ all_tied_weights_keys = {}
33
+
34
+ def __init__(self, config):
35
+ super().__init__(config)
36
+ self.cnn = nn.ModuleDict(
37
+ {
38
+ "conv1": nn.Conv2d(3, 32, 3, padding=1),
39
+ "conv2": nn.Conv2d(32, 64, 3, padding=1),
40
+ "fc1": nn.Linear(64 * 8 * 8, 128),
41
+ }
42
+ )
43
+ self.pool = nn.MaxPool2d(2, 2)
44
+ self.adapt = nn.AdaptiveAvgPool2d((8, 8))
45
+ self.emb = nn.Embedding(config.vocab_size, 64, padding_idx=0)
46
+ self.tfc = nn.Linear(64, 128)
47
+ self.iproj = nn.Linear(128, 128)
48
+ self.tproj = nn.Linear(128, 128)
49
+ self.logit_scale = nn.Parameter(torch.tensor(4.0))
50
+ self.drop = nn.Dropout(config.dropout)
51
+
52
+ def forward(self, image, tokens):
53
+ import torch.nn.functional as F
54
+
55
+ x = self.pool(torch.relu(self.cnn["conv1"](image)))
56
+ x = self.pool(torch.relu(self.cnn["conv2"](x)))
57
+ x = self.adapt(x)
58
+ xi = self.iproj(self.drop(torch.relu(self.cnn["fc1"](x.view(x.size(0), -1)))))
59
+ mask = (tokens != 0).float().unsqueeze(-1)
60
+ te = self.emb(tokens) * mask
61
+ xt = self.tproj(
62
+ self.drop(torch.relu(self.tfc(te.sum(1) / mask.sum(1).clamp(min=1))))
63
+ )
64
+ xi = F.normalize(xi, dim=-1)
65
+ xt = F.normalize(xt, dim=-1)
66
+ return self.logit_scale.exp().clamp(max=100.0) * (xi * xt).sum(-1)
67
+
68
+ def match(self, image, caption, vocab):
69
+ """image: HxWx3 float array in [0, 1]. caption: string.
70
+ Returns (match_probability, predicted_match)."""
71
+ import re
72
+
73
+ self.eval()
74
+ img = torch.as_tensor(image, dtype=torch.float32).permute(2, 0, 1)
75
+ ids = [vocab.get(w, 1) for w in re.findall(r"[a-z']+", caption.lower())]
76
+ ids = ids[:32] + [0] * (32 - len(ids))
77
+ with torch.no_grad():
78
+ logit = self.forward(
79
+ img.unsqueeze(0),
80
+ torch.tensor([ids]),
81
+ )[0]
82
+ prob = float(torch.sigmoid(logit))
83
+ return prob, prob > 0.5
84
+
85
+
86
+ def register():
87
+ AutoConfig.register(FuseConfig.model_type, FuseConfig, exist_ok=True)
88
+ AutoModel.register(FuseConfig, FuseMatcher, exist_ok=True)
89
+
90
+
91
+ register()