Spaces:
Sleeping
Sleeping
Download src/train.py from kshitiz14/product_attribute: direct link, hf CLI and curl.
- Browser
- Download file 3.15 kB
-
https://huggingface.co/spaces/kshitiz14/product_attribute/resolve/main/src/train.py
- Command line
-
hf download hf://spaces/kshitiz14/product_attribute/src/train.py
-
curl -L -o train.py https://huggingface.co/spaces/kshitiz14/product_attribute/resolve/main/src/train.py
3.15 kB
| """ | |
| Trains one multi-label classifier per attribute type on the labeled dataset. | |
| Approach | |
| -------- | |
| - Features: character n-gram TF-IDF (char_wb, 2-5) over the raw description. | |
| Character n-grams generalize better than word n-grams on a 61-row dataset | |
| because they share sub-word signal across morphological variants | |
| ("embroidered"/"embroidery", "beaded"/"beading", "off-shoulder"/ | |
| "off shoulder") without needing a stemmer. | |
| - Label space: for each attribute type, the label space is exactly the | |
| canonical vocabulary in lexicon.py (colors use COLOR_VOCAB). This keeps the | |
| model closed-vocabulary and directly comparable to the rules baseline. | |
| - Classifier: OneVsRestClassifier(LogisticRegression) per attribute type, | |
| i.e. independent binary classifiers per label, combined into a multi-label | |
| prediction per attribute (a description can have 0, 1, or many labels for | |
| a given attribute, e.g. two colors). | |
| - Given the small dataset, model artifacts are ensembled at inference time | |
| with the deterministic rules extractor (see predict.py) rather than relied | |
| on in isolation. | |
| """ | |
| import json | |
| import joblib | |
| from sklearn.feature_extraction.text import TfidfVectorizer | |
| from sklearn.linear_model import LogisticRegression | |
| from sklearn.multiclass import OneVsRestClassifier | |
| from sklearn.preprocessing import MultiLabelBinarizer | |
| from lexicon import ATTRIBUTE_LEXICON, COLOR_VOCAB | |
| ATTR_TYPES = ["silhouette", "fabric", "neckline", "sleeve", "length", | |
| "embellishment", "category", "color"] | |
| def label_space(attr): | |
| if attr == "color": | |
| return COLOR_VOCAB | |
| return list(ATTRIBUTE_LEXICON[attr].keys()) | |
| def load_dataset(path="../data/dataset.json"): | |
| with open(path) as f: | |
| return json.load(f) | |
| def train_all(dataset_path="../data/dataset.json", out_path="model.joblib"): | |
| data = load_dataset(dataset_path) | |
| texts = [d["text"] for d in data] | |
| vectorizer = TfidfVectorizer(analyzer="char_wb", ngram_range=(2, 5), min_df=1) | |
| X = vectorizer.fit_transform(texts) | |
| models = {} | |
| binarizers = {} | |
| for attr in ATTR_TYPES: | |
| classes = label_space(attr) | |
| mlb = MultiLabelBinarizer(classes=classes) | |
| y_labels = [d["labels"][attr] for d in data] | |
| Y = mlb.fit_transform(y_labels) | |
| # Skip labels with zero positive examples (OneVsRest can't fit them); | |
| # in practice every label in our lexicon appears >=1 time in the 61 | |
| # rows, but guard anyway for robustness on future dataset edits. | |
| if Y.sum() == 0: | |
| models[attr] = None | |
| binarizers[attr] = mlb | |
| continue | |
| clf = OneVsRestClassifier( | |
| LogisticRegression(max_iter=1000, class_weight="balanced") | |
| ) | |
| clf.fit(X, Y) | |
| models[attr] = clf | |
| binarizers[attr] = mlb | |
| artifact = { | |
| "vectorizer": vectorizer, | |
| "models": models, | |
| "binarizers": binarizers, | |
| "attr_types": ATTR_TYPES, | |
| } | |
| joblib.dump(artifact, out_path) | |
| print(f"Trained models for attrs: {ATTR_TYPES}") | |
| print(f"Saved artifact to {out_path}") | |
| return artifact | |
| if __name__ == "__main__": | |
| train_all() | |