Word2Vec Skip-gram
Suppose you want a computer to know that "espresso" and "latte" are similar. It should learn this without ever being told about coffee. A dictionary cannot do this cleanly. Hand-built taxonomies are expensive and brittle. Skip-gram solves this problem differently. It turns a purely statistical regularity into a supervised learning problem. The regularity: words that appear in similar contexts tend to be similar. This approach requires no labeled data at all. The trick is deceptively simple. Slide a window across raw text. At each position, ask a tiny neural network to predict the surrounding words. It predicts them from the current word. Training this prediction task forces the network to compress each word. Each word becomes a dense vector capturing its distributional behavior. The predictions themselves are thrown away. The byproduct — the learned vectors — is the actual product. This is the seed of modern representation learning. The same philosophy was later reused by contextual encoders. Pretraining objectives across NLP also share this philosophy. Skip-gram was introduced as part of the word2vec family. It mattered because it made high-quality embeddings cheap to train. This training used huge unlabeled corpora. It replaced the sparse, high-dimensional one-hot and count-based representations. Those representations had dominated earlier NLP. Skip-gram scaled to billions of tokens on ordinary hardware. It produced vectors with strikingly linear semantic structure. This structure gave rise to the famous "king − man + woman ≈ queen" analogies. This article gives a precise statement of the skip-gram objective. It includes a full derivation of its gradient. It also provides working pseudocode and a worked numerical trace. Finally, it gives an honest account of where naive softmax training breaks down computationally. This breakdown motivates the negative-sampling trick. That trick is covered in the companion article.
- Skip-gram predicts context words from a single center word using a shallow neural network with no nonlinearity.
- Two embedding matrices are learned: one for words-as-centers, one for words-as-contexts; only the first is usually kept.
- The training objective is a softmax over the entire vocabulary at every context position, which is the main computational bottleneck.
- Correctness follows from maximum-likelihood estimation of a log-bilinear conditional model over co-occurring word pairs.
- Naive softmax costs O(|V|) per training example, which is why negative sampling and hierarchical softmax exist in practice.
- The resulting embeddings exhibit linear analogical structure and became the standard input representation before contextual encoders.
Skip-gram learns word vectors as a side effect of training a simple classifier to predict a word's neighbors, so that words with similar neighbors end up with similar vectors.
The idea
Skip-gram is a self-supervised objective for learning word embeddings from raw text, with no human-provided labels. The supervision signal is generated automatically from co-occurrence: for every word in the corpus, the words within a small window around it — say — are treated as the "correct answers" the model should predict given as input. This is the mirror image of Continuous Bag-of-Words (CBOW), which instead predicts the center word from its context; skip-gram predicts outward from the center. Formally, each word type in the vocabulary is assigned two vectors: a target (center) embedding , and a context embedding . These live in two separate weight matrices, (rows are target vectors) and (rows are context vectors). The model defines the conditional probability of a context word given a center word via a softmax over a dot-product score, exactly the same log-bilinear form used in multinomial logistic regression:
There is deliberately no hidden nonlinearity — the architecture is a linear projection into a shared embedding space followed directly by a softmax classifier. This is what makes the model cheap enough (in principle) to train on web-scale corpora, and it is also why the induced geometry is so linear: the score is literally an inner product, so directions in the embedding space correspond to consistent semantic/syntactic shifts.
How it works
Training proceeds by maximum likelihood: for every (center, context) pair observed within a window in the corpus, increase . Concretely, given a corpus of tokens and window size , the corpus log-likelihood objective is
and we perform gradient ascent on (equivalently, descent on ) with respect to all entries of and .
Deriving the gradient. Fix one training pair . Let be the center vector and write the score of context word as . The per-example loss is
Differentiating with respect to the score vector gives the classic softmax-cross-entropy gradient,
i.e. the model's predicted probability minus the one-hot true label — the same "error signal" you derive for ordinary softmax regression. Since , the chain rule gives update directions for every context vector and for the center vector:
Note the second equation sums over the entire vocabulary — this is the crux of the efficiency problem discussed below.
Training loop. At the corpus level, we slide a window over the text, generate all (center, context) pairs inside it, and take a stochastic gradient step per pair (or a mini-batch of pairs). ``` function TRAIN_SKIPGRAM(corpus, V, d, window_size m, learning_rate eta, epochs E): initialize W as |V| x d matrix of small random values # target embeddings initialize W' as |V| x d matrix of small random values # context embeddings for epoch in 1..E: for t in 1..length(corpus): w := corpus[t] # center word context_positions := [t-m, ..., t-1, t+1, ..., t+m] ∩ valid indices for t' in context_positions: c := corpus[t'] # true context word v_w := W[w] # center vector, length d
# forward pass: softmax over full vocabulary scores := [ dot(W'[c'], v_w) for c' in 1..|V| ] probs := softmax(scores) # length |V|
# backward pass error := probs # copy error[c] := error[c] - 1 # probs - one_hot(c) grad_v_w := zeros(d) for c' in 1..|V|: grad_v_w += error[c'] W'[c'] W'[c'] := W'[c'] - eta error[c'] v_w # update context vector c' W[w] := W[w] - eta grad_v_w # update center vector w return W, W' # keep W (or (W+W')/2) as the final word embeddings ``` Two implementation notes matter. First, updates to touch every row on every single training pair, because the softmax denominator involves all vocabulary words — this is what negative sampling later replaces with a handful of rows. Second, the two matrices are usually not tied; after training, practitioners typically discard and use (or the average ) as the final embedding table, since is the representation that every word plays the "input" role for.
Complexity & correctness
Time complexity. For a single (center, context) training pair, the forward pass computes dot products of length to build the softmax scores, costing ; the backward pass touches every row of again for the same cost, . Over a corpus of tokens with window size , there are training pairs, so one epoch of naive skip-gram costs
With realistic settings ( in the hundreds of thousands, ), this is the dominant cost and is prohibitive at web scale — this is exactly why practical word2vec systems never use the plain softmax described above, and instead use negative sampling (each update touches only rows, giving ) or hierarchical softmax (a binary tree reduces the per-example cost to ). Those are the subjects of the companion article; the pseudocode above is the reference objective they approximate.
Space complexity is for each of and , i.e. total, independent of corpus size — the parameter count depends only on vocabulary size and embedding dimension, which is what makes embeddings reusable across downstream tasks regardless of how large the training corpus was.
Correctness argument. Skip-gram is exact maximum-likelihood estimation of a well-defined probabilistic model, so "correctness" reduces to two claims: (1) the objective is a valid log-likelihood, and (2) gradient ascent on it is a sound optimization procedure. For (1): as defined is a genuine categorical distribution over for each (nonnegative, sums to 1 by construction of softmax), so is precisely the average log-likelihood of the observed (center, context) pairs under this model — the same object one maximizes in any softmax classifier. Because is a sum of log-likelihoods of i.i.d.-style observations (treating each window pair as a sample), maximizing it is the standard MLE recipe; consistent parameter estimation follows under the usual regularity conditions, modulo the fact that the "labels" here are自generated from co-occurrence rather than ground truth. For (2): the per-pair loss is convex in the score vector (softmax cross-entropy is a convex function of its input logits), which is why the softmax gradient has the clean, well-behaved form derived above — this is the same gradient identity used in multinomial logistic regression, and it guarantees that each SGD step moves scores in a direction that provably decreases the per-example loss for a small enough step size. The full objective is not jointly convex in because the score is a bilinear (product) function of the two embedding matrices, so global convergence is not guaranteed — like most deep learning objectives, we only get convergence to a stationary point of a non-convex landscape. Empirically this stationary point is good: the vectors that come out satisfy analogical linear structure (), which can be explained heuristically because the loss encourages to track , i.e. embeddings end up organized around log co-occurrence ratios — the same statistical quantity that GloVe optimizes for directly and explicitly.
Honest tradeoff. Skip-gram (with negative sampling) tends to do better on rare words than CBOW, because it generates one training example per context word rather than averaging context vectors together, giving rare words more individual gradient signal — at the cost of more training pairs (and thus more compute) per pass over the same corpus.
Worked example
Take the toy sentence, window size , vocabulary , , embedding dim : > the cat sat on the mat Tokenized with positions: `[the(1), cat(2), sat(3), on(4), the(5), mat(6)]`.
Step 1 — generate (center, context) pairs with (each word paired with its immediate left/right neighbor):
| center | context |
|---|---|
| the(1) | cat |
| cat(2) | the, sat |
| sat(3) | cat, on |
| on(4) | sat, the |
| the(5) | on, mat |
| mat(6) | the |
This yields 10 training pairs total from a 6-token sentence — illustrating the pair count.
Step 2 — one gradient step in detail. Take the pair (center = "cat", context = "the"). Suppose after random initialization the relevant rows are:
Compute raw scores :
| dot product | ||
|---|---|---|
| the | ||
| cat | ||
| sat | ||
| on | ||
| mat |
Softmax (using and normalizing; exponentials rounded to 4 dp): : the , cat , sat , on , mat . Sum .
True label is "the", so the error vector (probs minus one-hot) is:
The loss — high, as expected before training, since the model has not yet learned that "the" should follow "cat". Gradient w.r.t. is , which (working through the sum) points mostly in the direction because that error term dominates. Applying the update with pushes toward 's direction and pushes toward — exactly the intended effect: repeated exposure to "cat" and "the" co-occurring nudges their vectors closer, while and the others (which had positive error, i.e. over-predicted) are nudged slightly away. Over thousands of such updates across the whole corpus, words sharing many context partners converge to nearby vectors.
Common pitfalls & traps
| Pitfall | Why it happens | Fix | ||
|---|---|---|---|---|
| Forgetting to exclude the center word itself from context pairs | Off-by-one on window indices lets slip through | Explicitly skip when generating context offsets | ||
| Using and interchangeably during training | They play asymmetric roles (center vs. context) and mixing them breaks the gradient derivation | Keep two separate matrices; only merge/average after training finishes | ||
| Treating softmax over as "just a detail" | Naive implementations silently become O(\ | V\ | ) per step and never finish on real corpora | Budget compute explicitly, or switch immediately to negative sampling / hierarchical softmax |
| Not subsampling frequent words ("the", "a", "of") | Uninformative high-frequency pairs dominate gradients and drown out rare-word signal | Apply frequency-based subsampling before generating training pairs | ||
| Ignoring window edge effects at sentence/document boundaries | Windows near position 1 or T reach outside valid indices | Clip context indices to and reduce effective window near boundaries | ||
| Assuming embeddings are directly interpretable per-dimension | Dimensions are entangled linear combinations, not human concepts | Only trust aggregate geometric properties (cosine similarity, analogy offsets), not single coordinates |
Pioneers & history
Origins · 1 minTomas Mikolov and how this was discovered
Tomas Mikolov led the development of word2vec while at Google, publishing the skip-gram and CBOW architectures and, crucially, the negative-sampling training trick that made them practical at scale. His earlier work on recurrent neural network language models set the stage for treating distributed word representations as a first-class object of study, and word2vec's release as a fast, open-source tool made dense embeddings a standard preprocessing step across the NLP industry almost overnight.
Where this matters
Skip-gram embeddings (and the broader word2vec toolkit) became a near-universal preprocessing step in NLP pipelines throughout the mid-2010s: search engines used them for query expansion and semantic matching, recommendation systems embedded item and user tokens with the same architecture, and countless text-classification and named-entity-recognition systems bootstrapped their input layer from pretrained word2vec vectors rather than learning embeddings from scratch on small labeled datasets. The core idea — learn a representation by solving an auxiliary prediction task on unlabeled data — is the direct ancestor of pretraining objectives in modern contextual encoders and large language models, even though those models replace the shallow log-bilinear score with deep Transformer-style attention. Understanding skip-gram is the cleanest way to see the distributional hypothesis turned into a concrete, trainable, differentiable objective, which is why it remains the canonical worked example before moving on to Continuous Bag-of-Words (CBOW), Negative Sampling and Training Word2Vec, and GloVe.
Ask the tutor: How exactly does negative sampling replace the full softmax while still approximating the same probabilistic objective?
Is skip-gram supervised or unsupervised learning?
It is best described as self-supervised: no human ever labels the training examples, yet the model is trained with a genuine supervised objective (softmax cross-entropy against a known target). The "labels" — which word is the correct context — are mechanically extracted from raw text co-occurrence patterns using the sliding window. This distinction matters because it explains why skip-gram can exploit arbitrarily large unlabeled corpora, while still enjoying all the clean gradient machinery of ordinary supervised classifiers like softmax and logistic regression.
Why does skip-gram use two separate embedding matrices instead of one?
Separating the center-word matrix W from the context-word matrix W' avoids a degenerate solution where a word is forced to have high dot product with itself, since P(w|w) would otherwise be pushed toward an artificially large value whenever a word appears near itself in overlapping windows. It also gives the model more capacity: a word's role as "the thing being described" and its role as "a clue describing something else" can differ. In practice, many implementations discard W' after training and just use W, or average the two, since both encode overlapping semantic information.
How does skip-gram differ from CBOW?
Skip-gram predicts each context word individually from a single center word, generating one training example per (center, context) pair inside a window. Continuous Bag-of-Words (CBOW) does the reverse: it averages (or sums) the embeddings of all context words and predicts the single center word from that combined representation. Because skip-gram creates more distinct training signals per occurrence of a rare word, it tends to represent infrequent words better, while CBOW trains faster since it processes one combined example per window rather than one per context word.
What replaces the expensive full softmax in real word2vec training?
Two standard tricks exist. Negative sampling reframes each update as several small binary logistic regression problems — distinguish the true context word from a handful of randomly sampled "negative" words — so an update touches only k+1 rows instead of the entire vocabulary. Hierarchical softmax instead arranges the vocabulary as a binary tree (often a Huffman tree) and computes the probability of a word as a product of binary decisions along the tree path, reducing cost to O(log|V|) per example. Negative sampling is covered in detail in the companion article on training tricks.
Why do skip-gram embeddings support analogies like king − man + woman ≈ queen?
The training objective pushes the dot product u_c · v_w to approximate the log of how much more often c and w co-occur than chance would predict. When two words share a systematic relationship (like gender pairs, or singular/plural pairs), the log co-occurrence ratios shift in a consistent direction across many word pairs, and because the score function is linear (a dot product), consistent ratio shifts translate into consistent vector offsets. This is the same underlying statistical quantity that GloVe models explicitly and directly, rather than implicitly through a softmax classifier.
Can skip-gram handle words never seen during training?
No — plain skip-gram, like most word-level embedding methods, has a fixed vocabulary fixed at training time and produces no vector for out-of-vocabulary words. Common mitigations include mapping rare/unseen words to a shared UNK vector, falling back to subword-based models such as fastText that build word vectors from character n-grams, or moving to contextual encoders that tokenize with subword units and handle novel words gracefully. This limitation is one of the main reasons static word embeddings were eventually superseded by contextual, subword-based architectures.
Is skip-gram still used in modern NLP systems?
Direct use of static word2vec embeddings has declined in favor of contextual embeddings from large pretrained models, but skip-gram remains pedagogically central and practically relevant in lower-resource settings, as a fast baseline, and in domains like recommendation systems and graph embeddings (e.g., node2vec, item2vec) where the same skip-gram objective is reused verbatim on non-text sequences. Understanding its objective and gradient is also the cleanest on-ramp to understanding contrastive and self-supervised pretraining objectives used throughout modern deep learning.