Download search.py from Modularcomputing/Native-Bird2: direct link, hf CLI and curl.
- Browser
- Download file 3.45 kB
-
https://huggingface.co/Modularcomputing/Native-Bird2/resolve/main/search.py
- Command line
-
hf download hf://Modularcomputing/Native-Bird2/search.py
-
curl -L -o search.py https://huggingface.co/Modularcomputing/Native-Bird2/resolve/main/search.py
3.45 kB
| """Text -> image search with a trained Native-Bird x SigLIP alignment checkpoint. | |
| python search.py --ckpt runs/native_bird/best.pt "Nkịta na-agba ọsọ n'ahịhịa" "A dog running on grass" -k 5 | |
| python search.py --ckpt runs/native_bird/best.pt --split test --json out.json "query 1" "query 2" | |
| The gallery is every image of --split (default test, 809 images). Image features come from the same frozen | |
| image encoder the checkpoint was trained with (cached in data/features/, computed on first use; needs the | |
| Flickr8k images at data.image_dir). Works on CPU or GPU. | |
| """ | |
| import argparse, json, os, torch | |
| from nbmm.common import (seed_all, pick_device, load_split, image_features, text_features, load_sentence_model, | |
| text_arm, TrainableText, AlignModel, rel, _set) | |
| class Searcher: | |
| def __init__(self, ckpt, split="test", device="auto", overrides=()): | |
| ck = torch.load(rel(ckpt), map_location="cpu", weights_only=False) | |
| self.cfg = cfg = ck["cfg"] | |
| for o in overrides: | |
| k, v = o.split("=", 1); _set(cfg, k, v) | |
| seed_all(cfg["seed"]) | |
| self.dev = pick_device(device) | |
| self.df = load_split(cfg, split) | |
| self.ids = self.df.image_id.tolist() | |
| I = image_features(cfg, self.df, split, self.dev) | |
| self.model = AlignModel(ck["d_img"], ck["d_txt"], cfg).to(self.dev).eval() | |
| self.model.load_state_dict(ck["model"]) | |
| self.unfreeze = cfg["text_encoder"]["unfreeze_last_n_layers"] > 0 | |
| if self.unfreeze: | |
| self.txt = TrainableText(cfg, self.dev).to(self.dev).eval() | |
| self.txt.load_state_dict(ck["text_trainable"], strict=False) | |
| else: | |
| self.arm = text_arm(cfg) | |
| self.st = load_sentence_model(self.arm, self.dev) | |
| with torch.no_grad(): | |
| self.zi = self.model.img(I.to(self.dev)) | |
| def embed_text(self, texts): | |
| if self.unfreeze: | |
| e = self.txt(texts) | |
| else: | |
| e = self.st.encode([self.arm.get("prompt", "") + t for t in texts], convert_to_tensor=True, | |
| normalize_embeddings=True).float().to(self.dev) | |
| return self.model.txt(e) | |
| def search(self, texts, k=5): | |
| sims = self.embed_text(texts) @ self.zi.t() | |
| out = [] | |
| for t, row in zip(texts, sims): | |
| order = row.argsort(descending=True) | |
| top = order[:k].tolist() | |
| out.append({"query": t, "top": [{"image_id": self.ids[j], "score": round(row[j].item(), 4)} for j in top], | |
| "_order": order}) | |
| return out | |
| def rank_of(self, res, image_id): | |
| j = self.ids.index(image_id) | |
| return int((res["_order"] == j).nonzero()[0].item()) + 1 # 1-based | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--ckpt", required=True) | |
| ap.add_argument("--split", default="test") | |
| ap.add_argument("-k", type=int, default=5) | |
| ap.add_argument("--json", default=None) | |
| ap.add_argument("queries", nargs="+") | |
| a = ap.parse_args() | |
| s = Searcher(a.ckpt, a.split) | |
| res = s.search(a.queries, a.k) | |
| for r in res: | |
| r.pop("_order") | |
| print(r["query"]) | |
| for i, h in enumerate(r["top"], 1): | |
| print(f" {i}. {h['image_id']} {h['score']}") | |
| if a.json: | |
| json.dump(res, open(a.json, "w"), indent=2, ensure_ascii=False) | |
| if __name__ == "__main__": | |
| main() | |