Skip to main content

Programs as computational graphs

Any program built from elementary operations is a directed acyclic graph, and running it leaves an evaluation trace of intermediate values. Build the trace and the graph of ln x1 + x1 x2 - sin x2 by hand, and see why neither finite differences nor symbolic algebra suits training.
M05.8.1L2 Intermediate25 min
In review (what this means)

The maths and code on this page are checked by automated tests on every change, but an independent reader has not signed it off yet. Spot a mistake or a confusing step? Tell us.

Before this lesson: M05.2 Partial derivatives and the gradient M05.4 Jacobians and the multivariable chain rule M04.3 Differentiation rules and the chain rule

What you will be able to do
  • Break a formula or a short program into an evaluation trace of elementary operations with intermediate variables v_i.
  • Draw the computational graph of a trace and evaluate it in a topological order.
  • Explain how loops and branches unroll into a trace for the inputs actually given.
  • Explain why finite differences (2n evaluations and rounding error) and symbolic differentiation (expression swell) do not suit training.

The question

A language model has billions of weights, and every training step needs the slope of the loss in each one of them. Nudging the weights one at a time would take billions of runs of the model per step. Yet a library returns all of those slopes for about the price of a couple of runs. What does it see in your code that makes this possible?

Intuition

A computer never evaluates a formula in one go. It runs a sequence of tiny steps, each one an operation it knows well: add, multiply, take a logarithm, a sine, an exponential. Write each step on its own line and give its result a name, and you have the evaluation trace of the program. Draw each step as a node, with an arrow from every value to each step that uses it, and you have its computational graph.

Two parts side by side. Left, titled The trace: a list of steps, eight lines: v minus 1 equals x1 and v0 equals x2 in orange, then in blue v1 equals ln v minus 1, v2 equals v minus 1 times v0, v3 equals sine v0, v4 equals v1 plus v2, v5 equals v4 minus v3, and y equals v5. Right, titled The graph: a node per step, the same seven variables as rounded boxes. Two orange input boxes, v minus 1 holding x1 and v0 holding x2, sit on the left. Arrows run from v minus 1 to v1 (ln) and to v2 (times), from v0 to v2 and to v3 (sin), from v1 and v2 to v4 (plus), and from v4 and v3 to v5 (minus), the output y. The formula ln x1 plus x1 x2 minus sine x2 is written underneath.
M05.8.1-F1

The running program of this module, \({f(x_1, x_2) = \ln x_1 + x_1 x_2 - \sin x_2}\), as a trace (left) and as a graph (right). Each line of the trace is one node; each arrow says “this value is used here”.

The two pictures hold the same information. The trace is the order in which the computer did the work. The graph shows what depends on what. Both are made of pieces whose derivatives we already know, joined by the chain rule (M04.3.4, and its many-variable form in M05.4). That is the whole secret of automatic differentiation: differentiate each tiny step, and let the graph say how to combine the results. This lesson builds the graph; the next three run derivatives through it.

Build it up

Step 1. Name every intermediate result. Take \({f(x_1, x_2) = \ln x_1 + x_1 x_2 - \sin x_2}\), the worked example of section 3 of the survey by Baydin et al. Following their numbering, the inputs get the indices \({-1}\) and \({0}\), so that the working variables start at 1: \({v_{-1} = x_1}\) and \({v_0 = x_2}\). Then each operation gets the next index: \({v_1 = \ln v_{-1}}\), \({v_2 = v_{-1} v_0}\), \({v_3 = \sin v_0}\), \({v_4 = v_1 + v_2}\) and \({v_5 = v_4 - v_3}\). The output is \({y = v_5}\). This list is also called a Wengert list, after the man who used it in 1964 to compute derivatives automatically.

Step 2. Each line uses only earlier lines. That is what makes it a program that can run: when the computer reaches line \(v_4\), the values \(v_1\) and \(v_2\) already exist. In the graph, it means every arrow points from an earlier node to a later one, so no path ever leads back to where it started. A graph with that property is a directed acyclic graph, a DAG.

Step 3. A value used twice is one node with two arrows out. The input \(x_1\) feeds both the logarithm and the product. In the graph it is one node, \(v_{-1}\), with two outgoing arrows. This sharing will matter in lesson M05.8.3, where derivatives flowing back along both arrows must be added.

Step 4. Loops and branches disappear. A real program has for loops and if statements. But any single run, on particular inputs, executes particular lines in a particular order. Record them and the loop is unrolled, and only the branch actually taken appears.

Top left, a grey panel holds a short program: l equals x; for k equals 1, 2, 3: l equals 4 l times 1 minus l; if l greater than 0.5, y equals l squared; else y equals 1 minus l. To its right a note says that run on x equals 0.2 the loop body runs three times, the else branch is taken, and what is left is a straight trace with no loop at all. Underneath, titled The trace for this input, five boxes joined by arrows: an orange box x, 0.2; then pass 1, 0.6400; pass 2, 0.9216; pass 3, 0.2890, each labelled 4 l times 1 minus l above it; and a last box y equals 1 minus l, 0.7110, labelled l greater than 0.5? no.
M05.8.1-F2

A loop of three passes of the logistic map \({l \mapsto 4l(1 - l)}\), followed by a branch. For the input \({x = 0.2}\) the run leaves a straight chain: each pass becomes three elementary steps, and the branch not taken leaves no trace.

The trace depends on the input: for \({x = 0.3}\) the third pass gives about 0.9943, the test \({l > 0.5}\) passes, and the other branch is recorded. A derivative computed from a trace is the derivative of the function along that path. At an input where the test switches, the function can jump, and no method of differentiation can give a slope there.

The formal statement

NoteDefinition: evaluation trace and computational graph

Let \({f : \R^n \to \R^m}\) be computed by a program built from elementary operations. An evaluation trace of \(f\) at a point is a list of variables

  • the inputs \({v_{i-n} = x_i}\) for \({i = 1, \dots, n}\),
  • the working variables \({v_i = \varphi_i(\text{parents of } v_i)}\) for \({i = 1, \dots, \ell}\), where each \(\varphi_i\) is an elementary operation and every parent of \(v_i\) has a smaller index,
  • and the outputs, which are the last \(m\) working variables.

The computational graph has one node per variable and an edge from \(v_j\) to \(v_i\) whenever \(v_j\) is a parent of \(v_i\). It is a directed acyclic graph.

The elementary operations are those whose derivatives are known exactly: addition, subtraction, multiplication, division, and functions such as \(\exp\), \(\ln\), \(\sin\), \(\cos\), \(\tanh\) and the sigmoid \(\sigma\) (whose slope \({\sigma(1 - \sigma)}\) is M04.3.3).

Why the graph has no cycle. An edge from \(v_j\) to \(v_i\) means \({j < i}\). Along any path the index rises at every step, so a path can never come back to the node it started from.

Evaluating a graph. A topological order is a list of all the nodes in which each node comes after all of its parents. The trace is one such order. Evaluating the nodes in any topological order computes the same values, because each node then sees exactly the values of its parents, and those do not depend on the order. When a graph arrives as a set of nodes in no particular order, a topological order can always be found.

Go deeper: every directed acyclic graph has a topological order

First, a finite DAG has a node with no parents. Suppose instead that every node had a parent. Start anywhere and keep stepping to a parent. You never get stuck, and there are only finitely many nodes, so some node is visited twice. The steps between the two visits form a cycle, which a DAG does not have.

Now build the order: take a node with no parents, put it next in the list, and delete it with its edges. What remains is still a finite DAG (deleting cannot create a cycle), so it again has a node with no parents. Repeat until no nodes remain. Each node enters the list only after all its parents have been deleted, which means they are earlier in the list. This is Kahn’s algorithm, and the code below runs it.

Why not just use finite differences? The central difference of M05.2.4 approximates each partial derivative with two runs of \(f\), so a gradient in \(n\) inputs costs \(2n\) runs. For a network with a million weights that is two million runs of the network for one training step. The answer is also inexact: the error falls like \(h^2\) until rounding takes over, and then grows again (M04.2.4).

Why not symbolic differentiation? A computer algebra system applies the rules of differentiation to the formula itself. But a formula written out in full is a tree, not a graph: a shared subexpression is copied every time it is used. The logistic map \({l_{k+1} = 4\,l_k(1 - l_k)}\), started from \({l_1 = x}\), uses \(l_k\) twice, so the tree of \(l_n\) doubles with each pass, and the product rule copies each factor again into the derivative. This blow-up is called expression swell.

Two plots. Left, titled Finite differences: on log scales, the error of the central difference for the partial derivative of f in x1 at (2, 5) against the step h from 10 to the minus 13 up to 0.1. The purple curve falls from the right as h shrinks, reaches a minimum of about 10 to the minus 11 near h equals 10 to the minus 5, then rises again to about 10 to the minus 2 for the smallest steps. The right side is labelled truncation wins and the left side rounding wins. Right, titled Symbolic: expression swell: node counts on a log scale against n from 1 to 7, where l n is the logistic map applied n minus 1 times to l 1 equals x. The purple derivative-tree curve climbs to about 3,500, the blue tree of l n to 379, and the vermillion trace grows slowly, by three nodes per pass, to 19.
M05.8.1-F6

Left: a finite difference is either truncated (large \(h\)) or swamped by rounding (small \(h\)). Right: written as trees, the logistic map and above all its derivative swell; the trace grows by three steps per pass.

Automatic differentiation avoids both troubles. It works on the trace, where every shared value is stored once, and it applies the exact derivative of each elementary step, so there is no step size \(h\) and no truncation.

Worked example

The running program at (2, 5). Go down the trace, filling in numbers:

  • \({v_{-1} = 2}\) and \({v_0 = 5}\).
  • \({v_1 = \ln 2 \approx 0.693}\).
  • \({v_2 = 2 \times 5 = 10}\).
  • \({v_3 = \sin 5 \approx -0.959}\) (5 radians lies in the fourth quarter of the circle).
  • \({v_4 = v_1 + v_2 \approx 10.693}\).
  • \({v_5 = v_4 - v_3 \approx 11.652}\): subtracting \(-0.959\) adds 0.959.

So \({f(2, 5) \approx 11.652}\).

The computational graph of ln x1 plus x1 x2 minus sine x2 with every value filled in. Orange input boxes: v minus 1 equals x1 holding 2, and v0 equals x2 holding 5. Blue boxes, each with a yellow numbered badge, hold the values in vermillion: badge 1, v1 equals ln v minus 1, 0.693; badge 2, v2 equals v minus 1 times v0, 10; badge 3, v3 equals sine v0, minus 0.959; badge 4, v4 equals v1 plus v2, 10.693; badge 5, v5 equals v4 minus v3, 11.652, marked y equals f of 2, 5. A note reads: badges show the order of evaluation; a node runs once its inputs are known.
M05.8.1-F3

The worked trace. The badges give one topological order; the order 3, 1, 2, 4, 5 would do as well, but \(v_4\) can never come before \(v_1\) or \(v_2\).

The running chain of M04.3.4. A classifier with weight \({w = 0.5}\) scores the input \({x = 2}\), turns the score into a probability, and pays the squared error against the target \({y = 1}\). Treat \(w\) as the only input, and \(x\) and \(y\) as fixed numbers:

  • \({v_0 = w = 0.5}\).
  • \({v_1 = v_0 \cdot 2 = 1}\).
  • \({v_2 = \sigma(v_1) \approx 0.731}\).
  • \({v_3 = v_2 - 1 \approx -0.269}\).
  • \({v_4 = v_3^2 \approx 0.072}\).
A chain of boxes for the error of one example, E equals sigma of w x minus y, squared, at w equals 0.5, x equals 2 and y equals 1. An orange box v0 equals w, 0.5, and a fixed box x, 2, both feed v1 equals w x, 1. Then v2 equals sigma of v1, 0.731; v3 equals v2 minus y, minus 0.269, which also takes the fixed box y, 1; and finally a purple box v4 equals v3 squared, 0.072, labelled the error E.
M05.8.1-F4

The running chain as a graph. It is a straight line because every value is used once: the simplest kind of computational graph, and the shape of every layer-by-layer network.

This is the forward pass that lesson M04.3.4 ran before multiplying local slopes backwards. The only new idea is to see it as a graph, because the same backward walk works on any graph, not only on a chain (M05.8.3).

Code

From scratch: a program stored as a list of nodes, deliberately out of order. Each node names its operation and its inputs. A topological sort puts the list in order, and evaluation walks along it.

import math

OPS = {
    "add": lambda a, b: a + b, "sub": lambda a, b: a - b, "mul": lambda a, b: a * b,
    "ln": math.log, "sin": math.sin, "exp": math.exp, "tanh": math.tanh,
    "sigmoid": lambda z: 1 / (1 + math.exp(-z)), "square": lambda a: a * a,
}

# (name, operation, inputs); an input is the name of a node or a plain number
PROGRAM = [
    ("v5", "sub", ["v4", "v3"]),
    ("v2", "mul", ["x1", "x2"]),
    ("v4", "add", ["v1", "v2"]),
    ("v1", "ln", ["x1"]),
    ("v3", "sin", ["x2"]),
]

def topological_order(program, inputs):
    """Kahn's algorithm: repeatedly take a node whose parents are all known."""
    known, order, waiting = set(inputs), [], list(program)
    while waiting:
        ready = [node for node in waiting
                 if all(isinstance(a, float) or a in known for a in node[2])]
        if not ready:
            raise ValueError("the graph has a cycle or a missing input")
        for node in ready:
            order.append(node)
            known.add(node[0])
            waiting.remove(node)
    return order

def evaluate(program, inputs):
    values = dict(inputs)
    for name, op, args in topological_order(program, inputs):
        values[name] = OPS[op](*[a if isinstance(a, float) else values[a] for a in args])
    return values

values = evaluate(PROGRAM, {"x1": 2.0, "x2": 5.0})
print([name for name, _, _ in topological_order(PROGRAM, ["x1", "x2"])])
print({k: round(v, 3) for k, v in values.items()})
['v2', 'v1', 'v3', 'v4', 'v5']
{'x1': 2.0, 'x2': 5.0, 'v2': 10.0, 'v1': 0.693, 'v3': -0.959, 'v4': 10.693, 'v5': 11.652}

The running chain uses the same machinery, with the fixed numbers \(x\) and \(y\) written into the nodes:

CHAIN = [
    ("v1", "mul", ["w", 2.0]),
    ("v2", "sigmoid", ["v1"]),
    ("v3", "sub", ["v2", 1.0]),
    ("v4", "square", ["v3"]),
]
chain = evaluate(CHAIN, {"w": 0.5})
print({k: round(v, 3) for k, v in chain.items()})
{'w': 0.5, 'v1': 1.0, 'v2': 0.731, 'v3': -0.269, 'v4': 0.072}

A loop unrolls when we record what actually runs. The recorder below runs the program of the second figure and writes one node for each elementary step it performs:

def record_logistic(x, passes=3):
    trace, l = [], "x"
    for k in range(1, passes + 1):            # p{k} is the value after pass k
        trace += [(f"a{k}", "mul", [4.0, l]), (f"b{k}", "sub", [1.0, l]), (f"p{k}", "mul", [f"a{k}", f"b{k}"])]
        l = f"p{k}"
    last = evaluate(trace, {"x": x})[l]
    trace.append(("y", "square", [l]) if last > 0.5 else ("y", "sub", [1.0, l]))
    return trace

for x in (0.2, 0.3):
    trace = record_logistic(x)
    out = evaluate(trace, {"x": x})
    print(f"x = {x}: {len(trace)} steps, after pass 3 {out['p3']:.4f}, last step {trace[-1][1]}, y = {out['y']:.4f}")
x = 0.2: 10 steps, after pass 3 0.2890, last step sub, y = 0.7110
x = 0.3: 10 steps, after pass 3 0.9943, last step square, y = 0.9887

Last, the two alternatives. Finite differences: count the runs a central-difference gradient needs. Symbolic differentiation: build the logistic map as a tree, differentiate it by the sum and product rules alone, and count nodes.

runs = 0
def f(x):
    global runs
    runs += 1
    return math.log(x[0]) + x[0] * x[1] - math.sin(x[1])

def central_gradient(f, x, h=1e-5):
    grad = []
    for i in range(len(x)):
        up, down = list(x), list(x)
        up[i] += h
        down[i] -= h
        grad.append((f(up) - f(down)) / (2 * h))
    return grad

print("central-difference gradient:", [round(g, 6) for g in central_gradient(f, [2.0, 5.0])], "runs:", runs)

def tree(n):                      # l_1 = x, l_(k+1) = 4 l_k (1 - l_k), with no sharing
    t = ("x",)
    for _ in range(n - 1):
        t = ("*", ("*", ("c", 4), t), ("-", ("c", 1), t))
    return t

def d(t):                         # the sum and product rules, nothing else
    if t[0] == "x":
        return ("c", 1)
    if t[0] == "c":
        return ("c", 0)
    a, b = t[1], t[2]
    if t[0] in "+-":
        return (t[0], d(a), d(b))
    return ("+", ("*", d(a), b), ("*", a, d(b)))

def size(t):
    return 1 if t[0] in "xc" else 1 + size(t[1]) + size(t[2])

for n in range(1, 8):
    print(f"n = {n}: tree of l_n {size(tree(n)):4d} nodes, its derivative {size(d(tree(n))):5d}, "
          f"trace {1 + 3 * (n - 1):3d}")
central-difference gradient: [5.5, 1.716338] runs: 4
n = 1: tree of l_n    1 nodes, its derivative     1, trace   1
n = 2: tree of l_n    7 nodes, its derivative    19, trace   4
n = 3: tree of l_n   19 nodes, its derivative    73, trace   7
n = 4: tree of l_n   43 nodes, its derivative   217, trace  10
n = 5: tree of l_n   91 nodes, its derivative   577, trace  13
n = 6: tree of l_n  187 nodes, its derivative  1441, trace  16
n = 7: tree of l_n  379 nodes, its derivative  3457, trace  19

The tree of \(l_n\) has \({3 \cdot 2^n - 5}\) nodes for every \({n \ge 1}\), doubling with each pass, and its naive derivative grows faster still, while the trace grows by three steps a pass. Real algebra systems simplify as they go, which helps, but the tree form is the root of the trouble.

Explore

This module’s interactive, the graph stepper, lives in lesson M05.8.3. For now, choose its reverse mode and press Step seven times: that is the forward pass alone, each node getting its value once its parents are known. Move the sliders for \(x_1\) and \(x_2\) and every value follows.

Pitfalls

Two diagrams of ln x1 plus x1 x2 minus sine x2. Left, marked with a cross and titled A tree copies the inputs: an expression tree with minus at the top, plus and sine below it, ln and times below plus, and four orange leaves at the bottom, x1, x1, x2 and x2, each input drawn twice. Right, marked with a tick and titled A graph shares them: the same operation boxes, but only two orange input boxes, x1 and x2, each with two arrows leaving it, x1 to ln and to times, and x2 to times and to sine.
M05.8.1-F5

Wrong beside right. A formula read as a tree copies every input it uses twice. The computational graph keeps one node per value and lets its arrows fan out.

“A computational graph is the same as the formula’s expression tree.”

A tree gives every use of a value its own copy, so shared work is repeated and grows exponentially, as with the logistic map. The graph stores each value once. When derivatives later flow back through the graph, the two arrows out of \(x_1\) carry two contributions that must be added; in a tree they would sit on two separate leaves and could be missed.

“Automatic differentiation is a clever finite difference.”

It never chooses a step \(h\) and never subtracts two nearly equal values of \(f\). It applies the exact derivative of each elementary step to the numbers in the trace, so its answer is exact up to the rounding of ordinary arithmetic, the same rounding that computing \(f\) itself suffers.

“Programs with loops and if-statements cannot be differentiated automatically.”

They can: a run leaves a trace with the loop unrolled and only the branch taken. What a trace cannot give is a slope at an input where a branch switches and the function jumps; there the function has no slope to give.

Check yourself

Recall

1. What is an evaluation trace, or Wengert list?

Show answer

The list of intermediate variables \(v_i\) that a program computes, one elementary operation per line, with every line using only inputs and earlier lines.

2. Why is a computational graph acyclic?

Show answer

Every edge runs from a variable to one with a larger index, so along any path the index rises and can never return to its starting value.

3. What is \(f(2, 5)\) for \({f(x_1, x_2) = \ln x_1 + x_1 x_2 - \sin x_2}\), and which value of the trace is \({\sin 5}\)?

Show answer

About 11.652. \({\sin 5 \approx -0.959}\) is \(v_3\).

4. How many runs of \(f\) does a central-difference gradient in \(n\) inputs need?

Show answer

\(2n\): one on each side of the point for each input.

5. What is expression swell?

Show answer

The blow-up in size of a formula and of its symbolic derivative when shared subexpressions are copied at every use, as the product rule does; the tree of the logistic map doubles with each pass.

Exercises

1. ★ Write the evaluation trace of \({g(x_1, x_2) = e^{x_1 x_2} + x_2}\) and evaluate it at \({(1, 2)}\).

Show solution

\({v_{-1} = x_1 = 1}\), \({v_0 = x_2 = 2}\), \({v_1 = v_{-1} v_0 = 2}\), \({v_2 = \exp v_1 = e^2 \approx 7.389}\), \({v_3 = v_2 + v_0 \approx 9.389}\). So \({g(1, 2) = e^2 + 2 \approx 9.389}\). The input \(v_0\) is used twice, so its node has two arrows out.

2. ★★ The single-example log loss of logistic regression is \({E = -\ln \sigma(w x + b)}\) for a positive example. Write its trace with inputs \(w\) and \(b\), the fixed input \({x = 2}\), and evaluate it at \({w = 0.5}\), \({b = 0}\). Which node would change if the example were negative, with loss \({-\ln(1 - \sigma(wx + b))}\)?

Show solution

\({v_{-1} = w = 0.5}\), \({v_0 = b = 0}\), \({v_1 = 2 v_{-1} = 1}\), \({v_2 = v_1 + v_0 = 1}\), \({v_3 = \sigma(v_2) \approx 0.731}\), \({v_4 = \ln v_3 \approx -0.313}\), \({v_5 = -v_4 \approx 0.313}\). For a negative example a new node \({1 - v_3}\) sits between \(v_3\) and the logarithm; the rest of the graph is unchanged. In code, an if on the label chooses which trace is recorded.

3. ★★★ The expression tree of \(l_n\) has \(T_n\) nodes, with \({T_1 = 1}\). Show that \({T_{n+1} = 2T_n + 5}\) and hence \({T_n = 3 \cdot 2^n - 5}\) for \({n \ge 1}\). How many nodes does the trace of \(l_n\) have?

Show solution

The tree of \(l_{n+1}\), the product of \({4\,l_n}\) and \({1 - l_n}\), has a root \(\times\), an inner \(\times\) with the constant 4, a \(-\) with the constant 1, and two copies of the tree of \(l_n\): five new nodes and \(2T_n\) copied ones, so \({T_{n+1} = 2T_n + 5}\). Check the formula: \({3 \cdot 2 - 5 = 1 = T_1}\), and if \({T_n = 3 \cdot 2^n - 5}\) then

\[ \begin{aligned} T_{n+1} &= 2\,(3 \cdot 2^n - 5) + 5 \\ &= 3 \cdot 2^{n+1} - 5 . \end{aligned} \]

By induction the formula holds for every \(n\). The trace stores \(l_n\) once, so each pass adds three steps: \({1 + 3(n - 1)}\) nodes, counting the input. Linear against exponential.

Summary card

A program is a graph of elementary steps.

The anchor figure again: the trace of ln x1 plus x1 x2 minus sine x2 as seven lines on the left, and the same seven variables as a graph on the right, with arrows from each value to the steps that use it.
M05.8.1-F1

One line of the trace, one node of the graph.

  • Trace: \({v_i = \varphi_i(\text{parents})}\), each parent earlier in the list.
  • Graph: a DAG; evaluate it in any topological order.
  • Running program: \({f(2, 5) \approx 11.652}\).
  • Finite differences cost \(2n\) runs and are inexact; symbolic trees swell.

Sources and next steps

Sources. Each was opened and checked against this lesson on 15 November 2026.

  • A. G. Baydin, B. A. Pearlmutter, A. A. Radul and J. M. Siskind, Automatic Differentiation in Machine Learning: a Survey, JMLR 18(153), 2018. Section 2.1: a gradient by finite differences costs \(O(n)\) evaluations, and a Jacobian \(2mn\) when each output is counted separately; section 2.2 and Table 1: expression swell on the logistic map \({l_{n+1} = 4l_n(1 - l_n)}\); section 3: the evaluation trace, called a Wengert list, with inputs numbered from \({v_{1-n}}\), Figure 4 (the graph of the running program) and Table 2 (its primal trace at \({(2, 5)}\), \({y = 11.652}\)); and the remark that a trace records only the control-flow path taken.
  • P. Bright, A. Edelman and S. G. Johnson, Matrix Calculus (for Machine Learning and Beyond), notes for MIT course 18.S096. Section 8.2 shows naive symbolic differentiation of a simple iteration swelling; section 8.3 builds automatic differentiation on computational graphs.
  • M. P. Deisenroth, A. A. Faisal and C. S. Ong, Mathematics for Machine Learning, section 5.6.2: Example 5.14 breaks a function into intermediate variables with a computation graph (Figure 5.11), and equation (5.143) writes a graph as \({x_i = g_i(x_{\text{Pa}(x_i)})}\).

Next. M05.8.2 Forward mode: tangents and dual numbers carries a derivative along with every value of the trace.

This lesson unlocks

M05.8.2 Forward mode: tangents and dual numbers

Your progress is saved in this browser only. No account, no tracking.
Back to top