summaryrefslogtreecommitdiff
path: root/src/de/lmu/ifi/dbs/elki/distance/similarityfunction/kernel/KernelMatrix.java
blob: ed3731ba5a48c36010a44ee9beae5612c6f20985 (plain)
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
package de.lmu.ifi.dbs.elki.distance.similarityfunction.kernel;

/*
 This file is part of ELKI:
 Environment for Developing KDD-Applications Supported by Index-Structures

 Copyright (C) 2013
 Ludwig-Maximilians-Universität München
 Lehr- und Forschungseinheit für Datenbanksysteme
 ELKI Development Team

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU Affero General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Affero General Public License for more details.

 You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;

import de.lmu.ifi.dbs.elki.data.FeatureVector;
import de.lmu.ifi.dbs.elki.database.ids.ArrayDBIDs;
import de.lmu.ifi.dbs.elki.database.ids.DBIDUtil;
import de.lmu.ifi.dbs.elki.database.relation.Relation;
import de.lmu.ifi.dbs.elki.distance.distancevalue.DoubleDistance;
import de.lmu.ifi.dbs.elki.distance.similarityfunction.PrimitiveSimilarityFunction;
import de.lmu.ifi.dbs.elki.logging.LoggingUtil;
import de.lmu.ifi.dbs.elki.math.linearalgebra.Matrix;

/**
 * Provides a class for storing the kernel matrix and several extraction methods
 * for convenience.
 * 
 * @author Simon Paradies
 * 
 * @apiviz.uses de.lmu.ifi.dbs.elki.distance.similarityfunction.PrimitiveSimilarityFunction
 */
public class KernelMatrix {
  /**
   * The kernel matrix
   */
  Matrix kernel;
  
  /**
   * Wraps the matrixArray in a KernelMatrix
   * 
   * @param matrixArray two dimensional double array
   */
  public KernelMatrix(final double[][] matrixArray) {
    kernel = new Matrix(matrixArray);
  }

  /**
   * Provides a new kernel matrix.
   * 
   * @param kernelFunction the kernel function used to compute the kernel matrix
   * @param database the database for which the kernel matrix is computed
   * 
   * @deprecated ID mapping is not reliable!
   */
  @Deprecated
  public <O extends FeatureVector<?>> KernelMatrix(final PrimitiveSimilarityFunction<? super O, DoubleDistance> kernelFunction, final Relation<? extends O> database) {
    this(kernelFunction, database, DBIDUtil.ensureArray(database.getDBIDs()));
  }

  /**
   * Provides a new kernel matrix.
   * 
   * @param kernelFunction the kernel function used to compute the kernel matrix
   * @param database the database that holds the objects
   * @param ids the IDs of those objects for which the kernel matrix is computed
   */
  public <O extends FeatureVector<?>> KernelMatrix(final PrimitiveSimilarityFunction<? super O, DoubleDistance> kernelFunction, final Relation<? extends O> database, final ArrayDBIDs ids) {
    LoggingUtil.logExpensive(Level.FINER, "Computing kernel matrix");
    kernel = new Matrix(ids.size(), ids.size());
    double value;
    for(int idx = 0; idx < ids.size(); idx++) {
      for(int idy = idx; idy < ids.size(); idy++) {
        value = kernelFunction.similarity(database.get(ids.get(idx)), database.get(ids.get(idy))).doubleValue();
        kernel.set(idx, idy, value);
        kernel.set(idy, idx, value);
      }
    }
  }

  /**
   * Makes a new kernel matrix from matrix (with data copying).
   * 
   * @param matrix a matrix
   */
  public KernelMatrix(final Matrix matrix) {
    kernel = matrix.copy();
  }

  /**
   * Returns the kernel distance between the two specified objects.
   * 
   * @param o1 first ObjectID
   * @param o2 second ObjectID
   * @return the distance between the two objects
   */
  // FIXME: really use objectids!
  public double getDistance(final int o1, final int o2) {
    return Math.sqrt(getSquaredDistance(o1, o2));
  }

  /**
   * Get the kernel matrix.
   * 
   * @return kernel
   */
  public Matrix getKernel() {
    return kernel;
  }

  /**
   * Returns the kernel value of object o1 and object o2
   * 
   * @param o1 ID of first object
   * @param o2 ID of second object
   * @return the kernel value of object o1 and object o2
   */
  public double getSimilarity(final int o1, final int o2) {
    return kernel.get(o1 - 1, o2 - 1); // correct index shifts.
  }

  /**
   * Returns the squared kernel distance between the two specified objects.
   * 
   * @param o1 first ObjectID
   * @param o2 second ObjectID
   * @return the distance between the two objects
   */
  public double getSquaredDistance(final int o1, final int o2) {
    return getSimilarity(o1, o1) + getSimilarity(o2, o2) - 2 * getSimilarity(o1, o2);
  }

  /**
   * Returns the ith kernel matrix column for all objects in ids
   * 
   * @param i the column which should be returned
   * @param ids the objects
   * @return the ith kernel matrix column for all objects in ids
   */
  public Matrix getSubColumn(final int i, final List<Integer> ids) {
    final int[] ID = new int[1];
    ID[0] = i - 1; // correct index shift
    final int[] IDs = new int[ids.size()];
    for(int x = 0; x < IDs.length; x++) {
      IDs[x] = ids.get(x) - 1; // correct index shift
    }
    return kernel.getMatrix(IDs, ID);
  }

  /**
   * Returns a sub kernel matrix for all objects in ids
   * 
   * @param ids the objects
   * @return a sub kernel matrix for all objects in ids.
   */
  public Matrix getSubMatrix(final Collection<Integer> ids) {
    final int[] IDs = new int[ids.size()];
    int i = 0;
    for(Iterator<Integer> it = ids.iterator(); it.hasNext(); i++) {
      IDs[i] = it.next() - 1; // correct index shift
    }
    return kernel.getMatrix(IDs, IDs);
  }

  /**
   * Centers the matrix in feature space according to Smola et. Schoelkopf,
   * Learning with Kernels p. 431 Alters the input matrix. If you still need the
   * original matrix, use
   * <code>centeredMatrix = centerKernelMatrix(uncenteredMatrix.copy()) {</code>
   * 
   * @param matrix the matrix to be centered
   * @return centered matrix (for convenience)
   */
  public static Matrix centerMatrix(final Matrix matrix) {
    final Matrix normalizingMatrix = new Matrix(matrix.getRowDimensionality(), matrix.getColumnDimensionality(), 1.0 / matrix.getColumnDimensionality());
    return matrix.minusEquals(normalizingMatrix.times(matrix)).minusEquals(matrix.times(normalizingMatrix)).plusEquals(normalizingMatrix.times(matrix).times(normalizingMatrix));
  }

  @Override
  public String toString() {
    return super.toString();
  }

  /**
   * Centers the Kernel Matrix in Feature Space according to Smola et.
   * Schoelkopf, Learning with Kernels p. 431 Alters the input matrix. If you
   * still need the original matrix, use
   * <code>centeredMatrix = centerKernelMatrix(uncenteredMatrix.copy()) {</code>
   * 
   * @param kernelMatrix the kernel matrix to be centered
   * @return centered kernelMatrix (for convenience)
   */
  public static Matrix centerKernelMatrix(final KernelMatrix kernelMatrix) {
    return centerMatrix(kernelMatrix.getKernel());
  }
}