CBOW Model › Training CBOW with Gradient Descent

Michael BrenndoerferApril 3, 20254 min read
Language AI HandbookAll chapters
Part 4 of 67Word Embeddings
Chapter 20CBOW Model › Training CBOW with Gradient Descent
Reading section 7 of 15 · Training CBOW with Gradient Descent · 4 minSection 7 of 15 · 4 min

Book contents

Section 7 of 15 · 4 min read

CBOW Model › Training CBOW with Gradient DescentOne section · Sections in this chapter7 / 15 ↓
Reading tools Definitions & inline links

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

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.

In[15]:
Code
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,
)
Out[16]:
Console
Training complete.
  Vocabulary size: 17
  Training pairs: 106
  Epochs: 300
  Final avg loss: 0.0536
  Initial avg loss: 2.8332
  Loss reduction: 98.1%
Out[17]:
Visualization
Line plot of CBOW training loss decreasing over epochs from initial value to a lower plateau.
CBOW training loss over 300 epochs on a small corpus, measured in average negative log-likelihood per training example. The loss drops sharply in the first 50 epochs as the model learns the dominant co-occurrence patterns, then continues to decline more gradually as embeddings are refined. This characteristic rapid-then-gradual shape appears consistently in Word2Vec training across different corpus sizes.

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 W′\mathbf{W}' is typically initialized with the same small-random scheme. Some implementations initialize W′\mathbf{W}' to zeros, relying on the non-zero W\mathbf{W} 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.