Learned Embeddings for Categorical Features
An embedding replaces a category, like a ticker or a sector, with a short list of numbers learned from data, so that categories the model finds similar end up sitting near each other in that number space instead of being treated as arbitrary and unrelated labels.
Prerequisites: The Multilayer Perceptron
A stock's sector is a category — "Technology," "Utilities," "Energy" — not a number. Feed it to a model as one-hot encoding (a column of zeros with a single 1 marking which sector) and every sector becomes equally different from every other. The model is told nothing about the fact that Technology and Communication Services often move together, while Technology and Utilities usually don't. One-hot vectors are all the same distance apart, by construction, whether the categories are close cousins or total opposites.
An embedding fixes that by learning, from the data itself, where each category should sit. Instead of "Technology" being a single 1 among five hundred zeros, it becomes a short list of numbers, say eight of them, and the model learns those eight numbers the same way it learns any other weight — by gradient descent on the training loss. If sectors that behave alike end up needing similar predictions, gradient descent will naturally pull their embeddings close together, because that's what lowers the loss. Think of it like a seating chart at a large conference that nobody drew up in advance: people who keep ending up in the same conversations gradually migrate to sit near each other, and by the end of the event, physical closeness on the chart tells you who has similar interests, purely as a byproduct of many small social decisions.
The mechanics, one symbol at a time
Say a categorical feature has possible values (e.g., sectors). An embedding table is a matrix with rows, one per category, and columns, where (the embedding dimension) is a size you choose, typically much smaller than . For a category , its embedding is simply that row:
In words: look up row of a table, and the numbers you find there are that category's learned representation. There's no formula computing from anything else — the table's entries are free parameters, initialized randomly and then updated by backpropagation exactly like any weight matrix, based on how much moving would have reduced the loss on examples where category appeared.
Worked example 1: a two-dimensional embedding lookup
Suppose and the embedding table for four sectors, after training, has settled at:
| sector | ||
|---|---|---|
| Technology | 1.8 | 0.9 |
| Communication Services | 1.6 | 1.1 |
| Utilities | -1.5 | 0.2 |
| Energy | -1.3 | -0.8 |
Technology's row is looked up as , and that pair of numbers, not a one-hot vector, is what's concatenated with the stock's other features (say, momentum and volatility) before being fed to the rest of the network.
Compute the (Euclidean) distance between Technology and Communication Services: , close together. Between Technology and Energy: , far apart. Nobody told the model sectors should cluster this way; it emerged because Technology and Communication Services examples needed similar predictions during training, while Energy examples needed very different ones.
Worked example 2: why one-hot loses information a small model can't recover
Ten sectors, one-hot encoded, feeding a linear layer with weight per sector. The layer's contribution for sector is just — ten completely independent numbers with zero sharing between them. If the smallest sector has only 40 training examples, that sector's is estimated from 40 points alone.
With a embedding instead, that same small sector's prediction is a function of two numbers sitting inside a continuous space shared by every sector. Gradient descent, when nudging Technology's embedding based on thousands of Technology examples, also slightly reshapes the local geometry near sectors that started close to it — so a data-starved sector inherits some signal from its neighbors, rather than learning everything from its own 40 examples in isolation. This is the practical payoff: embeddings let rare categories borrow statistical strength from similar, common ones.
The explorer above shows a different setting (a matrix stretching the unit circle) but the same underlying idea applies: a embedding table is a linear map from a high-dimensional one-hot space down to a low-dimensional space, and training bends that map so that geometric closeness in the small space means learned similarity, not an accident of alphabetical category order.
What this means in practice
Embedding dimension is a hyperparameter — too small re-creates the "everything looks similar" problem for genuinely different categories, too large wastes parameters and risks overfitting a category with few examples. A common rule of thumb scales with (something like ), but it's worth tuning per problem. Embeddings need enough examples per category to be learned well; a category seen five times will end up with a nearly-arbitrary embedding, so rare categories are sometimes grouped into an explicit "other" bucket first. The same core idea underlies word embeddings — a ticker, a word, a merchant category are all just categories waiting for a learned coordinate.
An embedding replaces an arbitrary category label with a small vector of learned numbers, trained by gradient descent alongside the rest of the model, so that categories the model treats similarly end up geometrically close.
The classic confusion: treating embeddings as fixed, meaningful features that can be inspected in isolation like a hand-built feature. They are model-specific and re-learned from scratch (or fine-tuned) for every new training run and every dataset — the number that represents "Technology" in one model has no necessary relationship to the number representing "Technology" in a different model trained on different data, so embeddings from two separately trained models cannot be compared or combined directly.
Related concepts
Practice in interviews
Further reading
- Guo & Berkhahn, Entity Embeddings of Categorical Variables
- Chollet, Deep Learning with Python, ch. 6