/**
* Copyright (c) 2010, Regents of the University of Colorado All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer. Redistributions in binary
* form must reproduce the above copyright notice, this list of conditions and
* the following disclaimer in the documentation and/or other materials provided
* with the distribution. Neither the name of the University of Colorado at
* Boulder nor the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package clear.train.algorithm;
import clear.train.AbstractTrainer;
import clear.train.kernel.AbstractKernel;
/**
* LibLinear L2-SVM algorithm.
*
* @author Jinho D. Choi <b>Last update:</b> 11/4/2010
*/
public class LibLinearL2 implements IAlgorithm {
private byte i_lossType;
private double d_c;
private double d_eps;
private double d_bias;
public LibLinearL2(byte lossType, double c, double eps, double bias) {
i_lossType = lossType;
d_c = c;
d_eps = eps;
d_bias = bias;
}
@Override
public double[] getWeight(AbstractKernel kernel, int currLabel) {
final int MAX_ITER = 1000;
// Random rand = new Random(0);
double[] QD = new double[kernel.N];
double[] alpha = new double[kernel.N];
double[] weight = new double[kernel.D];
double U, G, d, alpha_old;
int[] index = new int[kernel.N];
byte[] aY = new byte[kernel.N];
int active_size = kernel.N;
int i, j, s, iter;
byte yi;
int[] xi;
double[] vi = null;
// PG: projected gradient, for shrinking and stopping
double PG;
double PGmax_old = Double.POSITIVE_INFINITY;
double PGmin_old = Double.NEGATIVE_INFINITY;
double PGmax_new, PGmin_new;
// for loss function
double[] diag = {0, 0, 0};
double[] upper_bound = {d_c, 0, d_c};
if (i_lossType == 2) {
diag[0] = 0.5 / d_c;
diag[2] = 0.5 / d_c;
upper_bound[0] = Double.POSITIVE_INFINITY;
upper_bound[2] = Double.POSITIVE_INFINITY;
}
for (i = 0; i < kernel.N; i++) {
index[i] = i;
aY[i] = (kernel.a_ys.get(i) == currLabel) ? (byte) 1 : (byte) -1;
QD[i] = diag[GETI(aY, i)];
if (kernel.b_binary) {
QD[i] += kernel.a_xs.get(i).length;
} else {
for (double value : kernel.a_vs.get(i)) {
QD[i] += (value * value);
}
}
if (d_bias > 0) {
QD[i] += (d_bias * d_bias);
}
}
for (iter = 0; iter < MAX_ITER; iter++) {
PGmax_new = Double.NEGATIVE_INFINITY;
PGmin_new = Double.POSITIVE_INFINITY;
/*
* for (i=0; i<active_size; i++) { int j = i +
* r_rand.nextInt(active_size - i); swap(index, i, j);
}
*/
for (s = 0; s < active_size; s++) {
i = index[s];
yi = aY[i];
xi = kernel.a_xs.get(i);
if (!kernel.b_binary) {
vi = kernel.a_vs.get(i);
}
G = (d_bias > 0) ? weight[0] * d_bias : 0;
for (j = 0; j < xi.length; j++) {
if (kernel.b_binary) {
G += weight[xi[j]];
} else {
G += (weight[xi[j]] * vi[j]);
}
}
G = G * yi - 1;
G += alpha[i] * diag[GETI(aY, i)];
U = upper_bound[GETI(aY, i)];
if (alpha[i] == 0) {
if (G > PGmax_old) {
active_size--;
swap(index, s, active_size);
s--;
continue;
}
PG = Math.min(G, 0);
} else if (alpha[i] == U) {
if (G < PGmin_old) {
active_size--;
swap(index, s, active_size);
s--;
continue;
}
PG = Math.max(G, 0);
} else {
PG = G;
}
PGmax_new = Math.max(PGmax_new, PG);
PGmin_new = Math.min(PGmin_new, PG);
if (Math.abs(PG) > 1.0e-12) {
alpha_old = alpha[i];
alpha[i] = Math.min(Math.max(alpha[i] - G / QD[i], 0.0), U);
d = (alpha[i] - alpha_old) * yi;
if (d_bias > 0) {
weight[0] += d * d_bias;
}
for (j = 0; j < xi.length; j++) {
if (kernel.b_binary) {
weight[xi[j]] += d;
} else {
weight[xi[j]] += (d * vi[j]);
}
}
}
}
if (PGmax_new - PGmin_new <= d_eps) {
if (active_size == kernel.N) {
break;
} else {
active_size = kernel.N;
PGmax_old = Double.POSITIVE_INFINITY;
PGmin_old = Double.NEGATIVE_INFINITY;
continue;
}
}
PGmax_old = PGmax_new;
PGmin_old = PGmin_new;
if (PGmax_old <= 0) {
PGmax_old = Double.POSITIVE_INFINITY;
}
if (PGmin_old >= 0) {
PGmin_old = Double.NEGATIVE_INFINITY;
}
}
int nSV = 0;
for (i = 0; i < kernel.N; i++) {
if (alpha[i] > 0) {
++nSV;
}
}
StringBuilder build = new StringBuilder();
build.append("- label = ");
build.append(currLabel);
build.append(": iter = ");
build.append(iter);
build.append(", nSV = ");
build.append(nSV);
AbstractTrainer.out.println(build.toString());
return weight;
}
private int GETI(byte[] y, int i) {
return y[i] + 1;
}
private void swap(int[] array, int idxA, int idxB) {
int temp = array[idxA];
array[idxA] = array[idxB];
array[idxB] = temp;
}
}