Mathematical Foundations

I.8 · Mathematical Language

A sum is a loop.

Machine learning is mostly sums over examples, over features and over layers. Learn to read sigma notation as a loop with an accumulator, to count its terms without an off-by-one, and to rewrite a sum without changing its value.

A sum is a loop

Try these

The terms ai are

from −5 to 20, whole numbers only

from −5 to 20, whole numbers only

∑i=15i=1+2+3+4+5=15\sum_{i=1}^{5}i=1+2+3+4+5=15
acc=10+5=15\mathrm{acc}=10+5=15

Step 5 of 5: i = 5, a_5 = 5, so acc = 10 + 5 = 15.

The terms a_i for i from 1 to 5 and the accumulator after each one
iaiacc after
111
223
336
4410
5515
number of terms (0 when n < m)
5
the sum, by the loop
15
the closed form, evaluated
15

= exact (every term and every sum is an exact fraction, never a rounded double)

The Python being run

acc = 0
for i in range(1, 5 + 1):
    acc += i

The loop gives 15 and the closed form gives 15: they agree exactly.

∑i=mnai=S(n)−S(m−1),S(n)=n(n+1)2\sum_{i=m}^{n}a_i=S(n)-S(m-1),\quad S(n)=\frac{n(n+1)}{2}
5(5+1)2=15\frac{5(5+1)}{2}=15

Choose the terms and the two bounds, then press Play: the accumulator takes one term at a time, the matching row of the table is outlined, and the Python being run is shown next to the numbers. Try the ready-made cases, including the empty sum and a term that does not exist. Typed values that are not allowed are rejected, not changed.

Try it before reading on. Press Play and watch the running total grow one term at a time; press n < m: empty and see that a sum with no terms is not an error; press 1/i through 0 and see what happens when one term does not exist. Under the picture, the verdict compares the loop with a closed-form formula whenever one applies. By the end of the chapter you will read a sum as a loop, count its terms correctly, and know which rewrites keep its value.

Intuition: a sum is a loop

Every sum in a machine-learning paper is a loop in disguise. The average loss over a batch is a loop over examples. The score of a linear model is a loop over features. A dot product is a loop over coordinates. Writing the loop in mathematical notation is shorter, and the notation also tells you three things you must not get wrong: where the loop starts, where it stops, and what is added each time.

The picture is an accumulator. Start with a total of 00. For each index in turn, compute a term and add it to the total. When the indices run out, the total is the answer. Nothing else is going on: the sum symbol is the word for, the bounds are the range, and the body is what the loop adds.

Two features of that picture need care, because they are the source of nearly every error with sums:

  • Both ends are included. In mathematics the sum from i=mi=m to i=ni=n includes the term at nn. Python’s range does not: range(m, n) stops before nn. Counting the terms is a fencepost problem: from 33 to 1111 there are nine terms, not eight.
  • A sum can have no terms. If the stop is below the start, the loop does nothing and the total stays at its starting value 00. That is a number, not a failure, and it is the right answer to a question such as “the average of nothing is a sum over no examples”.

The index itself is a dummy: renaming ii as kk changes nothing, exactly as renaming a loop variable changes nothing in code. What matters is the range and the body.

Definition

Sigma notation. For integers mm and nn and a rule that gives a number aia_i for each integer ii in the range,

∑i=mnai  =  am+am+1+⋯+an.\sum_{i=m}^{n} a_i \;=\; a_m + a_{m+1} + \cdots + a_n .

The integer mm is the lower bound, nn the upper bound, ii the index and aia_i the term (or body). Both bounds are included.

The number of terms. If n≥mn\ge m, the sum has n−m+1n-m+1 terms. The “plus one” is the fencepost: a fence from post 33 to post 1111 has nine posts and eight gaps.

The empty sum. If n<mn<m there are no indices, and by convention the sum equals 00, the starting value of the accumulator. This is the only value that keeps the rules below true for every range, and it is what the loop returns.

A term that does not exist. If the rule is not defined at some index in the range, the sum is undefined; it is not 00 and the offending term is not skipped. For example ∑i=−231i\sum_{i=-2}^{3} \frac1i is undefined, because 10\frac10 does not exist.

Closed forms. Some sums have a formula that gives the result without adding the terms. For n≥1n\ge 1:

∑i=1ni=n(n+1)2,∑i=1ni2=n(n+1)(2n+1)6,∑i=1n(2i−1)=n2.\sum_{i=1}^{n} i=\frac{n(n+1)}{2},\qquad \sum_{i=1}^{n} i^2=\frac{n(n+1)(2n+1)}{6},\qquad \sum_{i=1}^{n} (2i-1)=n^2 .

A constant added n−m+1n-m+1 times gives ∑i=mnc=(n−m+1) c\sum_{i=m}^{n} c=(n-m+1)\,c. A geometric sum has a term that is a constant ratio rr to the power of the index. If r≠1r\ne1,

∑i=mnri=rm−rn+11−r,\sum_{i=m}^{n} r^{i}=\frac{r^{m}-r^{n+1}}{1-r},

and if r=1r=1 every term is 11, so the sum is n−m+1n-m+1. The geometric formula comes from a telescoping trick: multiplying the sum by (1−r)(1-r) makes every term but the first and the last cancel in pairs.

A convention to notice. In a geometric sum the term at i=0i=0 is r0r^{0}, and we read it as 11 for every rr, including r=0r=0 (it is the empty product, and it is what Python’s Fraction(0) ** 0 gives). Chapter I.1 leaves 000^{0} undefined for ordinary powers of numbers; the two do not conflict, because here the convention belongs to the notation of a geometric sequence and is stated, not smuggled in. A negative power of 00 stays undefined everywhere: if r=0r=0 and a term needs rir^{i} with i<0i<0, the sum is undefined. An empty sum is different again: it has no terms, so it is 00 even when the bounds are ones where rmr^{m} would not exist.

Rules for rewriting a sum. For the same bounds on both sums and constants cc:

∑i=mn(c ai+bi)=c∑i=mnai+∑i=mnbi(linearity),\sum_{i=m}^{n}\bigl(c\,a_i+b_i\bigr)=c\sum_{i=m}^{n}a_i+\sum_{i=m}^{n}b_i \quad\text{(linearity)}, ∑i=mnai=∑i=mpai+∑i=p+1nai(splitting at any p with m−1≤p≤n),\sum_{i=m}^{n}a_i=\sum_{i=m}^{p}a_i+\sum_{i=p+1}^{n}a_i \quad\text{(splitting at any }p\text{ with }m-1\le p\le n\text{)}, ∑i=mnai=∑j=m+kn+kaj−k(shifting the index by k).\sum_{i=m}^{n}a_i=\sum_{j=m+k}^{n+k}a_{j-k} \quad\text{(shifting the index by }k\text{)}.

In the shift, j=i+kj=i+k, so i=j−ki=j-k: the bounds go up by kk, and the term must be rewritten with j−kj-k wherever it had ii.

Double sums. A sum can have a sum as its body. In ∑i=1n∑j=1kaij\sum_{i=1}^{n}\sum_{j=1}^{k}a_{ij} the inner sum is added completely for each ii, so the cells aija_{ij} of a grid are added row by row. For a finite grid the total does not depend on the order, so adding column by column gives the same number. If the inner bound depends on the outer index, as in ∑i=1n∑j=1iaij\sum_{i=1}^{n}\sum_{j=1}^{i}a_{ij}, the cells form a triangle, and swapping the order means rewriting the bounds so that the same cells are visited.

Python is zero-based. A list a stores its first entry at position 00. If the mathematics indexes from 11, then aia_i is a[i - 1]. Mixing the two conventions is the most common way to get a sum quietly wrong.

Mechanics: how to work with a sum

Off by one: five loops, one sum

The four numbers mm, nn, the start of the range and the stop of the range are easy to mix up. The stage below runs five loops for the same sum, shows the Python of each, and reports which ones return the true value. For the sum to be informative the terms are chosen so that a mistake changes the result.

Off by one: five loops, one sum

Try these

The terms ai are

from 1 to 12, whole numbers only

from 1 to 12, whole numbers only

The list of terms, zero-based: a[0] holds a1

a = [1, 3, 5, 7, 9]  # a[0] is a_1
the sum Σ from i = m to n
25
of the five loops give this total
2

= exact

  • range(m, n + 1) Correct

    acc = 0
    for i in range(1, 5 + 1):
        acc += 2 * i - 1
    i takes the values
    1, 2, 3, 4, 5
    terms added
    5 terms: a_1 … a_5
    result
    total 25: correct

    range stops BEFORE its second argument, so range(m, n + 1) visits i = m, …, n. This is the sum Σ from i = m to n.

  • range(m, n): stops one term short Wrong total

    acc = 0
    for i in range(1, 5):
        acc += 2 * i - 1
    i takes the values
    1, 2, 3, 4
    terms added
    4 terms: a_1 … a_4
    result
    total 16, but the sum is 25 (off by −9)

    range(m, n) stops before n, so the last term a_n is never added: the loop computes the sum up to n − 1.

  • range(m − 1, n): the whole window slid down Wrong total

    acc = 0
    for i in range(1 - 1, 5):
        acc += 2 * i - 1
    i takes the values
    0, 1, 2, 3, 4
    terms added
    5 terms: a_0 … a_4
    result
    total 15, but the sum is 25 (off by −10)

    range(m - 1, n) starts one too early and stops one too early: it adds a_(m−1), …, a_(n−1), so the whole window slid down by one.

  • a[i] with a 1-based i: reads the next entry Error

    acc = 0
    for i in range(1, 5 + 1):
        acc += a[i]
    i takes the values
    1, 2, 3, 4, 5
    terms added
    4 terms: a_2 … a_5
    result
    IndexError: a[5] does not exist: the list a = [a_1, …, a_5] has indices 0 to 4.

    The list is zero-based: a[i] holds a_(i+1), not a_i. Using the mathematical index i directly reads the NEXT term each time, and the last access runs off the end of the list.

  • a[i − 1]: the right entry of a zero-based list Correct

    acc = 0
    for i in range(1, 5 + 1):
        acc += a[i - 1]
    i takes the values
    1, 2, 3, 4, 5
    terms added
    5 terms: a_1 … a_5
    result
    total 25: correct

    Keep the mathematical index i in the loop and subtract 1 only when you index the zero-based list: a[i - 1] is a_i. This computes the correct sum.

range(m, n + 1) and a[i − 1] give the sum 25. 2 loops return a wrong total without any warning. 1 loop stops with an error.

Choose the terms and the bounds. Each card is one loop: the indices it visits, the terms it adds, and what it returns. The correct loop and the shifted-list loop always agree with the exact sum; the others are the classic slips. With n below m no loop has any pass and all five return 0, so no off-by-one shows.

Read the cards in turn for the odd numbers from 11 to 55. The loop that stops one early misses the last term. The loop whose window slides down by one adds a term before the start and drops the last one. The unshifted list loop reads one position too far and Python raises an error, and a version with a negative index would not raise at all but would silently read the end of the list. Then change the terms to the whole numbers: the extra term at i=0i=0 is 00, so the slid window and the loop that stops early return the same total, and the total alone cannot tell you which slip you made. A single test case is never proof that the bounds are right.

Double sums: rows, columns and triangles

A sum inside a sum

Try these

Cells in the sum
Add the cells
Entries

from 1 to 5, whole numbers only

from 1 to 5, whole numbers only

Rows i, columns j. Totals of each row on the right, of each column underneath.
1234row total
1123410
2246820
33691230
column total612182460

A grid of 3 rows and 4 columns. Each entry is i times j. Row totals are shown at the right and column totals underneath.

acc=48+12=60\mathrm{acc}=48+12=60

Cell 12 of 12: row 3, column 4, a_34 = 12, so acc = 48 + 12 = 60.

cells in the sum, n·k
12
the double sum, in the chosen order
60
the double sum, in the other order
60

= exact

The Python being run

acc = 0
for i in range(1, 3 + 1):
    for j in range(1, 4 + 1):
        acc += i * j

Row by row and column by column visit the same 12 cells, so both orders give 60. Adding finitely many numbers in a different order does not change the total.

∑i=13∑j=14aij=60\sum_{i=1}^{3}\sum_{j=1}^{4}a_{ij}=60
∑j=14∑i=13aij=60\sum_{j=1}^{4}\sum_{i=1}^{3}a_{ij}=60
(∑i=13i)(∑j=14j)=6⋅10=60\left(\sum_{i=1}^{3}i\right)\left(\sum_{j=1}^{4}j\right)=6\cdot10=60

Choose the shape of the grid, the order in which the cells are added and the rule for the entries, then step through the cells. Each cell is outlined when it is added, and the row and column totals are listed. Choose a triangle with the data table and column-by-column order to see what happens when the bounds are not rewritten after swapping.

On a rectangle the order cannot matter: both orders add the same cells, and the stage shows the two totals agreeing. When the entry factors as a function of ii times a function of jj, the double sum is a product of two single sums, which is why the first preset needs only a few additions. On a triangle the swap needs care: the inner bounds must be rewritten, and the verdict reports what the loop would return if its bounds were not rewritten: it visits the other triangle. When the entries are symmetric, such as i⋅ji\cdot j, the other triangle happens to have the same total, so the slip goes unnoticed; a data table with unequal entries shows it.

Rewriting a sum: three rules

Rewriting a sum: three rules

Try these

from −5 to 20, whole numbers only

from −5 to 20, whole numbers only

from −5 to 5, whole numbers only

from −5 to 5, whole numbers only

Linearity

The sum of c·i + i² can be split: pull the constant c out front and add the two sums separately.

∑i=14(2 i+i2)=50\sum_{i=1}^{4}\left(2\,i+i^{2}\right)=50
2∑i=14i+∑i=14i2=2⋅10+30=502\sum_{i=1}^{4}i+\sum_{i=1}^{4}i^{2}=2\cdot10+30=50

Both sides are 50: one loop that adds c·i + i² term by term, and two separate loops combined at the end, give the same exact number.

Shifting the index

Renaming i as j − k moves both bounds by k. The term must change too.

∑i=14i2=30\sum_{i=1}^{4}i^{2}=30
∑j=36(j−2)2=30\sum_{j=3}^{6}\left(j-2\right)^{2}=30
∑j=36j2=86\sum_{j=3}^{6}j^{2}=86

Substituting j = i + 2 changes the bounds to 3 and 6 AND turns the term i² into (j − 2)², and the sum stays 30. Moving only the bounds, and keeping j², gives 86, which is off by 56: a different sum.

Gauss's pairing of m, …, n

First with last, second with second last, and so on: every pair has the same sum.

  • 1 + 4 = 5
  • 2 + 3 = 5
∑i=14i=2⋅5=10\sum_{i=1}^{4}i=2\cdot5=10

2 pairs, every one adding to 5. Total: 10.

left side of linearity
50
right side of linearity
50
shifted sum, term changed
30
shifted sum, term forgotten
86

= exact (every sum is added exactly, never as a rounded double)

Choose the bounds, a constant c and a shift k. The left panel checks linearity for the sum of c times i plus i squared, the middle panel shifts the index with and without changing the term, and the right panel pairs the first term with the last. Every value is computed exactly.

Three observations, each visible in the panels. Linearity lets you move a constant out of a sum and split a sum of two terms into two sums; both sides always agree, including for negative constants and for ranges that contain negative indices. Shifting the index is correct only when the term changes with it: moving the bounds and leaving the body alone computes a different sum. Look for the preset where the wrong shift happens to give the right number by coincidence, and notice that this is not a reason to trust it. Finally, Gauss’s pairing explains the closed form for consecutive whole numbers: the first and last add to the same total as the second and second-last, and so on. When the number of terms is odd, one middle term has no partner.

Worked examples

Five computations, each shown step by step with every number taken from the same model as the stages. Step forward and back with the buttons, or press Play; going back selects an earlier step, and nothing is undone.

Worked examples, step by step

Choose an example

∑i=311ai\sum_{i=3}^{11}a_i

How many terms does this sum have? Both bounds are included: the terms are a_3, a_4, …, a_11.

Choose an example and step through the work: counting terms, adding the whole numbers by pairing, a geometric sum by the telescoping trick, shifting an index, and swapping the order of a triangular double sum.

Executable reference code

Run the Python examples below to follow the calculations step by step. The comments record what each line returns.

First the loop itself, with exact fractions so that no rounding enters. It shows the accumulator, the empty sum, the closed forms checked against the loop, the geometric sum including the case r=1r=1, and the off-by-one variants on a zero-based list.

code/python/summation_loops.pya sum as a loop, closed forms and off-by-one slips
from fractions import Fraction

# The sum of a_i for i = m, ..., n (both ends INCLUDED) is a loop with an accumulator.
# Python's range(start, stop) stops BEFORE stop, so the loop that visits i = m, ..., n is range(m, n + 1).


def summation(term, m, n):
    acc = Fraction(0)  # the empty sum: the value before any term has been added
    for i in range(m, n + 1):
        acc += term(i)
    return acc


total = summation(lambda i: Fraction(i), 1, 5)  # 1 + 2 + 3 + 4 + 5 = 15
squares = summation(lambda i: Fraction(i) ** 2, 1, 5)  # 1 + 4 + 9 + 16 + 25 = 55
empty = summation(lambda i: Fraction(i), 3, 2)  # n < m: range(3, 3) has no values, so the sum is 0
single = summation(lambda i: Fraction(i), 4, 4)  # one term

# Closed forms are claims about EVERY n; compare them with the loop for a few bounds.
n = 10
gauss_ok = summation(lambda i: Fraction(i), 1, n) == Fraction(n * (n + 1), 2)  # n(n+1)/2
squares_ok = summation(lambda i: Fraction(i) ** 2, 1, n) == Fraction(n * (n + 1) * (2 * n + 1), 6)
odd_ok = summation(lambda i: Fraction(2 * i - 1), 1, n) == n**2  # the first n odd numbers add up to n squared

# Geometric: (1 - r) * (r**m + ... + r**n) telescopes to r**m - r**(n + 1), so for r != 1 the sum is (r**m - r**(n+1)) / (1 - r).
# Convention (stated in I.8; I.1 leaves 0**0 undefined for ordinary powers): the term r**0 of a geometric sum is 1 for every r, even r = 0,
# which is what Fraction(0) ** 0 gives. A negative power of 0 (r = 0, i < 0) is an error; an empty range(...) never evaluates it.
r = Fraction(1, 2)
geometric = summation(lambda i: r**i, 0, 4)  # 1 + 1/2 + 1/4 + 1/8 + 1/16 = 31/16
geometric_closed = (r**0 - r**5) / (1 - r)
geometric_ok = geometric == geometric_closed
all_ones = summation(lambda i: Fraction(1) ** i, 0, 4)  # r = 1 needs its own formula: n - m + 1 = 5, not 0/0

# Off-by-one: the same sum written four ways. Only the first one is the mathematical sum from 1 to 5.
a = [Fraction(2 * i - 1) for i in range(1, 6)]  # the list holds a_1, ..., a_5, so a[i - 1] is a_i (zero-based list!)
correct = sum(a[i - 1] for i in range(1, 5 + 1))  # 1 + 3 + 5 + 7 + 9 = 25
stops_early = sum(a[i - 1] for i in range(1, 5))  # range(1, 5) misses i = 5: 16, not 25
try:
    sum(a[i] for i in range(1, 5 + 1))  # a[5] does not exist: the list has indices 0 .. 4
    index_error = False
except IndexError:
    index_error = True
slid_window = sum(Fraction(2 * i - 1) for i in range(0, 5))  # reads a_0 ... a_4 = -1 + 1 + 3 + 5 + 7 = 15, not 25

# A negative index does not fail: a[-1] is the LAST entry. a_0 "exists" in code even though the list starts at a_1.
silent_wrap = a[0 - 1] == a[-1]  # True

print(total, squares, empty, single, gauss_ok, squares_ok, odd_ok, geometric, geometric_ok, all_ones, correct, stops_early, index_error, slid_window, silent_wrap)

Then double sums: rows first and columns first, the triangle with the bounds rewritten, and the same triangle with the bounds kept by mistake.

code/python/summation_nested.pydouble sums, triangles and swapping the order
from itertools import product

# A double sum is a loop inside a loop. Rows i = 1..n, columns j = 1..k, entries a[i][j] (here written with 1-based indices).
n, k = 3, 4


def entry(i, j):
    return i * j


# Rectangular: the inner bound does not depend on i. Every cell of the grid is visited exactly once.
rows_first = 0
for i in range(1, n + 1):
    for j in range(1, k + 1):
        rows_first += entry(i, j)

columns_first = 0
for j in range(1, k + 1):
    for i in range(1, n + 1):
        columns_first += entry(i, j)

same_total = rows_first == columns_first == 60  # (1+2+3)(1+2+3+4): the sum factors because entry(i, j) = i * j
cells = len(list(product(range(1, n + 1), range(1, k + 1))))  # n * k = 12 cells

# Triangular: the inner bound is the OUTER index, so the cells with j <= i form a triangle with n(n+1)/2 cells.
m = 4
by_rows = 0
for i in range(1, m + 1):
    for j in range(1, i + 1):
        by_rows += entry(i, j)

# Swapping the order must swap the bounds too: j runs 1..m and then i must run from j up to m (the cells with i >= j).
by_columns = 0
for j in range(1, m + 1):
    for i in range(j, m + 1):
        by_columns += entry(i, j)

triangle_cells = sum(1 for i in range(1, m + 1) for j in range(1, i + 1))  # 1 + 2 + 3 + 4 = 10
triangle_ok = by_rows == by_columns == 65 and triangle_cells == m * (m + 1) // 2

# The classic slip: keeping the bounds 1..i for j and 1..m for i after swapping the loops visits a DIFFERENT set of cells.
wrong = 0
for j in range(1, m + 1):
    for i in range(1, j + 1):  # now i <= j: the other triangle (the upper one)
        wrong += entry(i, j)
wrong_same_here = wrong == by_rows  # True ONLY because entry(i, j) = i * j is symmetric; not for a general table

table = [[2, -1, 0], [1, 4, -2], [0, 5, 1]]
lower = sum(table[i][j] for i in range(3) for j in range(i + 1))  # cells with j <= i: 2 + 1 + 4 + 0 + 5 + 1 = 13
upper_wrong = sum(table[i][j] for j in range(3) for i in range(j + 1))  # cells with i <= j instead: 2 + (-1) + 4 + 0 + (-2) + 1 = 4
wrong_differs = lower != upper_wrong

print(rows_first, columns_first, same_total, cells, by_rows, by_columns, triangle_ok, wrong_same_here, lower, upper_wrong, wrong_differs)

Finally the form sums take on data. NumPy is zero-based too; a dot product and a mean are sums; and the file shows that adding floating-point numbers is not exact, that the order of additions matters, and what a compensated sum does about it.

code/python/summation_numpy_float.pysigma notation with NumPy, and the limits of float addition
import math

import numpy as np

# Sigma notation as arrays. Index convention: NumPy is zero-based, the mathematics here starts at i = 1.
n = 10
i = np.arange(1, n + 1)  # the indices 1, 2, ..., n  (np.arange stops BEFORE its second argument, like range)
total_int = int(i.sum())  # 55 = n(n+1)/2, exact in integer arithmetic
squares = int((i**2).sum())  # 385

# The weighted sum, the dot product and the mean are all sums.
w = np.array([0.5, 0.25, 0.25])
x = np.array([4.0, 8.0, 12.0])
dot = float(w @ x)  # sum_i w_i x_i = 2 + 2 + 3 = 7
mean = float(x.sum() / x.size)  # (4 + 8 + 12) / 3 = 8

# Sums of doubles are NOT exact, and the order of addition matters: adding a tiny term to a huge accumulator can lose it completely.
big, tiny = 1e16, 1.0
lost = (big + tiny) - big  # 0.0: 1e16 + 1 rounds back to 1e16 (the spacing of doubles near 1e16 is 2)
kept = (big - big) + tiny  # 1.0: add the small term to a small accumulator instead

# Adding 0.1 ten times does not give 1.0 with a plain loop; math.fsum tracks the lost low-order bits and does.
acc = 0.0
for _ in range(10):
    acc += 0.1
naive_sum = acc  # 0.9999999999999999
careful_sum = math.fsum([0.1] * 10)  # 1.0
naive_is_one = naive_sum == 1.0  # False
careful_is_one = careful_sum == 1.0  # True

# For many terms the plain left-to-right loop drifts; NumPy's pairwise summation drifts much less. Compare both with the correctly rounded fsum.
values = np.full(10**5, 0.1)
exact = math.fsum(values.tolist())
pairwise_error = abs(float(values.sum()) - exact)
left_to_right = 0.0
for v in values.tolist():
    left_to_right += v
loop_error = abs(left_to_right - exact)
pairwise_wins = pairwise_error < loop_error  # True: same numbers, same exact answer, different order of additions

print(total_int, squares, dot, mean, lost, kept, naive_sum, careful_sum, naive_is_one, careful_is_one, pairwise_error, loop_error, pairwise_wins)

Explore: change the sum

These questions have definite answers; check each against the stages above.

  • In the first stage, choose the whole numbers and set the bounds to 11 and 2020. How many terms are there? What does the closed form n(n+1)2\frac{n(n+1)}{2} give, and does the loop agree?
  • Choose the squares from −3-3 to 33. The terms are symmetric around 00; is the total zero? Now choose the whole numbers from −3-3 to 33. Why is the total zero this time?
  • Make the bounds equal. How many terms are there? Now set the upper bound one below the lower bound, and then three below. What do you read, and does the number of terms become negative?
  • With the powers of a ratio, set the ratio to 22 and then to −1-1, from 00 to 44. Compare each total with the closed form. Then set the ratio to 11: why does the formula have to be replaced by a count?
  • Type your own list into the first stage, for example five numbers of your choice, and read the sum from 11 to 55. Then read it from 11 to 66: the stage shows the loop that would raise an error. Why would the same slip with a negative index raise nothing?
  • In the identities stage, set the shift to 00. Why does forgetting to change the term do no harm there? Find the preset where it does no harm by coincidence, and then change the bounds slightly to break it.

Common mistakes

Numerical considerations

Sums of whole numbers and fractions are exact. Sums of floating-point numbers are not: each addition rounds to the nearest double, the rounding errors accumulate, and the result depends on the order of the additions.

Two other numerical habits follow from the rest of the chapter. A closed form such as n(n+1)2\frac{n(n+1)}{2} is exact in integer arithmetic but can overflow a fixed-width integer for large nn; Python’s integers do not overflow, NumPy’s integer arrays do. And a geometric sum with ∣r∣<1\lvert r\rvert<1 and many terms is dominated by its first few terms: the later ones are so small that adding them to a large accumulator loses them, which is harmless there but is why the order of addition matters when the terms vary in size.

Finally, a sum of many terms with the terms of both signs can lose most of its significant digits to cancellation: two nearly equal numbers subtract to something small, and the rounding errors that were tiny compared with the numbers are no longer tiny compared with the result. Sorting by magnitude, or using a compensated sum, helps.

Connections to engineering and machine learning

  • A loss is a mean over examples. The batch loss L=1N∑iℓiL=\frac1N\sum_{i}\ell_i is a sum divided by the number of terms. If the indices run from 00 to N−1N-1 as in code, the count is NN, not N−1N-1; the mean of a batch of nothing is undefined (a division by zero), which is a different thing from the empty sum, which is 00.
  • A dot product is a sum of products. w⋅x=∑iwixi\mathbf{w}\cdot\mathbf{x}=\sum_{i}w_ix_i. A linear model, a neuron before its activation and the attention score are all sums of this kind. Rewriting them is sum algebra: linearity moves a constant weight out front, and splitting a sum splits a model across devices.
  • Softmax has a sum in its denominator. The normalizing constant ∑jezj\sum_j e^{z_j} is a sum over all classes. The numerical care of the previous section applies directly: summing very different magnitudes is exactly where order and compensation matter.
  • Sums over axes. A matrix is a double sum waiting to happen. X.sum(axis=0) adds down the rows and X.sum(axis=1) along the columns; summing everything is the double sum, and the order cannot matter for a finite grid, apart from floating-point rounding.
  • Triangular sums count pairs. The number of pairs (i,j)(i,j) with j≤ij\le i is a triangular number, and an algorithm with a loop inside a loop whose inner bound is the outer index has a running time proportional to n2n^2 with the constant 12\tfrac12 that the closed form gives. Comparing every example with every other example in a batch is this pattern.
  • Geometric sums are discounting and learning-rate schedules. A discounted return ∑tγtrt\sum_t\gamma^{t}r_t and a moving average with decay both weight terms by powers of a ratio; the closed form with ∣r∣<1\lvert r\rvert<1 tells you how much total weight a long horizon can carry.

Exercises

Answer each question and press Check. A wrong answer tells you which mistake it looks like. After one attempt you may open a worked solution.

calculation

How many terms?

How many terms does the sum Σ from i = 4 to 15 of a_i have? Then: what is the value of the sum of i for i from 7 to 3 (the upper bound is below the lower bound)?

explanation

Which loop visits the right indices?

You want Python to add a_i for i = 2, 3, …, 9, with both ends included. Which loop header visits exactly those indices?

Choose one answer

calculation

A short geometric sum

Compute the sum from i = 0 to 3 of (1/3)^i. Give the exact value as a fraction.

prediction

The ratio 1

What is the sum from i = 0 to 9 of 1^i? The usual formula (r^0 − r^10)/(1 − r) is available.

Choose one answer

construction

Shift the index

Rewrite the sum from i = 1 to 5 of i² using the new index j = i + 3. Which expression is equal to it?

Choose one answer

calculation

Counting the cells of a double sum

How many terms are added by the sum over i from 1 to 6 of the sum over j from 1 to i of 1 (a triangle of cells)? And what is the value of the sum over i from 1 to 3 of the sum over j from 1 to 4 of i·j?

construction

Swap the order of a triangular sum

The sum over i from 1 to n of the sum over j from 1 to i of a_ij adds the cells with j ≤ i. Which double sum adds exactly the same cells with the loops swapped (j outside, i inside)?

Choose one answer

calculation

The mean loss of a batch

A model's squared errors on a batch are e_0 = 4, e_1 = 1, e_2 = 9, e_3 = 0, e_4 = 6, indexed from 0 as in code. The loss is L = (1/N) times the sum of e_i over the whole batch. What are N and L?

explanation

Which rule is false?

Exactly one of these rules about sums is NOT true in general. Which one?

Choose one answer

prediction

Adding a tenth ten times

In Python, a loop starts with acc = 0.0 and runs acc += 0.1 ten times. What does the comparison acc == 1.0 evaluate to? Each term is the double 0.1, which is not exactly one tenth.

Choose one answer

Summary and checkpoints

  • A sum is a loop with an accumulator that starts at 00: for each index from the lower bound to the upper bound, both included, add the term. The index is a dummy name.
  • The number of terms is n−m+1n-m+1. When n<mn<m the sum is empty and equals 00. If a term is undefined, the whole sum is undefined; terms are never skipped.
  • Closed forms: n(n+1)2\frac{n(n+1)}{2} for 1,…,n1,\dots,n, n(n+1)(2n+1)6\frac{n(n+1)(2n+1)}{6} for the squares, n2n^2 for the first nn odd numbers, (n−m+1)c(n-m+1)c for a constant, and rm−rn+11−r\frac{r^m-r^{n+1}}{1-r} for a geometric sum when r≠1r\ne1 (and n−m+1n-m+1 when r=1r=1).
  • Valid rewrites: pull a constant out, split a sum into two sums over the same bounds, split the range, shift the index and change the term to match. Pairing the first term with the last explains the formula for consecutive whole numbers.
  • A double sum adds a grid of cells; on a rectangle the order does not matter; on a triangle the inner bound must be rewritten when the order is swapped.
  • In Python, range(m, n + 1) visits m,…,nm,\dots,n and a list is zero-based, so a one-based aia_i is a[i - 1]; a negative index wraps silently.
  • Floating-point sums round at each step and depend on the order of the additions; keep exact values, or use a compensated or pairwise sum.

Checkpoints. You can move on when you can, without the stage:

  1. Write the sum of aia_i from i=mi=m to i=ni=n as a Python loop, and say how many times the body runs.
  2. State what a sum with the upper bound below the lower bound equals, and what a sum with a term that does not exist equals.
  3. Use a closed form for a sum of consecutive whole numbers, and say when the geometric formula cannot be used.
  4. Shift the index of a sum by kk and change both the bounds and the term.
  5. Swap the order of a double sum over a triangle and write the new bounds.

The map of the book

97 of 97 chapters written · eight Parts

demo Foundation demo Try the live vector example Open as a page

Part I

Mathematical Language

The notation and mental objects everything else uses.

9 of 9 chapters written

  1. I.1 Scalars, quantities, units, powers and roots available
  2. I.2 Coordinates and points available
  3. I.3 Vectors: components, magnitude, and direction reference chapter available
  4. I.4 Algebraic expressions, equations and inequalities available
  5. I.5 Functions as input → transformation → output available
  6. I.6 Essential function families available
  7. I.7 Sets, intervals, and relations available
  8. I.8 Indexed quantities and summation available
  9. I.9 The notation map for ML available

Part II

Linear Algebra

Matrices as transformations: the strongest foundation in the book.

19 of 19 chapters written

  1. II.1 Vector addition and subtraction available
  2. II.2 Scalar multiplication and vector scaling available
  3. II.3 Norms, magnitude, and normalization available
  4. II.4 Dot product, angle, and orthogonality available
  5. II.5 Projection and distance to a direction available
  6. II.6 Linear combinations and span available
  7. II.7 Linear independence, basis, and coordinates available
  8. II.8 Matrices as linear transformations available
  9. II.9 Matrix × vector available
  10. II.10 Matrix × matrix as composition available
  11. II.11 Transpose and orthogonality available
  12. II.12 Determinant: orientation and area/volume scaling available
  13. II.13 Linear systems as intersecting constraints available
  14. II.14 Gaussian elimination and row operations available
  15. II.15 Rank and invertibility available
  16. II.16 Inverse matrices available
  17. II.17 Eigenvalues and eigenvectors available
  18. II.18 Orthogonal bases and least-squares geometry available
  19. II.19 Singular Value Decomposition available

Part III

Calculus

Change, local approximation, and sensitivity made visible.

15 of 15 chapters written

  1. III.1 Limits through approach available
  2. III.2 Continuity and local behavior available
  3. III.3 Derivative as secant → tangent available
  4. III.4 Derivative as rate of change and local linear model available
  5. III.5 Derivative rules available
  6. III.6 Chain rule available
  7. III.7 Multivariable functions and partial derivatives available
  8. III.8 Gradient and directional derivatives available
  9. III.9 Jacobian available
  10. III.10 Computational graphs and automatic differentiation available
  11. III.11 Hessian and curvature available
  12. III.12 Integrals as accumulation available
  13. III.13 Fundamental derivative/integral connection available
  14. III.14 Numerical differentiation available
  15. III.15 Numerical integration available

Part IV

Probability

Uncertainty through sample spaces, evidence, and repetition.

16 of 16 chapters written

  1. IV.1 Random experiments, outcomes, and sample spaces available
  2. IV.2 Events and probability mass available
  3. IV.3 Counting and finite probability where needed available
  4. IV.4 Conditional probability as restricted sample space available
  5. IV.5 Independence available
  6. IV.6 Bayes' theorem as probability-mass update available
  7. IV.7 Random variables available
  8. IV.8 PMF, PDF, and CDF available
  9. IV.9 Bernoulli and Binomial distributions available
  10. IV.10 Uniform and Gaussian distributions available
  11. IV.11 Expectation available
  12. IV.12 Variance and standard deviation available
  13. IV.13 Joint, marginal, and conditional distributions available
  14. IV.14 Covariance and correlation available
  15. IV.15 Law of Large Numbers available
  16. IV.16 Central Limit Theorem available

Part V

Statistics

From observed data to estimates, intervals, and likelihood.

10 of 10 chapters written

  1. V.1 Data summaries: mean, median, mode available
  2. V.2 Spread: variance, standard deviation, robust interpretation available
  3. V.3 Population, sample, and sampling variability available
  4. V.4 Estimators and estimation available
  5. V.5 Confidence intervals through repeated sampling available
  6. V.6 Covariance matrices and multivariate spread available
  7. V.7 Regression fundamentals available
  8. V.8 Likelihood available
  9. V.9 Maximum Likelihood Estimation available
  10. V.10 Maximum A Posteriori estimation available

Part VI

Optimization

Learning as repeated parameter updates governed by an objective.

10 of 10 chapters written

  1. VI.1 Objective functions and optimization landscapes available
  2. VI.2 Local vs global optima and stationary points available
  3. VI.3 Convexity available
  4. VI.4 Gradient descent flagship chapter available
  5. VI.5 Learning rate and convergence behavior available
  6. VI.6 Momentum available
  7. VI.7 Stochastic and mini-batch gradient descent available
  8. VI.8 Adam intuition available
  9. VI.9 Constraints and projected/penalized intuition available
  10. VI.10 Numerical stopping and optimization diagnostics available

Part VII

Information Theory

Logarithms, entropy, and the loss behind token prediction.

8 of 8 chapters written

  1. VII.1 Logarithms and log probabilities in computation available
  2. VII.2 Self-information available
  3. VII.3 Entropy available
  4. VII.4 Cross entropy and negative log-likelihood available
  5. VII.5 KL divergence available
  6. VII.6 Logits and softmax available
  7. VII.7 Numerical stability: log-sum-exp and stable softmax available
  8. VII.8 From token logits to language-model loss available

Part VIII

Numerical Computing

Why equivalent expressions behave differently on real machines.

10 of 10 chapters written

  1. VIII.1 Floating-point representation available
  2. VIII.2 Rounding and representation error available
  3. VIII.3 Error propagation and conditioning available
  4. VIII.4 Catastrophic cancellation available
  5. VIII.5 Overflow and underflow available
  6. VIII.6 Stable algorithms available
  7. VIII.7 Numerical linear algebra realities available
  8. VIII.8 Sparse matrices and sparse structure available
  9. VIII.9 Vectorized computation available
  10. VIII.10 Reproducibility, random seeds, and nondeterminism available