/*
* SvmLibSVM.java
*
* Yaoyong Li 22/03/2007
*
* $Id: SvmLibSVM.java, v 1.0 2007-03-22 12:58:16 +0000 yaoyong $
*/
package gate.learning.learners;
import gate.learning.LabelsOfFV;
import gate.learning.LogService;
import gate.learning.SparseFeatureVector;
import gate.learning.UsefulFunctions;
import gate.learning.learners.svm.svm;
import gate.learning.learners.svm.svm_node;
import gate.learning.learners.svm.svm_parameter;
import gate.learning.learners.svm.svm_problem;
import gate.learning.learners.svm.svm.decision_function;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
/**
* SVM traning using the LibSVM and SVM classification. Currently one the binary
* SVM classification is implemented.
*/
public class SvmLibSVM extends SupervisedLearner {
/** The uneven margins parameter. */
public float tau = (float)1.0;
svm_parameter param;
/** Class constructor without parameter. */
public SvmLibSVM() {
}
/** Class constructor with tau parameter. */
public SvmLibSVM(float t) {
this.tau = t;
}
/** Get the parameters from the command line. */
public void getParametersFromCommmand() {
param = new svm_parameter();
if(commandLine == null) {
if(LogService.minVerbosityLevel > 1)
System.out.println("no options specified, using the default ones!");
parseCommandLine(" ");
} else {
String[] items = commandLine.split("[ \t]+");
// Get the tau value
for(int i = 0; i < items.length; ++i)
if(items[i].equalsIgnoreCase("-tau") && i + 1 < items.length)
this.tau = new Float(items[i + 1]).floatValue();
commandLine.concat(" ");
String commandLineSVM = obtainSVMCommandline(commandLine);
if(LogService.minVerbosityLevel > 1)
System.out.println("**commandLineSVM= " + commandLineSVM);
parseCommandLine(commandLineSVM);
}
}
/** Using the methods of libSVM to training the SVM model. */
public void training(BufferedWriter modelFile,
SparseFeatureVector[] dataLearning, int totalNumFeatures,
short[] classLabels, int numTraining) {
// Set the svm problem for libsvm
svm_problem svmProb = new svm_problem();
svmProb.l = numTraining; // set the number of FVs
svmProb.y = new double[svmProb.l];
for(int i = 0; i < numTraining; ++i)
svmProb.y[i] = classLabels[i]; // set the label
svmProb.x = new svm_node[svmProb.l][];
for(int i = 0; i < numTraining; ++i) {
//int len = dataLearning[i].getLen();
//svmProb.x[i] = new svm_node[len];
//for(int j = 0; j < len; ++j) { // set each FV
//svmProb.x[i][j] = new svm_node();
//svmProb.x[i][j].index = dataLearning[i].indexes[j];
//svmProb.x[i][j].value = dataLearning[i].values[j];
//}
svmProb.x[i] = dataLearning[i].nodes;
}
// We got the parameter setting already in svmTrain
// Call the svm_train_one to train a binary classification
// set the weight for two class as 1.0, namely j=1 in svm_light
// System.out.println("svm_one learning begins... ");
// System.out.println("kernelType="+param.kernel_type);
// System.out.println("cost="+param.C);
// System.out.println("cachsize="+param.cache_size);
decision_function decisionFunc = svm
.svm_train_one(svmProb, param, param.C, param.C); //1.0, 1.0);
// System.out.println("end of the SVM_one.");
// decisionFunc includes the alphas (with *y) and rho (=-b)
// Write the svm model into the model file
try {
float b = -1 * (float)decisionFunc.rho;
if(param.kernel_type == svm_parameter.LINEAR) {
// Convert the dual form into primal form
float[] w = new float[totalNumFeatures];
for(int i = 0; i < svmProb.l; ++i) {
if(Math.abs(decisionFunc.alpha[i]) > 0)
for(int j = 0; j < svmProb.x[i].length; ++j)
w[svmProb.x[i][j].index] += svmProb.x[i][j].value
* decisionFunc.alpha[i];
}
writeLinearModelIntoFile(modelFile, b, w, totalNumFeatures);
} else {
modelFile.append(b + "\n");
int numSV = 0;
for(int i = 0; i < svmProb.l; ++i)
if(Math.abs(decisionFunc.alpha[i]) > 0) ++numSV;
modelFile.append(numSV + "\n");
for(int i = 0; i < svmProb.l; ++i) {
if(Math.abs(decisionFunc.alpha[i]) > 0) {
modelFile.append(new Double(decisionFunc.alpha[i]).toString());
for(int j = 0; j < svmProb.x[i].length; ++j)
modelFile.append(" " + svmProb.x[i][j].index + ":"
+ svmProb.x[i][j].value);
modelFile.append(" #\n");// in order to keep the same as model file
// in svm_light
}
}
}
} catch(IOException e) {
e.printStackTrace();
}
}
/** Apply the SVM model to the data. */
public void applying(BufferedReader modelFile, DataForLearning dataFVinDoc,
int totalNumFeatures, int classIndex) {
float optB;
if(isUseTau)
optB = (1 - tau) / (1 + tau);
else optB = 0;
svmApplying(modelFile, dataFVinDoc, totalNumFeatures, classIndex, optB,
param, this.isUseTauALLCases);
}
/** Applying the svm models. */
public static void svmApplying(BufferedReader modelFile,
DataForLearning dataFVinDoc, int totalNumFeatures, int classIndex,
float optB, svm_parameter svmParam, boolean isUseTauAll) {
if(svmParam.kernel_type == svm_parameter.LINEAR) {
if(LogService.minVerbosityLevel > 1)
System.out.println("Linear kernel used.");
applyLinearModel(modelFile, dataFVinDoc, totalNumFeatures, classIndex,
optB, isUseTauAll);
} else {
if(LogService.minVerbosityLevel > 1)
System.out.println("Non-linear kernel used.");
applyingDualFormModel(modelFile, dataFVinDoc, classIndex, optB, svmParam,
isUseTauAll);
}
}
/** Applying the svm models in dual form. */
public static void applyingDualFormModel(BufferedReader modelFile,
DataForLearning dataFVinDoc, int classIndex, float optB,
svm_parameter svmParam, boolean isUseTauAll) {
try {
// for each class
if(LogService.minVerbosityLevel > 1) {
System.out.println("**** classIndex=" + classIndex);
System.out.println(" d=" + svmParam.degree + ", g=" + svmParam.gamma
+ ", r=" + svmParam.coef0);
}
float b;
String[] items = modelFile.readLine().split(" ");
// Get the number of positive examples and negative examples
int[] instDist = new int[2];
obtainInstDist(items, instDist);
b = new Float(modelFile.readLine()).floatValue();
if(isUseTauAll)
b += optB;
else {
if(instDist[0] > 0 && instDist[1] / instDist[0] > 10) b += optB;
if(instDist[1] > 0 && instDist[0] / instDist[1] > 10) b -= optB;
}
int numSV;
numSV = new Integer(modelFile.readLine()).intValue();
SparseFeatureVector[] svFVs = new SparseFeatureVector[numSV];
double[] alphas = new double[numSV];
for(int i = 0; i < numSV; ++i) {
alphas[i] = readOneSV(modelFile.readLine(), svFVs, i);
}
for(int i = 0; i < dataFVinDoc.getNumTrainingDocs(); ++i) {
SparseFeatureVector[] fvs = dataFVinDoc.trainingFVinDoc[i].getFvs();
for(int j = 0; j < dataFVinDoc.trainingFVinDoc[i].getNumInstances(); ++j) {
double sum = 0.0;
for(int j1 = 0; j1 < numSV; ++j1) {
sum += alphas[j1] * kernel_function(fvs[j], svFVs[j1], svmParam);
}
sum += b;
sum = UsefulFunctions.sigmoid(sum);
dataFVinDoc.labelsFVDoc[i].multiLabels[j].probs[classIndex] = (float)sum;
}
}
} catch(NumberFormatException e) {
e.printStackTrace();
} catch(IOException e) {
e.printStackTrace();
}
}
/** Read one binary svm model. */
public static void readOneSVMModel(BufferedReader svmModelBuff, int numSV,
SparseFeatureVector[] svFVs, double[] alphas) throws IOException {
for(int i = 0; i < numSV; ++i) {
String line = svmModelBuff.readLine();
alphas[i] = readOneSV(line, svFVs, i);
}
}
/** Read one support vector of the SVM model. */
public static double readOneSV(String line, SparseFeatureVector[] svFVs, int iCounter) {
final int i = iCounter;
String[] items;
if(line.endsWith("#"))
line = line.substring(0, line.length()-1);
line = line.trim();items = line.split(" ");
double alpha = new Double(items[0]).doubleValue();
int len = 0;
len = items.length-1;
svFVs[i] = new SparseFeatureVector(len);
for(int j = 0; j < len; ++j) {
String[] indexValue = items[j+1].split(":");
svFVs[i].nodes[j].index = new Integer(indexValue[0]).intValue();
svFVs[i].nodes[j].value = new Float(indexValue[1]).floatValue();
}
return alpha;
}
/** Kernel function computation for non-linear kernel. */
public static double kernel_function(SparseFeatureVector x,
SparseFeatureVector y, svm_parameter param) {
switch(param.kernel_type){
case svm_parameter.LINEAR:
return dot(x, y);
case svm_parameter.POLY:
return powi(param.gamma * dot(x, y) + param.coef0, param.degree);
case svm_parameter.RBF: {
double sum = 0;
int xlen = x.getLen();
int ylen = y.getLen();
int i = 0;
int j = 0;
while(i < xlen && j < ylen) {
if(x.nodes[i].index == y.nodes[j].index) {
double d = x.nodes[i++].value - y.nodes[j++].value;
sum += d * d;
} else if(x.nodes[i].index > y.nodes[i].index) {
sum += y.nodes[j].value * y.nodes[j].value;
++j;
} else {
sum += x.nodes[i].value * x.nodes[i].value;
++i;
}
}
while(i < xlen) {
sum += x.nodes[i].value * x.nodes[i].value;
++i;
}
while(j < ylen) {
sum += y.nodes[j].value * y.nodes[j].value;
++j;
}
return Math.exp(-param.gamma * sum);
}
case svm_parameter.SIGMOID:
return Math.tanh(param.gamma * dot(x, y) + param.coef0);
// case svm_parameter.PRECOMPUTED:
// return x[(int)(y[0].value)].value;
default:
return 0.0;
}
}
/** A fast computation of the polynomial functions. */
private static double powi(double base, int times) {
double tmp = base, ret = 1.0;
for(int t = times; t > 0; t /= 2) {
if(t % 2 == 1) ret *= tmp;
tmp = tmp * tmp;
}
return ret;
}
/** Inner product between two sparse feature vectors. */
static double dot(SparseFeatureVector x, SparseFeatureVector y) {
double sum = 0;
int xlen = x.getLen();
int ylen = y.getLen();
int i = 0;
int j = 0;
while(i < xlen && j < ylen) {
if(x.nodes[i].index == y.nodes[j].index)
sum += x.nodes[i++].value * y.nodes[j++].value;
else {
if(x.nodes[i].index > y.nodes[j].index)
++j;
else ++i;
}
}
return sum;
}
/** Write the linear SVM model into file. */
public static void writeLinearModelIntoFile(BufferedWriter modelFile,
float b, float[] w, int totalFeatures) throws IOException {
float verySmallFloat = (float)0.000000001;
modelFile.append(b + "\n");
int num;
num = 0;
for(int i = 0; i < totalFeatures; ++i)
if(Math.abs(w[i]) > verySmallFloat) ++num;
modelFile.append(num + " "+totalFeatures+"\n");
for(int i = 0; i < totalFeatures; ++i)
if(Math.abs(w[i]) > verySmallFloat)
modelFile.append(i + " " + w[i] + "\n");
return;
}
/** Read the linear SVM model from file. */
public static float readWeightVectorFromFile(BufferedReader modelFile,
float[] w) throws NumberFormatException, IOException {
float b;
b = new Float(modelFile.readLine()).floatValue();
int num;
String[] lineItems;
lineItems = modelFile.readLine().split(" ");
num = new Integer(lineItems[0]).intValue();
int index;
float value;
for(int i = 0; i < num; ++i) {
lineItems = modelFile.readLine().split(" ");
index = new Integer(lineItems[0]).intValue();
value = new Float(lineItems[1]).floatValue();
w[index] = value;
}
return b;
}
/** Normalisation of weight vector */
public static float normalisation(float[] w, float b) {
double sum = 0;
for(int i = 0; i < w.length; ++i)
if(Math.abs(w[i]) > 0.0000000001) sum += w[i] * w[i];
sum += b * b;
sum = Math.sqrt(sum);
if(sum > 0.000000000001) {
for(int i = 0; i < w.length; ++i)
if(Math.abs(w[i]) > 0.0000000001) w[i] /= sum;
b /= sum;
}
return b;
}
/** Apply the linear SVM model to the data. */
public static void applyLinearModel(BufferedReader modelFile,
DataForLearning dataFVinDoc, int totalNumFeatures, int classIndex,
float optB, boolean isUseTauAll) {
try {
float b;
float[] w = new float[totalNumFeatures];
String items[] = modelFile.readLine().split(" ");
// Get the number of positive examples and negative examples
int[] instDist = new int[2];
obtainInstDist(items, instDist);
b = readWeightVectorFromFile(modelFile, w);
// normalise the weight vector
// b = normalisation(w, b);
// modify the b by using the uneven margins parameter tau
if(isUseTauAll)
b += optB;
else {
if(instDist[0] > 0 && instDist[1] / instDist[0] > 10) b += optB;
if(instDist[1] > 0 && instDist[0] / instDist[1] > 10) b -= optB;
}
for(int i = 0; i < dataFVinDoc.getNumTrainingDocs(); ++i) {
SparseFeatureVector[] fvs = dataFVinDoc.trainingFVinDoc[i].getFvs();
for(int j = 0; j < dataFVinDoc.trainingFVinDoc[i].getNumInstances(); ++j) {
//int[] index = fvs[j].getIndexes();
//float[] value = fvs[j].getValues();
double sum = 0.0;
for(int j1 = 0; j1 < fvs[j].getLen(); ++j1)
sum += fvs[j].nodes[j1].value * w[fvs[j].nodes[j1].index];
sum += b;
sum = UsefulFunctions.sigmoid(sum);
dataFVinDoc.labelsFVDoc[i].multiLabels[j].probs[classIndex] = (float)sum;
}
}
} catch(NumberFormatException e) {
e.printStackTrace();
} catch(IOException e) {
e.printStackTrace();
}
}
/**
* Get the numbers of positive and negative examples in the data set.
*/
static void obtainInstDist(String[] items, int[] instDist) {
for(int i = 0; i < items.length; ++i) {
if(items[i].startsWith("numTraining="))
instDist[1] = Integer.parseInt(items[i]
.substring(items[i].indexOf("=") + 1));
if(items[i].startsWith("numPos="))
instDist[0] = Integer.parseInt(items[i]
.substring(items[i].indexOf("=") + 1));
}
instDist[1] -= instDist[0];
}
/**
* Parse the command line of the SVM to obtain the parameter values for the
* LibSVM.
*/
public void parseCommandLine(String commandSVM) {
commandSVM = commandSVM.trim();
// System.out.println("The command in parse: *"+commandSVM+"*");
String[] argv = commandSVM.split("[ \t]+");
// default values
param.svm_type = svm_parameter.C_SVC;
param.kernel_type = svm_parameter.LINEAR;
param.degree = 3;
param.gamma = 1; // 1/k;
param.coef0 = 1;
param.nu = 0.5;
param.cache_size = 100;
param.C = 1;
param.eps = 1e-3;
param.p = 0.1;
param.shrinking = 1;
param.probability = 0;
param.nr_weight = 0;
param.weight_label = new int[0];
param.weight = new double[0];
// parse options
int i = 0;
while(i < argv.length) {
if(argv[i].charAt(0) != '-') {
if(LogService.minVerbosityLevel > 1)
System.out
.println("no other options specified for the SVM, using the default options.");
break;
}
++i;
if(i >= argv.length) {
break;
}
switch(argv[i - 1].charAt(1)){
case 's':
param.svm_type = Integer.parseInt(argv[i]);
break;
case 't':
param.kernel_type = Integer.parseInt(argv[i]);
break;
case 'd':
param.degree = Integer.parseInt(argv[i]);
break;
case 'g':
param.gamma = Double.parseDouble(argv[i]);
break;
case 'r':
param.coef0 = Double.parseDouble(argv[i]);
break;
case 'n':
param.nu = Double.parseDouble(argv[i]);
break;
case 'm':
param.cache_size = Double.parseDouble(argv[i]);
break;
case 'c':
param.C = Double.parseDouble(argv[i]);
break;
case 'e':
param.eps = Double.parseDouble(argv[i]);
break;
case 'p':
param.p = Double.parseDouble(argv[i]);
break;
case 'h':
param.shrinking = Integer.parseInt(argv[i]);
break;
case 'b':
param.probability = Integer.parseInt(argv[i]);
break;
case 'v':
break;
case 'w':
++param.nr_weight;
{
int[] old = param.weight_label;
param.weight_label = new int[param.nr_weight];
System
.arraycopy(old, 0, param.weight_label, 0, param.nr_weight - 1);
}
{
double[] old = param.weight;
param.weight = new double[param.nr_weight];
System.arraycopy(old, 0, param.weight, 0, param.nr_weight - 1);
}
param.weight_label[param.nr_weight - 1] = Integer
.parseInt(argv[i - 1].substring(2));
param.weight[param.nr_weight - 1] = Float.parseFloat(argv[i]);
break;
default:
System.err.print("unknown option\n");
}
++i;
}
}
/**
* Get the command line for the svm_learn, by removing the tau parameter which
* should be used for application.
*/
public static String obtainSVMCommandline(String commandLine) {
StringBuffer commandSVM = new StringBuffer();
String[] items = commandLine.split("[ \t]+");
int len = 0;
while(len < items.length) {
if(items[len].equalsIgnoreCase("-tau")) {
++len;
} else {
commandSVM.append(" " + items[len]);
}
++len;
}
return commandSVM.toString().trim();
}
/** Method for training, by reading from data file for feature vectors, and
* with label as input. */
public void trainingWithDataFile(BufferedWriter modelFile,
BufferedReader dataFile, int totalNumFeatures,
short[] classLabels, int numTraining) {
}
}