/*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
* MILR.java
* Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
*
*/
package weka.classifiers.mi;
import weka.classifiers.Classifier;
import weka.classifiers.AbstractClassifier;
import weka.core.Capabilities;
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;
import weka.core.Capabilities.Capability;
import java.util.Enumeration;
import java.util.Vector;
/**
* 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 (eibe@cs.waikato.ac.nz)
* @author Xin Xu (xx5@cs.waikato.ac.nz)
* @version $Revision: 5928 $
*/
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
*/
public Enumeration listOptions() {
Vector result = new Vector();
result.addElement(new Option(
"\tTurn on debugging output.",
"D", 0, "-D"));
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]"));
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
*/
public void setOptions(String[] options) throws Exception {
String tmpStr;
setDebug(Utils.getFlag('D', options));
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));
}
}
/**
* Gets the current settings of the classifier.
*
* @return an array of strings suitable for passing to setOptions
*/
public String[] getOptions() {
Vector result;
result = new Vector();
if (getDebug())
result.add("-D");
result.add("-R");
result.add("" + getRidge());
result.add("-A");
result.add("" + m_AlgorithmType);
return (String[]) 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 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
*/
protected double objectiveFunction(double[] x){
double nll = 0; // -LogLikelihood
switch (m_Type) {
case ALGORITHMTYPE_DEFAULT:
for(int i=0; i=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=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 /= (double)nI;
nll -= Math.log(bag);
}
break;
case ALGORITHMTYPE_GEOMETRIC:
for(int i=0; i=0; k--)
exp += m_Data[i][k][j]*x[k+1];
exp += x[0];
if(m_Classes[i]==1)
bag -= exp/(double)nI;
else
bag += exp/(double)nI;
}
nll += Math.log(1.0+Math.exp(bag));
}
break;
}
// ridge: note that intercepts NOT included
for(int r=1; r=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; p0) 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=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; p0) 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=0; k--)
exp += m_Data[i][k][j]*x[k+1];
exp += x[0];
if(m_Classes[i]==1){
bag -= exp/(double)nI;
for(int q=0; q0) m=m_Data[i][q-1][j];
sumX[q] -= m/(double)nI;
}
}
else{
bag += exp/(double)nI;
for(int q=0; q0) m=m_Data[i][q-1][j];
sumX[q] += m/(double)nI;
}
}
}
for(int p=0; p 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]/(double)(nC-missingbags[j]);
xSD[j] = Math.sqrt(Math.abs(xSD[j]/((double)(nC-missingbags[j])-1.0)
-xMean[j]*xMean[j]*(double)(nC-missingbags[j])/
((double)(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=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
*/
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 1e10) ? "" + ORc : Utils.doubleToString(ORc, 12, 4));
}
result += "\n";
return result;
}
/**
* Returns the revision string.
*
* @return the revision
*/
public String getRevision() {
return RevisionUtils.extract("$Revision: 5928 $");
}
/**
* 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);
}
}