Kenpache's picture
Quick start: pass trust_remote_code=True to AutoTokenizer
a676d03 verified
|
Raw
History Blame Contribute Delete
8.91 kB
---
language:
- en
license: mit
library_name: transformers
pipeline_tag: text-classification
base_model: BAAI/bge-m3
inference: false
metrics:
- accuracy
- recall
tags:
- hs-code
- hs6
- harmonized-system
- hts
- tariff
- tariff-classification
- customs
- customs-clearance
- trade-compliance
- import-export
- international-trade
- logistics
- supply-chain
- ecommerce
- product-classification
- product-categorization
- text-classification
- multi-class-classification
- english
- xlm-roberta
- bge-m3
---
# HS Code Classifier (HS6, English)
**Give it an English product description, get back the 6-digit Harmonized System (HS)
code** β€” the commodity code that drives customs tariff classification, duty rates, HTS
lookups and trade compliance.
```
"men's cotton knitted t-shirt, short sleeve" β†’ 610910 (0.998)
"portable laptop computer, 14 inch display, 1.2 kg" β†’ 847130 (0.994)
"lithium-ion rechargeable battery, 3.7 V, 5000 mAh" β†’ 850760 (0.929)
"roasted arabica coffee beans, not decaffeinated" β†’ 090121 (0.811)
```
Fine-tuned end-to-end over the full **6,750-class HS6 space** β€” not a toy subset of
the most common chapters. HS4 (1,266 headings) and HS2 (97 chapters) come out of the
same forward pass and are **guaranteed consistent** with the HS6 answer.
| | |
|---|---|
| **Task** | Product description β†’ HS6 tariff code |
| **Input language** | **English** |
| **Label space** | 6,750 HS6 Β· 1,266 HS4 Β· 97 HS2 |
| **Accuracy on full product descriptions** | **71.94% top-1 Β· 91.08% top-5** |
| **Parameters** | 573.6M (fp32, 2.2 GB) |
| **Context window** | 1,024 tokens at inference |
What you get depends on what you give it: a full description runs at ~72% top-1, a
two-word catalogue stub at ~38%. The complete breakdown β€” by input type, by length, by
confidence β€” is right below.
---
## Accuracy
Measured on a frozen held-out test set of **8,065 product descriptions** that has zero
overlap with training and was never used for tuning any hyperparameter or threshold.
### Accuracy depends heavily on how much you tell it
This is the single most important table in this card:
| Input | Share of test | Top-1 | Top-5 |
|---|---:|---:|---:|
| **Full product description** | 35% | **71.94%** | **91.08%** |
| Short subject line | 22% | 46.34% | 73.17% |
| Two- or three-word phrase | 43% | 38.01% | 61.70% |
By raw character length:
| Length | Top-1 |
|---|---:|
| 0–40 chars | 39.00% |
| 40–100 | 45.15% |
| 100–200 | 46.98% |
| 200–500 | 73.75% |
| 500–1500 | **74.72%** |
| 1500+ | 64.77% |
**Do not truncate your inputs.** Material, construction, and intended use are exactly
the features that separate one subheading from another; `"two folding cots"` does not
contain them and no model can recover them. Feed the whole description β€” the spec
sheet, the paragraph, the invoice line with attributes β€” and accuracy roughly doubles.
### Averaged over the whole benchmark
The benchmark is deliberately hostile: 62% of its inputs are under 100 characters, so
the average is dragged down by fragments that carry no classifiable features at all.
Averaged over all 8,065 items:
| Metric | Score |
|---|---|
| HS6 top-1 (exact 6-digit match) | 51.70% |
| HS4 top-1 (heading) | 60.99% |
| HS2 top-1 (chapter) | 74.28% |
| HS6 top-5 recall | 74.48% |
Which of the two numbers applies to you is decided by your input, not by the model:
feed full descriptions and expect the 72% row, feed catalogue stubs and expect the 38%
row. Nothing is hidden here β€” both are stated so you can predict your own result
before you download 2.2 GB.
### Confidence is usable as a threshold
Top-1 softmax probability is monotonically informative, which makes a
straight-through / review split practical:
| Confidence | Share of traffic | Accuracy |
|---|---:|---:|
| 0.8 – 1.0 | 62.8% | 63.7% |
| 0.6 – 0.8 | 12.6% | 28.4% |
| 0.4 – 0.6 | 12.3% | 21.6% |
| 0.2 – 0.4 | 9.1% | 12.0% |
| 0.0 – 0.2 | 3.2% | 3.1% |
With temperature `T β‰ˆ 1.75` applied to the logits, calibration improves further: you
can auto-accept 41.5% of the flow at 80.0% accuracy. Recommended pattern β€” auto-clear
high-confidence items, route the rest to a human with the top-5 list attached
(top-5 covers 74% of everything and 91% of full descriptions).
### Where the errors go
| Outcome | Share |
|---|---:|
| Correct at top-1 | 51.70% |
| Correct code present in top-5, but not ranked first | 22.78% |
| Correct code absent from top-5 | 25.52% |
Of the misses, 18.1% stay inside the correct HS4 heading and 26.6% inside the correct
HS2 chapter β€” i.e. a large part of the error is near-miss, not nonsense.
---
## Quick start
```bash
pip install transformers torch sentencepiece
```
Verified on `transformers` 5.5 / `torch` 2.11. The model code uses only long-stable
`transformers` APIs, so 4.4x and later should work as well.
```python
from transformers import AutoModel, AutoTokenizer
REPO = "Kenpache/hs-code-classifier-en"
model = AutoModel.from_pretrained(REPO, trust_remote_code=True).eval()
tokenizer = AutoTokenizer.from_pretrained(REPO, trust_remote_code=True)
model.classify(["men's cotton knitted t-shirt, short sleeve"], tokenizer, top_k=5)
# [[{'hs6': '610910', 'score': 0.9983},
# {'hs6': '610990', 'score': 0.0010},
# {'hs6': '611020', 'score': 0.0005}, ...]]
```
`classify()` batches for you and accepts a list of any length:
```python
codes = model.classify(descriptions, tokenizer, top_k=5, batch_size=32)
```
### On GPU
```python
model = AutoModel.from_pretrained(REPO, trust_remote_code=True).to("cuda").eval()
```
CUDA, Apple Silicon (`mps`) and CPU all work. Roughly 3 GB of VRAM at batch 16 /
length 1024; about 4 GB of RAM on CPU.
**Leave the context window at 1,024 tokens.** It is already the default in
`config.json`; lowering `recommended_max_length` costs about 2.9 points on texts longer
than 1,500 characters, and raising it to 2,048 adds nothing.
### All three HS levels at once
```python
import torch
enc = tokenizer(["woven cotton fabric, dyed, 200 g/m2"], truncation=True,
max_length=1024, return_tensors="pt")
with torch.no_grad():
out = model(**enc)
hs6 = model.config.id2label[out.logits.argmax(-1).item()] # '520839'
hs4 = model.config.id2hs4[out.logits_hs4.argmax(-1).item()] # '5208'
hs2 = model.config.id2hs2[out.logits_hs2.argmax(-1).item()] # '52'
```
`hs4` and `hs2` are marginals of the same distribution (logsumexp over the children
of each parent), so **the levels can never contradict each other**: the model cannot
return heading `6109` and a subheading that lives under `6110`.
---
## Limitations
1. **English only.** The encoder is multilingual, but the head was trained on English
product descriptions. Other languages are untested and expected to be much weaker.
2. **HS6 only.** The first six digits are internationally harmonized; national
8–10 digit tariff lines are out of scope and this model does not predict them.
3. **Short inputs are hard**, as the tables above show. Under ~40 characters, expect
~39% top-1.
4. **Coverage is uneven across the 6,750 classes.** Rarely-seen codes are much weaker
than the headline number suggests; roughly 3,800 codes carry the bulk of the
model's competence.
5. **The ceiling is domain-imposed, not model-imposed.** Identical descriptions
legitimately receive different codes depending on context (end use, material
composition, degree of processing), which caps any text-only classifier.
6. **Not legal or customs advice.** Output is a ranked suggestion. Binding
classification is a decision of the competent authority. Use this to triage, to
pre-fill, and to route to a human β€” not to file unattended.
---
## Intended use
Good fits:
- pre-filling HS6 on customs declarations, then human review of low-confidence rows
- catalogue / marketplace enrichment at scale
- landed-cost and duty estimation tooling
- deduplicating and sanity-checking existing classifications (flag rows where the
model is confident and disagrees)
Poor fits: unattended filing, national tariff lines beyond 6 digits, non-English input.
---
## Files
| File | What it is |
|---|---|
| `model.safetensors` | weights, fp32, 2.2 GB |
| `config.json` | encoder config + head config + `id2label` for all 6,750 HS6 codes |
| `modeling_hs6.py`, `configuration_hs6.py` | model definition (loaded via `trust_remote_code=True`) |
| `tokenizer.json`, `sentencepiece.bpe.model`, … | XLM-R tokenizer, `model_max_length` 1024 |
## License
MIT, following the `BAAI/bge-m3` base model.
## Citation
```bibtex
@misc{hs6_classifier_en,
title = {HS Code Classifier (HS6, English)},
year = {2026},
note = {XLM-RoBERTa-large (bge-m3) with a flat 6,750-class HS6 head
and marginalized HS4/HS2 levels},
url = {https://huggingface.co/Kenpache/hs-code-classifier-en}
}
```