Skip to content

About

Lightweight HS code classifier using multilingual embeddings - POC for customs digitization

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

HSClassify micro

Multilingual HS / HTS code classification by embedding retrieval. ~500 lines of Python.

Question. Can an off-the-shelf multilingual sentence-embedding model assign 6-digit Harmonized System codes to free-text product descriptions, in any language, across the whole nomenclature rather than a hand-picked subset?

Method. Treat classification as retrieval. The index holds every HS 2022 subheading description (5,613 codes, heading prepended for context) plus 17k real product descriptions with known codes. A query is embedded with the same model and each code is scored by its best-matching index entry. Nothing is translated: multilinguality comes from the encoder.

query ──embed──▶ cosine vs index ──▶ best entry per code ──▶ top-k HS6 ──▶ US HTS lines
                 index = nomenclature (5,613) ∪ labelled examples (17,133)

Results

Held-out split of the real examples (3,023 English queries, 15% of data/examples.csv, never indexed), scored over all 5,613 codes.

Encoder HS6 top-1 HS6 top-3 HS6 top-5 HS4 top-1 HS2 top-1
intfloat/multilingual-e5-small (118M) 0.421 0.586 0.668 0.594 0.788
BAAI/bge-m3 (568M) — — — — —
Qwen/Qwen3-Embedding-0.6B — — — — —
e5-small + contrastive fine-tune — — — — —

Raw numbers: results/. Rows marked — have not been run yet (see Reproduce). The 78-case multilingual benchmark (data/benchmark.csv) is also reported by the evaluate script; its numbers are not listed here because the run predated the HS 2022 code fix in that file.

Observations so far:

  • Chapter (HS2) is right ~80% of the time; most errors are between sibling subheadings whose official wording differs by a qualifier ("other", "n.e.c.", a viscosity or weight threshold) that a short product description rarely states.
  • Non-English queries land in the right neighbourhood (heading) but miss the subheading more often; a small encoder is the likely bottleneck.
  • Earlier versions of this repo reported ~90% but only over 118 curated codes with templated synthetic data. Those numbers are not comparable.

Run

uv venv && uv pip install -e .            # or: pip install -r requirements.txt
python -m hsclassify.train                # build models/index with e5-small (~2 min on a laptop)
uvicorn app:app --reload                  # http://localhost:8000

POST /predict {"text": "冷冻虾"} returns the top-k codes with the matched index entry; GET /hts/{hs6} returns the US HTS 2026 tariff lines and general duty rate under a code; POST /ocr runs Tesseract on an uploaded image.

Reproduce / extend

python -m hsclassify.evaluate                                   # default encoder
python -m hsclassify.evaluate -m Qwen/Qwen3-Embedding-0.6B      # another encoder (one at a time)
python -m hsclassify.evaluate --no-examples                     # zero-shot: nomenclature only
python -m hsclassify.train --finetune --epochs 1                # MNRL fine-tune, then index
python -m hsclassify.evaluate --index models/index              # score a saved index

Fine-tuning pairs each training description with the nomenclature text of its code (MultipleNegativesRankingLoss, in-batch negatives); the held-out split is never seen. Encoders larger than ~300M parameters need a GPU or a machine with more than 16 GB of RAM; --device cpu is slower but safe.

Layout

hsclassify/data.py      load nomenclature, examples, benchmark, US HTS       (70 lines)
hsclassify/model.py     HSClassifier: build index, predict, save/load        (110 lines)
hsclassify/train.py     build index, optional contrastive fine-tune           (75 lines)
hsclassify/evaluate.py  held-out + benchmark metrics for one or more encoders (95 lines)
app.py                  FastAPI demo (predict, HTS lookup, OCR)               (80 lines)
templates/index.html    single-page UI
data/                   6.6 MB of CSVs, documented in data/README.md
results/                evaluation outputs

Built artefacts go to models/ (git-ignored). For the Hugging Face Space, upload that folder to a Hub repo and set HF_INDEX_REPO; app.py downloads it on start-up. The GitHub Action in .github/workflows mirrors main to the Space.

Data and licences

See data/README.md. HS nomenclature: ODC-PDDL via datasets/harmonized-system. US HTS: public domain (USITC). Labelled examples: CBP CROSS rulings, product_hscode and HSCodeComp, via the Hugging Face Hub. Code: MIT.

About

Lightweight HS code classifier using multilingual embeddings - POC for customs digitization

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages