Roadmap / Language Modeling Basics

✓

MLP Language Model (Bengio 2003)

mediumlanguage-modelinglayers

The problem

A bigram table cannot look further back than one token, and a bigger table grows out of control. Bengio et al. (2003) replaced the table with a small neural network, an MLP (multilayer perceptron), that reads the last nn tokens and outputs one score (logit) for every token of the vocabulary. You will write its forward pass, its loss, and the function that cuts a text into training examples.

The forward pass

For a batch of BB contexts, ids of shape (B,n)(B, n) (each row holds nn token ids, oldest first):

  1. Embed. Look up each token in the embedding table CC of shape (V,D)(V, D). Result: shape (B,n,D)(B, n, D), one vector of DD numbers per token.
  2. Concatenate. Put the nn vectors of each context side by side, oldest first. Result: ee, shape (B,nD)(B, nD).
  3. Hidden layer. h=tanh⁡(eW1+b1)h = \tanh(e W_1 + b_1), with W1W_1 of shape (nD,H)(nD, H): HH numbers per context.
  4. Logits. z=hW2+b2z = h W_2 + b_2, with W2W_2 of shape (H,V)(H, V): one score per token of the vocabulary.

where W1W_1, b1b_1, W2W_2, b2b_2 are the weights and biases of the two layers.

Example. V=3V = 3 tokens, D=2D = 2, n=2n = 2, H=2H = 2, and a batch of B=2B = 2 contexts, ids = [[0, 2], [1, 0]].

  1. With CC = [[1, 0], [0, 1], [1, 1]], context 0 looks up rows 0 and 2: [[1, 0], [1, 1]]. Context 1 looks up rows 1 and 0: [[0, 1], [1, 0]].
  2. Concatenated: ee = [[1, 0, 1, 1], [0, 1, 1, 0]], shape (2,4)(2, 4). The first two numbers of each row belong to the oldest token.
  3. With W1=0.5⋅W_1 = 0.5 \cdot [[1, 0], [0, 1], [1, 0], [0, 1]] and b1=0b_1 = 0: eW1eW_1 = [[1, 0.5], [0.5, 0.5]], and h=tanh⁡h = \tanh of it ≈\approx [[0.762, 0.462], [0.462, 0.462]].
  4. With W2W_2 = [[1, 0, -1], [0, 1, 1]] and b2=0b_2 = 0: z≈z \approx [[0.762, 0.462, -0.299], [0.462, 0.462, 0]], shape (2,3)(2, 3).

The loss

The loss is the mean cross-entropy of the logits against targets, the right next token of each context (section 2): for each row, LSE(zb)−zb,yb\mathrm{LSE}(z_b) - z_{b, y_b}, averaged over the batch.

Example. With targets = [2, 1]: row 0 has loss about 1.7971.797 (the model gave token 2 the lowest score), row 1 about 0.9670.967, and the mean is about 1.3821.382.

Training examples from a text

To train, a long text is cut into windows: every run of nn consecutive tokens is a context, and the token right after it is its target.

Example. build_contexts([5, 6, 7, 8, 9], n=2) returns ids = [[5, 6], [6, 7], [7, 8]] and targets = [7, 8, 9]. A text of TT tokens gives T−nT - n examples.

What to implement

  • mlp_lm_logits(ids, params): params is a dict with keys "C", "W1", "b1", "W2", "b2". Return the (B,V)(B, V) logits.
  • mlp_lm_loss(ids, targets, params): the mean cross-entropy of the logits against targets (shape (B,)(B,)), computed stably.
  • build_contexts(tokens, n): return (ids, targets), where row tt of ids is tokens[t : t + n] and targets[t] is tokens[t + n], for every tt that fits.

Requirements

  • Concatenation order matters: the first DD columns of ee are the oldest token.
  • No Python loops in mlp_lm_logits and mlp_lm_loss. build_contexts may use a loop, or sliding_window_view from np.lib.stride_tricks.

Hints

  • Step 2 is a single reshape of the (B,n,D)(B, n, D) lookup to (B,nD)(B, nD): reshape reads the last axis fastest, so each token's DD numbers stay together, in order.
  • You already wrote every piece: the embedding lookup (section 4), a linear layer (section 1) and cross-entropy from logits (section 2).
solution.py
Python idle

Press Run (⌘/Ctrl + Enter) to test your code.