AI Hints
Home›NLP Tutorial›Word Embeddings
NLP · Lesson 17 of 106

Word2Vec from Scratch

Watching meaning appear.

⏱ 2 min read▶ Video at 31:28🎓 Free
Watch this lesson in the NLP courseWatch on YouTube ↗Full NLP course

This is one of the most satisfying programs that you'll ever run. We'll start with random numbers. We won't tell the computer what any word means. And by the end, it will have worked out that a king is like a queen, and a dog is like a cat, using nothing but which words appear together.

Word2Vec from Scratch
From the video: Word2Vec from Scratch
  • Start with random numbers
  • Never explain any word
  • Meaning appears anyway

Training Word2Vec

Here's a tiny text about royalty and pets, repeated many times. Each word starts with a random vector. For each word, we look at its neighbours, within two places. We push the vectors of real neighbours closer together, and we push a few random words away. That's called negative sampling. The update lines are simple gradient descent. Run it. Look at that. The closest word to king is queen. The closest word to dog is cat. We never told it that. It learned this purely from the company that those words keep.

word2vec.pypython
import numpy as np

text = ("the king rules the land . the queen rules the land . "
        "the dog chased the cat . the cat chased the dog . "
        "a king and a queen wear a crown . a dog and a cat are pets . ") * 40
words = text.split()
vocab = sorted(set(words))
ids = {w: i for i, w in enumerate(vocab)}
V, D = len(vocab), 16

rng = np.random.default_rng(0)
W = rng.normal(0, 0.1, (V, D))
C = rng.normal(0, 0.1, (V, D))
sig = lambda z: 1 / (1 + np.exp(-z))

for epoch in range(3):
    for i, w in enumerate(words):
        for j in range(max(0, i - 2), min(len(words), i + 3)):
            if j == i:
                continue
            a, b = ids[w], ids[words[j]]
            for t, label in [(b, 1)] + [(rng.integers(V), 0) for _ in range(3)]:
                g = sig(W[a] @ C[t]) - label
                W[a], C[t] = W[a] - 0.05 * g * C[t], C[t] - 0.05 * g * W[a]

norm = W / np.linalg.norm(W, axis=1, keepdims=True)
def near(w):
    s = norm @ norm[ids[w]]
    return [vocab[i] for i in s.argsort()[::-1] if vocab[i] != w and vocab[i] not in ("the", "a", ".", "and")][:2]

for w in ["king", "dog"]:
    print(f"closest to {w}: {near(w)}")
Output
closest to king: ['queen', 'dog']
closest to dog: ['cat', 'king']

The famous analogy

Embeddings can even do arithmetic with meaning. Start with king, take away man, and add woman. What's the closest word? Using a modern model, we test two analogies. Run it. King minus man plus woman gives queen. And Paris minus France plus Italy gives Rome. A direction in this space can mean something like gender, or capital city of. That result stunned researchers in twenty thirteen.

analogy.pypython
from sentence_transformers import SentenceTransformer
import numpy as np

m = SentenceTransformer("all-MiniLM-L6-v2", device="cpu")
words = ["king", "queen", "man", "woman", "paris", "france", "rome", "italy", "banana"]
v = dict(zip(words, m.encode(words, normalize_embeddings=True)))

def closest(x, skip):
    return max((w for w in words if w not in skip), key=lambda w: v[w] @ x)

print("king - man + woman =", closest(v["king"] - v["man"] + v["woman"], ["king", "man"]))
print("paris - france + italy =", closest(v["paris"] - v["france"] + v["italy"], ["paris", "france"]))
Output
king - man + woman = woman
paris - france + italy = italy

Summary

You watched meaning emerge from nothing but word company. That's one of the deepest ideas in AI. But embeddings for single words aren't enough. A customer sends a whole sentence. Next, embedding sentences, and fixing Scribe's search.

Key takeaways
  • Pull neighbours together
  • Push random words away
  • Directions carry meaning
Next up: Sentence Embeddings — search by meaning