Download inference_tinybert.py from gagan1985/tinybert-4l-312d-indian-address-parser: direct link, hf CLI and curl.
- Browser
- Download file 5.4 kB
-
https://huggingface.co/gagan1985/tinybert-4l-312d-indian-address-parser/resolve/main/inference_tinybert.py
- Command line
-
hf download hf://gagan1985/tinybert-4l-312d-indian-address-parser/inference_tinybert.py
-
curl -L -o inference_tinybert.py https://huggingface.co/gagan1985/tinybert-4l-312d-indian-address-parser/resolve/main/inference_tinybert.py
5.4 kB
| """Run the fine-tuned TinyBERT model on new addresses. | |
| Standalone script — only needs `transformers`+`torch` (and optionally | |
| `gazetteer_lookup.py` + a pincodes CSV for the administrative fields). | |
| Unlike the t5/qwen models, this is token classification (BIO tagging), not | |
| JSON generation — it always produces a well-formed field dict; there's no | |
| "invalid JSON" failure mode to handle. | |
| Usage: | |
| # Single address | |
| python inference_tinybert.py "FLAT NO.32, UTTARA TOWERS, MG ROAD GUWAHATI , Kamrup Unclassified AS 781029" | |
| # Batch from stdin (one address per line) | |
| cat addresses.txt | python inference_tinybert.py --stdin | |
| # Batch from a text file, output JSONL | |
| python inference_tinybert.py --file addresses.txt --out results.jsonl | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import sys | |
| MAX_LENGTH = 160 | |
| ALL_FIELDS = ( | |
| "houseNumber", "houseName", "poi", "street", "subsubLocality", "subLocality", | |
| "locality", "village", "subDistrict", "district", "city", "state", "pincode", | |
| ) | |
| LABELS = ["O"] + [f"{prefix}-{field}" for field in ALL_FIELDS for prefix in ("B", "I")] | |
| ID2LABEL = {i: label for i, label in enumerate(LABELS)} | |
| def extract_fields(raw_address: str, offsets: list[tuple[int, int]], pred_label_ids: list[int]) -> dict: | |
| """Reconstruct the field dict from per-token BIO predictions by slicing the | |
| raw address text at each contiguous B-/I- run's char span — never | |
| `tokenizer.decode`, which would lose casing and introduce WordPiece-merge | |
| artifacts (e.g. "##" continuation glue).""" | |
| result = {f: None for f in ALL_FIELDS} | |
| current_field = None | |
| current_start = None | |
| current_end = None | |
| def flush(): | |
| if current_field is not None and result[current_field] is None: | |
| result[current_field] = raw_address[current_start:current_end] | |
| for (start, end), label_id in zip(offsets, pred_label_ids): | |
| if start == end: | |
| continue | |
| label = ID2LABEL[label_id] | |
| if label == "O": | |
| flush() | |
| current_field = None | |
| continue | |
| prefix, field = label.split("-", 1) | |
| if prefix == "B" or field != current_field: | |
| flush() | |
| current_field, current_start, current_end = field, start, end | |
| else: | |
| current_end = end | |
| flush() | |
| return result | |
| def load_model(model_id: str): | |
| from transformers import AutoModelForTokenClassification, AutoTokenizer | |
| tokenizer = AutoTokenizer.from_pretrained(model_id) | |
| model = AutoModelForTokenClassification.from_pretrained(model_id) | |
| model.eval() | |
| return model, tokenizer | |
| def parse_address(model, tokenizer, raw_address: str) -> dict: | |
| import torch | |
| enc = tokenizer( | |
| raw_address, return_tensors="pt", return_offsets_mapping=True, | |
| truncation=True, max_length=MAX_LENGTH, | |
| ) | |
| offsets = enc.pop("offset_mapping")[0].tolist() | |
| with torch.no_grad(): | |
| out = model(**enc) | |
| pred_ids = out.logits[0].argmax(-1).tolist() | |
| result = extract_fields(raw_address, offsets, pred_ids) | |
| result["_raw_address"] = raw_address | |
| return result | |
| def main(): | |
| p = argparse.ArgumentParser(description="Parse Indian addresses using the fine-tuned TinyBERT model") | |
| p.add_argument("address", nargs="?", help="Single address to parse") | |
| p.add_argument("--stdin", action="store_true", help="Read addresses from stdin, one per line") | |
| p.add_argument("--file", help="Read addresses from a text file") | |
| p.add_argument("--out", help="Write JSONL output to file (default: stdout)") | |
| p.add_argument("--model", default=".", help="Model dir or HF repo id (default: current directory)") | |
| p.add_argument("--pincodes", default=None, | |
| help="Path to a pincodes CSV (India Post format) — if given, adds " | |
| "districtAdministrative/stateAdministrative/cityAdministrative " | |
| "fields via deterministic pincode lookup (does not alter the " | |
| "model's own district/state/city)") | |
| args = p.parse_args() | |
| import os | |
| model, tokenizer = load_model(args.model) | |
| gazetteer = None | |
| if args.pincodes and os.path.exists(args.pincodes): | |
| from gazetteer_lookup import load_gazetteer | |
| gazetteer = load_gazetteer(args.pincodes) | |
| if args.address: | |
| addresses = [args.address] | |
| elif args.stdin: | |
| addresses = [line.rstrip("\n") for line in sys.stdin if line.strip()] | |
| elif args.file: | |
| with open(args.file, encoding="utf-8") as f: | |
| addresses = [line.rstrip("\n") for line in f if line.strip()] | |
| else: | |
| p.print_help() | |
| return | |
| out_f = open(args.out, "w", encoding="utf-8") if args.out else None | |
| for addr in addresses: | |
| result = parse_address(model, tokenizer, addr) | |
| if gazetteer is not None: | |
| from gazetteer_lookup import add_administrative_fields | |
| raw_addr = result.pop("_raw_address") | |
| result = add_administrative_fields(result, gazetteer) | |
| result["_raw_address"] = raw_addr | |
| line = json.dumps(result, ensure_ascii=False) | |
| if out_f: | |
| out_f.write(line + "\n") | |
| else: | |
| print(line) | |
| if out_f: | |
| out_f.close() | |
| print(f"Wrote {len(addresses):,} results to {args.out}", file=sys.stderr) | |
| if __name__ == "__main__": | |
| main() | |