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.ml.linear.learner.perceptron; 031 032import gnu.trove.list.array.TDoubleArrayList; 033import gnu.trove.list.array.TIntArrayList; 034 035import java.util.ArrayList; 036import java.util.List; 037 038import org.openimaj.citation.annotation.Reference; 039import org.openimaj.citation.annotation.ReferenceType; 040import org.openimaj.citation.annotation.References; 041import org.openimaj.math.matrix.MatlibMatrixUtils; 042import org.openimaj.ml.linear.kernel.Kernel; 043import org.openimaj.ml.linear.learner.OnlineLearner; 044import org.openimaj.util.pair.IndependentPair; 045 046import ch.akuhn.matrix.DenseMatrix; 047import ch.akuhn.matrix.DenseVector; 048import ch.akuhn.matrix.Matrix; 049import ch.akuhn.matrix.Vector; 050 051/** 052 * 053 * @author Sina Samangooei (ss@ecs.soton.ac.uk) 054 */ 055@References(references = { 056 @Reference( 057 type = ReferenceType.Article, 058 author = { "Francesco Orabona", "Claudio Castellini", "Barbara Caputo", "Luo Jie", "Giulio Sandini" }, 059 title = "On-line independent support vector machines", 060 year = "2010", 061 journal = "Pattern Recognition", 062 pages = { "1402", "1412" }, 063 number = "4", 064 volume = "43"), 065}) 066public class OISVM implements OnlineLearner<double[], PerceptronClass>{ 067 068 069 private static final int DEFAULT_NEWTON_ITER = 20; 070 071 private Kernel<double[]> kernel; 072 073 // This is Caligraphic Beta (calB), there are B of these 074 protected List<double[]> supports = new ArrayList<double[]>(); 075 protected TIntArrayList supportIndex = new TIntArrayList(); 076 077 // This is y, holding the expected y of the supports 078 private List<PerceptronClass> expected = new ArrayList<PerceptronClass>(); 079 080 // This is K^{-1}_{calB,calB} in the paper 081 private Matrix Kinv; 082 083 // This is bold K s.t. K_ij is the kernel.appliy(support_i,support_j) 084 private Matrix K; 085 086 // the threshold of projection 087 private double eta; 088 089 // the weights, italic Beta (itB) in the paper 090 private Vector beta; 091 092 093 094 int newtonMaxIter = DEFAULT_NEWTON_ITER; 095 096 double C = 1; 097 098 private List<double[]> nonSupports = new ArrayList<double[]>(); 099 100 101 102 /** 103 * 104 * @param kernel 105 * @param eta 106 */ 107 public OISVM(Kernel<double[]> kernel, double eta) { 108 this.kernel = kernel; 109 this.eta = eta; 110 this.K = DenseMatrix.dense(0, 0); 111 this.Kinv = DenseMatrix.dense(0, 0); 112 } 113 @Override 114 public void process(double[] x, PerceptronClass y) { 115 double kii = this.kernel.apply(IndependentPair.pair(x,x)); 116 117 // First calculate optimal weighting vector d 118 Vector kt = calculatekt(x); 119 Vector k = kt.times(y.v()); 120 Vector d_optimal = Kinv.mult(k); 121 double delta = kii - d_optimal.dot(k); 122 123 if(delta > eta){ 124 updateSupports(x, y, k, kii, d_optimal,delta); 125 } 126 else { 127 updateNonSupports(x,y,kt,d_optimal); 128 } 129 130 double ot = k.dot(beta); 131 if(ot < 1){ 132 int newtonIter = 0; 133 TIntArrayList I = new TIntArrayList(); 134 while(newtonIter++ < this.newtonMaxIter){ 135 TIntArrayList newI = newtonStep(I); 136 if(newI.equals(I)){ 137 break; 138 } 139 I = newI; 140 } 141 142 } 143 144 } 145 146 private void updateSupports(double[] x, PerceptronClass y,Vector k, double kii, Vector d_optimal, double delta) { 147 this.supports.add(x); 148 this.expected.add(y); 149 supportIndex.add(this.K.columnCount()-1); 150 151 if(this.supports.size() > 1){ 152 updateKinv(d_optimal, delta); 153 updateK(k,kii); 154 updateBeta(); 155 } else { 156 init(); 157 } 158 } 159 160 private void updateNonSupports(double[] x, PerceptronClass y,Vector kk, Vector d_optimal) { 161 this.nonSupports.add(d_optimal.unwrap()); 162 MatlibMatrixUtils.appendColumn(this.K,kk); 163 164 } 165 private TIntArrayList newtonStep(TIntArrayList currentI) { 166 TIntArrayList Icols = new TIntArrayList(this.K.rowCount()); 167 TDoubleArrayList Yvals = new TDoubleArrayList(this.K.rowCount()); 168 // K . itB (i.e. kernel weighted by beta) 169 Vector v = this.K.mult(beta); 170 171 172 // g = K . itB - K_BI . (y_I - K_BI . itB) 173 for (int i = 0; i < K.rowCount(); i++) { 174 int yi = this.expected.get(i).v(); 175 double yioi = v.get(i) * yi; 176 if(1 - yioi > 0) { 177 Icols.add(i); 178 Yvals.add(yi); 179 } 180 } 181 if(currentI.equals(Icols))return Icols; 182 Matrix Kbi = MatlibMatrixUtils.subMatrix(this.K, 0, this.K.rowCount(),Icols); 183 184 // here we calculate g = K . itB - K_BI (y_I - o_I) 185 Vector g = DenseVector.wrap(Yvals.toArray()); // g = y_I 186 MatlibMatrixUtils.minusInplace(g, Kbi.mult(beta)); // g = y_I - K_BI . itB 187 g = Kbi.mult(g); // g = K_BI . (y_I - K_BI . itB) 188 g = MatlibMatrixUtils.minus(v,g); // g = K . itB - K_BI (y_I - o_I) 189 190 // Here we calculate: P = K + C * Kbi . Kbi^T, then P^-1 191 Matrix P = new DenseMatrix(K.rowCount(), K.columnCount()); // P = 0 192 MatlibMatrixUtils.dotProductTranspose(Kbi, Kbi, P); // P = Kbi . Kbi^T 193 MatlibMatrixUtils.scaleInplace(P, C); // P = C * Kbi . Kbi^T 194 MatlibMatrixUtils.plusInplace(P, K); // P = K + C * Kbi . Kbi^T 195 Matrix Pinv = MatlibMatrixUtils.fromJama(MatlibMatrixUtils.toJama(P).inverse()); 196 197 // here we update itB = itB - Pinv . g 198 199 MatlibMatrixUtils.minusInplace(beta, Pinv.mult(g)); 200 return Icols; 201 } 202 203 204 @Override 205 public PerceptronClass predict(double[] x) { 206 return PerceptronClass.fromSign(Math.signum(calculatekt(x).dot(beta))); 207 } 208 209 private void init() { 210 double[] only = this.supports.get(0); 211 this.K = DenseMatrix.dense(1, 1); 212 this.Kinv = DenseMatrix.dense(1, 1); 213 double kv = this.kernel.apply(IndependentPair.pair(only,only)); 214 Kinv.put(0, 0, 1/kv); 215 K.put(0, 0, kv); 216 this.beta = DenseVector.dense(1); 217 } 218 219 private void updateK(Vector kt, double kii) { 220 // We're updating K to: [ K kt; kt' kii] 221 Vector row = DenseVector.dense(K.columnCount()); 222 row.put(K.columnCount()-1, kii); 223 // TODO: Fill this row... K[Beta] = kt while K[~Beta] has to be calculated 224 Matrix newK = MatlibMatrixUtils.appendRow(K, row ); 225 this.K = newK; 226 } 227 228 private void updateBeta() { 229 // We're updating itB to: [ K kt; kt' kii] 230 Vector newB = DenseVector.dense(this.beta.size() + 1); 231 MatlibMatrixUtils.setSubVector(newB, 0, beta); 232 this.beta = newB; 233 } 234 235 private void updateKinv(Vector d_optimal, double delta) { 236 Matrix newKinv = null; 237 238 // We're updating Kinv by calculating: [ Kinv 0; 0... 0] + (1/delta) [d -1]' . [d -1] 239 240 // construct the column vector matrix [d -1]' 241 Matrix expandDMat = DenseMatrix.dense(d_optimal.size() + 1, 1); 242 MatlibMatrixUtils.setSubVector(expandDMat.column(0), 0, d_optimal); 243 expandDMat.column(0).put(d_optimal.size(), -1); 244 245 // construct a new, expanded Kinv matrix 246 newKinv = new DenseMatrix(Kinv.rowCount()+1, Kinv.columnCount() + 1); 247 MatlibMatrixUtils.setSubMatrix(newKinv, 0, 0, Kinv); 248 249 // construct [d -1]' [d -1] 250 Matrix expandDMult = newKinv.newInstance(); 251 MatlibMatrixUtils.dotProductTranspose(expandDMat, expandDMat, expandDMult); 252 253 // scale the new matrix by 1/delta 254 MatlibMatrixUtils.scaleInplace(expandDMult, 1/delta); 255 // add it to the new Kinv 256 MatlibMatrixUtils.plusInplace(newKinv, expandDMult); 257 this.Kinv = newKinv; 258 } 259 260 private Vector calculatekt(double[] x) { 261 Vector ret = Vector.dense(this.supports.size()); 262 for (int i = 0; i < this.supports.size(); i++) { 263 ret.put(i, this.kernel.apply(IndependentPair.pair(x,this.supports.get(i)))); 264 } 265 return ret; 266 } 267 268}