CBOW Model: Architecture, Gradients, Word Embedding Training
Examines the CBOW Word2Vec architecture covering context averaging, the objective function, gradient derivation, training dynamics.
Language AI HandbookAll chapters CBOW ModelFull chapter · Sections in this chapter1 / 15 ↓
Reading tools Definitions, links & notes
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
CBOW Model
In the previous chapter, we explored the Skip-gram model, which learns word embeddings by training a neural network to predict surrounding context words from a single center word. The Continuous Bag of Words (CBOW) model reverses this relationship: given several surrounding context words, predict the center word that belongs in the middle. This seemingly small inversion produces a model with different strengths, training dynamics, and practical applications.
Both architectures were introduced by Mikolov et al. in the 2013 Word2Vec paper. While Skip-gram excels at capturing representations for rare words, CBOW trains faster, scales better to large corpora, and produces stronger embeddings for frequent words. Understanding both models gives you a complete picture of the Word2Vec framework and helps you choose the right tool for a given task.
This chapter covers the CBOW architecture from the ground up. We build intuition for why averaging context embeddings works, work through the mathematics of the forward pass and objective function, derive the gradients used in training, implement a working CBOW model from scratch, and compare the two architectures in depth.
The Core Idea: Predicting Words from Context
Think about the fill-in-the-blank task: "The quick brown ___ jumps over the lazy dog." Given the surrounding words, you can immediately guess "fox." Your brain combines information from all the context words together and uses the collective signal to identify what fits in the middle. CBOW does the same thing computationally.
The CBOW model learns word representations by training a neural network to predict a center (target) word from its surrounding context words. All context word embeddings are averaged into a single representation before making the prediction. The learned embedding matrix becomes the word embeddings.
The name "Continuous Bag of Words" carries a specific meaning. "Continuous" refers to the use of continuous-valued dense vectors rather than discrete one-hot encodings. "Bag of Words" signals that the model treats context as an unordered collection: it does not distinguish between a word appearing two positions to the left and one appearing one position to the right. Both are simply "context words." This positional indifference is a deliberate design choice that simplifies the model and reduces the number of parameters needed.
This design philosophy connects directly to the distributional hypothesis that underlies all word embedding methods. The hypothesis, sometimes paraphrased as "you shall know a word by the company it keeps," holds that words with similar meanings appear in similar contexts. CBOW operationalizes this idea by forcing the model to reconstruct a word from a compressed representation of its neighborhood. If the model learns to do this well, the embedding of each context word must carry information about the kinds of targets it surrounds. After training, those embeddings capture distributional similarity in a dense, continuous vector space.
The key question is: why does averaging the context embeddings work? At first glance, it seems aggressive to collapse four distinct word vectors into a single centroid. The answer lies in how the training signal accumulates across examples. Consider that "fox" appears surrounded by words like "quick," "brown," "runs," "clever," and "jumped" across thousands of sentences in a large corpus. Each training example shifts the embeddings of those context words slightly toward a region of the embedding space associated with fox-like targets. After many such updates, the centroid of those context words consistently points toward the region where "fox" lives. The averaging operation, far from losing information, acts as a kind of implicit regularization: it forces the model to find embeddings that work well collectively rather than relying on any single context word.
PythonIn [3]Expand
# Demonstrating CBOW's prediction task
sentence = "The quick brown fox jumps over the lazy dog"
words = sentence.lower().split()
def get_cbow_example(words, target_idx, window_size=2):
"""Generate a CBOW training example: (context_words, target_word)."""
target = words[target_idx]
context = []
for offset in range(-window_size, window_size + 1):
if offset == 0:
continue
context_idx = target_idx + offset
if 0 <= context_idx < len(words):
context.append(words[context_idx])
return context, target
# Get CBOW example for predicting "fox" (index 3)
context, target = get_cbow_example(words, target_idx=3, window_size=2)ConsoleOut [4]Expand
CBOW Training Example ============================================= Sentence: 'The quick brown fox jumps over the lazy dog' Target word: 'fox' (position 3) Window size: 2 Context words: ['quick', 'brown', 'jumps', 'over'] Task: predict 'fox' from ['quick', 'brown', 'jumps', 'over']
This single training example contains one prediction task using four context words. Compare this to Skip-gram: for the same sentence position, Skip-gram would generate four separate training pairs: (fox, quick), (fox, brown), (fox, jumps), and (fox, over). CBOW collapses these into one example. This difference in data efficiency affects training speed in a fundamental way, which we discuss in depth later.
Generating All CBOW Training Examples
A training corpus is converted into CBOW examples by sliding a window over every position in every sentence. For each position, the center word becomes the target and the words within the window on each side become the context.
The window-based approach encodes an important assumption: words that are closer in a sentence are more relevant to predicting the center word than words that are far away. The window size controls how wide the context is. A small window (size 1 or 2) captures tight syntactic relationships: adjectives next to nouns, verbs near their subjects. A larger window (size 5 or more) captures broader topical co-occurrence: words that appear in the same paragraph because they relate to the same subject. The optimal window size depends on what you want your embeddings to capture.
PythonIn [5]Expand
def generate_cbow_pairs(words, window_size=2):
"""Generate all CBOW (context, target) training pairs from a sequence of words."""
pairs = []
for target_idx in range(len(words)):
context = []
for offset in range(-window_size, window_size + 1):
if offset == 0:
continue
context_idx = target_idx + offset
if 0 <= context_idx < len(words):
context.append(words[context_idx])
if context: # Only include positions with at least one context word
pairs.append((context, words[target_idx]))
return pairs
all_pairs = generate_cbow_pairs(words, window_size=2)ConsoleOut [6]Expand
Sentence: 'The quick brown fox jumps over the lazy dog' Window size: 2 All CBOW training pairs (9 total): ------------------------------------------------------------ [quick, brown ] -> 'the' [the, brown, fox ] -> 'quick' [the, quick, fox, jumps ] -> 'brown' [quick, brown, jumps, over ] -> 'fox' [brown, fox, over, the ] -> 'jumps' [fox, jumps, the, lazy ] -> 'over' [jumps, over, lazy, dog ] -> 'the' [over, the, dog ] -> 'lazy' [the, lazy ] -> 'dog'
Notice that positions near the edges of the sentence have fewer context words. The model handles this naturally: those positions contribute fewer context embeddings to average, but the averaging still works. Words at the sentence edges are neither ignored nor given special treatment. The averaging simply operates over whatever context words are available, and the model learns to work with variable-size contexts.
This flexibility is a practical advantage. Unlike models that require fixed-length inputs, CBOW naturally accommodates the varying context sizes that arise at sentence boundaries. There is no padding required and no masking mechanism: the forward pass simply averages the embeddings of however many context words are present.
Architecture: Context Averaging
CBOW shares its overall structure with Skip-gram but adds a critical averaging step between the embedding lookup and the output layer. Understanding this step is essential to understanding both what CBOW can learn and why it makes the design choices it does.
The architecture has three components: an input embedding matrix, a context averaging operation, and an output projection matrix. These three pieces work in sequence to convert a set of context word identifiers into a probability distribution over the vocabulary, and backpropagation through this sequence adjusts the parameters so that the correct center word receives the highest probability.
VisualizationOut [7]Expand

The two weight matrices in CBOW mirror those in Skip-gram:
- Embedding matrix (size ): Each row is the -dimensional embedding for one vocabulary word. When CBOW receives context words, it looks up each context word's row and averages them.
- Output matrix (size ): Each column corresponds to a vocabulary word's "context representation." This matrix converts the averaged embedding into a score vector over the full vocabulary.
These two matrices serve distinct roles during training even though both are learned simultaneously. The embedding matrix is the primary product: these are the word vectors you use after training. The output matrix is more of a scaffolding: it provides the contrastive signal needed to push word embeddings apart during training. Some practitioners use as an alternative embedding matrix (averaging the two is sometimes beneficial), but in most applications only is retained.
Why Not Use a Nonlinear Hidden Layer?
You might wonder why CBOW has no activation function between the embedding lookup and the output layer. Skip-gram also omits nonlinearities. This is a deliberate choice, and it carries practical effects.
Nonlinearities like ReLU or sigmoid introduce computational cost and complicate backpropagation. More importantly, they change the geometry of the embedding space. A linear architecture means the learned embeddings live in a space where vector arithmetic is meaningful. The famous Word2Vec property that king - man + woman = queen holds precisely because the embedding space is linear. Adding a nonlinearity would destroy this arithmetic structure.
There is also a theoretical argument from information theory. The task of predicting a word from its context is a self-supervised objective that forces the model to compress distributional information into a fixed-size vector. The compression happens through the bottleneck of the embedding dimension , not through a nonlinearity. As long as is much smaller than , the model must find a compact representation of the word's distributional properties, and that representation turns out to be geometrically meaningful.
Context Word Averaging
The averaging step is CBOW's defining operation. How should the model combine information from multiple context words into a single vector? CBOW takes the simplest possible answer: element-wise mean. If the context words "quick," "brown," "jumps," and "over" each contribute a -dimensional embedding, the averaged representation is the vector whose -th element is the mean of all four embeddings' -th elements.
More formally, given context words with indices in the vocabulary, the hidden representation is:
where:
- : the averaged hidden representation, a -dimensional vector representing the "centroid" of the context in embedding space
- : the number of context words (typically for a window of size , but fewer at sentence boundaries)
- : the embedding of the -th context word, the row at index in the embedding matrix
This averaging has an important geometric interpretation. Each embedding vector corresponds to a point in -dimensional space. The average corresponds to the centroid of those points. If the context words are semantically coherent (which they tend to be in natural language), their centroid points toward the region of the embedding space where the center word should live.
To see why this works, consider what the model must learn for the centroid to be useful. Each context word embedding must encode the word's own meaning and information about what words typically appear near it. After training, the centroid of "quick," "brown," "jumps," and "over" should point toward the region where nouns like "fox" live, because that is the region the model was repeatedly trained to predict from such context centroids.
There is an alternative design choice worth understanding: instead of averaging, you could sum the context embeddings. The sum is mathematically equivalent to the average (they differ only by a constant scaling factor), but the average has a practical advantage: the magnitude of the hidden representation does not grow with the number of context words. This stability is helpful for maintaining consistent gradient magnitudes during training regardless of window size or sentence boundary effects.
PythonIn [8]Expand
import numpy as np
# Small vocabulary for demonstration
small_vocab = ["the", "quick", "brown", "fox", "jumps", "over", "lazy", "dog"]
word_to_idx = {word: i for i, word in enumerate(small_vocab)}
idx_to_word = {i: word for word, i in word_to_idx.items()}
# Initialize small embedding matrix (random, untrained)
embedding_dim = 4
np.random.seed(42)
W = np.random.randn(len(small_vocab), embedding_dim) * 0.5
def average_context_embeddings(context_words, W, word_to_idx):
"""Average the embedding vectors of a set of context words."""
embeddings = np.array([W[word_to_idx[word]] for word in context_words])
return np.mean(embeddings, axis=0)
context = ["quick", "brown", "jumps", "over"]
avg_embedding = average_context_embeddings(context, W, word_to_idx)ConsoleOut [9]Expand
Context Word Averaging (embedding_dim=4): ---------------------------------------------------- Context words: ['quick', 'brown', 'jumps', 'over'] Individual embeddings: quick : [-0.117, -0.117, +0.790, +0.384] brown : [-0.235, +0.271, -0.232, -0.233] jumps : [-0.506, +0.157, -0.454, -0.706] over : [+0.733, -0.113, +0.034, -0.712] Averaged embedding (h-bar): h_bar : [-0.031, +0.050, +0.034, -0.317] Verification (manual column averages): Dim 1: mean(['-0.117', '-0.235', '-0.506', '+0.733']) = -0.031 Dim 2: mean(['-0.117', '+0.271', '+0.157', '-0.113']) = +0.050 Dim 3: mean(['+0.790', '-0.232', '-0.454', '+0.034']) = +0.034 Dim 4: mean(['+0.384', '-0.233', '-0.706', '-0.712']) = -0.317
The averaged embedding is not the embedding of any particular word. It is a synthetic point in the embedding space that represents the combined semantic context. After training, this averaged vector will be positioned such that the center word's row in receives the highest score.
VisualizationOut [10]Expand

The heatmap makes the averaging operation concrete. Each column of the upper four rows contains the embedding values for one dimension. The bottom row shows how the average smooths these values, placing the centroid between the extremes. In this random-initialization example, the values carry no semantic meaning yet, but the same averaging process applies once the embeddings have been trained. After training, the average of "quick," "brown," "jumps," and "over" will be a vector that the output matrix can reliably decode as "fox."
The Geometric View of Averaging
It is worth dwelling on the geometry a little longer because it explains both the strengths and the limitations of CBOW. In -dimensional embedding space, each word occupies a point. The average of points is the centroid, the center of mass of the configuration. When the context words form a tight cluster (because they are semantically similar or syntactically related), the centroid is a stable, representative point. When the context words are semantically diverse or scattered across the embedding space, the centroid can end up in an unusual region, away from any natural cluster.
This geometry explains CBOW.s stability on frequent words. Frequent words appear in many different contexts. Over many training examples, the model encounters "fox" predicted from contexts like ["quick", "brown", "jumps", "over"], ["cunning", "red", "ran", "through"], ["the", "chased", "by", "hound"], and many more. Each of these averages lands near the "fox" region of the space, and the gradients reinforce the embedding accordingly. The signal is consistent and strong.
Rare words, by contrast, may appear in only a handful of contexts. If "dhole" (a type of wild canid) appears in contexts mostly involving predation and forests, its averaged context vectors may cluster in a region that also corresponds to many other animals. The signal is weaker, and neighboring words may not be reliable discriminators. This is why Skip-gram, which trains on each context word individually, tends to outperform CBOW on rare vocabulary.
The CBOW Objective Function
With the averaged context representation in hand, CBOW needs to assign high probability to the correct center word and low probability to all other vocabulary words. This is a multiclass classification problem across the entire vocabulary.
From Hidden Representation to Scores
After computing , the model computes a score for every vocabulary word by multiplying against the output matrix :
where:
- : a vector of raw scores (logits), one per vocabulary word, shape
- : the output weight matrix of shape
- : the averaged context embedding of shape
Each element of this score vector measures how well the averaged context aligns with word 's output representation. The dot product is high when the two vectors point in a similar direction and have large magnitudes. Words whose output vectors point in a similar direction to receive high scores. After sufficient training, the output vector for the correct center word will align closely with the averaged context embedding.
The output matrix encodes a different kind of information than . Each column of is a "context signature" for that word: the direction in embedding space from which the word is most easily predicted. This is subtly different from the word's embedding in , which represents how the word behaves when it is a context word. As a consequence, the two matrices can diverge. In practice, researchers have found that averaging and sometimes produces better embeddings than using either alone, because the two matrices capture complementary distributional properties.
The Softmax Distribution
To convert raw scores into probabilities, CBOW applies the softmax function. For each vocabulary word :
where:
- : the probability that is the center word given context
- : the raw score for word , the dot product of 's output vector and the averaged context embedding
- : the normalizing constant summing over all vocabulary words
The exponential function ensures all probabilities are positive, and dividing by the sum ensures they add up to one. Words with higher dot products with receive exponentially higher probability, making the distribution sharply peaked around the highest-scoring word.
The sum in the denominator is the computational bottleneck. With a vocabulary of 100,000 words, every single forward pass requires computing 100,000 dot products. This is why full softmax training is impractical for large vocabularies, and approximations like negative sampling are essential for real-world use.
Training Objective: Maximize Log-Likelihood
For a single training example with context and true center word , we want to maximize . Across all training examples, we maximize the log-likelihood:
Expanding the softmax, this becomes:
which can be rewritten as:
where:
- : the score of the correct center word, which we want to be as large as possible
- : the log-sum-exp over all words, which is a normalizing constant
This decomposition is illuminating. The first term rewards increasing the score for the true center word. The second term penalizes any increase in the scores for all words (including the true one), because the log-sum-exp grows when any score grows. The result is a tug-of-war: the model must push up the score for the true word while keeping the scores for all other words low. This is exactly the competitive pressure that shapes the embedding space.
In practice, we minimize the negative log-likelihood (equivalent to minimizing cross-entropy loss), so the loss for one training example is:
PythonIn [11]Expand
def softmax(x):
"""Numerically stable softmax."""
x = x - np.max(x) # Subtract max for numerical stability
e_x = np.exp(x)
return e_x / e_x.sum()
def cbow_forward(context_words, W, W_prime, word_to_idx):
"""
CBOW forward pass.
Returns averaged hidden representation and softmax probabilities.
"""
# Step 1: Look up embeddings for context words
context_embs = np.array([W[word_to_idx[w]] for w in context_words])
# Step 2: Average context embeddings
h_bar = np.mean(context_embs, axis=0)
# Step 3: Compute output scores
scores = W_prime.T @ h_bar
# Step 4: Apply softmax
probs = softmax(scores)
return h_bar, probs
# Initialize model parameters
V = len(small_vocab)
d = 4
np.random.seed(42)
W = np.random.randn(V, d) * 0.1
W_prime = np.random.randn(d, V) * 0.1
# Run forward pass
h_bar, probs = cbow_forward(context, W, W_prime, word_to_idx)ConsoleOut [12]Expand
CBOW Forward Pass:
--------------------------------------------------
Context: ['quick', 'brown', 'jumps', 'over']
Averaged h_bar: [-0.0063, 0.0099, 0.0069, -0.0634]
Output probabilities (before training):
dog : 0.1258 ########################
lazy : 0.1257 ########################
the : 0.1255 ########################
over : 0.1250 ########################
quick : 0.1249 ########################
jumps : 0.1248 ########################
brown : 0.1244 ########################
fox : 0.1239 ########################
P('fox' | context) = 0.1239
(Random init, so probabilities should be roughly uniform)With random initialization, probabilities are roughly uniform across all vocabulary words. Training will push "fox" to receive much higher probability for this context. The roughly equal initial distribution also illustrates a key feature of softmax: without meaningful parameters, every word is equally likely, which is the maximum-entropy distribution. Training breaks this symmetry by adjusting parameters so that the correct word becomes more probable.
CBOW Gradient Derivation
To train CBOW with gradient descent, we need to compute how the loss changes with respect to every parameter. The parameters are the embedding matrix and the output matrix . Understanding these gradients is important for implementing the model and for developing intuition about what happens during training and why the averaging operation produces the regularizing effect we described earlier.
Gradient with Respect to the Output Matrix
The loss for one training example is . Expanding this through the softmax, the gradient with respect to the output score (before softmax) is the prediction error:
where:
- : the model's predicted probability for word
- : 1 if is the true center word, 0 otherwise
This is the standard softmax gradient, and it has a beautiful interpretation. The model is right when (error near 0), and wrong when (error near 1). For all non-target words, the error is , meaning the model is penalized for assigning any probability to wrong words. The gradient is zero only when the model assigns probability 1 to the correct word and 0 to all others, an ideal that is never achieved in practice.
This prediction-error form has a deep connection to maximum likelihood estimation. The gradient of the negative log-likelihood with respect to the pre-softmax logits is exactly the difference between the predicted distribution and the one-hot target distribution. This clean form is one reason softmax cross-entropy is the standard objective for multiclass classification.
Since , the gradient with respect to the output column vector is:
In matrix form, the full gradient for is the outer product of the prediction error vector and the hidden representation:
where is the error vector ( is the softmax probability vector and is the one-hot target vector).
The key insight here is that the update to touches every column. Every column is updated, rather than only the target word.s column. Every vocabulary word's output vector is updated at every training step: the target word's column is pushed toward , and every other word's column is pushed slightly away from (because their prediction errors are positive). This global coupling is what makes the vocabulary's output representations mutually competitive: they cannot all be similar to every context average.
Gradient with Respect to the Hidden Representation
To propagate gradients back to the embedding matrix, we first need the gradient with respect to :
where:
- : the output weight matrix of shape
- : the prediction error vector of shape
This is a weighted sum of all the output column vectors, weighted by the prediction errors. Words with large positive errors contribute their output vectors in the direction that increases the score for those words. Words with large negative errors (the true target word with error , which is negative) pull toward their output vectors.
The gradient can be interpreted as "the direction in which the averaged context embedding should move to reduce the loss." If the model currently assigns high probability to the wrong word "dog" and low probability to the correct word "fox," this gradient will point toward the output vector for "fox" and away from the output vector for "dog." Moving in this direction makes "fox" more predictable from the current context.
Gradient with Respect to the Embedding Matrix
Since , and each context word embedding contributes equally to the average, the gradient with respect to each context word's embedding is:
The factor is the derivative of the average with respect to one component. Each context word receives the same gradient signal, scaled by . This is the "bag" property in action: all context words are treated identically, and the gradient for each is the same averaged signal.
This equal sharing of gradients has a regularizing effect. If the model makes an error, the blame is distributed equally across all context words. A single context word cannot receive a large, unstable gradient update because it must share the blame with all its neighbors. This dampening makes CBOW more stable during training than a model that attributes prediction errors to individual context words.
Importantly, the only rows of that receive gradient updates for a given training example are the rows corresponding to the context words present in that example. Words not in the context window receive zero gradient for this example. This sparsity is computationally advantageous: even though the vocabulary may contain 100,000 words, a given training step touches at most rows of (where is the window size), regardless of vocabulary size.
PythonIn [13]Expand
def cbow_backward(
context_words, target_word, h_bar, probs, W, W_prime, word_to_idx
):
"""
CBOW backward pass. Returns parameter gradients.
"""
V = W_prime.shape[1]
target_idx = word_to_idx[target_word]
# Step 1: Prediction error (softmax gradient)
e = probs.copy()
e[target_idx] -= 1.0 # Subtract 1 from the correct word
# Step 2: Gradient for W_prime
dW_prime = np.outer(h_bar, e) # shape: (d, V)
# Step 3: Gradient for hidden representation
dh_bar = W_prime @ e # shape: (d,)
# Step 4: Gradient for embedding matrix (distribute equally to all context words)
dW = np.zeros_like(W)
C = len(context_words)
for word in context_words:
dW[word_to_idx[word]] += dh_bar / C
return dW, dW_prime
def compute_loss(target_word, probs, word_to_idx):
"""Negative log-likelihood loss."""
return -np.log(probs[word_to_idx[target_word]] + 1e-10)
# Compute gradients for our example
dW, dW_prime = cbow_backward(
context, "fox", h_bar, probs, W, W_prime, word_to_idx
)
loss = compute_loss("fox", probs, word_to_idx)ConsoleOut [14]Expand
CBOW Backward Pass: -------------------------------------------------- Loss (before gradient step): 2.0879 Prediction error vector (e = p - y): the : +0.1255 quick : +0.1249 brown : +0.1244 fox : -0.8761 <<< target jumps : +0.1248 over : +0.1250 lazy : +0.1257 dog : +0.1258 Gradient norms: dW norm: 0.07822 dW_prime norm: 0.06071
The prediction error is largest for "fox" (the target), where the model needs to increase probability the most. The gradients flow back through the output matrix and distribute equally across all context word embeddings. Notice that the target word "fox" has a negative error (the model should increase its probability), while all other words have small positive errors (the model should decrease their probabilities slightly).
Training CBOW with Gradient Descent
With the forward pass and gradients in hand, we can train CBOW using stochastic gradient descent. Each training step processes one (context, target) pair, computes the loss, backpropagates, and updates both weight matrices.
Stochastic gradient descent updates parameters after each individual training example rather than accumulating gradients over the entire dataset. For word embedding training, this is essential: the dataset is too large to load into memory, and the stochastic updates provide a stream of small gradient signals that collectively shape the embedding space. Each update is noisy (a single training example is an imperfect estimate of the true gradient), but across millions of examples the noise averages out and the embeddings converge.
The learning rate controls the magnitude of each update. Starting with a relatively high learning rate and decaying it as training progresses is standard practice in Word2Vec. Early in training, large updates quickly move embeddings from their random initializations into meaningful regions. Later in training, smaller updates fine-tune the embeddings without overshooting.
PythonIn [15]Expand
def train_cbow(
corpus_words,
vocab,
word_to_idx,
embedding_dim=8,
window_size=2,
learning_rate=0.01,
epochs=200,
):
"""
Train a CBOW model using stochastic gradient descent.
Returns the trained embedding matrix.
"""
V = len(vocab)
np.random.seed(42)
W_emb = np.random.randn(V, embedding_dim) * 0.01
W_out = np.random.randn(embedding_dim, V) * 0.01
# Generate all training pairs
pairs = generate_cbow_pairs(corpus_words, window_size)
losses = []
for epoch in range(epochs):
epoch_loss = 0.0
np.random.shuffle(pairs)
for context_words, target_word in pairs:
# Forward pass
h_bar, probs = cbow_forward(
context_words, W_emb, W_out, word_to_idx
)
loss = compute_loss(target_word, probs, word_to_idx)
epoch_loss += loss
# Backward pass
dW, dW_prime = cbow_backward(
context_words,
target_word,
h_bar,
probs,
W_emb,
W_out,
word_to_idx,
)
# Update parameters
W_emb -= learning_rate * dW
W_out -= learning_rate * dW_prime
losses.append(epoch_loss / len(pairs))
return W_emb, W_out, losses
# Training corpus (repeated for more data)
corpus = (
" ".join(
[
"the quick brown fox jumps over the lazy dog",
"the dog chased the fox",
"the fox and the dog played",
"quick fox jumped high",
"lazy dog slept all day",
"the brown dog and the quick fox",
]
)
* 3
)
corpus_words = corpus.lower().split()
# Build vocabulary from corpus
full_vocab = sorted(set(corpus_words))
full_word_to_idx = {w: i for i, w in enumerate(full_vocab)}
# Train the model
W_trained, W_out_trained, train_losses = train_cbow(
corpus_words,
full_vocab,
full_word_to_idx,
embedding_dim=8,
window_size=2,
learning_rate=0.05,
epochs=300,
)ConsoleOut [16]Expand
Training complete. Vocabulary size: 17 Training pairs: 106 Epochs: 300 Final avg loss: 0.0536 Initial avg loss: 2.8332 Loss reduction: 98.1%
VisualizationOut [17]Expand

The loss curve shows the typical pattern for Word2Vec training: rapid early improvement as the model captures the most common patterns, followed by slower refinement. On small corpora the final loss is still relatively high because the model cannot distinguish many fine-grained contexts with limited data. Training on a real corpus of millions or billions of tokens would continue pushing the loss lower, and the embeddings would become much more precisely positioned.
Initialization and Stability
A practical note on initialization: CBOW parameters should be initialized with small random values, not zeros. If all embeddings start at zero, the gradient for every context word is identical (they all produce the same averaged vector), and the symmetry is never broken. Small random values ensure that different words receive different initial gradients, allowing them to diverge into distinct regions of the embedding space.
The output matrix is typically initialized with the same small-random scheme. Some implementations initialize to zeros, relying on the non-zero embeddings to break symmetry through the backward pass. Both approaches work in practice, though small random initialization for both matrices tends to converge slightly faster in our experience.
The CBOW Objective vs. Skip-gram Objective
One of the most important differences between CBOW and Skip-gram is not architectural but statistical: what they count as one "training example." This difference ripples through training speed, embedding quality, and practical applicability, making it one of the key decisions in the Word2Vec framework.
Skip-gram Generates More Examples
Skip-gram treats each (center, context) word pair as an independent training example. For a window of size 2, each center word generates up to 4 separate training pairs (2 from the left, 2 from the right). For a corpus of tokens, Skip-gram generates approximately training pairs where is the window size.
CBOW instead creates one training example for each center word position, using all context words simultaneously. This means CBOW generates roughly training examples from the same corpus, while Skip-gram generates approximately .
For a window size of 2, Skip-gram generates roughly 4 times as many training examples as CBOW from the same corpus. For window size 5, the ratio grows to 10. This multiplicative factor has enormous practical consequences.
Consequence: Training Speed
This difference directly affects training speed. Skip-gram performs more gradient updates per sentence because each context position generates a separate update. CBOW performs fewer updates but each update incorporates more information (averaging context embeddings).
In practice, CBOW trains faster than Skip-gram on the same corpus because:
- CBOW processes fewer training examples (roughly times fewer)
- Each CBOW forward pass is cheap since averaging is a simple mean operation
- The gradient update for CBOW writes to fewer parameters per step (only the context words present in the window)
For large vocabularies, this speed advantage can be substantial. On a 100-billion-word corpus, the difference between 4 hours and 20 hours of training is significant. This is why CBOW became the standard choice for applications where embeddings need to be retrained frequently or where computational resources are limited.
However, faster training does not mean better embeddings. The Skip-gram model's additional training examples provide richer gradient signal, particularly for rare words. The quality difference between the two models depends on the vocabulary distribution and the specific downstream task.
Consequence: Handling Rare vs. Frequent Words
The averaging step affects how well each model represents different types of words in a subtle but important way.
Skip-gram trains each word independently as a center word, allowing the model to specialize its representation based on the word's unique distribution of contexts. A rare word that only appears 10 times still has its own dedicated training signal from those 10 examples. Each appearance generates 4 separate gradient updates (for window size 2), yielding 40 total updates for the rare word's embedding. These updates may be noisy, but they are specifically targeted at the rare word's actual co-occurrence patterns.
CBOW averages context embeddings before making a prediction. This averaging provides a regularizing effect: the signal for any one context word is diluted by the other context words. For frequent words (which appear in many different contexts and receive many gradient updates), this regularization is beneficial, smoothing out noise. For rare words (which appear infrequently and may have atypical contexts), the averaging can obscure the signal.
Consider a concrete example. Suppose "serendipity" appears 5 times in the training corpus. In CBOW, each appearance contributes one training example where "serendipity" is a context word averaging with 3 other context words. The gradient update for "serendipity" is thus diluted by a factor of 4 (for window size 2). Skip-gram, by contrast, would generate 5 training examples where "serendipity" is the center word and each surrounding word is the target, plus additional examples where "serendipity" appears as a context word. The signal for "serendipity" is stronger and less diluted.
This leads to a practical guideline:
- Use CBOW when your application needs fast training, handles primarily frequent words, or works with a large corpus where training speed matters.
- Use Skip-gram when your application requires good representations for rare or specialized vocabulary.
VisualizationOut [18]Expand

Worked Example: Full Training Step
Let us trace through a complete CBOW training step for the example sentence, confirming that all the pieces fit together.
Consider the sentence "The quick brown fox jumps over the lazy dog" with target word "fox" and context ["quick", "brown", "jumps", "over"] (window size 2). We will perform one complete gradient descent step and verify the loss decreases. This walkthrough connects the mathematical derivations above to the concrete numerical operations that happen in every training iteration.
The procedure has four stages. First, the forward pass computes from the context embeddings and then computes the probability distribution over the vocabulary using the output matrix and softmax. Second, the loss measures how far the current distribution is from the ideal (all probability on "fox"). Third, the backward pass computes gradients for all parameters. Fourth, the update subtracts the scaled gradients from the current parameters.
PythonIn [19]Expand
# Trace a single training step end-to-end
np.random.seed(7)
V_small = len(small_vocab)
d_small = 3 # Tiny embedding for visibility
W_step = np.random.randn(V_small, d_small) * 0.3
W_prime_step = np.random.randn(d_small, V_small) * 0.3
lr = 0.1
target = "fox"
context_ex = ["quick", "brown", "jumps", "over"]
# --- Before update ---
h_before, probs_before = cbow_forward(
context_ex, W_step, W_prime_step, word_to_idx
)
loss_before = compute_loss(target, probs_before, word_to_idx)
# Backward pass
dW_step, dWp_step = cbow_backward(
context_ex,
target,
h_before,
probs_before,
W_step,
W_prime_step,
word_to_idx,
)
# Update
W_step -= lr * dW_step
W_prime_step -= lr * dWp_step
# --- After update ---
h_after, probs_after = cbow_forward(
context_ex, W_step, W_prime_step, word_to_idx
)
loss_after = compute_loss(target, probs_after, word_to_idx)ConsoleOut [20]Expand
Full CBOW Training Step ================================================== Context: ['quick', 'brown', 'jumps', 'over'] Target: 'fox' Word P before P after Change --------------------------------------------- the 0.1355 0.1352 -0.0003 quick 0.1209 0.1214 +0.0005 brown 0.1383 0.1382 -0.0000 fox 0.1283 0.1299 +0.0016 <-- target jumps 0.1242 0.1238 -0.0004 over 0.1102 0.1104 +0.0002 lazy 0.1139 0.1138 -0.0001 dog 0.1287 0.1273 -0.0013 Loss before: 2.0535 Loss after: 2.0409 Loss decrease: 0.0126
After a single gradient step, the probability assigned to "fox" increases while probabilities for incorrect words decrease. The loss decreases, confirming that the gradient step moved the parameters in the right direction. This is the fundamental loop of CBOW training: observe the prediction error, compute gradients that would reduce the error, and take a small step in that direction. Repeat this across millions of training examples, and the embeddings gradually organize themselves so that words with similar distributions end up in similar regions of the space.
Notice that the improvement from a single step is modest: the probability of "fox" might increase from 0.10 to 0.12, while the loss decreases by a fraction of a nit. This is expected and desirable. Large updates would overfit to individual training examples. The embedding space is shaped by the aggregate of millions of small updates, each nudging the representations slightly toward the global structure encoded in the corpus.
Visualizing Trained Embeddings
After training, the embedding matrix contains meaningful word representations. Even on a small corpus, words that appear in similar contexts cluster together in embedding space. We can project the 8-dimensional embeddings down to 2D using PCA to visualize these relationships.
Principal Component Analysis (PCA) finds the directions of maximum variance in the embedding space and projects the data onto them. The first principal component captures the most variance, and the second captures the next most. A 2D PCA projection preserves as much structure as possible while making the data visualizable. The resulting scatter plot does not show the full geometry (much information is lost in reducing 8 dimensions to 2), but it reveals the broad clustering structure that the model has learned.
VisualizationOut [21]Expand

Even with this small corpus and brief training, words with similar grammatical roles and contextual distributions tend to cluster together. The nouns "fox" and "dog" should occupy nearby positions because both appear as subjects and objects in similar sentence structures. Adjectives like "quick," "brown," and "lazy" should cluster because they all appear before nouns. With a larger corpus and more training, these clusters become much tighter and semantically meaningful. The embeddings capture the statistical regularities of the text, placing words in positions that reflect their distributional behavior.
Key Parameters
The behavior of CBOW is controlled by several important hyperparameters. Choosing these correctly has a significant impact on the quality of the resulting embeddings.
Embedding dimension (): The dimensionality of the word vectors. Common values range from 50 to 300. Higher dimensions can capture more nuance but require more training data and memory. On a small corpus, high-dimensional embeddings tend to overfit: the model has more degrees of freedom than the data can constrain, and the extra dimensions fill with noise. On large corpora (billions of words), embeddings of 300 dimensions consistently outperform smaller sizes. The sweet spot depends on corpus size and downstream task requirements.
Window size (): The number of words on each side of the center to include as context. Larger windows capture broader topical context because words that appear in the same general topic area will co-occur within the wider window even if they are not directly adjacent. Smaller windows capture tighter syntactic context: adjectives and their nouns, verbs and their subjects. For tasks that depend on syntactic similarity (parsing, part-of-speech tagging), smaller windows (1 or 2) tend to work better. For tasks that depend on semantic similarity (topic modeling, information retrieval), larger windows (5 or more) tend to be superior.
Learning rate: The step size for gradient descent updates. Too large causes divergence; too small causes slow convergence. Typically initialized around 0.025 and decayed during training. The original Word2Vec implementation uses a linear decay schedule: starting at 0.025 and linearly decreasing to 0.0001 as training progresses. This adaptive schedule allows aggressive initial learning while stabilizing the final embeddings.
Vocabulary size (): Determined by the corpus. Larger vocabularies require more memory for the output matrix (size ) and make the softmax sum more expensive to compute. With and , the output matrix requires roughly 80 MB of memory. For practical applications with full softmax, vocabulary sizes above 100,000 become computationally prohibitive, which motivates the approximations covered in the next chapter.
Minimum count: Words appearing fewer than this many times in the corpus are excluded from the vocabulary. Setting min_count to 5 is common, filtering out rare words that have insufficient data to train well. These rare words receive so few gradient updates that their embeddings remain neededly random. This provides no useful information. Excluding them reduces vocabulary size, speeds up training, and avoids including low-quality embeddings in later applications.
VisualizationOut [22]Expand

The hyperparameter sensitivity plot illustrates how window size and embedding dimension affect the training trajectory. Wider windows and larger embedding dimensions both reduce the final training loss on this corpus, but they also slow each training epoch because wider windows produce more context combinations and larger matrices require more computation. In production, these tradeoffs must be balanced against available compute and the requirements of the downstream task.
Subsampling Frequent Words
The original Word2Vec paper introduced an important technique called subsampling (or frequency-based downsampling) that significantly improves embedding quality. The idea is simple: very frequent words like "the," "is," and "a" appear in almost every context and provide little useful signal about the meaning of their neighbors. Instead of using every occurrence of these words in training, the model randomly discards them with a probability that increases with word frequency.
The subsampling probability for word with frequency in the corpus is:
where:
- : the fraction of the corpus occupied by word (frequency divided by total tokens)
- : a threshold hyperparameter, typically set to
Words with frequency below are never discarded. Words with frequency much greater than are discarded with high probability. The threshold means that any word making up more than 1 in 100,000 tokens is a candidate for subsampling. In a typical English corpus, this catches words like "the" (which might appear in 5% of all positions) very aggressively, while leaving most content words untouched.
Subsampling has two distinct benefits. First, it speeds up training by reducing the number of training examples from very common words that provide little discriminative signal. Second, it improves embedding quality: when "the" is discarded from a context window, the remaining context words form more semantically meaningful co-occurrences. Instead of "fox" co-occurring with "the" (a useless relationship), "fox" is more likely to co-occur with semantically rich words that happen to appear within the same window after "the" is removed.
Dynamic Window Sizes
Another practical technique from the original implementation is dynamic window sizing. Rather than always using the maximum window size , the model samples the actual window size uniformly from for each training example.
This produces an interesting statistical effect. Words immediately adjacent to the center word are included in every window size from 1 to , while words at the maximum distance are only included when the window size is sampled as . Words at distance are included with probability .
The result is a soft weighting: nearby words contribute more frequently to training examples than distant words, without requiring any explicit weighting scheme. This implicitly encodes the linguistic intuition that a word's immediate neighbors carry more information about its meaning than words several positions away. Dynamic window sizing is enabled by default in the original Word2Vec implementation and is one reason the original CBOW embeddings outperformed naive implementations that used fixed window sizes.
Limitations and Impact
The Full Softmax Bottleneck
The full softmax in CBOW sums over every vocabulary word in the denominator during training. With a vocabulary of 100,000 or more words, this is computationally expensive: every gradient step must compute dot products and update many rows of . This is the primary bottleneck for training on large corpora.
To put the scale in perspective, consider training on the Google News corpus (100 billion words) with a vocabulary of 3 million words and embedding dimension 300. Each forward pass requires 3 million dot products of 300-dimensional vectors. Each backward pass requires updating 3 million column vectors of the output matrix. With billions of training examples, this full-softmax approach is computationally infeasible.
In practice, this is addressed through two approximations: negative sampling and hierarchical softmax. Negative sampling replaces the sum over all words with a sum over a small number of randomly selected "negative" words, reducing the per-step cost from to where is typically 5 to 20. Hierarchical softmax represents the vocabulary as a binary tree and computes probabilities as products along tree paths, also reducing per-step cost to . These approximations are covered in detail in the next chapter.
The Positional Blindness Problem
The averaging operation, while elegant, discards positional information entirely. "The dog bit the man" and "The man bit the dog" would produce identical context representations for the center word "bit" because the context words are the same, just in different positions. Similarly, "Alice knows Bob" and "Bob knows Alice" have the same bag of context words but entirely different meanings from the perspective of the subject and object.
This means CBOW cannot learn different representations for semantically distinct orderings. A word in subject position cannot be distinguished from the same word in object position. For many NLP tasks, this positional blindness is an acceptable tradeoff for the speed advantage. But for tasks that require understanding syntactic structure or argument roles, positional information is essential.
More expressive architectures overcome this limitation. LSTMs preserve word order by processing sequences left-to-right and right-to-left, building up a state that depends on the entire preceding context. Transformers use positional encodings to give the model explicit access to word positions, and attention mechanisms allow the model to weigh different context words differently based on both their content and position. These models produce contextual embeddings where the same word receives different representations in different sentence positions, a capability CBOW fundamentally lacks.
Static Embeddings and Polysemy
CBOW, like all Word2Vec variants, produces static embeddings: each word type (distinct word form) receives exactly one vector in the embedding matrix. This is problematic for polysemous words, words with multiple distinct meanings.
The word "bank" can refer to a financial institution, the side of a river, or the act of tilting an aircraft. In CBOW's embedding space, all these senses are collapsed into a single vector that represents some kind of average over all the contexts in which "bank" appears. This averaged vector may not represent any single meaning well. In downstream tasks like word sense disambiguation or named entity recognition, the conflated embedding provides mixed signals.
This limitation motivated the development of contextualized word representations, where the embedding of a word depends on the sentence context in which it appears. ELMo (Embeddings from Language Models) was an early approach that ran a language model over the full sentence and used hidden states as contextualized embeddings. BERT and GPT later demonstrated that transformer-based language models produce rich contextual representations that capture polysemy and context-sensitivity in ways that static embeddings cannot. We will cover these models in depth in later chapters.
Impact and Legacy
Despite these limitations, CBOW's contribution to NLP cannot be overstated. The Word2Vec paper demonstrated that simple neural models could produce word embeddings that captured analogical relationships (king - man + woman = queen) in a way that no prior method had achieved. This went beyond a qualitative demonstration: the embeddings quantifiably outperformed prior approaches on a suite of analogy and similarity tasks.
CBOW's speed made it practical to train on the web-scale corpora needed to learn these relationships. Earlier neural word embedding methods (like the language models of Bengio et al. from 2003) were correct in principle but too slow to scale to the data volumes required for high-quality embeddings. CBOW's linear architecture and the approximations developed for it (negative sampling, subsampling) made billion-word training feasible.
The embeddings produced by Word2Vec became standard features for almost every NLP task from 2013 to 2018. Sentiment analysis, named entity recognition, machine translation, question answering, and text classification all benefited from pre-trained Word2Vec embeddings as starting points. Researchers no longer needed to train embeddings from scratch for every task: the pre-trained vectors captured general linguistic knowledge that could be fine-tuned for specific applications. This transfer learning paradigm, initiated in earnest by Word2Vec, became the dominant approach in NLP and eventually gave rise to the massive pre-trained language models that define the field today.
GloVe, FastText, and dependency-based embeddings all refined the ideas introduced by CBOW and Skip-gram. GloVe reframed the objective as matrix factorization of co-occurrence counts. This provides a stronger theoretical foundation. FastText extended the model to use subword information, capturing morphological structure that pure word-level models miss. Dependency-based embeddings used syntactic dependency paths rather than linear windows, producing embeddings more sensitive to grammatical structure. Each of these methods acknowledged its debt to Word2Vec while addressing one of its specific weaknesses.
Summary
The CBOW model learns word embeddings by training a neural network to predict a center word from its surrounding context. The key concepts are:
- Context averaging: CBOW averages the embedding vectors of all context words into a single representation before making a prediction. This is what makes CBOW a "bag of words" model, trading positional information for computational efficiency and stability.
- Objective function: The model maximizes the log-probability of the correct center word given the context, equivalent to minimizing the negative log-likelihood of the training data. The full softmax over all vocabulary words is expensive and motivates the approximations in the next chapter.
- Gradient derivation: Gradients flow from the output layer back through the averaging operation, distributing equally to all context word embeddings with a factor of . This shared gradient creates the regularizing effect that benefits frequent words.
- Training speed: CBOW generates fewer training examples per word than Skip-gram (one per center position versus per center position), making it faster to train on large corpora but providing weaker signal for rare words.
- Frequent vs. rare words: CBOW's averaging helps frequent words by smoothing noisy contexts, but can disadvantage rare words by diluting their limited training signal. Skip-gram is preferred when rare vocabulary quality matters.
- Practical techniques: Subsampling frequent words and using dynamic window sizes significantly improve both training speed and embedding quality beyond the basic algorithm described here.
- Static embeddings: CBOW produces one vector per word type, which cannot capture polysemy or context-sensitivity. Contextual embedding methods like BERT overcome this fundamental limitation at much greater computational cost.
The next chapter covers negative sampling, an approximation technique that eliminates the expensive full softmax computation and enables Word2Vec to train on truly large-scale corpora.
Quiz
Ready to test your understanding? Take this quick quiz to reinforce what you've learned about the CBOW model and Word2Vec word embeddings.
CBOW Model Quiz
Join the discussionAsk a question or share what you learned.
Keep exploring
Reference
Citation details
Cite or share this article.


Comments
Add to the conversation
A question, a correction, or an example from your own work.
No comments yet. Be the first to share your thoughts!