The problem
Models represent words, documents and images as vectors: lists of numbers, where similar things get vectors that are close to each other. A very common job is to compare every vector of one set with every vector of another, for example to find which stored documents are closest to a question (retrieval, section 15). The result is a table of distances: one row per vector of the first set, one column per vector of the second.
You get two tables of vectors:
a, with rows: row is the vector ;b, with rows: row is the vector ;- both have columns, one per coordinate.
Return the table whose entry is the squared distance between and .
Squared distance
To compare two vectors: subtract them coordinate by coordinate, square each difference, and add up.
where:
- is coordinate of vector , and is coordinate of vector ;
- is the squared length of a vector : the sum of its coordinates squared;
- it is the usual distance of geometry, without the square root (which does not change which vectors are closest).
Example. .
ahas the rows and ;bhas the rows and .
- : , and .
- : , and .
- : , and .
- : , and .
The result is the table
[[25, 1], [13, 1]].
Method 1: all the differences at once (too much memory)
Broadcasting can build every difference in one expression: a[:, None, :] - b[None, :, :] has shape , one difference vector for every pair. Squaring and summing over the last axis gives the answer.
This is a good way to check your answer on small inputs, but it stores numbers. For and that is 11.5 million numbers, 92 MB (megabytes), only to keep 90,000 of them; with real data it grows to gigabytes.
Method 2: expand the square (little memory)
The square of a difference can be expanded, just like for numbers:
where is the dot product: multiply coordinate by coordinate and add up. Each of the three parts is cheap:
- for every : one number per row of
a, a column of numbers; - for every : one number per row of
b, a row of numbers; - for every pair: exactly the matrix product
a @ b.T, an table.
Example. Same vectors as above.
- Squared lengths of
a: , .- Squared lengths of
b: , .- Dot products
a @ b.T:[[0, 0], [7, 1]], for example .Entry : , the same as method 1. The whole table is again
[[25, 1], [13, 1]], and nothing bigger than was ever stored.
What to implement
pairwise_sq_dists(a, b): a has shape , b has shape . Return the table of squared distances.
Requirements
- No Python loops.
- Every distance is , and the distance of a vector to itself is (up to rounding).
- Memory: one test uses , and fails if memory use goes above 20 MB, so method 1 does not pass. Use method 2.
Hints
- Write method 1 first and keep it to check method 2 on small inputs.
- In method 2, the squared lengths of
amust be a column, shape , and those ofba row, shape . Added together, broadcasting turns them into the table of (see Broadcasting, example 2, on the theory page). - Rounding can make the result slightly negative, such as , when and are almost equal. Replace negative values with 0:
np.maximum(d, 0.0).