Skip to content

Computational Graphs

Computational graphs are a fundamental concept underpinning how many deep learning frameworks implement and optimize calculations, particularly the crucial process of backpropagation for gradient computation.

A computational graph is a directed graph where:

  • Nodes: Represent mathematical operations (e.g., addition, multiplication, activation functions) or variables (input data, parameters like weights and biases, intermediate values).
  • Edges: Represent the flow of data (tensors) between operations. The output of one operation becomes the input to another.

Essentially, they provide a way to visually and structurally represent complex mathematical expressions, breaking them down into a series of simpler operations.

For example, consider the expression: g = (x + y) * z

We can represent this as a graph:

  1. Input nodes for x, y, and z.
  2. An addition node (+) taking x and y as input, producing an intermediate result p = x + y.
  3. A multiplication node (*) taking p and z as input, producing the final result g.

Edges would direct the flow from x and y to the + node, from z to the * node, and from the + node (output p) to the * node.

A neural network’s forward pass can be naturally expressed as a computational graph. Each layer involves matrix multiplications, additions (biases), and activation functions, all of which become nodes in the graph. The input data and weights/biases are also nodes.

The forward pass involves evaluating the graph by feeding input values and computing the output of each node in topological order (from inputs towards the final output).

Using our example g = (x + y) * z, if x = 1, y = 3, and z = -3:

  1. The + node computes p = 1 + 3 = 4.
  2. The * node computes g = p * z = 4 * (-3) = -12.

This process mirrors how a neural network calculates its prediction.

Backward Pass (Backpropagation via Autograd)

Section titled “Backward Pass (Backpropagation via Autograd)”

The real power of computational graphs in deep learning lies in their ability to facilitate automatic differentiation (autograd) for the backward pass (backpropagation).

The goal of the backward pass is to compute the gradient of the final output (usually the loss function) with respect to each input variable and parameter (weights, biases). These gradients are needed to update the parameters during training using gradient descent.

Using the computational graph structure and the chain rule of calculus, frameworks can automatically compute these gradients by propagating derivatives backward through the graph.

For our example g = (x + y) * z, let p = x + y. We want to find ∂g/∂x, ∂g/∂y, and ∂g/∂z.

  1. Start at the output: The gradient of g with respect to itself is ∂g/∂g = 1.
  2. Backpropagate through * node: We need the local gradients ∂g/∂p and ∂g/∂z. Since g = p * z, we know ∂g/∂p = z and ∂g/∂z = p. Using the values from the forward pass (p=4, z=-3), we get ∂g/∂p = -3 and ∂g/∂z = 4.
  3. Backpropagate through + node: We need ∂p/∂x and ∂p/∂y. Since p = x + y, we have ∂p/∂x = 1 and ∂p/∂y = 1.
  4. Apply Chain Rule: To get the gradient of g with respect to the original inputs x and y, we use the chain rule:
  5. ∂g/∂x = (∂g/∂p) * (∂p/∂x) = (-3) * 1 = -3
  6. ∂g/∂y = (∂g/∂p) * (∂p/∂y) = (-3) * 1 = -3

We now have all the desired gradients: ∂g/∂x = -3, ∂g/∂y = -3, ∂g/∂z = 4.

Deep learning frameworks perform this process automatically. By defining the forward pass (either explicitly by building a graph or implicitly through operations), the framework records the sequence of operations and can then compute the gradients by applying the chain rule backward.

Static Graphs (e.g., TensorFlow 1.x, Theano): You first define the entire computational graph structure. Then, you execute parts of this graph potentially many times, feeding in different data. This allows for significant ahead-of-time optimization but can be less intuitive for debugging and handling dynamic control flow.

Dynamic Graphs (e.g., PyTorch, TensorFlow 2.x Eager Execution): The graph is built implicitly on-the-fly as operations are executed in Python. This feels more like standard programming, simplifies debugging, and easily handles conditional logic or variable sequence lengths. While potentially less optimized upfront, techniques like graph compilation (e.g., tf.function in TF2, torch.compile in PyTorch) can capture and optimize parts of the dynamic execution for performance.

Understanding the concept of computational graphs helps clarify how automatic differentiation works and why it’s so central to training deep neural networks efficiently.