CSCE 471 Natural Language Processing · Spring 2027

Learning-Based Word Vectors: Training and Variants

Kuan-Hao Huang · Course Website

Note: This page was created with the help of AI as a supplementary reading. It may contain errors or simplifications. The lecture slides are the primary reference for this course; please refer to them first.

The skip-gram model of Word2Vec learns word vectors by predicting the words that appear around each word. Writing down that model is only half the story. We still have to find vectors that actually make good predictions, and do it fast enough to handle billions of words of text.

This reading is about training. We'll derive the gradient of the skip-gram loss, run stochastic gradient descent, and see why negative sampling is needed to make training practical. Then we'll look at three related methods that change what is predicted or what is learned from: CBOW, GloVe, and FastText.

After reading this, you should be able to derive the gradient of the skip-gram loss for one (center, context) pair · describe the SGD training loop · explain why negative sampling is needed and what it computes · compare skip-gram with CBOW, GloVe, and FastText.

1Recap: the skip-gram model

Training data. Slide through the corpus. At each position $t$, the word $w_t$ is the center word, and every word within $m$ positions of it is a context word. Each (center, context) pair is one training example:

… problems turning into banking crises as …
(into, problems)(into, turning)(into, banking)(into, crises)
… problems turning into banking crises as …
(banking, turning)(banking, into)(banking, crises)(banking, as)
Figure 1. Training pairs from two positions, with window size $m=2$.

Model. Every word $w$ has two vectors: $\mathbf u_w\in\mathbb R^d$ when it is the center word, and $\mathbf v_w\in\mathbb R^d$ when it is a context word. The probability of a context word given a center word is a softmax over dot products:

$$P(w_{t+j}\mid w_t;\theta)=\frac{\exp(\mathbf u_{w_t}\cdot\mathbf v_{w_{t+j}})}{\sum_{k\in V}\exp(\mathbf u_{w_t}\cdot\mathbf v_k)}$$

Objective. The parameters $\theta=\{\mathbf u_k\},\{\mathbf v_k\}$ are chosen to minimize the average negative log-likelihood of all observed pairs in a corpus of $T$ words:

$$J(\theta)=-\frac{1}{T}\sum_{t=1}^{T}\sum_{\substack{-m\le j\le m\\ j\neq0}}\log P(w_{t+j}\mid w_t;\theta)$$

Throughout this reading, $\log$ is the natural log.

2Training with gradient descent

Stochastic gradient descent

We can't solve $\min_\theta J(\theta)$ in closed form, but we can improve $\theta$ step by step. The gradient $\nabla_\theta J(\theta)$ points in the direction in which $J$ increases fastest, so we move a little in the opposite direction:

$$\theta\;\leftarrow\;\theta-\eta\,\nabla_\theta J(\theta)$$

The learning rate $\eta$ controls the step size. Too small and training crawls; too large and the updates overshoot. Computing the full gradient needs a pass over the entire corpus, which is far too slow for one step. Stochastic gradient descent (SGD) instead estimates the gradient from a single training pair (or a small batch of pairs) and updates right away. Each estimate is noisy, but there are many of them and they are cheap.

The gradient for one pair

Since $J(\theta)$ is a sum of terms $-\log P(w_{t+j}\mid w_t;\theta)$, its gradient is the sum of the gradients of the terms. So it is enough to work out the gradient for one pair. Following the slides, write $o$ for the center word and $c$ for the context word, and let

$$y=-\log P(c\mid o)=-\log\frac{\exp(\mathbf u_o\cdot\mathbf v_c)}{\sum_{k\in V}\exp(\mathbf u_o\cdot\mathbf v_k)}=-\,\mathbf u_o\cdot\mathbf v_c+\log\sum_{k\in V}\exp(\mathbf u_o\cdot\mathbf v_k)$$
Notation warning. The slides use $o$ for the center word and $c$ for the context word. Some other sources, including Stanford's CS224n, use the letters the other way around ($c$ for center, $o$ for “outside”). The math is identical; only the names change.

Differentiate with respect to the center vector $\mathbf u_o$. The first term gives $-\mathbf v_c$. For the second, use $\frac{d}{dx}\log x=\frac1x$ and $\frac{d}{dx}e^x=e^x$ (the chain rule):

$$\frac{\partial y}{\partial\mathbf u_o}=-\mathbf v_c+\frac{\sum_{k\in V}\exp(\mathbf u_o\cdot\mathbf v_k)\,\mathbf v_k}{\sum_{k'\in V}\exp(\mathbf u_o\cdot\mathbf v_{k'})}=-\mathbf v_c+\sum_{k\in V}P(k\mid o)\,\mathbf v_k$$

This has a lovely reading. $\mathbf v_c$ is the context vector we observed, and $\sum_k P(k\mid o)\,\mathbf v_k$ is the context vector the model expected, the average of all context vectors weighted by the model's current probabilities. The gradient is “expected minus observed”, so the update $\mathbf u_o\leftarrow\mathbf u_o-\eta\,\partial y/\partial\mathbf u_o$ pulls $\mathbf u_o$ toward the observed context and away from what the model currently predicts. When the model already puts all its probability on $c$, the two cancel and nothing changes.

A similar calculation gives the gradient for each context vector:

$$\frac{\partial y}{\partial\mathbf v_k}=\bigl(P(k\mid o)-\mathbb 1[k=c]\bigr)\,\mathbf u_o$$

For the true context word ($k=c$), the coefficient $P(c\mid o)-1$ is negative, so $\mathbf v_c$ moves toward $\mathbf u_o$. For every other word, the coefficient $P(k\mid o)$ is positive, so $\mathbf v_k$ is pushed away from $\mathbf u_o$, more strongly the more probability the model wrongly gave it.

Putting it together

  1. Randomly initialize all $\mathbf u_i$ and $\mathbf v_i$ to small values.
  2. Walk through the corpus and collect (center, context) pairs $(o,c)$ as in Figure 1.
  3. For each pair, update $\mathbf u_o\leftarrow\mathbf u_o-\eta\,\dfrac{\partial y}{\partial\mathbf u_o}$ and $\mathbf v_k\leftarrow\mathbf v_k-\eta\,\dfrac{\partial y}{\partial\mathbf v_k}$ for all $k\in V$.
  4. Repeat for several passes (epochs) over the data.

The figure below runs exactly this algorithm, live in your browser, on a tiny corpus of 120 sentences such as “i bake the apple sweet” and “we install the laptop run”. To make the vectors directly plottable, it uses $d=2$. The plot shows each word's center vector $\mathbf u_w$.

learning rate η = 0.10
Epoch 0 · average loss –
Click a word in the plot to see its predicted context distribution:
Figure 2 (interactive). Skip-gram trained with SGD and a full softmax ($m=2$, $|V|=19$, $d=2$). Food nouns, food verbs/adjectives, tech nouns, tech verbs, and function words. The four food nouns never appear next to each other, yet they end up together because they share contexts.

Watch the loss curve. The loss drops quickly at first and then levels off. It can't reach 0, because the task is inherently uncertain: after bake, several food words are equally plausible. Then reset and try a large learning rate such as 0.5: the updates overshoot, and the loss stays high and bounces around instead of settling.

3Negative sampling

There is a serious efficiency problem hidden in the gradient. The softmax denominator sums over the entire vocabulary, so every single training pair requires computing $|V|$ dot products and updating all $|V|$ context vectors $\mathbf v_k$. With $|V|=100{,}000$ and billions of training pairs, that's hopeless.

Negative sampling (Mikolov et al., 2013b) replaces the $|V|$-way classification with a much cheaper question: “is this pair a real (center, context) pair, or a fake one?” For each real pair $(o,c)$, we draw $K$ random “negative” words $n_1,\dots,n_K$ (typically $K=5$ to $20$) that probably did not appear near $o$. The loss becomes

$$y=-\log\sigma(\mathbf u_o\cdot\mathbf v_c)-\sum_{j=1}^{K}\log\sigma(-\mathbf u_o\cdot\mathbf v_{n_j}),\qquad n_j\sim P_n(w),\qquad\sigma(x)=\frac{1}{1+e^{-x}}$$

The sigmoid $\sigma$ squashes a score into a probability between 0 and 1. The first term wants $\sigma(\mathbf u_o\cdot\mathbf v_c)$ close to 1: the real pair should get a high score. Since $\sigma(-x)=1-\sigma(x)$, the second term wants each $\sigma(\mathbf u_o\cdot\mathbf v_{n_j})$ close to 0: the fake pairs should get low scores. Now each update touches only $\mathbf u_o$ and $K+1$ context vectors, instead of all $|V|$ of them: with $|V|=100{,}000$ and $K=5$, that is over 10,000 times less work per pair.

Which words are sampled?

The negatives are drawn from the unigram distribution raised to the $3/4$ power, $P_n(w)\propto\text{count}(w)^{3/4}$. Compared with sampling in proportion to raw frequency, this samples very frequent words like the a bit less often, and rare words a bit more often.

Figure 3 (interactive). The noise distribution $P_n(w)$ for a toy vocabulary. Raising counts to the power 3/4 flattens the distribution without making it uniform.

Negative sampling is what made Word2Vec fast enough to train on billions of words on a single machine. The variant described here is called skip-gram with negative sampling (SGNS), and it is the version most people mean when they say “Word2Vec.”

4CBOW: predicting the center word

Word2Vec actually comes in two flavors, which flip the direction of the prediction:

Figure 4. The two Word2Vec architectures (after Mikolov et al., 2013). CBOW combines the context words to predict the center word. Skip-gram uses the center word to predict each context word separately.

Continuous bag of words (CBOW) averages the context vectors in the window into one vector, and uses it to predict the center word:

$$\bar{\mathbf v}_t=\frac{1}{2m}\sum_{\substack{-m\le j\le m\\ j\neq 0}}\mathbf v_{w_{t+j}},\qquad P(w_t\mid\{w_{t+j}\})=\frac{\exp(\mathbf u_{w_t}\cdot\bar{\mathbf v}_t)}{\sum_{k\in V}\exp(\mathbf u_k\cdot\bar{\mathbf v}_t)}$$

It's called a “bag of words” because averaging throws away the order of the context words. CBOW makes one prediction per position instead of $2m$, so it trains faster. Skip-gram makes a separate prediction for every context word, so rare words receive more training signal; it tends to give better vectors for infrequent words, which is why it's the more common choice.

5GloVe: back to global counts

Word2Vec learns from one window at a time and never looks at the corpus-wide counts directly. GloVe (“Global Vectors,” Pennington et al., 2014) takes a different approach: it first builds the global co-occurrence matrix $X$, where $X_{ij}$ counts how often word $j$ appears in the context of word $i$, and then learns vectors that fit those counts.

The key observation is that ratios of co-occurrence probabilities carry meaning. Compare the words ice and steam by checking how often various probe words $x$ appear near each:

x = solidx = gasx = waterx = fashion
P(x | ice)1.9 × 10⁻⁴6.6 × 10⁻⁵3.0 × 10⁻³1.7 × 10⁻⁵
P(x | steam)2.2 × 10⁻⁵7.8 × 10⁻⁴2.2 × 10⁻³1.8 × 10⁻⁵
ratio8.90.0851.360.96
Figure 5. Co-occurrence probabilities from a 6-billion-word corpus (Pennington et al., 2014). The individual probabilities are hard to interpret, but the ratio is large for words related to ice only, small for words related to steam only, and close to 1 for words related to both or neither.

GloVe wants this ratio to be captured by vector differences. Its model is log-bilinear, $\mathbf w_i\cdot\tilde{\mathbf w}_j\approx\log P(i\mid j)$, which implies

$$\mathbf w_x\cdot(\mathbf w_a-\mathbf w_b)=\log\frac{P(x\mid a)}{P(x\mid b)}$$

so the direction $\mathbf w_{\text{ice}}-\mathbf w_{\text{steam}}$ encodes the “solid vs. gas” contrast. In practice, GloVe minimizes a weighted least-squares loss over all nonzero cells of $X$:

$$J=\sum_{i,j=1}^{|V|}f(X_{ij})\left(\mathbf w_i^\top\tilde{\mathbf w}_j+b_i+\tilde b_j-\log X_{ij}\right)^2,\qquad f(x)=\begin{cases}(x/x_{\max})^{3/4}&x\lt x_{\max}\\1&\text{otherwise}\end{cases}$$

The biases $b_i$ and $\tilde b_j$ absorb how frequent each word is on its own. The weighting function $f$ (with $x_{\max}=100$ in the paper) gives rare, noisy co-occurrences little weight, and stops very frequent pairs like (the, of) from dominating. Pairs that never co-occur ($X_{ij}=0$) are skipped entirely, so training cost depends on the number of nonzero cells rather than the corpus size. That makes GloVe fast and able to scale to very large corpora.

6FastText: vectors for pieces of words

All the models so far treat each word as an atomic symbol. That causes two problems. A word never seen in training gets no vector at all, and related forms such as teach, teacher, and teaching are learned completely independently, even though they share most of their letters. This hurts most for rare words and for languages with rich morphology.

FastText (Bojanowski et al., 2017) keeps the skip-gram setup but represents each word by its character n-grams. The word is wrapped in boundary markers < and >, every character n-gram with $3\le n\le 6$ gets its own vector, and the word itself (with its markers) is added as one more unit. Type a word to see its pieces:

Word:
Figure 6 (interactive). The units FastText uses for one word. Note that the 3-gram her inside <where> is different from the whole word <her>, thanks to the boundary markers.

The score of a (center, context) pair is then the sum over all of the center word's units $\mathcal G(w)$:

$$\mathbf u_{w}\cdot\mathbf v_{c}\quad\text{is replaced by}\quad\sum_{g\in\mathcal G(w)}\mathbf u_g\cdot\mathbf v_{c}$$

Words that share n-grams share parameters, so teaching benefits from everything learned about teach and teacher. And a word never seen in training, like a typo or a new coinage, still gets a sensible vector by summing the vectors of its n-grams.

7Putting it all together

MethodLearns fromObjectiveNotable property
Skip-gramlocal windowspredict context from centergood for rare words
CBOWlocal windowspredict center from contextfaster to train
GloVeglobal countsweighted least squares on log countsscales to huge corpora
FastTextlocal windowsskip-gram over character n-gramsvectors for unseen words
Figure 7. The word-vector methods in this reading.

Skip-gram learns from local windows and GloVe from global counts, but the two are more closely related than they look. Levy & Goldberg (2014) showed that skip-gram with negative sampling implicitly factorizes a word–context matrix whose cells are $\text{PMI}(w,c)-\log K$, where $\text{PMI}(w,c)=\log\frac{P(w,c)}{P(w)\,P(c)}$ measures how much more often $w$ and $c$ co-occur than chance would predict. So both kinds of methods learn from the same underlying co-occurrence statistics.

You rarely need to train word vectors yourself. Pretrained vectors are freely available for all three methods: Word2Vec, GloVe, and FastText (which covers 157 languages). With the gensim library, loading and querying them takes a few lines:

import gensim.downloader as api

wv = api.load("glove-wiki-gigaword-100")        # 400k words, d = 100
wv.most_similar("banking", topn=5)               # nearest neighbors by cosine
wv.similarity("great", "excellent")                 # cosine similarity

# or train skip-gram with negative sampling on your own corpus
from gensim.models import Word2Vec
model = Word2Vec(sentences, vector_size=100, window=5, sg=1, negative=5)
Figure 8. Using pretrained vectors with gensim. sg=1 selects skip-gram (sg=0 is CBOW), and negative=5 sets $K$.

One limitation remains: every word gets a single vector, no matter which sense it's used in. bank in “river bank” and in “savings bank” share one vector. Fixing that requires vectors that depend on the surrounding sentence.

8Check your understanding

Exercise: one gradient step by hand

The vocabulary has three words $\{a,b,c\}$ and $d=2$. The center word has $\mathbf u_o=[1,0]$, and the context vectors are $\mathbf v_a=[2,0]$, $\mathbf v_b=[0,1]$, $\mathbf v_c=[-1,0]$. The observed context word is $a$. Compute (1) $P(a\mid o)$, $P(b\mid o)$, $P(c\mid o)$; (2) the loss $y=-\log P(a\mid o)$; (3) the gradient $\partial y/\partial\mathbf u_o$.

Scores: $\mathbf u_o\cdot\mathbf v_a=2$, $\mathbf u_o\cdot\mathbf v_b=0$, $\mathbf u_o\cdot\mathbf v_c=-1$.
Exponentials: $e^2\approx7.389$, $e^0=1$, $e^{-1}\approx0.368$, with sum $\approx8.757$.
(1) $P(a\mid o)\approx0.844$, $P(b\mid o)\approx0.114$, $P(c\mid o)\approx0.042$.
(2) $y=-\ln 0.844\approx0.170$.
(3) $\dfrac{\partial y}{\partial\mathbf u_o}=-\mathbf v_a+\sum_k P(k\mid o)\mathbf v_k=-[2,0]+0.844[2,0]+0.114[0,1]+0.042[-1,0]\approx[-0.354,\;0.114]$.
A gradient step $\mathbf u_o\leftarrow\mathbf u_o-\eta\,\partial y/\partial\mathbf u_o$ increases the first coordinate of $\mathbf u_o$, making it more aligned with $\mathbf v_a$, and decreases the second, moving it away from $\mathbf v_b$. Both changes raise $P(a\mid o)$.

Quick quiz

9Further reading