Deep Learning and Transformers

Generative AI & LLM Fundamentals · AI and machine learning basics · 12 min read · Updated

From words to a prediction

  1. You type words. The model first breaks them into pieces called tokens.
  2. Each token becomes an embedding: a list of numbers that carries its meaning.
  3. Attention lets every word look at the other words and decide which ones matter for its meaning here.
  4. Many transformer layers repeat this, building a richer understanding each time.
  5. At the end the model gives a score to each possible next token and picks one.

Deep learning is the technology behind modern image recognition, speech recognition and large language models. The name sounds mysterious, but the core idea is small. A neural network is a chain of simple calculations, repeated in layers, with numbers that are adjusted by learning. In this lesson you will learn what a neuron is, why layers matter, how training works, and what a transformer is. Then you will train a real neural network in your browser and watch it learn a problem that a simple model cannot solve.

A neuron is a small calculator

An artificial neuron does three things. It takes several input numbers. It multiplies each input by a weight (a number that says how important that input is) and adds the results together with one extra number called the bias. Then it passes the total through an activation function, a small curve that squeezes the result into a useful range. A common choice is the sigmoid, which turns any number into a value between 0 and 1.

Example: a neuron that guesses whether a candidate is a good match takes two inputs, years of experience and number of relevant skills. If its weights are 0.4 and 0.8, a person with 3 years and 5 skills gives 0.4×3 + 0.8×5 = 5.2, plus a bias. A large positive total gives an output near 1, which we read as “likely a match”.

Why we need layers

One neuron can only draw a straight line between two groups. Some problems cannot be split by a straight line. The classic example is XOR, short for “exclusive or”: the answer is 1 when exactly one of two inputs is 1, but 0 when both are 0 or both are 1. In words: “one or the other, but not both”. If you plot the four cases, no single straight line puts the two 1 answers on one side and the two 0 answers on the other.

The fix is to add a hidden layer: a middle row of neurons between the inputs and the output. Each hidden neuron makes its own simple judgement. The output neuron then combines their judgements. Together they can draw curves and corners that one neuron cannot. A network with many hidden layers is called a deep network, which is where the name deep learning comes from. Each layer learns something a bit more abstract than the layer before: in an image model, the first layers find edges, the next find shapes, and the later ones find faces or objects.

How a network trains

A new network starts with random weights, so its answers are nonsense. Training repeats four steps many times:

  1. Forward pass. Give the network an example and let it calculate an answer.
  2. Compare. Measure the error between its answer and the right answer.
  3. Backward pass. Use a method called backpropagation to work out how much each weight contributed to the error. This is the gradient from lesson 2, found for every weight.
  4. Update. Move each weight a small step in the direction that reduces the error, using the learning rate.

One full pass over all training examples is called an epoch. After many epochs, the error falls and the network’s answers get close to the targets. You will see this in the lab: the error bars get shorter as training goes on.

Neural networks need a lot of calculation, which is why graphics processors (GPUs) became important. They do thousands of small multiplications at the same time.

From networks to transformers

Before 2017, most language models read text one word at a time, in order, and kept a small memory of what came before. They were slow to train and forgot the start of long sentences. In 2017 researchers at Google published a paper called “Attention Is All You Need” that introduced the transformer, the design behind almost every large language model today.

The key idea is attention. When the model processes a word, attention lets it look at all the other words in the text and decide which ones matter for understanding this one. Take the sentence: “The candidate sent the resume to the manager because she liked the profile.” To understand “she”, the model must connect it to the right person. Attention gives each word a way to ask, “Which other words should I pay attention to?” and to collect information from them, weighted by importance.

Attention has two big advantages. It can connect words that are far apart, so long sentences are not forgotten. And all the words can be processed at the same time instead of one by one, which fits GPUs very well and made it possible to train on enormous amounts of text. A transformer stacks many layers of attention, each followed by a small neural network. Lesson 7 explains this stack in more detail.

A final point to remember: a transformer does not store facts in a database. Its knowledge is spread across billions of weights, adjusted during training. That is why it can write fluent text and also why it can be wrong without warning.

Your task: train a neural network on XOR

You will write a small neural network from scratch, with no libraries, and train it on the XOR problem. A page lets you choose the number of hidden units and training rounds, press a button, and see the answers and the error over time.

What you will build

  • A network type with weights and biases for a hidden layer and one output unit.
  • Functions to create a network with seeded random weights, run a forward pass and make a prediction.
  • A train function that does backpropagation for every example and updates the weights.
  • A page showing the four XOR cases, the network’s answer for each, and a bar for the error at several checkpoints.

Steps

  1. Create lib/nn.ts. Define sigmoid and its slope. Define the Net type: sizes plus arrays w and b for weights and biases.
  2. Write createNet(hidden, seed) with small random starting weights, using makeRng from lesson 3.
  3. Write forward(net, input) that returns the output of every layer, so training can reuse them.
  4. Write train(net, data, epochs, rate). For each example run forward, compute the error at the output, push it back through the hidden layer, and update every weight and bias a little.
  5. Record the average error every so often and return those checkpoints.
  6. Define the XOR data: four inputs with targets 0, 1, 1 and 0.
  7. Build the page with two sliders and a Train button. Yield to the browser between rounds so the page does not freeze.

Hints

  • Try one hidden unit and notice that the network cannot learn XOR. Try four and it succeeds almost every time.
  • A very high learning rate can make the error jump around instead of falling.
  • Training from a different seed can end in a different final error. Neural networks depend on their starting point.
  • Print the output for each of the four cases. You want numbers close to 0 and 1, not exactly 0 and 1.

You know it works when a network with four hidden units reaches a final error below 0.01 and gives answers near 0, 1, 1 and 0, while a network with one hidden unit stays stuck.

Solution

The network, with the forward pass and the training loop:

import { makeRng } from './rng';

// A tiny neural network: layers of simple units. Each unit adds up its inputs times weights,
// adds a bias, and squashes the result between 0 and 1 (the sigmoid function).
export type Net = { sizes: number[]; w: number[][][]; b: number[][] }; // w[layer][unit][input]

const sigmoid = (x: number) => 1 / (1 + Math.exp(-x));

export function createNet(hidden: number, seed: number): Net {
  const rng = makeRng(seed);
  const sizes = hidden > 0 ? [2, hidden, 1] : [2, 1]; // hidden = 0 means no hidden layer at all
  const w: number[][][] = [];
  const b: number[][] = [];
  for (let l = 1; l < sizes.length; l++) {
    w.push(Array.from({ length: sizes[l]! }, () => Array.from({ length: sizes[l - 1]! }, () => (rng() - 0.5) * 2)));
    b.push(Array.from({ length: sizes[l]! }, () => (rng() - 0.5) * 2));
  }
  return { sizes, w, b };
}

export function forward(net: Net, input: number[]): number[][] {
  const acts = [input];
  for (let l = 0; l < net.w.length; l++) {
    const prev = acts[l]!;
    acts.push(net.w[l]!.map((weights, u) => sigmoid(weights.reduce((s, wt, i) => s + wt * prev[i]!, net.b[l]![u]!))));
  }
  return acts;
}

export const predict = (net: Net, input: number[]) => forward(net, input).at(-1)![0]!;

export type Sample = { input: number[]; target: number };

// Training = repeat: make a guess, measure the error, nudge every weight a little to reduce the error.
// The nudging rule is called backpropagation.
export function train(net: Net, data: Sample[], epochs: number, rate: number) {
  const history: number[] = [];
  for (let e = 0; e < epochs; e++) {
    let loss = 0;
    for (const { input, target } of data) {
      const acts = forward(net, input);
      const out = acts.at(-1)![0]!;
      loss += (out - target) ** 2;
      // delta = how much each unit contributed to the error
      let delta = [(out - target) * out * (1 - out)];
      for (let l = net.w.length - 1; l >= 0; l--) {
        const prev = acts[l]!;
        const nextDelta = prev.map((a, i) => a * (1 - a) * delta.reduce((s, d, u) => s + d * net.w[l]![u]![i]!, 0));
        for (let u = 0; u < delta.length; u++) {
          for (let i = 0; i < prev.length; i++) net.w[l]![u]![i]! -= rate * delta[u]! * prev[i]!;
          net.b[l]![u]! -= rate * delta[u]!;
        }
        delta = nextDelta;
      }
    }
    if (e % Math.max(1, Math.floor(epochs / 50)) === 0 || e === epochs - 1) history.push(loss / data.length);
  }
  return history;
}

export const XOR: Sample[] = [
  { input: [0, 0], target: 0 },
  { input: [0, 1], target: 1 },
  { input: [1, 0], target: 1 },
  { input: [1, 1], target: 0 },
];

And the page that trains it and shows the results:

import { Component, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { XOR, createNet, predict, train, type Net } from '../lib/nn';

@Component({
  selector: 'app-neural',
  imports: [FormsModule],
  template: `
    <h1>A neural network in your browser</h1>
    <p class="hint">XOR means "one or the other, but not both". A single layer of units cannot learn it. Add a hidden layer and it can. Train and see.</p>

    <div class="card">
      <div class="grid2">
        <div><label for="h">Hidden units: {{ hidden() }}</label><input id="h" type="range" min="0" max="8" step="1" [ngModel]="hidden()" (ngModelChange)="hidden.set(+$event)" /></div>
        <div><label for="e">Training rounds: {{ epochs() }}</label><input id="e" type="range" min="500" max="10000" step="500" [ngModel]="epochs()" (ngModelChange)="epochs.set(+$event)" /></div>
      </div>
      <button (click)="run()" [disabled]="busy()">{{ busy() ? 'Training...' : 'Train from scratch' }}</button>
    </div>

    @if (result(); as r) {
      <div class="card">
        <h2>Results with {{ r.hidden }} hidden unit(s)</h2>
        <p>Final error: <strong [class]="r.loss < 0.02 ? 'ok' : 'bad'">{{ r.loss.toFixed(4) }}</strong> {{ r.loss < 0.02 ? '(learned it)' : '(did not learn it)' }}</p>
        <table>
          <thead><tr><th>Input</th><th>Should be</th><th>Network says</th></tr></thead>
          <tbody>
            @for (row of r.rows; track $index) {
              <tr><td>{{ row.input }}</td><td>{{ row.target }}</td><td [class]="row.ok ? 'ok' : 'bad'">{{ row.out.toFixed(2) }}</td></tr>
            }
          </tbody>
        </table>
        <p class="muted">Error over time (each bar is one checkpoint, taller means more wrong):</p>
        <div class="spark">@for (v of r.history; track $index) { <span [style.height.px]="Math.max(2, v * 160)"></span> }</div>
      </div>
    }
  `,
  styles: [`.spark { display:flex; align-items:flex-end; gap:2px; height:80px; } .spark span { flex:1; background:#4f46e5; border-radius:2px 2px 0 0; }`],
})
export class NeuralPage {
  Math = Math;
  hidden = signal(4);
  epochs = signal(4000);
  busy = signal(false);
  result = signal<{ hidden: number; loss: number; history: number[]; rows: { input: string; target: number; out: number; ok: boolean }[] } | null>(null);

  run() {
    this.busy.set(true);
    // setTimeout lets the button update before the browser is busy with training
    setTimeout(() => {
      const net: Net = createNet(this.hidden(), 3);
      const history = train(net, XOR, this.epochs(), 0.8);
      const rows = XOR.map((s) => {
        const out = predict(net, s.input);
        return { input: s.input.join(', '), target: s.target, out, ok: Math.round(out) === s.target };
      });
      this.result.set({ hidden: this.hidden(), loss: history.at(-1) ?? 1, history, rows });
      this.busy.set(false);
    }, 20);
  }
}

What you learned

  • A neuron multiplies inputs by weights, adds a bias and applies an activation function.
  • Hidden layers let a network solve problems that a single neuron cannot, such as XOR.
  • Training repeats forward pass, error, backpropagation and a small weight update, over many epochs.
  • A transformer uses attention to let every word look at the other words, and it can be trained in parallel on huge text collections.

In the next lesson you will move from learning in general to generative AI: systems that produce new text, images and more.

Download the solution (genai-basics-lesson-04.zip)

Frequently asked questions

What is the difference between deep learning and machine learning?

Deep learning is a kind of machine learning that uses neural networks with many layers. Other machine learning methods, such as k-NN or decision trees, are not deep learning.

What does attention do in a transformer?

Attention lets the model compare each word with every other word in the text and decide which ones are most useful for understanding it. This helps it handle long sentences and connect words that are far apart.

Why do neural networks need GPUs?

Training means a huge number of multiplications that can run at the same time. GPUs are built to do many such calculations in parallel, so they are much faster than ordinary processors for this work.

Can a neural network explain its decisions?

Not easily. The answer comes from many weights working together, so there is no single rule to point to. Researchers work on explanation methods, but for important decisions a human check is still needed.