Skip-gram with Negative Sampling (the algorithm behind Word2Vec) implemented with nothing but NumPy: hand-derived gradients verified numerically, subsampling, the unigram^0.75 noise distribution, dynamic windows, and a vectorized training loop. Trained on 3 million words of Wikipedia, it matches gensim's optimized implementation on standard benchmarks.
Embeddings learned from scratch: numbers, months, countries, family words and computing terms form clear clusters.
Same corpus, same hyperparameters, same evaluation code:
| Model | WordSim-353 (Spearman) | Google analogies | Training time |
|---|---|---|---|
| NumPy (from scratch) | 0.364 | 3.2% | 146 s |
| gensim (optimized C) | 0.365 | 3.1% | 14 s |
The from-scratch model reaches the same quality as gensim. gensim is about 10× faster, thanks to compiled C code and multithreading.
Nearest neighbors learned from scratch:
| Word | Neighbors |
|---|---|
| computer | microsoft, software, amiga, macintosh |
| january | february, december, june, october |
| war | civil, battle, vietnam, soviet, troops |
| river | lake, coast, bay, valley, mountains |
| one | seven, six, five, eight, three |
Each word has an input vector
This replaces a softmax over all 27,321 vocabulary words with 6 binary decisions per pair. The gradients:
Analytical gradients compared with finite differences:
| Gradient | Relative error |
|---|---|
| ∂L/∂v_c | 2.5 × 10⁻¹⁰ |
| ∂L/∂u_o | 2.1 × 10⁻¹⁰ |
| ∂L/∂u_neg | 5.2 × 10⁻¹⁰ |
| Component | Detail |
|---|---|
| Corpus | First 3M words of text8 (Wikipedia), vocabulary of 27,321 words (count ≥ 5) |
| Subsampling | Keep probability |
| Noise distribution | Unigram frequency raised to the 0.75 power |
| Dynamic window | Random window size from 1 to 5 per word |
| Training | Mini-batches of 1,024 pairs, 5 negatives each, learning rate decaying linearly from 0.025 |
| Updates |
np.add.at so repeated words in a batch accumulate correctly |
| Throughput | ~130,000 pairs/second on a single CPU core |
The loss starts exactly at the random-guessing value,
- Negative sampling is what makes Word2Vec practical: it turns an expensive V-way softmax into a handful of logistic regressions.
- Subsampling frequent words both speeds up training and improves vectors for rarer words.
- Gradient checking is essential when deriving gradients by hand.
- Analogies need far more data than neighbors. Both implementations reach only ~3% analogy accuracy on 3M words, while neighbors already look excellent. Analogies typically need 100M+ words.
- Silent truncation is a real trap: gensim ignores words beyond 10,000 in a single "sentence", so an unsegmented corpus must be chunked first.
Click the Open in Colab badge above and choose Runtime → Run all. text8 (31 MB) downloads automatically, and the full run takes about 4 minutes on a CPU.
pip install numpy pandas matplotlib scikit-learn gensimgensim is used only to download the corpus and for the benchmark comparison. The model itself is pure NumPy.
- CBOW from scratch
- Hierarchical softmax
- GloVe from co-occurrence counts
- A PyTorch version running on a GPU
Muhammad Mujtaba Khan Suri — CS @ UBIT, University of Karachi GitHub


