Continuous Bag-of-Words (CBOW)

Imagine a machine reading the sentence "the ___ sat on the mat." It needs to guess the missing word. Humans use the surrounding words — "the", "sat", "on", "the", "mat" — as pooled evidence. They don't read them one at a time in isolation. Continuous Bag-of-Words, or CBOW, builds exactly this intuition into a trainable model. It takes a window of context words and averages their vector representations. Then it asks a simple classifier to predict the word that belongs in the middle. CBOW matters because it was one of the two founding architectures of modern word embeddings. It was introduced alongside Word2Vec Skip-gram in the original Word2Vec papers. Both architectures learn dense vector representations of words purely from co-occurrence patterns in raw text. Neither one needs any labeled data. These embeddings became the backbone of early neural NLP pipelines. They fed into sentiment classifiers, named-entity recognizers, and machine translation systems. Later, contextual embeddings from transformers took over that role. The big picture is a trade-off. CBOW smooths over context by averaging, which makes it faster to train. This gives cleaner representations for frequent words. The cost is some sensitivity to rare words and fine-grained word order. Skip-gram inverts the prediction direction, predicting context from a target word. It tends to do better on rare words but trains more slowly. Understanding CBOW precisely — its architecture, its objective, its complexity — is the natural complement to understanding skip-gram. It also sets up the machinery of Negative Sampling, which makes both models tractable at web scale. This article gives a complete, self-contained treatment. It covers the precise mathematical model and a step-by-step training algorithm with pseudocode. It includes a worked numeric trace, complexity and correctness analysis, and common implementation bugs. It also covers the historical context in which CBOW emerged.

Key takeaways
  • CBOW predicts a target word from the averaged vectors of its surrounding context words — the reverse of skip-gram's target-to-context prediction.
  • The architecture is a shallow neural network: one shared input embedding matrix, an averaging/pooling layer, and one output (softmax) layer.
  • Averaging context vectors acts as a smoothing operation, making CBOW faster per epoch and generally better for frequent words than skip-gram.
  • Training uses ordinary supervised classification on a self-supervised signal: context windows generated by sliding over unlabeled text.
  • The naive softmax output layer costs O(vocabulary size) per training step, which is why Negative Sampling and hierarchical softmax were introduced.
  • CBOW embeddings and skip-gram embeddings occupy the same conceptual family as later contextual models, but are static, context-independent vectors — one vector per word type.
The core insight

CBOW learns word vectors by training a shallow network to predict a target word from the average of its context word vectors, trading fine-grained per-word signal for speed and smoother statistics.

The idea

CBOW is a word-embedding algorithm: it learns a mapping from vocabulary words to dense, low-dimensional real vectors (typically 100–300 dimensions) such that words with similar distributional behavior end up with similar vectors. It does this via a fake supervised task derived automatically from raw text — no human labels are needed. Given a window of size cc around a position tt in a corpus, the context is the set of words wt−c,…,wt−1,wt+1,…,wt+cw_{t-c}, \dots, w_{t-1}, w_{t+1}, \dots, w_{t+c}, and the target is the center word wtw_t. CBOW's task is: given the context words, predict the target word. The crucial architectural choice is how the context words are combined. CBOW looks up each context word's embedding vector and takes their average (or sum, in some formulations) to form a single fixed-size context vector, discarding information about which context position each word occupied and how many distinct words appear. This is why the model is called a "bag of words" — the context is treated as an unordered multiset, pooled into one vector, then passed through a single output layer to score every vocabulary word as a candidate target. This stands in direct contrast to Word2Vec Skip-gram, which uses a target word to predict each context word separately, producing 2c2c independent prediction tasks per window instead of CBOW's one. Averaging in CBOW means the training signal per window is smoothed across all context words simultaneously: a single gradient update nudges the shared context representation to be more predictive of the true center word, rather than nudging the center word to be predictive of each context word individually. This smoothing is the defining mathematical difference between the two architectures, and it drives essentially every practical difference in training speed, statistical behavior on rare versus frequent words, and downstream embedding quality that practitioners observe.

How it works

CBOW's parameters are two embedding matrices of the same shape: an input matrix Win∈RV×dW_{in} \in \mathbb{R}^{V \times d} and an output matrix Wout∈RV×dW_{out} \in \mathbb{R}^{V \times d}, where VV is vocabulary size and dd is the embedding dimension. Word ww's input embedding is row Win[w]W_{in}[w]; its output embedding (used only when ww is a candidate target) is row Wout[w]W_{out}[w]. Only WinW_{in} is kept as "the" word embeddings after training, though some implementations average or concatenate both.

Forward pass, for one training window: 1. Collect the context word indices c1,…,c2cc_1, \dots, c_{2c} around target position tt. 2. Look up their input embeddings and average them:

h=12c∑i=12cWin[ci]h = \frac{1}{2c} \sum_{i=1}^{2c} W_{in}[c_i]

3. Score every vocabulary word ww as a candidate target using a dot product with the output embeddings:

zw=Wout[w]⋅hz_w = W_{out}[w] \cdot h

4. Convert scores to a probability distribution over the whole vocabulary with softmax:

P(wt=w∣context)=exp⁡(zw)∑w′=1Vexp⁡(zw′)P(w_t = w \mid \text{context}) = \frac{\exp(z_w)}{\sum_{w'=1}^{V} \exp(z_{w'})}

Training objective. Over the whole corpus of TT tokens, CBOW maximizes the average log-likelihood of each true center word given its context:

J(θ)=1T∑t=1Tlog⁡P(wt∣wt−c,…,wt+c)J(\theta) = \frac{1}{T}\sum_{t=1}^{T} \log P(w_t \mid w_{t-c}, \dots, w_{t+c})

Maximizing JJ is done by stochastic gradient descent (or a minibatch variant), moving through the corpus window by window, computing the softmax loss for the true target, and back-propagating gradients into both WoutW_{out} (the output row for every vocabulary word, because softmax normalizes over all of them) and WinW_{in} (the input rows for the context words actually present in the window, since hh is their average). The backward pass is standard cross-entropy softmax backpropagation, with one subtlety: the gradient with respect to hh must be distributed equally to all context word vectors, since hh is their arithmetic mean. ``` ALGORITHM: Train-CBOW(corpus, window_size c, dim d, vocab V, learning_rate η, epochs E) Initialize W_in as V×d matrix with small random values Initialize W_out as V×d matrix with small random values for epoch in 1..E: for each position t in corpus (with valid window, i.e. c ≤ t ≤ T-c):

# --- build context and target --- target ← corpus[t] context_ids ← [ corpus[t-c], ..., corpus[t-1], corpus[t+1], ..., corpus[t+c] ] m ← length(context_ids) # = 2c away from corpus boundaries

# --- forward pass --- h ← (1/m) * sum( W_in[i] for i in context_ids ) # d-dim averaged context vector for w in 1..V: z[w] ← dot(W_out[w], h) p ← softmax(z) # V-dim probability vector

# --- loss (for monitoring only) --- loss ← -log( p[target] )

# --- backward pass ---

# dL/dz[w] = p[w] - 1{w == target} (standard softmax-cross-entropy gradient) for w in 1..V: g_z[w] ← p[w] - (1 if w == target else 0)

# gradient wrt h: dL/dh = sum_w g_z[w] W_out[w] g_h ← sum( g_z[w] W_out[w] for w in 1..V ) # d-dim vector

# gradient wrt W_out: dL/dW_out[w] = g_z[w] h for w in 1..V: W_out[w] ← W_out[w] - η g_z[w] * h

# gradient wrt each context input vector (chain rule through the average):

# dh/dW_in[i] = 1/m for each i in context_ids for i in context_ids: W_in[i] ← W_in[i] - η (g_h / m) return W_in # rows of W_in are the learned word embeddings ``` Two implementation notes matter for correctness. First, <strong>boundary windows</strong> near the start or end of a document must either be skipped or padded consistently; the pseudocode above simply restricts tt to positions with a full window on both sides. Second, the inner loops over "all ww in 1..V1..V" in the forward/backward pass are the expensive part discussed below — in practice they are replaced by <span class="wiki-link-missing">Negative Sampling</span> or hierarchical softmax, which the raw algorithm above deliberately avoids introducing so the exact* CBOW objective is visible first.

Figure
CBOW architecture: averaged context predicts the center word
CBOW: context words → averaged vector → softmax → target word "the" "quick" ? target "jumps" "over" v(the) v(quick) v(jumps) v(over) h = average(context vecs) softmax over W_out · h → P(target = "brown") highest

Complexity & correctness

Per-window time complexity (naive softmax). For one training example (one window), the forward pass computes hh in O(cd)O(cd) time (averaging 2c2c vectors of dimension dd), then computes a dot product with every vocabulary row to get zz, costing O(Vd)O(Vd). The backward pass is symmetric: computing all softmax gradients is O(V)O(V), updating every row of WoutW_{out} is O(Vd)O(Vd), and computing ghg_h requires summing VV vectors, O(Vd)O(Vd). So one training step costs

O(Vd)O(Vd)

dominating the O(cd)O(cd) context-averaging cost since V≫cV \gg c in realistic vocabularies (tens of thousands to millions of words). Over a corpus of TT tokens this gives O(TVd)O(TVd) total training time per epoch — the same asymptotic bottleneck that skip-gram faces, and precisely the cost that motivates replacing the full softmax with Negative Sampling (reducing the per-step cost to O(kd)O(kd) for a small constant number of negative samples kk) or a hierarchical softmax tree (reducing it to O(dlog⁡V)O(d \log V)).

Space complexity is O(Vd)O(Vd) for each of WinW_{in} and WoutW_{out}, i.e. O(Vd)O(Vd) total, since these are the only parameters — there are no recurrent or convolutional weights. This is modest even for V=106V = 10^6, d=300d = 300: about 3×1083 \times 10^8 floats per matrix, a few gigabytes in float32, comfortably fits on commodity hardware from the era CBOW was introduced.

Why averaging is a mathematically sound smoothing operation. The gradient computation shows precisely what "smoothing" means: since h=1m∑iWin[ci]h = \frac{1}{m}\sum_i W_{in}[c_i], the chain rule gives ∂h∂Win[ci]=1mI\frac{\partial h}{\partial W_{in}[c_i]} = \frac{1}{m} I for every context word ii. Consequently every context word receives an identical gradient signal (scaled by 1/m1/m) regardless of which position in the window it occupied or how informative it individually was. This is the formal justification for the informal claim "CBOW blurs context word order and identity" — it falls directly out of differentiating a mean. Because the gradient magnitude per context word shrinks by a factor of 1/m1/m compared to skip-gram (which gives each context word its own undivided gradient from a separate prediction), CBOW updates are individually gentler but there are effectively 2c2c times fewer distinct predictions per window (one softmax instead of 2c2c), which is the source of its speed advantage.

Correctness of the training procedure reduces to correctness of maximum-likelihood estimation via SGD on a well-defined, differentiable objective. J(θ)J(\theta) is a proper log-likelihood: P(wt∣context)P(w_t \mid \text{context}) is a valid probability distribution because softmax normalizes over the vocabulary, so −log⁡P-\log P is a valid cross-entropy loss bounded below by 0. Standard SGD convergence arguments apply: for suitably decaying learning rates and a smooth (here, doubly-differentiable) loss landscape, SGD converges to a stationary point of the empirical objective, though because JJ is non-convex (it factors through the bilinear form Wout[w]⋅hW_{out}[w] \cdot h), only local rather than global optimality is guaranteed. In practice this is not a correctness failure specific to CBOW — it is the same non-convexity every neural embedding model accepts, and empirically CBOW reaches representations useful for downstream tasks despite the lack of a global-optimum guarantee.

Honest tradeoff summary.

PropertyCBOWSkip-gram
Prediction directioncontext → targettarget → context
Training examples per window1up to 2c2c
Typical speedfaster per epochslower per epoch
Rare-word qualityweaker (averaging dilutes rare context signal)stronger
Frequent-word qualitystrongstrong
Sensitivity to word ordernone (bag of words)none, but per-pair signal is sharper

Worked example

Take the toy sentence, tokenized and lowercased: ``` the quick brown fox jumps over the lazy dog ``` with window size c=2c = 2 and target position tt pointing at "fox" (index 3, 0-indexed). The context is the two words on each side:

context={ "the","quick","brown","jumps" },target="fox"\text{context} = \{\,\text{"the"}, \text{"quick"}, \text{"brown"}, \text{"jumps"}\,\}, \qquad \text{target} = \text{"fox"}

Suppose d=2d = 2 for hand-traceability, and the current (partially trained) input embeddings are:

wordWinW_{in} vector
the(0.10,0.30)(0.10, 0.30)
quick(0.50,−0.20)(0.50, -0.20)
brown(0.20,0.40)(0.20, 0.40)
jumps(−0.10,0.10)(-0.10, 0.10)
fox(0.30,0.30)(0.30, 0.30)

Step 1 — average the context vectors.

h=(0.10,0.30)+(0.50,−0.20)+(0.20,0.40)+(−0.10,0.10)4=(0.70,0.60)4=(0.175, 0.150)h = \frac{(0.10,0.30) + (0.50,-0.20) + (0.20,0.40) + (-0.10,0.10)}{4} = \frac{(0.70, 0.60)}{4} = (0.175,\ 0.150)

Step 2 — score candidate targets. Suppose the output embeddings for a tiny 3-word slice of the vocabulary (restricting to fox, dog, quick for illustration) are:

wordWoutW_{out} vectorz=Wout⋅hz = W_{out}\cdot h
fox(0.40,0.10)(0.40, 0.10)0.40(0.175)+0.10(0.150)=0.0850.40(0.175)+0.10(0.150)=0.085
dog(−0.30,0.20)(-0.30, 0.20)−0.30(0.175)+0.20(0.150)=−0.0225-0.30(0.175)+0.20(0.150)=-0.0225
quick(0.05,−0.05)(0.05, -0.05)0.05(0.175)−0.05(0.150)=0.001250.05(0.175)-0.05(0.150)=0.00125

Step 3 — softmax over these scores (restricted subset, for illustration; real softmax runs over the full vocabulary):

exp⁡(0.085)=1.0887,exp⁡(−0.0225)=0.9778,exp⁡(0.00125)=1.0013\exp(0.085)=1.0887,\quad \exp(-0.0225)=0.9778,\quad \exp(0.00125)=1.0013
sum=3.0678  ⟹  P(fox)=0.355, P(dog)=0.319, P(quick)=0.326\text{sum} = 3.0678 \implies P(\text{fox})=0.355,\ P(\text{dog})=0.319,\ P(\text{quick})=0.326

Step 4 — loss and gradient. True target is "fox", so gz(fox)=0.355−1=−0.645g_z(\text{fox}) = 0.355 - 1 = -0.645, gz(dog)=0.319g_z(\text{dog}) = 0.319, gz(quick)=0.326g_z(\text{quick}) = 0.326 (each other vocabulary word similarly gets gz(w)=p(w)g_z(w) = p(w)). Loss is −log⁡(0.355)≈1.036-\log(0.355) \approx 1.036 nats.

Step 5 — update. Wout[fox]W_{out}[\text{fox}] moves toward hh (negative gradient direction, since gz<0g_z<0 there), while Wout[dog]W_{out}[\text{dog}] and Wout[quick]W_{out}[\text{quick}] move away from hh. Every context word's input vector — "the", "quick", "brown", "jumps" — receives the same gh/4g_h / 4 nudge, pulling all four slightly in a direction that would have made hh score "fox" higher next time. This last point is the concrete instance of the smoothing argument from the complexity section: none of "the", "brown", "jumps" gets individualized credit or blame even though intuitively "brown" is a far more informative context word for "fox" than "the" is.

Common pitfalls & traps

PitfallWhy it happensFix
Forgetting to divide gradient by context size mmConfusing sum-pooling with mean-pooling during backpropExplicitly divide ghg_h by mm when hh is a true average (or use sum-pooling consistently, adjusting learning rate)
Variable window boundary size not handledSentence/document edges have fewer than 2c2c context wordsTrack actual mm per window rather than assuming a fixed 2c2c
Using the same matrix for input and output embeddingsTying Win=WoutW_{in} = W_{out} collapses the target-context asymmetry the objective relies onKeep two separate matrices during training; discard or merge only after training completes
Full softmax left in production training codeLooks "correct" on toy vocab but is O(Vd)O(Vd) per step — intractable at scaleSwap in Negative Sampling or hierarchical softmax before scaling to real corpora
Comparing CBOW and skip-gram embeddings at equal epoch count without accounting for example countCBOW sees 1 prediction/window, skip-gram sees 2c2c — "epochs" are not comparable units of gradient signalCompare by total gradient updates or wall-clock time, not epoch count alone
Subsampling frequent words done inconsistently between context construction and target selectionWords like "the" get dropped from context lists but still counted as valid targets, or vice versaApply the same subsampling policy uniformly before window construction

Pioneers & history

CBOW was introduced by Tomas Mikolov and colleagues at Google as one half of the Word2Vec family, published alongside skip-gram in 2013. The paper's central claim — that a shallow, log-linear model trained purely on raw text co-occurrence could produce embeddings capturing rich semantic and syntactic regularities (the famous "king − man + woman ≈ queen" analogy) — reoriented the field's default approach to representing words, displacing older count-based methods like latent semantic analysis for many downstream tasks.

Origins · 1 minTomáš Mikolov and how this was discovered
TM
Tomáš Mikolov
born 1985 · Google Brain / Facebook AI Research, early 2010s

Mikolov, a Czech researcher, led the Word2Vec project while at Google, formalizing both CBOW and skip-gram as simplified, highly efficient alternatives to earlier neural language models such as feedforward and recurrent neural network language models he had also worked on during his PhD. His central engineering insight — strip a neural language model down to its bare minimum, discarding nonlinear hidden layers entirely — is what made training on billions of words practical on commodity hardware, directly enabling the negative-sampling and hierarchical-softmax tricks that followed.

History
2003
— Bengio et al. publish the neural probabilistic language model, the conceptual ancestor of neural word embeddings.
2010
— Mikolov's recurrent neural network language models demonstrate strong perplexity gains over n-gram baselines.
2013
— Mikolov, Chen, Corrado, and Dean publish the Word2Vec CBOW and skip-gram architectures with negative sampling.
2014
— Pennington, Socher, and Manning publish GloVe, a count-based alternative trained on global co-occurrence statistics.
2018
— Contextual embeddings (ELMo, BERT) begin to supersede static CBOW/skip-gram vectors for most downstream NLP tasks.

Where this matters

CBOW-style pretraining became a standard first step in NLP pipelines throughout the mid-2010s: word vectors trained via CBOW or skip-gram were plugged into downstream classifiers for sentiment analysis, part-of-speech tagging, named entity recognition, and as initialization for the embedding layers of early neural machine translation systems, well before end-to-end contextual models dominated. Libraries such as Gensim's `word2vec` module expose a `sg` flag that switches between skip-gram (`sg=1`) and CBOW (`sg=0`), and CBOW is frequently the default for large corpora precisely because of its speed advantage discussed above. Beyond direct embedding pretraining, the CBOW idea of "predict from pooled context" recurs structurally in other systems: fastText extends CBOW-style training with subword n-gram features to better handle rare and out-of-vocabulary words; paragraph/document embedding methods like Doc2Vec's distributed-memory variant borrow the same averaged-context-predicts-target scaffold at the document level; and average-pooling encoders in sentence embedding baselines echo the same smoothing tradeoff CBOW exhibits at the word level. Understanding CBOW's mechanics is therefore foundational not just historically, but as a recurring architectural pattern — pool, then predict — that reappears whenever engineers trade positional precision for computational efficiency.

Ask the tutor: How does replacing CBOW's full softmax with negative sampling change the training objective and gradient computation?

Is CBOW the same as a bag-of-words document classifier?

No. Classical bag-of-words document classifiers represent whole documents as sparse word-count or TF-IDF vectors and feed them to a classifier like logistic regression or Naive Bayes. CBOW is a word-embedding training scheme: it takes a small local window of context words around a target position in running text, averages their dense embeddings, and predicts the missing center word. The "bag" in CBOW refers only to how the local context window is pooled — order-insensitively — not to how an entire document is represented. The two ideas share the phrase "bag of words" because both discard word order within some unit of text, but they solve different problems at different granularities.

Why does CBOW tend to underperform skip-gram on rare words?

Averaging dilutes signal. When a rare word appears as one of several context words in a window, its individual vector contributes only a 1/(2c)1/(2c) fraction to the pooled vector hh, so the gradient update it receives is similarly diluted, and it must appear in many windows before accumulating a strong, well-shaped vector. Skip-gram instead uses each context word to predict the target in a wholly separate prediction, so a rare context word gets a full, undivided gradient every time it appears near any target, giving it more concentrated training signal per occurrence. This is the standard explanation for skip-gram's often-cited edge on rare-word and morphologically rich vocabularies.

Does CBOW use any word order information at all?

No — within a fixed window, CBOW treats the context words as an unordered multiset and averages their vectors, so swapping the positions of two context words leaves the training example completely unchanged. Only the identity of the words present, not their order, affects hh. This is a deliberate simplification for speed and tractability; models that need order sensitivity typically add positional embeddings, use recurrent/convolutional pooling instead of a flat average, or move to attention-based architectures, all of which sacrifice some of CBOW's simplicity and speed to recover order information.

Why do both CBOW and skip-gram use two separate embedding matrices instead of one?

Using distinct input (WinW_{in}) and output (WoutW_{out}) matrices lets a word play two structurally different roles: appearing as context (looked up in WinW_{in}) versus appearing as a candidate target (scored via WoutW_{out}). If a single shared matrix were used, the dot product of a word's vector with itself would be forced to behave consistently in both roles simultaneously, which empirically produces degenerate solutions where every word's vector collapses toward maximizing its own self-similarity rather than learning genuinely useful co-occurrence structure. After training, most applications discard WoutW_{out} and keep only WinW_{in}'s rows as "the" word embeddings, occasionally averaging the two for a modest quality boost.

How does CBOW relate to fastText?

FastText, developed by the same research group, extends the CBOW (and skip-gram) architecture by representing each word as the sum of its own vector plus vectors for its constituent character n-grams, rather than a single opaque lookup vector. This lets fastText produce reasonable embeddings for out-of-vocabulary words at inference time, since their n-grams may have been seen during training even if the whole word was not. The core prediction mechanism — averaged context predicts a target via softmax (or negative sampling) — is otherwise unchanged from CBOW, making fastText a direct architectural descendant rather than a different training paradigm.

Why did static embeddings like CBOW's eventually get replaced by contextual embeddings?

CBOW (and skip-gram) assign exactly one fixed vector per word type, so the word "bank" gets the identical vector whether it appears in "river bank" or "bank account" — the model cannot disambiguate meaning using sentence context at inference time. Contextual embedding models like ELMo, BERT, and their successors instead compute a fresh vector for each word occurrence, conditioned on its entire surrounding sentence via recurrent or attention mechanisms. This directly fixes the polysemy limitation inherent to CBOW's architecture, at the cost of far greater computational expense and the loss of a single reusable, precomputed lookup table.

Can CBOW be trained without a fixed window size?

The standard formulation uses a fixed maximum window radius cc, but Word2Vec implementations commonly sample a smaller effective window size uniformly at random up to cc for each training example, which downweights distant context words on average without changing the core averaging mechanism. This is a practical variance-reduction trick rather than a change to the underlying algorithm: the forward and backward pass equations above are unchanged, only the number of context words mm included per window fluctuates from example to example.

Before this

Where this leads