| |
| """Minimal zero-shot inference for ReLU-CLIP. Depends only on numpy, Pillow and a |
| TFLite runtime -- no torch, no CLIP, no code from this project. |
| |
| python example_infer.py cat.jpg |
| python example_infer.py cat.jpg --text text/demo_prompt_emb_vitl14.npz |
| |
| The image tower is a plain int8 TFLite graph; classification is a cosine between its |
| L2-normalised output and a stored text-embedding matrix. To classify your own classes, |
| build a new matrix once with sources/make_prompt_emb.py (needs torch + open_clip, on any |
| machine) and pass it with --text; the device side never needs those. |
| """ |
| import argparse |
| import json |
| import numpy as np |
| from PIL import Image |
|
|
| try: |
| from tflite_runtime.interpreter import Interpreter |
| except ImportError: |
| try: |
| from ai_edge_litert.interpreter import Interpreter |
| except ImportError: |
| import tensorflow as tf |
| Interpreter = tf.lite.Interpreter |
|
|
|
|
| def preprocess(path, spec): |
| """Resize shortest side to 224 (bicubic), centre-crop 224, scale to [0,1], CLIP-normalise.""" |
| s = spec["input"]["size"] |
| img = Image.open(path).convert("RGB") |
| w, h = img.size |
| |
| |
| nw, nh = (s, int(s * h / w)) if w <= h else (int(s * w / h), s) |
| img = img.resize((nw, nh), Image.BICUBIC) |
| |
| |
| left, top = int(round((nw - s) / 2.0)), int(round((nh - s) / 2.0)) |
| img = img.crop((left, top, left + s, top + s)) |
| x = np.asarray(img, np.float32) / 255.0 |
| x = (x - spec["input"]["mean"]) / spec["input"]["std"] |
| return x[None] |
|
|
|
|
| def main(): |
| ap = argparse.ArgumentParser() |
| ap.add_argument("image") |
| ap.add_argument("--model", default="efflite4-vitl14/student_int8.tflite") |
| ap.add_argument("--text", default="text/imagenet1k_text_emb_vitl14.npz") |
| ap.add_argument("--spec", default="preprocessing.json") |
| ap.add_argument("--topk", type=int, default=5) |
| a = ap.parse_args() |
|
|
| spec = json.load(open(a.spec)) |
|
|
| z = np.load(a.text, allow_pickle=True) |
| key = "embs" if "embs" in z.files else "text_emb" |
| T, labels = z[key].astype(np.float32), z["labels"] |
| T = T / np.linalg.norm(T, axis=1, keepdims=True) |
|
|
| itp = Interpreter(a.model) |
| itp.allocate_tensors() |
| inp, out = itp.get_input_details()[0], itp.get_output_details()[0] |
|
|
| x = preprocess(a.image, spec) |
| s_in, z_in = inp["quantization"] |
| itp.set_tensor(inp["index"], np.clip(np.round(x / s_in + z_in), -128, 127).astype(inp["dtype"])) |
| itp.invoke() |
|
|
| s_out, z_out = out["quantization"] |
| emb = (itp.get_tensor(out["index"]).astype(np.float32) - z_out) * s_out |
| emb /= np.linalg.norm(emb, axis=-1, keepdims=True) |
|
|
| sims = (emb @ T.T)[0] |
| for i in sims.argsort()[::-1][:a.topk]: |
| print(f"{sims[i]:+.4f} {labels[i]}") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|