File: distances.md

package info (click to toggle)
mlpack 4.7.0-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 32,064 kB
  • sloc: cpp: 233,202; python: 1,940; sh: 1,201; lisp: 414; makefile: 85
file content (422 lines) | stat: -rw-r--r-- 16,411 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
# Distances

mlpack includes a number of distance metrics for its distance-based techniques.
These all implement the [same API](../../developer/distances.md), providing one
`Evaluate()` method.  mlpack provides a number of supported distance metrics:

 * [`LMetric`](#lmetric): generalized L-metric/Lp-metric, including
   Manhattan/Euclidean/Chebyshev distances
 * [`IoUDistance`](#ioudistance): intersection-over-union distance
 * [`IPMetric<KernelType>`](#ipmetrickerneltype): inner product metric (e.g.
   induced metric over a [Mercer kernel](kernels.md))
 * [`MahalanobisDistance`](#mahalanobisdistance): weighted Euclidean distance
   with weights specified by a covariance matrix
 * [Implement a custom distance metric](../../developer/distances.md)

---

These distance metrics can be used with a variety of different techniques,
including:

<!-- TODO: better names for each link -->

 * [`KNN`](../methods/knn.md)
 * [`NeighborSearch`](/src/mlpack/methods/neighbor_search/neighbor_search.hpp)
 * [`RangeSearch`](/src/mlpack/methods/range_search/range_search.hpp)
 * [`LMNN`](../methods/lmnn.md)
 * [`EMST`](/src/mlpack/methods/emst/emst.hpp)
 * [`NCA`](../methods/nca.md)
 * [`RANN`](/src/mlpack/methods/rann/rann.hpp)
 * [`KMeans`](/src/mlpack/methods/kmeans/kmeans.hpp)

## `LMetric`

The `LMetric` template class implements a [generalized
L-metric](https://en.wikipedia.org/wiki/Lp_space#Preliminaries)
(L1-metric, L2-metric, etc.).  The class has two template parameters:

```
LMetric<Power, TakeRoot>
```

 * `Power` is an `int` representing the type of the metric; e.g., `2` would
   represent the L2-metric (Euclidean distance).
   - `Power` must be `1` or greater.
   - If `Power` is `INT_MAX`, the metric is the L-infinity distance (Chebyshev
     distance).

 * `TakeRoot` is a `bool` (default `true`) indicating whether the root of the
   distance should be taken.
   - If set to `false`, the metric will no longer satisfy the triangle
     inequality.

---

Several convenient typedefs are available:

 * `ManhattanDistance` (defined as `LMetric<1>`)
 * `EuclideanDistance` (defined as `LMetric<2>`)
 * `SquaredEuclideanDistance` (defined as `LMetric<2, false>`)
 * `ChebyshevDistance` (defined as `LMetric<INT_MAX>`)

---

The static `Evaluate()` method can be used to compute the distance between two
vectors.

*Note:* The vectors given to `Evaluate()` can have any type so long as the type
implements the Armadillo API (e.g. `arma::fvec`, `arma::sp_fvec`, etc.).

---

*Example usage:*

```c++
// Create two vectors: [0, 1.0, 5.0] and [1.0, 3.0, 5.0].
arma::vec a("0.0 1.0 5.0");
arma::vec b("1.0 3.0 5.0");

const double d1 = mlpack::ManhattanDistance::Evaluate(a, b);        // d1 = 3.0
const double d2 = mlpack::EuclideanDistance::Evaluate(a, b);        // d2 = 2.24
const double d3 = mlpack::SquaredEuclideanDistance::Evaluate(a, b); // d3 = 5.0
const double d4 = mlpack::ChebyshevDistance::Evaluate(a, b);        // d4 = 2.0
const double d5 = mlpack::LMetric<4>::Evaluate(a, b);               // d5 = 2.03
const double d6 = mlpack::LMetric<3, false>::Evaluate(a, b);        // d6 = 9.0

std::cout << "Manhattan distance:         " << d1 << "." << std::endl;
std::cout << "Euclidean distance:         " << d2 << "." << std::endl;
std::cout << "Squared Euclidean distance: " << d3 << "." << std::endl;
std::cout << "Chebyshev distance:         " << d4 << "." << std::endl;
std::cout << "L4-distance:                " << d5 << "." << std::endl;
std::cout << "Cubed L3-distance:          " << d6 << "." << std::endl;

// Compute the distance between two random 10-dimensional vectors in a matrix.
arma::mat m(10, 100, arma::fill::randu);

const double d7 = mlpack::EuclideanDistance::Evaluate(m.col(0), m.col(7));

std::cout << std::endl;
std::cout << "Distance between two random vectors: " << d7 << "." << std::endl;
std::cout << std::endl;

// Compute the distance between two 32-bit precision `float` vectors.
arma::fvec fa("0.0 1.0 5.0");
arma::fvec fb("1.0 3.0 5.0");

const double d8 = mlpack::EuclideanDistance::Evaluate(fa, fb); // d8 = 2.236

std::cout << "Euclidean distance (fvec): " << d8 << "." << std::endl;
```

## `IoUDistance`

The `IoUDistance` class implements the intersection-over-union distance metric,
a measure of the overlap between two bounding boxes related to the
[Jaccard index](https://en.wikipedia.org/wiki/Jaccard_index).

For two bounding boxes, the `IoUDistance` is computed as
`1 - (area of intersection / area of union)`.
If the bounding boxes overlap completely, the distance is 0; if they do not
overlap at all, the distance is 1.

---

The class has a boolean template parameter `UseCoordinates` that controls how
bounding boxes are specified.

 * `IoUDistance<>` (or `IoUDistance<false>`) expects bounding boxes to be
   provided to the `Evaluate()` as four-element vectors of the form
   `[x0, y0, h, w]`, where:
    - `(x0, y0)` is the lower left corner of the bounding box,
    - `h` is the height of the bounding box, and
    - `w` is the width of the bounding box.

 * `IoUDistance<true>` expects bounding boxes to be provided to the `Evaluate()`
   as four-element vectors of the form `[x0, y0, x1, y1]`, where:
    - `(x0, y0)` is the lower left corner of the bounding box, and
    - `(x1, y1)` is the upper right corner of the bounding box.

---

The static `Evaluate()` method can be used to compute the IoU distance between
two bounding boxes.

If either input vector does not have four elements, an exception will be thrown.

*Note:* The vectors given to `Evaluate()` can have any type so long as the type
implements the Armadillo API (e.g. `arma::vec`, `arma::fvec`, etc.).  The use of
sparse objects is not recommended to represent bounding boxes (as they are in
general not sparse).

---

*Example usage:*

```c++
// Create three bounding boxes by representing the lower left and size.
arma::vec bb1("0.0 0.0 3.0 5.0"); // Lower left at (0, 0), height=3, width=5.
arma::vec bb2("2.0 2.0 5.0 2.0"); // Lower left at (2, 2), height=5, width=2.
arma::vec bb3("1.0 1.0 1.5 1.0"); // Lower left at (1, 1), height=1.5, width=1.

// Represent the same three bounding boxes in lower left/upper right form.
arma::vec bb1Coord("0.0 0.0 5.0 3.0"); // Upper right is (5, 3).
arma::vec bb2Coord("2.0 2.0 4.0 7.0"); // Upper right is (4, 7).
arma::vec bb3Coord("1.0 1.0 2.0 2.5"); // Upper right is (2, 2.5).

// Compute the distance between each of the bounding boxes using the
// height/width representation.
const double d1 = mlpack::IoUDistance<>::Evaluate(bb1, bb2);
const double d2 = mlpack::IoUDistance<>::Evaluate(bb2, bb3);
const double d3 = mlpack::IoUDistance<>::Evaluate(bb1, bb3);

std::cout << "IoUDistance with width/height bounding box representations:"
    << std::endl;
std::cout << " - ll=(0, 0), h=3, w=5 and ll=(2, 2), h=5, w=2:   " << d1
    << "." << std::endl;
std::cout << " - ll=(0, 0), h=3, w=5 and ll=(1, 1), h=1.5, w=1: " << d3
    << "." << std::endl;
std::cout << " - ll=(2, 2), h=5, w=2 and ll=(1, 1), h=1.5, w=1: " << d2
    << "." << std::endl;

// Now compute the same distances with the other representation.
const double d1Coord = mlpack::IoUDistance<true>::Evaluate(bb1Coord, bb2Coord);
const double d2Coord = mlpack::IoUDistance<true>::Evaluate(bb2Coord, bb3Coord);
const double d3Coord = mlpack::IoUDistance<true>::Evaluate(bb1Coord, bb3Coord);

std::cout << "IoUDistance with two-coordinate bounding box representations:"
    << std::endl;
std::cout << "(same bounding boxes as above)" << std::endl;
std::cout << " - ll=(0, 0), ur=(5, 3) and ll=(2, 2), ur=(4, 7):   " << d1Coord
    << "." << std::endl;
std::cout << " - ll=(0, 0), ur=(5, 3) and ll=(1, 1), ur=(2, 2.5): " << d3Coord
    << "." << std::endl;
std::cout << " - ll=(2, 2), ur=(4, 7) and ll=(1, 1), ur=(2, 2.5): " << d2Coord
    << "." << std::endl;
```

## `IPMetric<KernelType>`

The `IPMetric<KernelType>` class implements the distance metric induced by the
given [`KernelType`](kernels.md).  This computes distances in
[kernel space](https://en.wikipedia.org/wiki/Kernel_method#Mathematics:_the_kernel_trick).
Using the fact that a kernel `k(x, y)` (represented by `KernelType`) implements
an inner product in kernel space, the `IPMetric` distance is defined as

```
d(x, y) = sqrt(k(x, x) + k(y, y) - 2 k(x, y)).
```

The template parameter `KernelType` can be any of mlpack's
[kernels](kernels.md), or a
[custom kernel](kernels.md#implement-a-custom-kernel).

This metric is used by the [FastMKS](/src/mlpack/methods/fastmks/fastmks.hpp)
method (fast max-kernel search).

### Constructors and properties

 * `d = IPMetric<KernelType>()`
   - Construct a new `IPMetric` using a default-constructed `KernelType`.
   - A default constructor for `KernelType` must be available
     (e.g. `k = KernelType()`).

 * `d = IPMetric<KernelType>(kernel)`
   - Construct a new `IPMetric` using the given `kernel` (a `KernelType`
     object).
   - `kernel` is not copied; ensure that `kernel` does not go out of scope while
     `d` is in use.

 * `d = IPMetric<KernelType>(other)`
   - Copy constructor: create a new `IPMetric` from the given `IPMetric`
     `other`.
   - This copies the internally-held `KernelType`.

 * The copy operator (`d = other;`) will also copy the internally-held
   `KernelType`.

 * The internally-held `KernelType` can be accessed with `d.Kernel()`.

### Distance evaluation

 * `d.Evaluate(x1, x2)`
   - Evaluate and return the distance in kernel space between two vectors `x1`
     and `x2`.
   - `x1` and `x2` should be vector types that implement the Armadillo API (e.g.
     `arma::vec`, `arma::sp_vec`, etc.).
   - `x1` and `x2` must be valid inputs to the `Evaluate()` function of the
     given `KernelType`.

### Example usage

```c++
// Create a few random points.
arma::vec x1(3, arma::fill::randu);
arma::vec x2(3, arma::fill::randu);
arma::vec x3(3, arma::fill::randu);

// Create a metric on the Epanechnikov kernel.
mlpack::EpanechnikovKernel ek(1.5 /* bandwidth */);
mlpack::IPMetric<mlpack::EpanechnikovKernel> ip1(ek);

// Compute distances in kernel space, and compare with kernel evaluations.
std::cout << "x1: " << x1.t();
std::cout << "x2: " << x2.t();
std::cout << "x3: " << x3.t();
std::cout << std::endl;

std::cout << "  ek(x1, x2): " << ek.Evaluate(x1, x2) << "." << std::endl;
std::cout << "  ip(x1, x2): " << ip1.Evaluate(x1, x2) << "." << std::endl;
std::cout << std::endl;

std::cout << "  ek(x2, x3): " << ek.Evaluate(x2, x3) << "." << std::endl;
std::cout << "  ip(x2, x3): " << ip1.Evaluate(x2, x3) << "." << std::endl;
std::cout << std::endl;

std::cout << "  ek(x1, x3): " << ek.Evaluate(x1, x3) << "." << std::endl;
std::cout << "  ip(x1, x3): " << ip1.Evaluate(x1, x3) << "." << std::endl;
std::cout << std::endl;

// Change the bandwidth of the kernel.
ip1.Kernel().Bandwidth(2.0);
std::cout << "With bandwidth 2.0:" << std::endl;
std::cout << "  ek(x1, x3): " << ek.Evaluate(x1, x3) << "." << std::endl;
std::cout << "  ip(x1, x3): " << ip1.Evaluate(x1, x3) << "." << std::endl;
std::cout << std::endl;

// Now create a metric on the LinearKernel.
// This one is a bit of a trick!  For the LinearKernel, the induced metric is
// exactly the Euclidean distance.
mlpack::IPMetric<mlpack::LinearKernel> ip2;

std::cout << "  Euclidean distance between x1/x2:     "
    << mlpack::EuclideanDistance::Evaluate(x1, x2) << "." << std::endl;
std::cout << "  IPMetric<LinearKernel> between x1/x2: "
    << ip2.Evaluate(x1, x2) << "." << std::endl;

// Compute the kernel space distance between two floating-point vectors.
arma::fvec fx1(10, arma::fill::randu);
arma::fvec fx2(10, arma::fill::randu);

std::cout << "IPMetric<EpanechnikovKernel> result between two random "
    << "10-dimensional 32-bit floating point vectors:" << std::endl;
std::cout << "  " << ip1.Evaluate(fx1, fx2) << "." << std::endl;
```

## `MahalanobisDistance`

The `MahalanobisDistance` class implements the weighted Euclidean distance known
as the
[Mahalanobis distance](https://en.wikipedia.org/wiki/Mahalanobis_distance).
This distance requires an inverse covariance matrix `Q` that controls the
weighting of individual dimensions in the distance calculation.  The metric is
defined as:

```
d_Q(x, y) = sqrt((x - y)^T Q (x - y))
```

The class has two template parameters:

```
MahalanobisDistance<TakeRoot = true, MatType = arma::mat>
```

 * When `TakeRoot` is manually specified as `false`, the `sqrt()` is omitted.
   This is slightly faster, but will cause the distance to no longer satisfy the
   triangle inequality.

 * `MatType` is the matrix type used to represent `Q`, and should be a matrix
   type satisfying the Armadillo API (e.g.  `arma::mat`, `arma::fmat`).

***Notes:***

 - Many descriptions of the Mahalanobis distance use the term `C^-1` instead of
   `Q` as used here.  Ensure that the given `Q` matrix is the inverted
   covariance (you can use, e.g.,
   [`arma::pinv()`](https://arma.sourceforge.net/docs.html#pinv)).

 - Instead of using `MahalanobisDistance` directly as a distance metric for
   mlpack machine learning algorithms, it can often be faster to simply multiply
   the dataset by the equivalent transformation implied by `Q` and then use that
   modified dataset with the Euclidean distance directly.  See the example usage
   below.

### Constructors and properties

 * `md = MahalanobisDistance()`
   - Create a `MahalanobisDistance` object without initializing the inverse
     covariance `Q`.
   - Call `Q()` to set the matrix before calling `Evaluate()`.

 * `md = MahalanobisDistance(dimensionality)`
   - Create a `MahalanobisDistance` where `Q` is the identity matrix of the
     given `dimensionality`.
   - This distance metric will be equivalent to the Euclidean distance.

 * `md = MahalanobisDistance(matQ)`
   - Create a `MahalanobisDistance` with the given `Q` matrix.
   - `matQ` must be positive definite and symmetric.

 * `md.Q()`
   - Access or modify the `Q` matrix.
   - For instance, to set the `Q` matrix, `md.Q() = myCustomQ;` can be used.
   - The `Q` matrix must be positive definite and symmetric.

### Distance evaluation

 * `md.Evaluate(x1, x2)`
   - Evaluate and return the Mahalanobis distance between two vectors `x1` and
     `x2`.
   - `x1` and `x2` should be vector types with element type equivalent to the
     element type of `MatType` (e.g. `arma::vec`, `arma::fvec`, etc.).

### Example usage

```c++
// Create random 10-dimensional data.
arma::mat dataset(10, 100, arma::fill::randu);

// Create a positive-definite Q matrix by using a weighting matrix W such that
// Q = W^T W.
arma::mat W(10, 10, arma::fill::randu);
arma::mat Q = W.t() * W;

// Create a MahalanobisDistance object with the given Q.
mlpack::MahalanobisDistance md(std::move(Q));

std::cout << "Mahalanobis distance between points 3 and 4: "
    << md.Evaluate(dataset.col(3), dataset.col(4)) << "." << std::endl;

// Now compare the Mahalanobis distance with the Euclidean distance on the
// dataset transformed with W.  (They are the same!)
arma::mat transformedDataset = W * dataset;
std::cout << "Mahalanobis distance between points 2 and 71:           "
    << md.Evaluate(dataset.col(2), dataset.col(71)) << "." << std::endl;
std::cout << "Euclidean distance between transformed points 2 and 71: "
    << mlpack::EuclideanDistance::Evaluate(transformedDataset.col(2),
                                           transformedDataset.col(71))
    << "." << std::endl;

// Create a Mahalanobis distance for 32-bit floating point data.
arma::fmat floatDataset(20, 100, arma::fill::randn);

// Use a random diagonal matrix as Q.
arma::fmat fQ = arma::diagmat(arma::randu<arma::fvec>(20));

mlpack::MahalanobisDistance<false /* do not take square root */,
                            arma::fmat> fmd;
fmd.Q() = std::move(fQ);

const double d1 = fmd.Evaluate(floatDataset.col(3), floatDataset.col(5));
const double d2 = fmd.Evaluate(floatDataset.col(11), floatDataset.col(31));

std::cout << "Squared Mahalanobis distance on 32-bit floating point data:"
    << std::endl;
std::cout << " - Points 3 and 5:   " << d1 << "." << std::endl;
std::cout << " - Points 11 and 31: " << d2 << "." << std::endl;

// Note that an equivalent transformation matrix can be recovered from Q with
// an upper Cholesky decomposition (Q -> R.t() * R).
arma::mat recoveredW = arma::chol(md.Q(), "lower");
// A transformed dataset can be created with `(recoveredW * dataset)`.
```