001: /*
002: * This program is free software; you can redistribute it and/or modify
003: * it under the terms of the GNU General Public License as published by
004: * the Free Software Foundation; either version 2 of the License, or
005: * (at your option) any later version.
006: *
007: * This program is distributed in the hope that it will be useful,
008: * but WITHOUT ANY WARRANTY; without even the implied warranty of
009: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
010: * GNU General Public License for more details.
011: *
012: * You should have received a copy of the GNU General Public License
013: * along with this program; if not, write to the Free Software
014: * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
015: */
016:
017: /*
018: * StackingC.java
019: * Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
020: * Copyright (C) 2002 Alexander K. Seewald
021: *
022: */
023:
024: package weka.classifiers.meta;
025:
026: import weka.classifiers.Classifier;
027: import weka.classifiers.functions.LinearRegression;
028: import weka.core.Instance;
029: import weka.core.Instances;
030: import weka.core.OptionHandler;
031: import weka.core.TechnicalInformation;
032: import weka.core.TechnicalInformationHandler;
033: import weka.core.Utils;
034: import weka.core.TechnicalInformation.Field;
035: import weka.core.TechnicalInformation.Type;
036: import weka.filters.Filter;
037: import weka.filters.unsupervised.attribute.MakeIndicator;
038: import weka.filters.unsupervised.attribute.Remove;
039:
040: import java.util.Random;
041:
042: /**
043: <!-- globalinfo-start -->
044: * Implements StackingC (more efficient version of stacking).<br/>
045: * <br/>
046: * For more information, see<br/>
047: * <br/>
048: * A.K. Seewald: How to Make Stacking Better and Faster While Also Taking Care of an Unknown Weakness. In: Nineteenth International Conference on Machine Learning, 554-561, 2002.<br/>
049: * <br/>
050: * Note: requires meta classifier to be a numeric prediction scheme.
051: * <p/>
052: <!-- globalinfo-end -->
053: *
054: <!-- technical-bibtex-start -->
055: * BibTeX:
056: * <pre>
057: * @inproceedings{Seewald2002,
058: * author = {A.K. Seewald},
059: * booktitle = {Nineteenth International Conference on Machine Learning},
060: * editor = {C. Sammut and A. Hoffmann},
061: * pages = {554-561},
062: * publisher = {Morgan Kaufmann Publishers},
063: * title = {How to Make Stacking Better and Faster While Also Taking Care of an Unknown Weakness},
064: * year = {2002}
065: * }
066: * </pre>
067: * <p/>
068: <!-- technical-bibtex-end -->
069: *
070: <!-- options-start -->
071: * Valid options are: <p/>
072: *
073: * <pre> -M <scheme specification>
074: * Full name of meta classifier, followed by options.
075: * Must be a numeric prediction scheme. Default: Linear Regression.</pre>
076: *
077: * <pre> -X <number of folds>
078: * Sets the number of cross-validation folds.</pre>
079: *
080: * <pre> -S <num>
081: * Random number seed.
082: * (default 1)</pre>
083: *
084: * <pre> -B <classifier specification>
085: * Full class name of classifier to include, followed
086: * by scheme options. May be specified multiple times.
087: * (default: "weka.classifiers.rules.ZeroR")</pre>
088: *
089: * <pre> -D
090: * If set, classifier is run in debug mode and
091: * may output additional info to the console</pre>
092: *
093: <!-- options-end -->
094: *
095: * @author Eibe Frank (eibe@cs.waikato.ac.nz)
096: * @author Alexander K. Seewald (alex@seewald.at)
097: * @version $Revision: 1.13 $
098: */
099: public class StackingC extends Stacking implements OptionHandler,
100: TechnicalInformationHandler {
101:
102: /** for serialization */
103: static final long serialVersionUID = -6717545616603725198L;
104:
105: /** The meta classifiers (one for each class, like in ClassificationViaRegression) */
106: protected Classifier[] m_MetaClassifiers = null;
107:
108: /** Filter to transform metaData - Remove */
109: protected Remove m_attrFilter = null;
110: /** Filter to transform metaData - MakeIndicator */
111: protected MakeIndicator m_makeIndicatorFilter = null;
112:
113: /**
114: * The constructor.
115: */
116: public StackingC() {
117: m_MetaClassifier = new weka.classifiers.functions.LinearRegression();
118: ((LinearRegression) (getMetaClassifier()))
119: .setAttributeSelectionMethod(new weka.core.SelectedTag(
120: 1, LinearRegression.TAGS_SELECTION));
121: }
122:
123: /**
124: * Returns a string describing classifier
125: * @return a description suitable for
126: * displaying in the explorer/experimenter gui
127: */
128: public String globalInfo() {
129:
130: return "Implements StackingC (more efficient version of stacking).\n\n"
131: + "For more information, see\n\n"
132: + getTechnicalInformation().toString()
133: + "\n\n"
134: + "Note: requires meta classifier to be a numeric prediction scheme.";
135: }
136:
137: /**
138: * Returns an instance of a TechnicalInformation object, containing
139: * detailed information about the technical background of this class,
140: * e.g., paper reference or book this class is based on.
141: *
142: * @return the technical information about this class
143: */
144: public TechnicalInformation getTechnicalInformation() {
145: TechnicalInformation result;
146:
147: result = new TechnicalInformation(Type.INPROCEEDINGS);
148: result.setValue(Field.AUTHOR, "A.K. Seewald");
149: result
150: .setValue(
151: Field.TITLE,
152: "How to Make Stacking Better and Faster While Also Taking Care of an Unknown Weakness");
153: result
154: .setValue(Field.BOOKTITLE,
155: "Nineteenth International Conference on Machine Learning");
156: result.setValue(Field.EDITOR, "C. Sammut and A. Hoffmann");
157: result.setValue(Field.YEAR, "2002");
158: result.setValue(Field.PAGES, "554-561");
159: result.setValue(Field.PUBLISHER, "Morgan Kaufmann Publishers");
160:
161: return result;
162: }
163:
164: /**
165: * String describing option for setting meta classifier
166: *
167: * @return string describing the option
168: */
169: protected String metaOption() {
170:
171: return "\tFull name of meta classifier, followed by options.\n"
172: + "\tMust be a numeric prediction scheme. Default: Linear Regression.";
173: }
174:
175: /**
176: * Process options setting meta classifier.
177: *
178: * @param options the meta options to parse
179: * @throws Exception if parsing fails
180: */
181: protected void processMetaOptions(String[] options)
182: throws Exception {
183:
184: String classifierString = Utils.getOption('M', options);
185: String[] classifierSpec = Utils.splitOptions(classifierString);
186: if (classifierSpec.length != 0) {
187: String classifierName = classifierSpec[0];
188: classifierSpec[0] = "";
189: setMetaClassifier(Classifier.forName(classifierName,
190: classifierSpec));
191: } else {
192: ((LinearRegression) (getMetaClassifier()))
193: .setAttributeSelectionMethod(new weka.core.SelectedTag(
194: 1, LinearRegression.TAGS_SELECTION));
195: }
196: }
197:
198: /**
199: * Method that builds meta level.
200: *
201: * @param newData the data to work with
202: * @param random the random number generator to use for cross-validation
203: * @throws Exception if generation fails
204: */
205: protected void generateMetaLevel(Instances newData, Random random)
206: throws Exception {
207:
208: Instances metaData = metaFormat(newData);
209: m_MetaFormat = new Instances(metaData, 0);
210: for (int j = 0; j < m_NumFolds; j++) {
211: Instances train = newData.trainCV(m_NumFolds, j, random);
212:
213: // Build base classifiers
214: for (int i = 0; i < m_Classifiers.length; i++) {
215: getClassifier(i).buildClassifier(train);
216: }
217:
218: // Classify test instances and add to meta data
219: Instances test = newData.testCV(m_NumFolds, j);
220: for (int i = 0; i < test.numInstances(); i++) {
221: metaData.add(metaInstance(test.instance(i)));
222: }
223: }
224:
225: m_MetaClassifiers = Classifier.makeCopies(m_MetaClassifier,
226: m_BaseFormat.numClasses());
227:
228: int[] arrIdc = new int[m_Classifiers.length + 1];
229: arrIdc[m_Classifiers.length] = metaData.numAttributes() - 1;
230: Instances newInsts;
231: for (int i = 0; i < m_MetaClassifiers.length; i++) {
232: for (int j = 0; j < m_Classifiers.length; j++) {
233: arrIdc[j] = m_BaseFormat.numClasses() * j + i;
234: }
235: m_makeIndicatorFilter = new weka.filters.unsupervised.attribute.MakeIndicator();
236: m_makeIndicatorFilter.setAttributeIndex(""
237: + (metaData.classIndex() + 1));
238: m_makeIndicatorFilter.setNumeric(true);
239: m_makeIndicatorFilter.setValueIndex(i);
240: m_makeIndicatorFilter.setInputFormat(metaData);
241: newInsts = Filter
242: .useFilter(metaData, m_makeIndicatorFilter);
243:
244: m_attrFilter = new weka.filters.unsupervised.attribute.Remove();
245: m_attrFilter.setInvertSelection(true);
246: m_attrFilter.setAttributeIndicesArray(arrIdc);
247: m_attrFilter.setInputFormat(m_makeIndicatorFilter
248: .getOutputFormat());
249: newInsts = Filter.useFilter(newInsts, m_attrFilter);
250:
251: newInsts.setClassIndex(newInsts.numAttributes() - 1);
252:
253: m_MetaClassifiers[i].buildClassifier(newInsts);
254: }
255: }
256:
257: /**
258: * Classifies a given instance using the stacked classifier.
259: *
260: * @param instance the instance to be classified
261: * @return the distribution
262: * @throws Exception if instance could not be classified
263: * successfully
264: */
265: public double[] distributionForInstance(Instance instance)
266: throws Exception {
267:
268: int[] arrIdc = new int[m_Classifiers.length + 1];
269: arrIdc[m_Classifiers.length] = m_MetaFormat.numAttributes() - 1;
270: double[] classProbs = new double[m_BaseFormat.numClasses()];
271: Instance newInst;
272: double sum = 0;
273:
274: for (int i = 0; i < m_MetaClassifiers.length; i++) {
275: for (int j = 0; j < m_Classifiers.length; j++) {
276: arrIdc[j] = m_BaseFormat.numClasses() * j + i;
277: }
278: m_makeIndicatorFilter.setAttributeIndex(""
279: + (m_MetaFormat.classIndex() + 1));
280: m_makeIndicatorFilter.setNumeric(true);
281: m_makeIndicatorFilter.setValueIndex(i);
282: m_makeIndicatorFilter.setInputFormat(m_MetaFormat);
283: m_makeIndicatorFilter.input(metaInstance(instance));
284: m_makeIndicatorFilter.batchFinished();
285: newInst = m_makeIndicatorFilter.output();
286:
287: m_attrFilter.setAttributeIndicesArray(arrIdc);
288: m_attrFilter.setInvertSelection(true);
289: m_attrFilter.setInputFormat(m_makeIndicatorFilter
290: .getOutputFormat());
291: m_attrFilter.input(newInst);
292: m_attrFilter.batchFinished();
293: newInst = m_attrFilter.output();
294:
295: classProbs[i] = m_MetaClassifiers[i]
296: .classifyInstance(newInst);
297: if (classProbs[i] > 1) {
298: classProbs[i] = 1;
299: }
300: if (classProbs[i] < 0) {
301: classProbs[i] = 0;
302: }
303: sum += classProbs[i];
304: }
305:
306: if (sum != 0)
307: Utils.normalize(classProbs, sum);
308:
309: return classProbs;
310: }
311:
312: /**
313: * Output a representation of this classifier
314: *
315: * @return a string representation of the classifier
316: */
317: public String toString() {
318:
319: if (m_MetaFormat == null) {
320: return "StackingC: No model built yet.";
321: }
322: String result = "StackingC\n\nBase classifiers\n\n";
323: for (int i = 0; i < m_Classifiers.length; i++) {
324: result += getClassifier(i).toString() + "\n\n";
325: }
326:
327: result += "\n\nMeta classifiers (one for each class)\n\n";
328: for (int i = 0; i < m_MetaClassifiers.length; i++) {
329: result += m_MetaClassifiers[i].toString() + "\n\n";
330: }
331:
332: return result;
333: }
334:
335: /**
336: * Main method for testing this class.
337: *
338: * @param argv should contain the following arguments:
339: * -t training file [-T test file] [-c class index]
340: */
341: public static void main(String[] argv) {
342: runClassifier(new StackingC(), argv);
343: }
344: }
|