gagan1985's picture
Upload folder using huggingface_hub
20d532b verified
Raw
History Blame Contribute Delete
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()