001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package org.openimaj.math.matrix; 031 032import Jama.Matrix; 033import ch.akuhn.matrix.Vector; 034import ch.akuhn.matrix.eigenvalues.SingularValues; 035 036/** 037 * Thin SVD based on Adrian Kuhn's wrapper around ARPACK. This can scale to 038 * really large matrices (bigger than RAM), given an implementation of 039 * {@link ch.akuhn.matrix.Matrix} that is backed by disk. 040 * <p> 041 * Note that the current version of (Java)ARPACK is not thread-safe. Allowances 042 * have been made in this implementation to synchronize the call to 043 * {@link SingularValues#decompose()} against the 044 * {@link ThinSingularValueDecomposition} class. Care must be taken if you are 045 * using JARPACK outside this class in a multi-threaded application. 046 * 047 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 048 * 049 */ 050public class ThinSingularValueDecomposition { 051 /** The U matrix */ 052 public Matrix U; 053 /** The singular values */ 054 public double[] S; 055 /** The transpose of the V matrix */ 056 public Matrix Vt; 057 058 /** 059 * Perform thin SVD on matrix, calculating at most ndims dimensions. 060 * 061 * @param matrix 062 * the matrix 063 * @param ndims 064 * the number of singular values/vectors to calculate; actual 065 * number may be less. 066 */ 067 public ThinSingularValueDecomposition(Matrix matrix, int ndims) { 068 this(new JamaDenseMatrix(matrix), ndims); 069 } 070 071 /** 072 * Perform thin SVD on matrix, calculating at most ndims dimensions. 073 * 074 * @param matrix 075 * the matrix 076 * @param ndims 077 * the number of singular values/vectors to calculate; actual 078 * number may be less. 079 */ 080 public ThinSingularValueDecomposition(ch.akuhn.matrix.Matrix matrix, int ndims) { 081 // FIXME: I'm (Jon) not sure why this was added, but it causes problems 082 // with big matrices... commented it out for the time being 083 // if (ndims > Math.min(matrix.rowCount(), matrix.columnCount())) { 084 // try { 085 // final no.uib.cipr.matrix.DenseMatrix mjtA = new 086 // no.uib.cipr.matrix.DenseMatrix(matrix.asArray()); 087 // no.uib.cipr.matrix.SVD svd; 088 // svd = no.uib.cipr.matrix.SVD.factorize(mjtA); 089 // this.S = svd.getS(); 090 // this.U = MatrixUtils.convert(svd.getU()); 091 // this.Vt = MatrixUtils.convert(svd.getVt()); 092 // 093 // this.S = Arrays.copyOf(this.S, Math.min(ndims, this.S.length)); 094 // this.U = U.getMatrix(0, U.getRowDimension() - 1, 0, Math.min(ndims, 095 // U.getColumnDimension()) - 1); 096 // this.Vt = Vt.getMatrix(0, Math.min(Vt.getRowDimension(), ndims) - 1, 097 // 0, Vt.getColumnDimension() - 1); 098 // } catch (final NotConvergedException e) { 099 // throw new RuntimeException(e); 100 // } 101 // } else { 102 final SingularValues sv = new SingularValues(matrix, ndims); 103 104 // Note: SingularValues uses JARPACK which isn't currently 105 // thread-safe :-( 106 synchronized (ThinSingularValueDecomposition.class) { 107 sv.decompose(); 108 } 109 110 S = reverse(sv.value); 111 U = vectorArrayToMatrix(sv.vectorLeft, false); 112 Vt = vectorArrayToMatrix(sv.vectorRight, true); 113 // } 114 } 115 116 protected double[] reverse(double[] vector) { 117 for (int i = 0; i < vector.length / 2; i++) { 118 final double tmp = vector[i]; 119 vector[i] = vector[vector.length - i - 1]; 120 vector[vector.length - i - 1] = tmp; 121 } 122 return vector; 123 } 124 125 protected Matrix vectorArrayToMatrix(Vector[] vectors, boolean rows) { 126 final int m = vectors.length; 127 128 final double[][] data = new double[m][]; 129 130 for (int i = 0; i < m; i++) 131 data[m - i - 1] = vectors[i].unwrap(); 132 133 Matrix mat = new Matrix(data); 134 135 if (!rows) { 136 mat = mat.transpose(); 137 } 138 return mat; 139 } 140 141 /** 142 * @return The S matrix 143 */ 144 public Matrix getSmatrix() { 145 final Matrix Smat = new Matrix(S.length, S.length); 146 147 for (int r = 0; r < S.length; r++) 148 Smat.set(r, r, S[r]); 149 150 return Smat; 151 } 152 153 /** 154 * @return The sqrt of the singular vals as a matrix. 155 */ 156 public Matrix getSmatrixSqrt() { 157 final Matrix Smat = new Matrix(S.length, S.length); 158 159 for (int r = 0; r < S.length; r++) 160 Smat.set(r, r, Math.sqrt(S[r])); 161 162 return Smat; 163 } 164 165 /** 166 * Reduce the rank of the input matrix using the thin SVD to get a lower 167 * rank least-squares estimate of the input. 168 * 169 * @param m 170 * matrix to reduce the rank of 171 * @param rank 172 * the desired rank 173 * @return the rank-reduced matrix 174 */ 175 public static Matrix reduceRank(Matrix m, int rank) { 176 if (rank > Math.min(m.getColumnDimension(), m.getRowDimension())) { 177 return m; 178 } 179 180 final ThinSingularValueDecomposition t = new ThinSingularValueDecomposition(m, rank); 181 return t.U.times(t.getSmatrix()).times(t.Vt); 182 } 183}