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}