Text Classification
Transformers
Safetensors
Turkish
bert
text-embeddings-inference

BERTturk Product Matcher

By opdullah. A Turkish BERT pair classifier for research on whether two product titles describe the same product. Label 1 means same; label 0 means different. This is a fine-tune of dbmdz/bert-base-turkish-cased. The default weights are the checkpoint after two complete epochs.

Training

Setting Value
Training pairs 1,803,494
Dataset opdullah/turkish-product-matching-706k, matching configuration
Dataset revision 2c515bdbb63eac04343eb6df628b4a7fe961fc51
Epochs 2
Optimizer updates 56,360
Hardware 1 NVIDIA A100 SXM 80 GB
Batch size / gradient accumulation 64 / 1
Maximum combined pair length 512 tokens, including special tokens
Learning rate 0.00002
Precision BF16 mixed precision
Seed 42

Training pairs are shuffled each epoch. Product title order is randomly swapped during training. Evaluation and inference average both title orders.

Evaluation of the two-epoch checkpoint

Validation contains 224,850 pairs and uses decision threshold 0.5.

Metric Value
Accuracy 96.2224%
Precision (same product) 98.1639%
Recall (same product) 94.2068%
F1 (same product) 96.1447%
ROC AUC 0.995083
Average precision 0.994994

Inference

Install the libraries:

pip install "torch>=2.6" "transformers>=4.57,<5" "huggingface_hub>=0.36,<1"

Run this setup once. It is shown in full so no helper script needs to be downloaded. CPU is supported; CUDA is selected automatically when available. You can pass device="mps" on a supported Apple computer. revision can be a commit ID for reproducible research.

import json
import torch
from huggingface_hub import hf_hub_download
from transformers import AutoTokenizer, AutoModelForSequenceClassification


class ProductMatcher:
    def __init__(self, model_id="opdullah/bertturk-product-matcher",
                 revision="main", device=None):
        self.device = torch.device(device or ("cuda" if torch.cuda.is_available() else "cpu"))
        self.tokenizer = AutoTokenizer.from_pretrained(model_id, revision=revision)
        self.model = AutoModelForSequenceClassification.from_pretrained(
            model_id, revision=revision).to(self.device).eval()
        decision_path = hf_hub_download(model_id, "decision.json", revision=revision)
        self.decision = json.loads(open(decision_path).read())
        self.positive_id = int(self.model.config.label2id["same"])

    @torch.inference_mode()
    def predict_batch(self, pairs, batch_size=32, threshold=None):
        if batch_size < 1:
            raise ValueError("batch_size must be positive")
        pairs = list(pairs)
        if any(not isinstance(pair, (tuple, list)) or len(pair) != 2
               or any(not isinstance(text, str) or not text.strip() for text in pair)
               for pair in pairs):
            raise ValueError("Each pair must contain two non-empty title strings")
        threshold = float(self.decision["threshold"] if threshold is None else threshold)
        if not 0 <= threshold <= 1:
            raise ValueError("threshold must be between 0 and 1")
        results = []
        for start in range(0, len(pairs), batch_size):
            chunk = pairs[start:start + batch_size]
            first = [title for a, b in chunk for title in (a, b)]
            second = [title for a, b in chunk for title in (b, a)]
            inputs = self.tokenizer(first, second, padding=True,
                truncation="longest_first", max_length=self.decision["max_length"],
                return_tensors="pt").to(self.device)
            scores = self.model(**inputs).logits.float().softmax(-1)[:, self.positive_id]
            scores = scores.reshape(-1, 2).mean(-1).cpu().tolist()
            for score in scores:
                same = score >= threshold
                results.append({"label": "same" if same else "different",
                    "is_match": same, "probability_same": score, "threshold": threshold})
        return results

    def predict(self, first, second, threshold=None):
        return self.predict_batch([(first, second)], threshold=threshold)[0]

matcher = ProductMatcher()

Single pair

result = matcher.predict(
    "Samsung Galaxy A55 128 GB Siyah",
    "Samsung A55 Siyah 128GB",
)
print(result)

Example output (rounded):

{"label": "same", "is_match": True, "probability_same": 0.999120, "threshold": 0.5}

Batch inference

pairs = [
    ("Samsung Galaxy A55 128 GB Siyah", "Samsung A55 Siyah 128GB"),
    ("Samsung Galaxy A55 128 GB Siyah", "Samsung Galaxy A55 256 GB Siyah"),
    ("Apple iPhone 15 128 GB Mavi", "iPhone 15 Mavi 128GB"),
]
results = matcher.predict_batch(pairs, batch_size=32)
for pair, result in zip(pairs, results):
    print(pair, result)

For the three pairs above, the verified outputs are:

Pair Label Same-product score
A55 128 GB / A55 128 GB same 0.999120
A55 128 GB / A55 256 GB different 0.001304
iPhone 15 128 GB / iPhone 15 128 GB same 0.998722

Scores are rounded; small floating-point differences across devices are expected.

Results are returned in input order with label (same or different), is_match, probability_same, and threshold. An empty batch returns an empty list. Each pair is evaluated in both directions and the two same-product scores are averaged before thresholding. batch_size=32 evaluates up to 64 ordered sequences per model forward pass; reduce it if device memory is limited. The combined input is truncated at 512 tokens. Scores are model outputs and have not been probability-calibrated. Set threshold only when justified by your own research validation data.

Limitations

The model compares title text; it does not use images, reviews, specifications, or seller information. Ambiguous titles, missing model codes and subtle variant differences can cause errors. It is intended for noncommercial research only.

Base model notice

BERTurk was released by the MDZ Digital Library team (dbmdz) at the Bavarian State Library under the MIT license. Its original license and notices remain applicable. The research-only terms apply to the contributions distributed here and do not replace the base model's license.

Research-only use and attribution

Released under the Research-Only Attribution License 1.0 in LICENSE.md. Use is limited to noncommercial scientific research. Commercial use, production deployment, and non-research uses require separate written permission. Research derivatives and redistributed copies must retain these terms and credit.

Credit opdullah and cite the dataset in any publication or research output that uses it. When using the fine-tuned model, cite the model as well as the dataset. Include the exact revision used for reproducibility.

@misc{opdullah2026turkishproductmatchingdata,
  author = {{opdullah}},
  title = {Turkish Product Matching 706K},
  year = {2026},
  howpublished = {Hugging Face dataset: opdullah/turkish-product-matching-706k}
}

@misc{opdullah2026turkishproductmatchingbert,
  author = {{opdullah}},
  title = {BERTturk Product Matcher},
  year = {2026},
  howpublished = {Hugging Face model: opdullah/bertturk-product-matcher}
}
Downloads last month
11
Safetensors
Model size
0.1B params
Tensor type
F32
·
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for opdullah/bertturk-product-matcher

Finetuned
(217)
this model

Dataset used to train opdullah/bertturk-product-matcher

Collection including opdullah/bertturk-product-matcher