Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
weka.classifiers.mi.MILR Maven / Gradle / Ivy
Go to download
A collection of multi-instance learning classifiers. Includes the Citation KNN method, several variants of the diverse density method, support vector machines for multi-instance learning, simple wrappers for applying standard propositional learners to multi-instance data, decision tree and rule learners, and some other methods.
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
/*
* MILR.java
* Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
*
*/
package weka.classifiers.mi;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Vector;
import weka.classifiers.AbstractClassifier;
import weka.core.Capabilities;
import weka.core.Capabilities.Capability;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.MultiInstanceCapabilitiesHandler;
import weka.core.Optimization;
import weka.core.Option;
import weka.core.OptionHandler;
import weka.core.RevisionUtils;
import weka.core.SelectedTag;
import weka.core.Tag;
import weka.core.Utils;
/**
* Uses either standard or collective multi-instance
* assumption, but within linear regression. For the collective assumption, it
* offers arithmetic or geometric mean for the posteriors.
*
*
*
* Valid options are:
*
*
*
* -D
* Turn on debugging output.
*
*
*
* -R <ridge>
* Set the ridge in the log-likelihood.
*
*
*
* -A [0|1|2]
* Defines the type of algorithm:
* 0. standard MI assumption
* 1. collective MI assumption, arithmetic mean for posteriors
* 2. collective MI assumption, geometric mean for posteriors
*
*
*
*
* @author Eibe Frank ([email protected] )
* @author Xin Xu ([email protected] )
* @version $Revision: 10369 $
*/
public class MILR extends AbstractClassifier implements OptionHandler,
MultiInstanceCapabilitiesHandler {
/** for serialization */
static final long serialVersionUID = 1996101190172373826L;
protected double[] m_Par;
/** The number of the class labels */
protected int m_NumClasses;
/** The ridge parameter. */
protected double m_Ridge = 1e-6;
/** Class labels for each bag */
protected int[] m_Classes;
/** MI data */
protected double[][][] m_Data;
/** All attribute names */
protected Instances m_Attributes;
protected double[] xMean = null, xSD = null;
/** the type of processing */
protected int m_AlgorithmType = ALGORITHMTYPE_DEFAULT;
/** standard MI assumption */
public static final int ALGORITHMTYPE_DEFAULT = 0;
/** collective MI assumption, arithmetic mean for posteriors */
public static final int ALGORITHMTYPE_ARITHMETIC = 1;
/** collective MI assumption, geometric mean for posteriors */
public static final int ALGORITHMTYPE_GEOMETRIC = 2;
/** the types of algorithms */
public static final Tag[] TAGS_ALGORITHMTYPE = {
new Tag(ALGORITHMTYPE_DEFAULT, "standard MI assumption"),
new Tag(ALGORITHMTYPE_ARITHMETIC,
"collective MI assumption, arithmetic mean for posteriors"),
new Tag(ALGORITHMTYPE_GEOMETRIC,
"collective MI assumption, geometric mean for posteriors"), };
/**
* Returns the tip text for this property
*
* @return tip text for this property suitable for displaying in the
* explorer/experimenter gui
*/
public String globalInfo() {
return "Uses either standard or collective multi-instance assumption, but "
+ "within linear regression. For the collective assumption, it offers "
+ "arithmetic or geometric mean for the posteriors.";
}
/**
* Returns an enumeration describing the available options
*
* @return an enumeration of all the available options
*/
@Override
public Enumeration listOptions() {
Vector result = new Vector ();
result.addElement(new Option("\tSet the ridge in the log-likelihood.", "R",
1, "-R "));
result.addElement(new Option("\tDefines the type of algorithm:\n"
+ "\t 0. standard MI assumption\n"
+ "\t 1. collective MI assumption, arithmetic mean for posteriors\n"
+ "\t 2. collective MI assumption, geometric mean for posteriors", "A",
1, "-A [0|1|2]"));
result.addAll(Collections.list(super.listOptions()));
return result.elements();
}
/**
* Parses a given list of options.
*
* @param options the list of options as an array of strings
* @throws Exception if an option is not supported
*/
@Override
public void setOptions(String[] options) throws Exception {
String tmpStr;
tmpStr = Utils.getOption('R', options);
if (tmpStr.length() != 0) {
setRidge(Double.parseDouble(tmpStr));
} else {
setRidge(1.0e-6);
}
tmpStr = Utils.getOption('A', options);
if (tmpStr.length() != 0) {
setAlgorithmType(new SelectedTag(Integer.parseInt(tmpStr),
TAGS_ALGORITHMTYPE));
} else {
setAlgorithmType(new SelectedTag(ALGORITHMTYPE_DEFAULT,
TAGS_ALGORITHMTYPE));
}
super.setOptions(options);
Utils.checkForRemainingOptions(options);
}
/**
* Gets the current settings of the classifier.
*
* @return an array of strings suitable for passing to setOptions
*/
@Override
public String[] getOptions() {
Vector result = new Vector();
result.add("-R");
result.add("" + getRidge());
result.add("-A");
result.add("" + m_AlgorithmType);
Collections.addAll(result, super.getOptions());
return result.toArray(new String[result.size()]);
}
/**
* Returns the tip text for this property
*
* @return tip text for this property suitable for displaying in the
* explorer/experimenter gui
*/
public String ridgeTipText() {
return "The ridge in the log-likelihood.";
}
/**
* Sets the ridge in the log-likelihood.
*
* @param ridge the ridge
*/
public void setRidge(double ridge) {
m_Ridge = ridge;
}
/**
* Gets the ridge in the log-likelihood.
*
* @return the ridge
*/
public double getRidge() {
return m_Ridge;
}
/**
* Returns the tip text for this property
*
* @return tip text for this property suitable for displaying in the
* explorer/experimenter gui
*/
public String algorithmTypeTipText() {
return "The mean type for the posteriors.";
}
/**
* Gets the type of algorithm.
*
* @return the algorithm type
*/
public SelectedTag getAlgorithmType() {
return new SelectedTag(m_AlgorithmType, TAGS_ALGORITHMTYPE);
}
/**
* Sets the algorithm type.
*
* @param newType the new algorithm type
*/
public void setAlgorithmType(SelectedTag newType) {
if (newType.getTags() == TAGS_ALGORITHMTYPE) {
m_AlgorithmType = newType.getSelectedTag().getID();
}
}
private class OptEng extends Optimization {
/**
* the type to use
*
* @see MILR#TAGS_ALGORITHMTYPE
*/
private final int m_Type;
/**
* initializes the object
*
* @param type the type top use
* @see MILR#TAGS_ALGORITHMTYPE
*/
public OptEng(int type) {
super();
m_Type = type;
}
/**
* Evaluate objective function
*
* @param x the current values of variables
* @return the value of the objective function
*/
@Override
protected double objectiveFunction(double[] x) {
double nll = 0; // -LogLikelihood
switch (m_Type) {
case ALGORITHMTYPE_DEFAULT:
for (int i = 0; i < m_Classes.length; i++) { // ith bag
int nI = m_Data[i][0].length; // numInstances in ith bag
double bag = 0.0, // NLL of each bag
prod = 0.0; // Log-prob.
for (int j = 0; j < nI; j++) {
double exp = 0.0;
for (int k = m_Data[i].length - 1; k >= 0; k--) {
exp += m_Data[i][k][j] * x[k + 1];
}
exp += x[0];
exp = Math.exp(exp);
if (m_Classes[i] == 1) {
prod -= Math.log(1.0 + exp);
} else {
bag += Math.log(1.0 + exp);
}
}
if (m_Classes[i] == 1) {
bag = -Math.log(1.0 - Math.exp(prod));
}
nll += bag;
}
break;
case ALGORITHMTYPE_ARITHMETIC:
for (int i = 0; i < m_Classes.length; i++) { // ith bag
int nI = m_Data[i][0].length; // numInstances in ith bag
double bag = 0; // NLL of each bag
for (int j = 0; j < nI; j++) {
double exp = 0.0;
for (int k = m_Data[i].length - 1; k >= 0; k--) {
exp += m_Data[i][k][j] * x[k + 1];
}
exp += x[0];
exp = Math.exp(exp);
if (m_Classes[i] == 1) {
bag += 1.0 - 1.0 / (1.0 + exp); // To avoid exp infinite
} else {
bag += 1.0 / (1.0 + exp);
}
}
bag /= nI;
nll -= Math.log(bag);
}
break;
case ALGORITHMTYPE_GEOMETRIC:
for (int i = 0; i < m_Classes.length; i++) { // ith bag
int nI = m_Data[i][0].length; // numInstances in ith bag
double bag = 0; // Log-prob.
for (int j = 0; j < nI; j++) {
double exp = 0.0;
for (int k = m_Data[i].length - 1; k >= 0; k--) {
exp += m_Data[i][k][j] * x[k + 1];
}
exp += x[0];
if (m_Classes[i] == 1) {
bag -= exp / nI;
} else {
bag += exp / nI;
}
}
nll += Math.log(1.0 + Math.exp(bag));
}
break;
}
// ridge: note that intercepts NOT included
for (int r = 1; r < x.length; r++) {
nll += m_Ridge * x[r] * x[r];
}
return nll;
}
/**
* Evaluate Jacobian vector
*
* @param x the current values of variables
* @return the gradient vector
*/
@Override
protected double[] evaluateGradient(double[] x) {
double[] grad = new double[x.length];
switch (m_Type) {
case ALGORITHMTYPE_DEFAULT:
for (int i = 0; i < m_Classes.length; i++) { // ith bag
int nI = m_Data[i][0].length; // numInstances in ith bag
double denom = 0.0; // denominator, in log-scale
double[] bag = new double[grad.length]; // gradient update with ith
// bag
for (int j = 0; j < nI; j++) {
// Compute exp(b0+b1*Xi1j+...)/[1+exp(b0+b1*Xi1j+...)]
double exp = 0.0;
for (int k = m_Data[i].length - 1; k >= 0; k--) {
exp += m_Data[i][k][j] * x[k + 1];
}
exp += x[0];
exp = Math.exp(exp) / (1.0 + Math.exp(exp));
if (m_Classes[i] == 1) {
// Bug fix: it used to be denom += Math.log(1.0+exp);
// Fixed 21 Jan 2005 (Eibe)
denom -= Math.log(1.0 - exp);
}
// Instance-wise update of dNLL/dBk
for (int p = 0; p < x.length; p++) { // pth variable
double m = 1.0;
if (p > 0) {
m = m_Data[i][p - 1][j];
}
bag[p] += m * exp;
}
}
denom = Math.exp(denom);
// Bag-wise update of dNLL/dBk
for (int q = 0; q < grad.length; q++) {
if (m_Classes[i] == 1) {
grad[q] -= bag[q] / (denom - 1.0);
} else {
grad[q] += bag[q];
}
}
}
break;
case ALGORITHMTYPE_ARITHMETIC:
for (int i = 0; i < m_Classes.length; i++) { // ith bag
int nI = m_Data[i][0].length; // numInstances in ith bag
double denom = 0.0;
double[] numrt = new double[x.length];
for (int j = 0; j < nI; j++) {
// Compute exp(b0+b1*Xi1j+...)/[1+exp(b0+b1*Xi1j+...)]
double exp = 0.0;
for (int k = m_Data[i].length - 1; k >= 0; k--) {
exp += m_Data[i][k][j] * x[k + 1];
}
exp += x[0];
exp = Math.exp(exp);
if (m_Classes[i] == 1) {
denom += exp / (1.0 + exp);
} else {
denom += 1.0 / (1.0 + exp);
}
// Instance-wise update of dNLL/dBk
for (int p = 0; p < x.length; p++) { // pth variable
double m = 1.0;
if (p > 0) {
m = m_Data[i][p - 1][j];
}
numrt[p] += m * exp / ((1.0 + exp) * (1.0 + exp));
}
}
// Bag-wise update of dNLL/dBk
for (int q = 0; q < grad.length; q++) {
if (m_Classes[i] == 1) {
grad[q] -= numrt[q] / denom;
} else {
grad[q] += numrt[q] / denom;
}
}
}
break;
case ALGORITHMTYPE_GEOMETRIC:
for (int i = 0; i < m_Classes.length; i++) { // ith bag
int nI = m_Data[i][0].length; // numInstances in ith bag
double bag = 0;
double[] sumX = new double[x.length];
for (int j = 0; j < nI; j++) {
// Compute exp(b0+b1*Xi1j+...)/[1+exp(b0+b1*Xi1j+...)]
double exp = 0.0;
for (int k = m_Data[i].length - 1; k >= 0; k--) {
exp += m_Data[i][k][j] * x[k + 1];
}
exp += x[0];
if (m_Classes[i] == 1) {
bag -= exp / nI;
for (int q = 0; q < grad.length; q++) {
double m = 1.0;
if (q > 0) {
m = m_Data[i][q - 1][j];
}
sumX[q] -= m / nI;
}
} else {
bag += exp / nI;
for (int q = 0; q < grad.length; q++) {
double m = 1.0;
if (q > 0) {
m = m_Data[i][q - 1][j];
}
sumX[q] += m / nI;
}
}
}
for (int p = 0; p < x.length; p++) {
grad[p] += Math.exp(bag) * sumX[p] / (1.0 + Math.exp(bag));
}
}
break;
}
// ridge: note that intercepts NOT included
for (int r = 1; r < x.length; r++) {
grad[r] += 2.0 * m_Ridge * x[r];
}
return grad;
}
/**
* Returns the revision string.
*
* @return the revision
*/
@Override
public String getRevision() {
return RevisionUtils.extract("$Revision: 10369 $");
}
}
/**
* Returns default capabilities of the classifier.
*
* @return the capabilities of this classifier
*/
@Override
public Capabilities getCapabilities() {
Capabilities result = super.getCapabilities();
result.disableAll();
// attributes
result.enable(Capability.NOMINAL_ATTRIBUTES);
result.enable(Capability.RELATIONAL_ATTRIBUTES);
// class
result.enable(Capability.BINARY_CLASS);
result.enable(Capability.MISSING_CLASS_VALUES);
// other
result.enable(Capability.ONLY_MULTIINSTANCE);
return result;
}
/**
* Returns the capabilities of this multi-instance classifier for the
* relational data.
*
* @return the capabilities of this object
* @see Capabilities
*/
@Override
public Capabilities getMultiInstanceCapabilities() {
Capabilities result = super.getCapabilities();
result.disableAll();
// attributes
result.enable(Capability.NOMINAL_ATTRIBUTES);
result.enable(Capability.NUMERIC_ATTRIBUTES);
result.enable(Capability.DATE_ATTRIBUTES);
result.enable(Capability.MISSING_VALUES);
// class
result.disableAllClasses();
result.enable(Capability.NO_CLASS);
return result;
}
/**
* Builds the classifier
*
* @param train the training data to be used for generating the boosted
* classifier.
* @throws Exception if the classifier could not be built successfully
*/
@Override
public void buildClassifier(Instances train) throws Exception {
// can classifier handle the data?
getCapabilities().testWithFail(train);
// remove instances with missing class
train = new Instances(train);
train.deleteWithMissingClass();
m_NumClasses = train.numClasses();
int nR = train.attribute(1).relation().numAttributes();
int nC = train.numInstances();
m_Data = new double[nC][nR][]; // Data values
m_Classes = new int[nC]; // Class values
m_Attributes = train.attribute(1).relation();
xMean = new double[nR]; // Mean of mean
xSD = new double[nR]; // Mode of stddev
double sY1 = 0, sY0 = 0; // Number of classes
int[] missingbags = new int[nR];
if (m_Debug) {
System.out.println("Extracting data...");
}
for (int h = 0; h < m_Data.length; h++) {
Instance current = train.instance(h);
m_Classes[h] = (int) current.classValue(); // Class value starts from 0
Instances currInsts = current.relationalValue(1);
int nI = currInsts.numInstances();
for (int i = 0; i < nR; i++) {
// initialize m_data[][][]
m_Data[h][i] = new double[nI];
double avg = 0, std = 0, num = 0;
for (int k = 0; k < nI; k++) {
if (!currInsts.instance(k).isMissing(i)) {
m_Data[h][i][k] = currInsts.instance(k).value(i);
avg += m_Data[h][i][k];
std += m_Data[h][i][k] * m_Data[h][i][k];
num++;
} else {
m_Data[h][i][k] = Double.NaN;
}
}
if (num > 0) {
xMean[i] += avg / num;
xSD[i] += std / num;
} else {
missingbags[i]++;
}
}
// Class count
if (m_Classes[h] == 1) {
sY1++;
} else {
sY0++;
}
}
for (int j = 0; j < nR; j++) {
xMean[j] = xMean[j] / (nC - missingbags[j]);
xSD[j] = Math.sqrt(Math.abs(xSD[j] / (nC - missingbags[j] - 1.0)
- xMean[j] * xMean[j] * (nC - missingbags[j])
/ (nC - missingbags[j] - 1.0)));
}
if (m_Debug) {
// Output stats about input data
System.out.println("Descriptives...");
System.out.println(sY0 + " bags have class 0 and " + sY1
+ " bags have class 1");
System.out.println("\n Variable Avg SD ");
for (int j = 0; j < nR; j++) {
System.out.println(Utils.doubleToString(j, 8, 4)
+ Utils.doubleToString(xMean[j], 10, 4)
+ Utils.doubleToString(xSD[j], 10, 4));
}
}
// Normalise input data and remove ignored attributes
for (int i = 0; i < nC; i++) {
for (int j = 0; j < nR; j++) {
for (int k = 0; k < m_Data[i][j].length; k++) {
if (xSD[j] != 0) {
if (!Double.isNaN(m_Data[i][j][k])) {
m_Data[i][j][k] = (m_Data[i][j][k] - xMean[j]) / xSD[j];
} else {
m_Data[i][j][k] = 0;
}
}
}
}
}
if (m_Debug) {
System.out.println("\nIteration History...");
}
double x[] = new double[nR + 1];
x[0] = Math.log((sY1 + 1.0) / (sY0 + 1.0));
double[][] b = new double[2][x.length];
b[0][0] = Double.NaN;
b[1][0] = Double.NaN;
for (int q = 1; q < x.length; q++) {
x[q] = 0.0;
b[0][q] = Double.NaN;
b[1][q] = Double.NaN;
}
OptEng opt = new OptEng(m_AlgorithmType);
opt.setDebug(m_Debug);
m_Par = opt.findArgmin(x, b);
while (m_Par == null) {
m_Par = opt.getVarbValues();
if (m_Debug) {
System.out.println("200 iterations finished, not enough!");
}
m_Par = opt.findArgmin(m_Par, b);
}
if (m_Debug) {
System.out.println(" ---------------------------");
}
// feature selection use
if (m_AlgorithmType == ALGORITHMTYPE_ARITHMETIC) {
double[] fs = new double[nR];
for (int k = 1; k < nR + 1; k++) {
fs[k - 1] = Math.abs(m_Par[k]);
}
int[] idx = Utils.sort(fs);
double max = fs[idx[idx.length - 1]];
for (int k = idx.length - 1; k >= 0; k--) {
System.out.println(m_Attributes.attribute(idx[k]).name() + "\t"
+ (fs[idx[k]] * 100 / max));
}
}
// Convert coefficients back to non-normalized attribute units
for (int j = 1; j < nR + 1; j++) {
if (xSD[j - 1] != 0) {
m_Par[j] /= xSD[j - 1];
m_Par[0] -= m_Par[j] * xMean[j - 1];
}
}
}
/**
* Computes the distribution for a given exemplar
*
* @param exmp the exemplar for which distribution is computed
* @return the distribution
* @throws Exception if the distribution can't be computed successfully
*/
@Override
public double[] distributionForInstance(Instance exmp) throws Exception {
// Extract the data
Instances ins = exmp.relationalValue(1);
int nI = ins.numInstances(), nA = ins.numAttributes();
double[][] dat = new double[nI][nA + 1];
for (int j = 0; j < nI; j++) {
dat[j][0] = 1.0;
int idx = 1;
for (int k = 0; k < nA; k++) {
if (!ins.instance(j).isMissing(k)) {
dat[j][idx] = ins.instance(j).value(k);
} else {
dat[j][idx] = xMean[idx - 1];
}
idx++;
}
}
// Compute the probability of the bag
double[] distribution = new double[2];
switch (m_AlgorithmType) {
case ALGORITHMTYPE_DEFAULT:
distribution[0] = 0.0; // Log-Prob. for class 0
for (int i = 0; i < nI; i++) {
double exp = 0.0;
for (int r = 0; r < m_Par.length; r++) {
exp += m_Par[r] * dat[i][r];
}
exp = Math.exp(exp);
// Prob. updated for one instance
distribution[0] -= Math.log(1.0 + exp);
}
// Prob. for class 0
distribution[0] = Math.exp(distribution[0]);
// Prob. for class 1
distribution[1] = 1.0 - distribution[0];
break;
case ALGORITHMTYPE_ARITHMETIC:
distribution[0] = 0.0; // Prob. for class 0
for (int i = 0; i < nI; i++) {
double exp = 0.0;
for (int r = 0; r < m_Par.length; r++) {
exp += m_Par[r] * dat[i][r];
}
exp = Math.exp(exp);
// Prob. updated for one instance
distribution[0] += 1.0 / (1.0 + exp);
}
// Prob. for class 0
distribution[0] /= nI;
// Prob. for class 1
distribution[1] = 1.0 - distribution[0];
break;
case ALGORITHMTYPE_GEOMETRIC:
for (int i = 0; i < nI; i++) {
double exp = 0.0;
for (int r = 0; r < m_Par.length; r++) {
exp += m_Par[r] * dat[i][r];
}
distribution[1] += exp / nI;
}
// Prob. for class 1
distribution[1] = 1.0 / (1.0 + Math.exp(-distribution[1]));
// Prob. for class 0
distribution[0] = 1 - distribution[1];
break;
}
return distribution;
}
/**
* Gets a string describing the classifier.
*
* @return a string describing the classifer built.
*/
@Override
public String toString() {
String result = "Modified Logistic Regression";
if (m_Par == null) {
return result + ": No model built yet.";
}
result += "\nMean type: "
+ getAlgorithmType().getSelectedTag().getReadable() + "\n";
result += "\nCoefficients...\n" + "Variable Coeff.\n";
for (int j = 1, idx = 0; j < m_Par.length; j++, idx++) {
result += m_Attributes.attribute(idx).name();
result += " " + Utils.doubleToString(m_Par[j], 12, 4);
result += "\n";
}
result += "Intercept:";
result += " " + Utils.doubleToString(m_Par[0], 10, 4);
result += "\n";
result += "\nOdds Ratios...\n" + "Variable O.R.\n";
for (int j = 1, idx = 0; j < m_Par.length; j++, idx++) {
result += " " + m_Attributes.attribute(idx).name();
double ORc = Math.exp(m_Par[j]);
result += " "
+ ((ORc > 1e10) ? "" + ORc : Utils.doubleToString(ORc, 12, 4));
}
result += "\n";
return result;
}
/**
* Returns the revision string.
*
* @return the revision
*/
@Override
public String getRevision() {
return RevisionUtils.extract("$Revision: 10369 $");
}
/**
* Main method for testing this class.
*
* @param argv should contain the command line arguments to the scheme (see
* Evaluation)
*/
public static void main(String[] argv) {
runClassifier(new MILR(), argv);
}
}