Build a Holiday Gift Recommendation Model: A Festive Deep Learning Project
December shopping has a particular flavor of panic: a list of names, a shrinking calendar, and no idea what the people on that list actually want. A recommender won't fix the calendar. It can fix the blank page. In this project we'll train a small model on gift purchase data — a few hundred lines of Python at most — and along the way you'll pick up a working intuition for embeddings, the idea sitting underneath most modern recommendation systems.
Start with the shape of the problem
Every recommendation problem begins as a table: rows are people, columns are things, and the cells say whether that person touched that thing. Here the things are gifts, and the signal is a purchase.
That means almost all of your data is implicit. A purchase is a weak yes. The absence of a purchase is a very noisy no — your brother may never have bought a scented candle because he doesn't want one, because nobody ever offered him one, or because he bought it years before you started recording. Treating missing cells as negatives makes the problem tractable. Treating them as true negatives makes models obnoxious.
You need two lists: buyers and gifts. If you have no real purchase history, build your own. Ask ten friends for five gifts they'd genuinely want and three they'd hate; that's already a usable dataset. A synthetic version works too: generate 400 buyers and 300 gifts, give each buyer a hidden taste vector, and sample purchases so similar tastes produce similar baskets. The model should recover that structure. If it can't rediscover a pattern you planted deliberately, it won't find one you didn't.
Turn purchases into a clean table
One row per purchase, nothing clever yet:
- buyer_id — a stable integer, not a name
- gift_id — again, an integer
- occasion — birthday, holiday, wedding, five or six buckets
- month — December behaves nothing like June
- price_band — a bracket, not a raw number
Then split, holding out roughly one in five purchases per buyer. Make sure every buyer and every gift still appears in training. If a gift shows up only in the test set, the model cannot know it exists, and your metrics will lie to you.
One more column is worth having: whether the buyer had purchased that gift before. Repeat buys are a different animal from first-time discovery, and mixing the two muddies everything.
Embeddings, in one honest paragraph
An embedding is a lookup table of numbers the model is allowed to move. Give every buyer 32 numbers and every gift 32 numbers. To score a pair, multiply the two vectors element by element and add up the results. That single dot product is the entire recommendation.
Training nudges the numbers so buyers and the gifts they actually bought end up pointing the same direction. Nobody labels the dimensions. They emerge: one axis might separate consumables from keepsakes, another might catch price sensitivity, a third might quietly encode "for a coworker I don't know well." You inspect them afterward and guess.
The first time a gift vector lands closest to the wrong category, that isn't a bug. It's the model telling you what your data actually contains rather than what you assumed.
Build it in about sixty lines
Any framework works. The structure is the same everywhere: two embedding matrices, a dot product, a bias for each buyer and each gift, a sigmoid.
- Create a user embedding with 400 rows and 32 columns, and a gift embedding with 300 rows and 32 columns.
- Add scalar bias parameters for each buyer and each gift.
- For a batch of pairs: score = sum(user_vec * gift_vec) + user_bias + gift_bias.
- Sample four random gifts for every real purchase, label those 0, label the purchase 1, and train with binary cross-entropy on the logits.
- Regularize. Weight decay around 1e-5 and a small initialization — roughly 0.01 standard deviation — will save you an afternoon of confused debugging.
Keep the dimension at 32 for a first pass. Jumping to 128 with a few thousand interactions mostly memorizes the training set.
Judge it against something dumb
Loss curves feel productive and mean very little here. What matters is whether your model beats a baseline that took thirty seconds to write: "recommend the ten most popular gifts from last December." If it can't, you have a data problem, not a modeling problem.
Use recall at 10. For each held-out buyer, hide one real purchase, ask the model for ten gifts, and count how often the hidden one appears. Report the number by segment: heavy buyers versus people with two purchases, common gifts versus long-tail gifts, December versus the rest of the year.
The popularity trap
Watch for popularity collapse — the failure mode where every user receives the same three crowd-pleasers. Your headline metric can look fine while the list is useless. Fixes include sampling negatives proportional to popularity instead of uniformly, and reranking the top 50 for diversity before showing anything.
Wrap it in something a person can use
Scores alone are not a gift list. Take the top 50 candidates, then filter: no more than two items in one category, respect the price band, drop anything the buyer already owns. Deliberately reserve one slot for a surprise — a well-scored item whose category profile differs from the rest of the list.
Cold start, honestly
A new user has no embedding. Ask five questions (who is this for, what do they talk about, what's the budget, what have you given them before, what went badly) and average the embeddings of the gifts that match those answers. Crude, and far better than nothing.
A short reason line under each suggestion makes the whole thing trustworthy: "close to two things they bought last fall." It also forces you to notice when the reason makes no sense.
Ship small, retrain in January
Run it locally on a laptop, keep purchase data off shared drives, and don't try to infer anything sensitive about a buyer from what they bought. Gifts are personal. The model suggests; it doesn't decide.
The best part of a December project is January. Returns are labels. A gift that came back is a strong negative, and a gift bought twice is a strong positive. Fold both into next year's training set, keep the popularity baseline around for comparison, and you'll have a model that improves for reasons you can actually explain.
