Tabular Classification
Scikit-learn
Joblib
fashion
size-recommendation

ThreadCraft Size/Fit Recommender

Gradient-boosted classifier predicting whether a garment size will run small, fit, or large for a given customer's body measurements.

Built for ThreadCraft, an AI-powered custom clothing design and ordering platform, as a final-year BSc Software Engineering project.

Results (held-out test split, n=19,247)

Metric Model Majority-class baseline
Accuracy 0.7140 0.7378
Balanced accuracy 0.3960 0.3333
Macro F1 0.4051 0.2830
Weighted F1 0.6623 0.6265

Macro F1 is +43.1% relative to the baseline.

Read macro F1 and balanced accuracy, not accuracy. The fit class is ~74% of the data, so a model that always predicted fit scores 73.8% accuracy while never once warning a customer that a size runs small. Accuracy flatters the useless model here.

Class weighting was chosen on evidence

config accuracy balanced_accuracy macro_f1 weighted_f1
baseline (always 'fit') 0.7378 0.3333 0.283 0.6265
weighting = None 0.7373 0.3409 0.3012 0.6331
weighting = sqrt 0.714 0.396 0.4051 0.6623
weighting = balanced 0.4001 0.4918 0.3684 0.4349

sqrt weighting was selected: fully balanced weighting collapses accuracy for a worse macro-F1, and no weighting barely improves on the baseline.

Recommender accuracy (the metric that actually matters)

Sweeping candidate sizes and taking the highest P(fit), evaluated on 3,000 orders the customer reported as fitting:

Exact size match 0.036
Within ±1 size 0.186
Within ±2 sizes 0.434

These are modest. "Exact" understates real usefulness — several sizes can legitimately fit one person and we only observe the one they ordered — but they are low enough that this model must be surfaced as an advisory starting point, never an authoritative size.

Intended use

A fit-risk advisory for a made-to-measure ordering flow: flagging that a given size is likely to run small or large for this body, as an overridable suggestion alongside proper measurement. It is not a replacement for taking measurements, and should not be presented as one.

Features

height_cm, weight_kg, bmi, bust_band, bust_cup, age, size, body_type, category, rented_for

Most predictive (permutation importance): size, category, weight_kg, rented_for.

Numeric missing values are handled natively by the model — no imputation, because inventing a weight for a customer who never gave one would be fabricating a body measurement.

Deliberately excluded to prevent leakage

rating, review_text, review_summary (all recorded after wearing the garment, and unavailable at prediction time), plus user_id and item_id.

Limitations

  • The single strongest predictor of fit is the specific garment, and it is excluded by design. Two dresses in the same nominal size fit differently; Misra et al. (2018) modelled exactly this with latent item factors. ThreadCraft makes bespoke garments, so there is no catalogue item to look up. Losing that signal is the principal reason absolute performance is limited here.
  • Trained on rental transactions, predominantly dresses and gowns (~70% of rows) and a largely US female customer base. Applying it to menswear, trousers, or South Asian garments such as kurtas and salwar kameez is extrapolation — those categories are barely present.
  • Fit labels are self-reported, so they encode subjective preference (some people simply prefer a looser fit) as well as objective sizing.
  • Body measurements are self-reported too, and self-reported weight is known to be biased.
  • Sizes are US rental sizes; mapping them to a made-to-measure specification is handled in the application layer.
  • ~16% of weight_kg and ~10% of bust_band are missing in the source data; predictions for customers supplying fewer measurements are correspondingly less certain.

Usage

import joblib, numpy as np, pandas as pd
from huggingface_hub import hf_hub_download

art = joblib.load(hf_hub_download("SamaGalagoda/threadcraft-fit-recommender", "fit_recommender.joblib"))
model, encoder = art["model"], art["encoder"]

customer = {"height_cm": 165, "weight_kg": 61, "bmi": 22.4, "bust_band": 34,
            "bust_cup": 2, "age": 29, "body_type": "hourglass",
            "category": "dress", "rented_for": "wedding"}

rows = []
for size in art["candidate_sizes"]:
    row = {f: customer.get(f, np.nan) for f in art["features"]}
    row["size"] = size
    rows.append(row)

frame = pd.DataFrame(rows)
for col in art["categorical_features"]:
    frame[col] = frame[col].fillna(art["missing_token"]).astype(str)

X = frame[art["numeric_features"]].astype(float)
enc = encoder.transform(frame[art["categorical_features"]])
for i, col in enumerate(art["categorical_features"]):
    X[col] = enc[:, i] + 1

proba = model.predict_proba(X[art["features"]])
fit_idx = list(model.classes_).index("fit")
best = max(zip(art["candidate_sizes"], proba[:, fit_idx]), key=lambda t: t[1])
print(f"Suggested starting size {best[0]:.0f} (P(fit)={best[1]:.2f})")

Training

Algorithm HistGradientBoostingClassifier (scikit-learn 1.6.1)
Boosting iterations 142 (early stopping, patience 25)
Learning rate 0.08
Max leaf nodes 31
Class weighting sqrt
Train / Val / Test 153,972 / 19,246 / 19,247
Hardware Kaggle CPU (no GPU required)

Citation

Misra, R., Wan, M., & McAuley, J. (2018). Decomposing fit semantics for product size recommendation in metric spaces. RecSys 2018.

Source data: CC BY 4.0, via the UCSD McAuley Lab.

Downloads last month
-
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Dataset used to train SamaGalagoda/threadcraft-fit-recommender