View Javadoc

1   /*
2    * Copyright (C) 2004 Christian Siefkes <christian@siefkes.net>.
3    * Development of this software is supported by the German Research Society,
4    * Berlin-Brandenburg Graduate School in Distributed Information Systems
5    * (DFG grant no. GRK 316).
6    *
7    * This library is free software; you can redistribute it and/or
8    * modify it under the terms of the GNU Lesser General Public
9    * License as published by the Free Software Foundation; either
10   * version 2.1 of the License, or (at your option) any later version.
11   *
12   * This library is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15   * Lesser General Public License for more details.
16   *
17   * You should have received a copy of the GNU Lesser General Public
18   * License along with this library; if not, visit
19   * http://www.gnu.org/licenses/lgpl.html or write to the Free Software
20   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
21   */
22  package de.fu_berlin.ties.classify;
23  
24  import java.io.File;
25  import java.util.Set;
26  
27  import org.apache.commons.lang.ArrayUtils;
28  import org.apache.commons.lang.builder.ToStringBuilder;
29  
30  import de.fu_berlin.ties.ContextMap;
31  import de.fu_berlin.ties.ProcessingException;
32  import de.fu_berlin.ties.TiesConfiguration;
33  import de.fu_berlin.ties.classify.feature.FeatureTransformer;
34  import de.fu_berlin.ties.classify.feature.FeatureVector;
35  import de.fu_berlin.ties.filter.TrainableFilter;
36  import de.fu_berlin.ties.util.Util;
37  
38  /***
39   * A meta classifier combines several layers of classifiers. For each layer
40   * (except the last one), there is a "judge" that decides whether or not the
41   * decision of this classifier is likely to be correct. If the judge decides
42   * that it is likely to be wrong, the next layer is invoked to correct the
43   * decision.
44   *
45   * @author Christian Siefkes
46   * @version $Revision: 1.3 $, $Date: 2004/12/09 18:09:14 $, $Author: siefkes $
47   */
48  class MetaClassifier extends TrainableClassifier {
49  
50      /***
51       * Key used to store the contexts of the inner classifiers.
52       */
53      private static final String KEY_INNER_CONTEXTS = "inner-context";
54  
55      /***
56       * Key used to store the prediction distributions returned by the inner
57       * classifiers.
58       */
59      private static final String KEY_INNER_DISTS = "inner-dist";
60  
61      /***
62       * Key used to store the contexts of the judges.
63       */
64      private static final String KEY_JUDGE_CONTEXTS = "judge-context";
65  
66      /***
67       * Key used to store the prediction distributions returned by jthe udges.
68       */
69      private static final String KEY_JUDGE_DISTS = "judge-dist";
70  
71      /***
72       * Key used to store the decisions made by the judges.
73       */
74      private static final String KEY_JUDGE_DECISIONS = "judge-decision";
75  
76  
77      /***
78       * The array of inner classifiers managed by this instance.
79       */
80      private final TrainableClassifier[] inner;
81  
82      /***
83       * The array of judges managed by this instance.
84       */
85      private final TrainableClassifier[] judges;
86  
87      /***
88       * Creates a new instance.
89       *
90       * @param allValidClasses the set of all valid classes; the first member
91       * of this set is considered as the "background" class, all further members
92       * are considered as "foreground" classes
93       * @param trans the last transformer in the transformer chain to use, or
94       * <code>null</code> if no feature transformers should be used
95       * @param runDirectory optional run directory passed to inner classifiers
96       * of the {@link ExternalClassifier} type
97       * @param innerSpec the specification used to initialize the inner
98       * classifiers, passed to the
99       * {@link TrainableClassifier#createClassifier(Set, File,
100      * FeatureTransformer, String[], TiesConfiguration)} factory method
101      * @param conf used to configure this instance and the inner classifiers
102      * @throws ProcessingException if an error occurred while creating this
103      * classifier or one of the wrapped classifiers
104      */
105     public MetaClassifier(Set<String> allValidClasses,
106             FeatureTransformer trans, final File runDirectory,
107             final String[] innerSpec, TiesConfiguration conf)
108     throws ProcessingException {
109         this(allValidClasses, trans, runDirectory, innerSpec,
110                 conf.getInt("classifier.meta.layers"),
111                 conf.getStringArray("classifier.meta.judge"), conf);
112     }
113 
114     /***
115      * Creates a new instance.
116      *
117      * @param allValidClasses the set of all valid classes; the first member
118      * of this set is considered as the "background" class, all further members
119      * are considered as "foreground" classes
120      * @param trans the last transformer in the transformer chain to use, or
121      * <code>null</code> if no feature transformers should be used
122      * @param runDirectory optional run directory passed to inner classifiers
123      * of the {@link ExternalClassifier} type
124      * @param innerSpec the specification used to initialize the inner
125      * classifiers, passed to the
126      * {@link TrainableClassifier#createClassifier(Set, File,
127      * FeatureTransformer, String[], TiesConfiguration)} factory method
128      * @param layers the number of layers to use, must be at least one
129      * @param judgeSpec the specification used to initialize the judges,
130      * passed to the {@link TrainableClassifier#createClassifier(Set, File,
131      * FeatureTransformer, String[], TiesConfiguration)} factory method
132      * @param conf used to configure this instance as well as the inner
133      * classifiers and judges
134      * @throws ProcessingException if an error occurred while creating this
135      * classifier or one of the wrapped classifiers
136      */
137     public MetaClassifier(Set<String> allValidClasses,
138             FeatureTransformer trans, final File runDirectory,
139             final String[] innerSpec, final int layers,
140             final String[] judgeSpec, TiesConfiguration conf)
141     throws ProcessingException {
142         super(allValidClasses, trans, conf);
143 
144         if (layers < 1) {
145             throw new IllegalArgumentException(
146                 "MetaClassifier requires at least 1 layer instead of "
147                     + layers);
148         }
149 
150         // Init classifiers for each layer
151         inner = new TrainableClassifier[layers];
152         for (int i = 0; i < inner.length; i++) {
153             // Transformer is set to null because features shouldn't be
154             // transformed twice
155             inner[i] = TrainableClassifier.createClassifier(allValidClasses,
156                 runDirectory, null, innerSpec, conf);
157             
158         }
159 
160         // Init judges for each layer except the very last one
161         judges = new TrainableClassifier[layers - 1];
162         for (int i = 0; i < judges.length; i++) {
163             // Judges classify between "true" and "false".
164             // Transformer is set to null, as above
165             judges[i] = TrainableClassifier.createClassifier(
166                     TrainableFilter.BOOLEAN_CLASSES, runDirectory, null,
167                     judgeSpec, conf);
168             
169         }
170     }
171 
172     /***
173      * {@inheritDoc}
174      */
175     protected PredictionDistribution doClassify(FeatureVector features,
176             Set candidateClasses, ContextMap context)
177             throws ProcessingException {
178         // Used to populate the context
179         PredictionDistribution[] innerDists =
180             new PredictionDistribution[inner.length];
181         ContextMap[] innerContexts = new ContextMap[inner.length];
182         PredictionDistribution[] judgeDists =
183             new PredictionDistribution[judges.length];
184         ContextMap[] judgeContexts = new ContextMap[judges.length];
185         boolean[] judgements = new boolean[judges.length];
186 
187         PredictionDistribution innerDist = null;
188         PredictionDistribution judgeDist;
189         ContextMap innerContext, judgeContext;
190         boolean judgement = false;
191         int i = 0;
192 
193         // invoke inner classifier until judge trusts a decision to be correct
194         // or all layers are exhausted
195         while ((i < inner.length) || !judgement) {
196             if (i > 0) {
197                 Util.LOG.debug("Invoking layer " + i
198                         + " classifier since decision of previous layer was "
199                         + "judged to be incorrect");
200             }
201 
202             // invoke i-th inner classifier
203             innerContext = new ContextMap();
204             innerDist = inner[i].doClassify(features, candidateClasses,
205                     innerContext);
206             innerContexts[i] = innerContext;
207             innerDists[i] = innerDist;
208 
209             // call judge if available (not for last layer)
210             if (i < judges.length) {
211                 judgeContext = new ContextMap();
212                 judgeDist = judges[i].doClassify(features,
213                         judges[i].getAllClasses(), judgeContext);
214                 judgeContexts[i] = judgeContext;
215                 judgeDists[i] = judgeDist;
216 
217                 // convert best class to boolean
218                 judgement = Util.asBoolean(judgeDist.best().getType());
219                 judgements[i] = judgement;
220             }
221 
222             i++;
223         }
224 
225         // store judgements, distributions, and contexts
226         context.put(KEY_INNER_CONTEXTS, innerContexts);
227         context.put(KEY_INNER_DISTS, innerDists);
228         context.put(KEY_JUDGE_CONTEXTS, judgeContexts);
229         context.put(KEY_JUDGE_DISTS, judgeDists);
230         context.put(KEY_JUDGE_DECISIONS, judgements);
231 
232         // return decision of last invoked layer
233         return innerDist;
234     }
235 
236     /***
237      * {@inheritDoc}
238      */
239     protected void doTrain(FeatureVector features, String targetClass,
240             ContextMap context) throws ProcessingException {
241         // TODO throw exception + add note in header similar to Winnow
242     }
243 
244     /***
245      * {@inheritDoc}
246      */
247     public void reset() throws ProcessingException {
248         // TODO delegate to all layers and judges
249     }
250 
251     /***
252      * Returns a string representation of this object.
253      *
254      * @return a textual representation
255      */
256     public String toString() {
257         return new ToStringBuilder(this)
258             .appendSuper(super.toString())
259             .append("inner classifiers", ArrayUtils.toString(inner))
260             .append("judges", ArrayUtils.toString(judges))
261             .toString();
262     }
263 
264     /***
265      * {@inheritDoc}
266      */
267     protected boolean trainOnErrorHook(final PredictionDistribution predDist,
268             final FeatureVector features, final String targetClass,
269             final Set candidateClasses, final ContextMap context)
270     throws ProcessingException {
271         // TODO implement, delegating to layers and judges as appropriate
272         return false;
273     }
274 
275 }