Instructions to use unnicr/master-data-twin-finder with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Scikit-learn
How to use unnicr/master-data-twin-finder with Scikit-learn:
from huggingface_hub import hf_hub_download import joblib model = joblib.load( hf_hub_download("unnicr/master-data-twin-finder", "sklearn_model.joblib") ) # only load pickle files from sources you trust # read more about it here https://skops.readthedocs.io/en/stable/persistence.html - Notebooks
- Google Colab
- Kaggle
Master Data Twin Finder
Finds duplicate product and vendor records that are written differently, the kind that pile up in an ERP when different people type the same item or supplier their own way.
| Record A | Record B | Verdict |
|---|---|---|
PARLE-G BISCUIT 100GM |
Parle G Bisc 100 g |
β duplicate |
Everest Haldi Powder 100g |
EVEREST TURMERIC PWDR 100GM (MRP 45) |
β duplicate |
Sugar 1kg |
Cheeni 1 Kilo |
β duplicate |
M/S SHARMA TRADERS PRIVATE LIMITED |
Sharma Traders (P) Ltd. |
β duplicate |
Parle-G 100g |
Parle-G 250g |
β different size |
Amul Taaza 1L x 6 |
Amul Taaza 1000ml |
β different pack |
Good Day Cashew 200g |
Good Day Butter 200g |
β different variant |
Sharma Traders |
Verma Traders |
β different vendor |
Try it: the web app runs entirely in your browser, so your data is never uploaded.
What makes it different
Plain fuzzy matching joins things that look alike. That merges 100 g with 250 g and Cashew with Butter,
and it misses Haldi = Turmeric. This model is built for Indian retail and distribution master data:
- Size and pack aware. It reads
100gm,0.1 kg,1 Ltr,1000 ML,x 6,Pack of 12and6 pcs, and converts units, so1 kg=1000 gm. Different sizes or packs are never merged. - Abbreviation and typo tolerant.
Bsct= Biscuit,Pwdr= Powder,Sunflwr= Sunflower,Cbues= Cubes. - HindiβEnglish food words.
haldi/turmeric,jeera/cumin,cheeni/sugar,namak/salt,chawal/rice,aata/atta,mirchi/chilli. These match even when misspelt (jera). - Vendor suffix aware. It ignores
M/s,Messrs,Pvt Ltd,(P) Ltd,Private LimitedandLLP, and it understandsSri/Shri/Shree,Bros,Ent.,Corpn,& CoandGenl Stores. - Variant aware. A generic extra word (
Cookies,Toned Milk,Detergent) is ignored. A distinctive extra word on both sides (CashewvsButter) means a different product. - Safe grouping. Duplicates are grouped with average linkage plus a size guard, so a vague name like
Parle Gcannot chain100 gand250 ginto one group. Groups where some names state no size are flaggedcheck_size. - Suggests which record to keep. It picks the spelling most of the group agrees with, so typo'd names are merged into the clean one.
How it works
name βββΊ normalize βββΊ 19 pair signals βββΊ gradient-boosted trees βββΊ probability βββΊ size-safe grouping
- Normalize: lower-case, strip MRP/price, parse size into grams/ml, parse pack count, remove legal suffixes and filler words (
pouch,jar,new), map synonyms, join split words (super marketβsupermarket). - Pair signals: character-trigram similarity (with and without spaces), soft word coverage from each side, unmatched distinctive words, first-word (brand) match, token-sort edit similarity, size equal/conflict/missing, pack conflict, number conflict.
- Model: scikit-learn
GradientBoostingClassifier, 250 trees of depth 4, exported tomodel.json. - Grouping: trigram blocking picks candidate pairs, then average-linkage merge above the threshold, never across different explicit sizes or packs.
twin_finder.py (Python) and space/twin.js (browser) are the same algorithm. test_parity.py checks that they give identical scores and groups.
Results
The test set uses products and vendors never seen during training.
| Test | Result |
|---|---|
| Pair accuracy (5,100 unseen pairs) | 99.3% (precision 99.5%, recall 98.9% for duplicates) |
| Different-size traps | 100% |
| Different-pack traps | 100% |
| Different-variant traps | 96.7% |
| Similar vendor names (Sharma/Verma, Gupta/Goyal) | 100% |
| Hand-written real-world cases | 37 / 40 |
| Grouping a messy 125-row product list | 93.9% pair precision, 96.4% recall |
| Grouping a messy 73-row vendor list | 100% precision, 100% recall |
Full log: train_log.txt.
Known limits. When one name has an extra distinctive word and the other doesn't (Dairy Milk 50g vs
Dairy Milk Silk 50g), the model can't know whether that word is a variant or just omitted text, so review these.
Names without any size can't be placed with certainty and are flagged check_size. The model is trained on
realistic synthetic data. Retraining with a few hundred labelled pairs from your own ERP will improve it further.
Use it
In the browser: open the web app, click Upload CSV, pick the name column and click Find twins, then Download result CSV.
From the command line (plain Python 3.8+, nothing to install):
python dedupe_csv.py m_product.csv # auto-detects the "Name" column
python dedupe_csv.py c_bpartner.csv --threshold 0.6 --out vendor_duplicates.csv
The report keeps your original columns (IDs, codes) and adds dup_group, keep, confidence and check_size.
Exporting from ADempiere / iDempiere:
SELECT M_Product_ID, Value, Name FROM M_Product
WHERE IsActive = 'Y' AND AD_Client_ID = <your client>;
SELECT C_BPartner_ID, Value, Name, TaxID FROM C_BPartner
WHERE IsActive = 'Y' AND IsVendor = 'Y' AND AD_Client_ID = <your client>;
In Python:
from twin_finder import TwinModel, find_duplicates
model = TwinModel.load("model.json")
print(model.score("PARLE-G BISCUIT 100GM", "Parle G Bisc 100 g")) # β 0.99
names = ["Tata Salt 1kg", "TATA NAMAK 1000 GM", "Tata Salt Lite 1kg", "Catch Salt 1kg"]
for g in find_duplicates(names, model):
print([names[i] for i in g["members"]], "keep:", names[g["master"]])
Retrain
pip install -r requirements.txt
python generate_data.py # creates data/*.csv
python train.py # trains, evaluates, writes model.json
python test_parity.py # checks the browser version matches (needs Node.js)
To teach it your own vocabulary, add words to SYNONYMS, GENERIC or STOPWORDS in twin_finder.py, or add your
own labelled pairs to data/pairs_train.csv (name_a,name_b,label,kind), then retrain.
Files
| File | Purpose |
|---|---|
model.json |
Trained model plus vocabulary, used by both Python and the browser |
model.joblib |
The same model as a scikit-learn object |
twin_finder.py |
Normalizer, features, scoring, duplicate grouping |
dedupe_csv.py |
Command-line tool for CSV exports |
space/twin.js |
Browser port (used by the web app) |
generate_data.py, train.py, test_parity.py |
Data generation, training and evaluation, parity test |
data/ |
Training/test pairs and sample messy lists |
- Downloads last month
- -