/*
* 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.
*/
/*
* FilteredClassifier.java
* Copyright (C) 1999 Len Trigg
*
*/
package weka.classifiers.meta;
import weka.classifiers.Evaluation;
import weka.classifiers.Classifier;
import weka.classifiers.DistributionClassifier;
import weka.classifiers.rules.ZeroR;
import java.util.Enumeration;
import java.util.Vector;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.Option;
import weka.core.OptionHandler;
import weka.core.Utils;
import weka.filters.Filter;
import weka.core.Attribute;
/**
* Class for running an arbitrary classifier on data that has been passed
* through an arbitrary filter.<p>
*
* Valid options from the command line are:<p>
*
* -B classifierstring <br>
* Classifierstring should contain the full class name of a classifier
* followed by options to the classifier.
* (required).<p>
*
* -F filterstring <br>
* Filterstring should contain the full class name of a filter
* followed by options to the filter.
* (required).<p>
*
* @author Len Trigg (trigg@cs.waikato.ac.nz)
* @version $Revision: 1.1.1.1 $
*/
public class FilteredClassifier extends DistributionClassifier
implements OptionHandler {
/** The classifier */
protected Classifier m_Classifier = new weka.classifiers.rules.ZeroR();
/** The filter */
protected Filter m_Filter = new weka.filters.supervised.attribute.AttributeSelection();
/** The instance structure of the filtered instances */
protected Instances m_FilteredInstances;
/**
* Default constructor specifying ZeroR as the classifier and
* AllFilter as the filter. Both of these are just placeholders
* for more useful selections.
*/
public FilteredClassifier() {
this(new weka.classifiers.rules.ZeroR(), new weka.filters.supervised.attribute.AttributeSelection());
}
/**
* Constructor that specifies the subclassifier and filter to use.
*
* @param classifier the Classifier to receive filtered instances.
* @param filter the Filter that will process instances before
* passing to the Classifier.
*/
public FilteredClassifier(Classifier classifier, Filter filter) {
m_Classifier = classifier;
m_Filter = filter;
}
/**
* Returns an enumeration describing the available options.
*
* @return an enumeration of all the available options.
*/
public Enumeration listOptions() {
Vector newVector = new Vector(2);
newVector.addElement(new Option(
"\tFull class name of classifier to use, followed\n"
+ "\tby scheme options. (required)\n"
+ "\teg: \"weka.classifiers.bayes.NaiveBayes -D\"",
"B", 1, "-B <classifier specification>"));
newVector.addElement(new Option(
"\tFull class name of filter to use, followed\n"
+ "\tby filter options. (required)\n"
+ "\teg: \"weka.filters.AttributeFilter -V -R 1,2\"",
"F", 1, "-F <filter specification>"));
return newVector.elements();
}
/**
* Parses a given list of options. Valid options are:<p>
*
* -B classifierstring <br>
* Classifierstring should contain the full class name of a classifier
* followed by options to the classifier.
* (required).<p>
*
* -F filterstring <br>
* Filterstring should contain the full class name of a filter
* followed by options to the filter.
* (required).<p>
*
* @param options the list of options as an array of strings
* @exception Exception if an option is not supported
*/
public void setOptions(String[] options) throws Exception {
String classifierString = Utils.getOption('B', options);
if (classifierString.length() == 0) {
throw new Exception("A classifier must be specified"
+ " with the -B option.");
}
String [] classifierSpec = Utils.splitOptions(classifierString);
if (classifierSpec.length == 0) {
throw new Exception("Invalid classifier specification string");
}
String classifierName = classifierSpec[0];
classifierSpec[0] = "";
setClassifier(Classifier.forName(classifierName, classifierSpec));
// Same for filter
String filterString = Utils.getOption('F', options);
if (filterString.length() == 0) {
throw new Exception("A filter must be specified"
+ " with the -F option.");
}
String [] filterSpec = Utils.splitOptions(filterString);
if (filterSpec.length == 0) {
throw new Exception("Invalid filter specification string");
}
String filterName = filterSpec[0];
filterSpec[0] = "";
setFilter((Filter) Utils.forName(Filter.class, filterName, filterSpec));
}
/**
* Gets the current settings of the Classifier.
*
* @return an array of strings suitable for passing to setOptions
*/
public String [] getOptions() {
String [] options = new String [4];
int current = 0;
options[current++] = "-B";
options[current++] = "" + getClassifierSpec();
// Same for filter
options[current++] = "-F";
options[current++] = "" + getFilterSpec();
while (current < options.length) {
options[current++] = "";
}
return options;
}
/**
* Sets the classifier
*
* @param classifier the classifier with all options set.
*/
public void setClassifier(Classifier classifier) {
m_Classifier = classifier;
}
/**
* Gets the classifier used.
*
* @return the classifier
*/
public Classifier getClassifier() {
return m_Classifier;
}
/**
* Gets the classifier specification string, which contains the class name of
* the classifier and any options to the classifier
*
* @return the classifier string.
*/
protected String getClassifierSpec() {
Classifier c = getClassifier();
if (c instanceof OptionHandler) {
return c.getClass().getName() + " "
+ Utils.joinOptions(((OptionHandler)c).getOptions());
}
return c.getClass().getName();
}
/**
* Sets the filter
*
* @param filter the filter with all options set.
*/
public void setFilter(Filter filter) {
m_Filter = filter;
}
/**
* Gets the filter used.
*
* @return the filter
*/
public Filter getFilter() {
return m_Filter;
}
/**
* Gets the filter specification string, which contains the class name of
* the filter and any options to the filter
*
* @return the filter string.
*/
protected String getFilterSpec() {
Filter c = getFilter();
if (c instanceof OptionHandler) {
return c.getClass().getName() + " "
+ Utils.joinOptions(((OptionHandler)c).getOptions());
}
return c.getClass().getName();
}
/**
* Build the classifier on the filtered data.
*
* @param data the training data
* @exception Exception if the classifier could not be built successfully
*/
public void buildClassifier(Instances data) throws Exception {
if (m_Classifier == null) {
throw new Exception("No base classifiers have been set!");
}
/*
String fname = m_Filter.getClass().getName();
fname = fname.substring(fname.lastIndexOf('.') + 1);
util.Timer t = util.Timer.getTimer("FilteredClassifier::" + fname);
t.start();
*/
m_Filter.setInputFormat(data);
data = Filter.useFilter(data, m_Filter);
//t.stop();
m_FilteredInstances = data.stringFreeStructure();
m_Classifier.buildClassifier(data);
}
/**
* Classifies a given instance after filtering.
*
* @param instance the instance to be classified
* @exception Exception if instance could not be classified
* successfully
*/
public double [] distributionForInstance(Instance instance)
throws Exception {
/*
System.err.println("FilteredClassifier:: "
+ m_Filter.getClass().getName()
+ " in: " + instance);
*/
if (m_Filter.numPendingOutput() > 0) {
throw new Exception("Filter output queue not empty!");
}
/*
String fname = m_Filter.getClass().getName();
fname = fname.substring(fname.lastIndexOf('.') + 1);
util.Timer t = util.Timer.getTimer("FilteredClassifier::" + fname);
t.start();
*/
if (!m_Filter.input(instance)) {
throw new Exception("Filter didn't make the test instance"
+ " immediately available!");
}
m_Filter.batchFinished();
Instance newInstance = m_Filter.output();
//t.stop();
/*
System.err.println("FilteredClassifier:: "
+ m_Filter.getClass().getName()
+ " out: " + newInstance);
*/
if (m_Classifier instanceof DistributionClassifier) {
return ((DistributionClassifier)m_Classifier)
.distributionForInstance(newInstance);
}
double pred = m_Classifier.classifyInstance(newInstance);
double [] result = new double [m_FilteredInstances.numClasses()];
if (Instance.isMissingValue(pred)) {
return result;
}
switch (instance.classAttribute().type()) {
case Attribute.NOMINAL:
result[(int) pred] = 1.0;
break;
case Attribute.NUMERIC:
result[0] = pred;
break;
default:
throw new Exception("Unknown class type");
}
return result;
}
/**
* Output a representation of this classifier
*/
public String toString() {
if (m_FilteredInstances == null) {
return "FilteredClassifier: No model built yet.";
}
String result = "FilteredClassifier using "
+ getClassifierSpec()
+ " on data filtered through "
+ getFilterSpec()
+ "\n\nFiltered Header\n"
+ m_FilteredInstances.toString()
+ "\n\nClassifier Model\n"
+ m_Classifier.toString();
return result;
}
/**
* Main method for testing this class.
*
* @param argv should contain the following arguments:
* -t training file [-T test file] [-c class index]
*/
public static void main(String [] argv) {
try {
System.out.println(Evaluation.evaluateModel(new FilteredClassifier(),
argv));
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
}