Text Classification
Transformers
Safetensors
English
hs6_classifier
feature-extraction
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
multi-class-classification
english
xlm-roberta
bge-m3
custom_code
Instructions to use Kenpache/hs-code-classifier-en with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use Kenpache/hs-code-classifier-en with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-classification", model="Kenpache/hs-code-classifier-en", trust_remote_code=True)# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("Kenpache/hs-code-classifier-en", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
File size: 5,476 Bytes
4284421 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | """HS6 product classifier: XLM-RoBERTa encoder + one flat linear head.
HS4 and HS2 are not separate heads. They are marginals of the same HS6
distribution (logsumexp over the children of each parent), so the levels are
consistent by construction: the model cannot name one heading at 4 digits and a
code from a different heading at 6 digits.
The forward pass below must stay identical to the one used in training,
otherwise the released weights do not mean what the metrics say they mean.
"""
from dataclasses import dataclass
from typing import List, Optional
import torch
import torch.nn as nn
from transformers.modeling_outputs import ModelOutput
from transformers.modeling_utils import PreTrainedModel
from transformers.models.xlm_roberta.configuration_xlm_roberta import XLMRobertaConfig
from transformers.models.xlm_roberta.modeling_xlm_roberta import XLMRobertaModel
from .configuration_hs6 import HS6ClassifierConfig
# fields that belong to the classifier, not to the encoder
_HEAD_ONLY = ("n2", "n4", "n6", "head_dropout", "pooling", "recommended_max_length",
"id2hs4", "id2hs2", "id2label", "label2id", "auto_map", "architectures",
"model_type")
def _encoder_config(config: HS6ClassifierConfig) -> XLMRobertaConfig:
"""A plain XLM-R config, so the encoder does not warn about the wrapper type."""
raw = {k: v for k, v in config.to_dict().items() if k not in _HEAD_ONLY}
return XLMRobertaConfig(**raw)
@dataclass
class HS6ClassifierOutput(ModelOutput):
"""`logits` is the HS6 level, so the standard text-classification tooling works.
`logits_hs4` / `logits_hs2` are the marginals over the same distribution.
"""
loss: Optional[torch.FloatTensor] = None
logits: Optional[torch.FloatTensor] = None
logits_hs4: Optional[torch.FloatTensor] = None
logits_hs2: Optional[torch.FloatTensor] = None
class HS6ClassifierModel(PreTrainedModel):
config_class = HS6ClassifierConfig
base_model_prefix = "encoder"
supports_gradient_checkpointing = True
def __init__(self, config: HS6ClassifierConfig):
super().__init__(config)
# no pooling layer: the head reads the CLS token of the last hidden state
self.encoder = XLMRobertaModel(_encoder_config(config), add_pooling_layer=False)
self.pooling = config.pooling
self.drop = nn.Dropout(config.head_dropout)
self.head_6 = nn.Linear(config.hidden_size, config.n6)
self.n4, self.n2 = config.n4, config.n2
# parent of every HS6 class: its first 4 and first 2 digits. Stored in the
# checkpoint so the mapping cannot drift away from the trained weights.
self.register_buffer("parent4", torch.zeros(config.n6, dtype=torch.long))
self.register_buffer("parent2", torch.zeros(config.n6, dtype=torch.long))
self.post_init()
def _marginal(self, l6, parent, n_parent):
mx = l6.max(1, keepdim=True).values
e = (l6 - mx).exp()
s = torch.zeros(l6.size(0), n_parent, device=l6.device, dtype=e.dtype)
s.index_add_(1, parent, e)
return s.clamp_min(1e-20).log() + mx
def forward(
self,
input_ids: Optional[torch.LongTensor] = None,
attention_mask: Optional[torch.Tensor] = None,
labels: Optional[torch.LongTensor] = None,
return_dict: Optional[bool] = None,
**kwargs,
):
return_dict = True if return_dict is None else return_dict
out = self.encoder(input_ids=input_ids, attention_mask=attention_mask)
if self.pooling == "mean":
h = out.last_hidden_state
m = attention_mask.unsqueeze(-1).to(h.dtype)
pooled = (h * m).sum(1) / m.sum(1).clamp(min=1)
else:
pooled = out.last_hidden_state[:, 0, :]
l6 = self.head_6(self.drop(pooled))
l4 = self._marginal(l6, self.parent4, self.n4)
l2 = self._marginal(l6, self.parent2, self.n2)
loss = None
if labels is not None:
loss = nn.functional.cross_entropy(l6, labels)
if not return_dict:
return (loss, l6, l4, l2) if loss is not None else (l6, l4, l2)
return HS6ClassifierOutput(loss=loss, logits=l6, logits_hs4=l4, logits_hs2=l2)
@torch.no_grad()
def classify(self, texts, tokenizer, top_k: int = 5, batch_size: int = 16,
max_length: Optional[int] = None) -> List[List[dict]]:
"""Convenience wrapper: texts in, ranked HS6 codes with probabilities out."""
if isinstance(texts, str):
texts = [texts]
max_length = max_length or self.config.recommended_max_length
device = next(self.parameters()).device
results = []
for start in range(0, len(texts), batch_size):
chunk = [t if isinstance(t, str) and t.strip() else " "
for t in texts[start:start + batch_size]]
enc = tokenizer(chunk, truncation=True, max_length=max_length,
padding=True, return_tensors="pt").to(device)
logits = self(**enc).logits
probs = torch.softmax(logits.float(), dim=1)
conf, idx = probs.topk(min(top_k, probs.size(1)), dim=1)
for c, i in zip(conf.cpu().tolist(), idx.cpu().tolist()):
results.append([
{"hs6": self.config.id2label[j], "score": round(v, 6)}
for j, v in zip(i, c)
])
return results
|