Linearized OT
We have many point clouds, each drawn from a member of a family of shapes. The aims are to find the family back from the clouds alone, and then to say which member a new cloud comes from, in the Wasserstein sense.
Point clouds are not vectors. Draw twice from the same shape, and the
This is linearized optimal transport (Wang et al., 2013). With a semi-discrete transport,
The map of a cloud
sdot.plan gives the weights
import numpy as np
import sdot
x = np.random.default_rng( 123 ).random( ( 4096, 2 ) ) # where every map is read
def transport_map( y ):
"""T( x ): the dirac of the power cell of each reading point"""
w = np.asarray( sdot.plan( sdot.SumOfDiracs( y ), sdot.Box() ).weights )
return y[ ( ( y * y ).sum( axis = 1 ) - w - 2 * x @ y.T ).argmin( axis = 1 ) ]The argmin over all the diracs is the power cell of each reading point. For many diracs, the cell complex finds it with a tree: cells, _ = sdot.PowerDiagram( y, w, boundaries = ... ).cell_complex().locate( x ).
The family of the example is a ring, centered in the square. Its inner and outer boundaries each go from a circle (
The star in the middle is where the map tears. The square has no hole and the ring has one, so no continuous map goes from one to the other. The center of the square is split between all the diracs of the inner boundary. This tear decides most of what follows.
Learning the family
200 draws of
maps = np.stack( [ transport_map( y ).ravel() for y in draws ] ) # [ 200, 4096 * 2 ]
mean = maps.mean( axis = 0 )
_, sv, modes = np.linalg.svd( maps - mean, full_matrices = False )
coords = ( maps - mean ) @ modes[ :4 ].T| mode | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| share of the variance | 0.616 | 0.081 | 0.066 | 0.058 | 0.016 | 0.012 |
The first mode is the outer boundary,
A point of the learned space is a measure:
Identifying a new draw
A new cloud is identified by its map. Its 4 coordinates
On 10 new draws, the mean error is 0.039 on
Outside of the family
A ring whose outer square is turned by 45° is not a member of the family. Its distance to the learned space, 0.057, is more than five times that of the test draws (at most 0.0104), while its unturned twin is at 0.0097. Reading its coordinates as parameters would give
The script
docs/examples/lot_dictionary.py computes everything on this page. It is NumPy only, with one sdot.plan per draw, and takes about three minutes on a laptop.
python docs/examples/lot_dictionary.py # every page and table
python docs/examples/lot_dictionary.py 1d cells # the quick ones