/*
* 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.
*/
/*
* MINND.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.DenseInstance;
import weka.core.Instances;
import weka.core.MultiInstanceCapabilitiesHandler;
import weka.core.Option;
import weka.core.OptionHandler;
import weka.core.RevisionUtils;
import weka.core.TechnicalInformation;
import weka.core.TechnicalInformationHandler;
import weka.core.Utils;
import weka.core.Capabilities.Capability;
import weka.core.TechnicalInformation.Field;
import weka.core.TechnicalInformation.Type;
import java.util.Enumeration;
import java.util.Vector;
/**
* Multiple-Instance Nearest Neighbour with Distribution learner.
*
* It uses gradient descent to find the weight for each dimension of each exeamplar from the starting point of 1.0. In order to avoid overfitting, it uses mean-square function (i.e. the Euclidean distance) to search for the weights.
* It then uses the weights to cleanse the training data. After that it searches for the weights again from the starting points of the weights searched before.
* Finally it uses the most updated weights to cleanse the test exemplar and then finds the nearest neighbour of the test exemplar using partly-weighted Kullback distance. But the variances in the Kullback distance are the ones before cleansing.
*
* For more information see:
*
* Xin Xu (2001). A nearest distribution approach to multiple-instance learning. Hamilton, NZ.
*
* @misc{Xu2001, * address = {Hamilton, NZ}, * author = {Xin Xu}, * note = {0657.591B}, * school = {University of Waikato}, * title = {A nearest distribution approach to multiple-instance learning}, * year = {2001} * } ** * * Valid options are: * *
-K <number of neighbours> * Set number of nearest neighbour for prediction * (default 1)* *
-S <number of neighbours> * Set number of nearest neighbour for cleansing the training data * (default 1)* *
-E <number of neighbours> * Set number of nearest neighbour for cleansing the testing data * (default 1)* * * @author Xin Xu (xx5@cs.waikato.ac.nz) * @version $Revision: 5987 $ */ public class MINND extends AbstractClassifier implements OptionHandler, MultiInstanceCapabilitiesHandler, TechnicalInformationHandler { /** for serialization */ static final long serialVersionUID = -4512599203273864994L; /** The number of nearest neighbour for prediction */ protected int m_Neighbour = 1; /** The mean for each attribute of each exemplar */ protected double[][] m_Mean = null; /** The variance for each attribute of each exemplar */ protected double[][] m_Variance = null; /** The dimension of each exemplar, i.e. (numAttributes-2) */ protected int m_Dimension = 0; /** header info of the data */ protected Instances m_Attributes;; /** The class label of each exemplar */ protected double[] m_Class = null; /** The number of class labels in the data */ protected int m_NumClasses = 0; /** The weight of each exemplar */ protected double[] m_Weights = null; /** The very small number representing zero */ static private double m_ZERO = 1.0e-45; /** The learning rate in the gradient descent */ protected double m_Rate = -1; /** The minimum values for numeric attributes. */ private double [] m_MinArray=null; /** The maximum values for numeric attributes. */ private double [] m_MaxArray=null; /** The stopping criteria of gradient descent*/ private double m_STOP = 1.0e-45; /** The weights that alter the dimnesion of each exemplar */ private double[][] m_Change=null; /** The noise data of each exemplar */ private double[][] m_NoiseM = null, m_NoiseV = null, m_ValidM = null, m_ValidV = null; /** The number of nearest neighbour instances in the selection of noises in the training data*/ private int m_Select = 1; /** The number of nearest neighbour exemplars in the selection of noises in the test data */ private int m_Choose = 1; /** The decay rate of learning rate */ private double m_Decay = 0.5; /** * Returns a string describing this filter * * @return a description of the filter suitable for * displaying in the explorer/experimenter gui */ public String globalInfo() { return "Multiple-Instance Nearest Neighbour with Distribution learner.\n\n" + "It uses gradient descent to find the weight for each dimension of " + "each exeamplar from the starting point of 1.0. In order to avoid " + "overfitting, it uses mean-square function (i.e. the Euclidean " + "distance) to search for the weights.\n " + "It then uses the weights to cleanse the training data. After that " + "it searches for the weights again from the starting points of the " + "weights searched before.\n " + "Finally it uses the most updated weights to cleanse the test exemplar " + "and then finds the nearest neighbour of the test exemplar using " + "partly-weighted Kullback distance. But the variances in the Kullback " + "distance are the ones before cleansing.\n\n" + "For more information see:\n\n" + getTechnicalInformation().toString(); } /** * Returns an instance of a TechnicalInformation object, containing * detailed information about the technical background of this class, * e.g., paper reference or book this class is based on. * * @return the technical information about this class */ public TechnicalInformation getTechnicalInformation() { TechnicalInformation result; result = new TechnicalInformation(Type.MISC); result.setValue(Field.AUTHOR, "Xin Xu"); result.setValue(Field.YEAR, "2001"); result.setValue(Field.TITLE, "A nearest distribution approach to multiple-instance learning"); result.setValue(Field.SCHOOL, "University of Waikato"); result.setValue(Field.ADDRESS, "Hamilton, NZ"); result.setValue(Field.NOTE, "0657.591B"); return result; } /** * Returns default capabilities of the classifier. * * @return the capabilities of this classifier */ public Capabilities getCapabilities() { Capabilities result = super.getCapabilities(); result.disableAll(); // attributes result.enable(Capability.NOMINAL_ATTRIBUTES); result.enable(Capability.RELATIONAL_ATTRIBUTES); result.enable(Capability.MISSING_VALUES); // class result.enable(Capability.NOMINAL_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 */ public Capabilities getMultiInstanceCapabilities() { Capabilities result = super.getCapabilities(); result.disableAll(); // 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; } /** * As normal Nearest Neighbour algorithm does, it's lazy and simply * records the exemplar information (i.e. mean and variance for each * dimension of each exemplar and their classes) when building the model. * There is actually no need to store the exemplars themselves. * * @param exs the training exemplars * @throws Exception if the model cannot be built properly */ public void buildClassifier(Instances exs)throws Exception{ // can classifier handle the data? getCapabilities().testWithFail(exs); // remove instances with missing class Instances newData = new Instances(exs); newData.deleteWithMissingClass(); int numegs = newData.numInstances(); m_Dimension = newData.attribute(1).relation().numAttributes(); m_Attributes = newData.stringFreeStructure(); m_Change = new double[numegs][m_Dimension]; m_NumClasses = exs.numClasses(); m_Mean = new double[numegs][m_Dimension]; m_Variance = new double[numegs][m_Dimension]; m_Class = new double[numegs]; m_Weights = new double[numegs]; m_NoiseM = new double[numegs][m_Dimension]; m_NoiseV = new double[numegs][m_Dimension]; m_ValidM = new double[numegs][m_Dimension]; m_ValidV = new double[numegs][m_Dimension]; m_MinArray = new double[m_Dimension]; m_MaxArray = new double[m_Dimension]; for(int v=0; v < m_Dimension; v++) m_MinArray[v] = m_MaxArray[v] = Double.NaN; for(int w=0; w < numegs; w++){ updateMinMax(newData.instance(w)); } // Scale exemplars Instances data = m_Attributes; for(int x=0; x < numegs; x++){ Instance example = newData.instance(x); example = scale(example); for (int i=0; i
-K <number of neighbours> * Set number of nearest neighbour for prediction * (default 1)* *
-S <number of neighbours> * Set number of nearest neighbour for cleansing the training data * (default 1)* *
-E <number of neighbours> * Set number of nearest neighbour for cleansing the testing data * (default 1)* * * @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{ setDebug(Utils.getFlag('D', options)); String numNeighbourString = Utils.getOption('K', options); if (numNeighbourString.length() != 0) setNumNeighbours(Integer.parseInt(numNeighbourString)); else setNumNeighbours(1); numNeighbourString = Utils.getOption('S', options); if (numNeighbourString.length() != 0) setNumTrainingNoises(Integer.parseInt(numNeighbourString)); else setNumTrainingNoises(1); numNeighbourString = Utils.getOption('E', options); if (numNeighbourString.length() != 0) setNumTestingNoises(Integer.parseInt(numNeighbourString)); else setNumTestingNoises(1); } /** * 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("-K"); result.add("" + getNumNeighbours()); result.add("-S"); result.add("" + getNumTrainingNoises()); result.add("-E"); result.add("" + getNumTestingNoises()); 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 numNeighboursTipText() { return "The number of nearest neighbours to the estimate the class prediction of test bags."; } /** * Sets the number of nearest neighbours to estimate * the class prediction of tests bags * @param numNeighbour the number of citers */ public void setNumNeighbours(int numNeighbour){ m_Neighbour = numNeighbour; } /** * Returns the number of nearest neighbours to estimate * the class prediction of tests bags * @return the number of neighbours */ public int getNumNeighbours(){ return m_Neighbour; } /** * Returns the tip text for this property * * @return tip text for this property suitable for * displaying in the explorer/experimenter gui */ public String numTrainingNoisesTipText() { return "The number of nearest neighbour instances in the selection of noises in the training data."; } /** * Sets the number of nearest neighbour instances in the * selection of noises in the training data * * @param numTraining the number of noises in training data */ public void setNumTrainingNoises (int numTraining){ m_Select = numTraining; } /** * Returns the number of nearest neighbour instances in the * selection of noises in the training data * * @return the number of noises in training data */ public int getNumTrainingNoises(){ return m_Select; } /** * Returns the tip text for this property * * @return tip text for this property suitable for * displaying in the explorer/experimenter gui */ public String numTestingNoisesTipText() { return "The number of nearest neighbour instances in the selection of noises in the test data."; } /** * Returns The number of nearest neighbour instances in the * selection of noises in the test data * @return the number of noises in test data */ public int getNumTestingNoises(){ return m_Choose; } /** * Sets The number of nearest neighbour exemplars in the * selection of noises in the test data * @param numTesting the number of noises in test data */ public void setNumTestingNoises (int numTesting){ m_Choose = numTesting; } /** * Returns the revision string. * * @return the revision */ public String getRevision() { return RevisionUtils.extract("$Revision: 5987 $"); } /** * Main method for testing. * * @param args the options for the classifier */ public static void main(String[] args) { runClassifier(new MINND(), args); } }