package LBJ2.learn; import java.io.PrintStream; import LBJ2.classify.Classifier; import LBJ2.classify.DiscretePrimitiveStringFeature; import LBJ2.classify.Feature; import LBJ2.classify.FeatureVector; import LBJ2.classify.ScoreSet; import LBJ2.util.ExceptionlessInputStream; import LBJ2.util.ExceptionlessOutputStream; import LBJ2.util.FVector; import LBJ2.util.OVector; import LBJ2.util.Sort; /** * A <code>MuxLearner</code> uses one of many <code>Learner</code>s indexed * by the first feature in an example to produce a classification. During * training, the features produced by the first child classifier of this * classifier's composite generator feature extractor are taken to determine * which <code>Learner</code>s will learn from the training object. For any * given example, there must be one <code>Feature</code> produced by the * labeler for each <code>Feature</code> produced by the first child * classifier. If this classifier's feature extractor is not a composite * generator, the first feature it produces is the only one taken. * * <p> It is assumed that the <code>Learner</code> being multiplexed expects * a single label feature on each training example, and that the feature(s) * used to do the multiplexing are <code>DiscreteFeature</code>(s). * Furthermore, if this classifier's feature extractor is a composite * generator, it must produce the same number of features as this * classifier's labeler, and they must correspond to each other in the order * produced. * * <p> This algorithm's user-configurable parameters are stored in member * fields of this class. They may be set via either a constructor that names * each parameter explicitly or a constructor that takes an instance of * {@link LBJ2.learn.MuxLearner.Parameters Parameters} as input. The * documentation in each member field in this class indicates the default * value of the associated parameter when using the former type of * constructor. The documentation of the associated member field in the * {@link LBJ2.learn.MuxLearner.Parameters Parameters} class indicates the * default value of the parameter when using the latter type of constructor. * * @author Nick Rizzolo **/ public class MuxLearner extends Learner { /** Default for {@link #baseLearner}. */ public static final Learner defaultBaseLearner = new SparseNetworkLearner(new SparseAveragedPerceptron()); /** Default for {@link #defaultPrediction}. */ public static final String defaultDefaultPrediction = null; /** * Instances of this learning algorithm will be multiplexed; default * <code>null</code>. **/ protected Learner baseLearner; /** A map from feature values to learners. */ protected OVector network; /** * This string is returned during testing when the multiplexed * <code>Learner</code> doesn't exist; default * {@link #defaultDefaultPrediction}. **/ protected String defaultPrediction; /** A feature whose value is {@link #defaultPrediction}. */ protected Feature defaultFeature; /** For the LBJ compiler; not for use by the LBJ user. */ public MuxLearner() { } /** * Instantiates this multiplexed learner using the specified base learning * algorithm. This constructor will normally only be called by the * compiler. * * @param base Instances of this learner will be multiplexed. **/ public MuxLearner(Learner base) { this("", base); } /** * Instantiates this multiplexed learner using the specified base learning * algorithm. * * @param base Instances of this learner will be multiplexed. * @param d This prediction will be returned during testing when the * multiplexed <code>Learner</code> does not exist. **/ public MuxLearner(Learner base, String d) { this("", base, d); } /** * Initializing constructor. Sets all member variables to their associated * settings in the {@link MuxLearner.Parameters} object. * * @param p The settings of all parameters. **/ public MuxLearner(Parameters p) { this("", p); } /** For the LBJ compiler; not for use by the LBJ user. */ public MuxLearner(String n) { super(n); } /** * Instantiates this multiplexed learner using the specified base learning * algorithm. * * @param n The name of the classifier. * @param base Instances of this learner will be multiplexed. **/ public MuxLearner(String n, Learner base) { this(n, base, defaultDefaultPrediction); } /** * Instantiates this multiplexed learner using the specified base learning * algorithm. * * @param n The name of the classifier. * @param base Instances of this learner will be multiplexed. * @param d This prediction will be returned during testing when the * multiplexed <code>Learner</code> does not exist. **/ public MuxLearner(String n, Learner base, String d) { super(n); Parameters p = new Parameters(); p.baseLearner = base; p.defaultPrediction = d; setParameters(p); network = new OVector(); } /** * Initializing constructor. Sets all member variables to their associated * settings in the {@link MuxLearner.Parameters} object. * * @param n The name of the classifier. * @param p The settings of all parameters. **/ public MuxLearner(String n, Parameters p) { super(n); setParameters(p); network = new OVector(); } /** * Sets the values of parameters that control the behavior of this learning * algorithm. * * @param p The parameters. **/ public void setParameters(Parameters p) { setBase(p.baseLearner); defaultPrediction = p.defaultPrediction; setDefaultFeature(); } /** * Sets the value of {@link #defaultFeature} according to the current value * of {@link #defaultPrediction}. **/ protected void setDefaultFeature() { defaultFeature = new DiscretePrimitiveStringFeature( containingPackage, name, "default", defaultPrediction, valueIndexOf(defaultPrediction), (short) allowableValues().length); } /** * Retrieves the parameters that are set in this learner. * * @return An object containing all the values of the parameters that * control the behavior of this learning algorithm. **/ public Learner.Parameters getParameters() { Parameters p = new Parameters(super.getParameters()); p.baseLearner = baseLearner; p.defaultPrediction = defaultPrediction; return p; } /** * Sets {@link #baseLearner}. This method will <i>not</i> have any effect * on the learners that already exist in the network. However, new * learners created after this method is executed will be of the same type * as the object specified. * * @param base The new base learning algorithm. **/ public void setBase(Learner base) { baseLearner = base; baseLearner.containingPackage = containingPackage; baseLearner.name = name + "::base"; } /** * Sets the labeler. * * @param l A labeling classifier. **/ public void setLabeler(Classifier l) { super.setLabeler(l); setBase(baseLearner); } /** * Sets the label lexicon. * * @param l A feature lexicon. **/ public void setLabelLexicon(Lexicon l) { super.setLabelLexicon(l); if (network != null) { int N = network.size(); for (int i = 0; i < N; ++i) { Learner learner = (Learner) network.get(i); if (learner != null) learner.setLabelLexicon(l); } } } /** * The training example is multiplexed to the appropriate * <code>Learner</code>(s). * * @param exampleFeatures The example's array of feature indices. * @param exampleValues The example's array of feature values. * @param exampleLabels The example's label(s). * @param labelValues The labels' values. **/ public void learn(int[] exampleFeatures, double[] exampleValues, int[] exampleLabels, double[] labelValues) { assert exampleFeatures.length > exampleLabels.length : "MuxLearner ERROR: The example vector must have more features " + "than labels, since it is assumed that there is a correspondence " + "between the labels and the first features in the vector."; int F = exampleFeatures.length; int L = exampleLabels.length; int[] example = new int[F - L]; double[] values = new double[F - L]; int[] selections = new int[L]; int i = 0, j = 0; for ( ; i < L; i++) { selections[i] = exampleFeatures[i]; } for ( ; i < F; i++, j++) { example[j] = exampleFeatures[i]; values[j] = exampleValues[i]; } for (i = 0; i < L; i++) { Learner l = (Learner) network.get(selections[i]); if (l == null) { l = (Learner) baseLearner.clone(); l.setLabelLexicon(labelLexicon); network.set(selections[i], l); } int[] labels = new int[1]; labels[0] = exampleLabels[i]; double[] labelVal = new double[1]; labelVal[0] = labelValues[i]; l.learn(example, values, labels, labelVal); } } /** Clears the network. */ public void forget() { super.forget(); network = new OVector(); } /** * Produces a set of scores indicating the degree to which each possible * discrete classification value is associated with the given example * object. These scores are just the scores produced by the multiplexed * <code>Learner</code>'s <code>scores(Object)</code> method. * * @see Learner#scores(Object) * @param exampleFeatures The example's array of feature indices. * @param exampleValues The example's array of feature values. **/ public ScoreSet scores(int[] exampleFeatures, double[] exampleValues) { int[] example = new int[exampleFeatures.length - 1]; double[] values = new double[exampleFeatures.length - 1]; System.arraycopy(exampleFeatures, 1, example, 0, example.length); System.arraycopy(exampleValues, 1, values, 0, values.length); int selection = exampleFeatures[0]; Learner l = (Learner) network.get(selection); if (l == null) return new ScoreSet(new String[]{ defaultPrediction }, new double[]{ 1 }); return l.scores(example, values); } /** * Returns the value of the discrete feature that would be returned by this * classifier. * * @param exampleFeatures The example's array of feature indices. * @param exampleValues The example's array of feature values. * @return A single value with the winning linear threshold unit's * associated value. **/ public String discreteValue(int[] exampleFeatures, double[] exampleValues) { return featureValue(exampleFeatures, exampleValues).getStringValue(); } /** * Returns the value of the real feature that would be returned by this * classifier. * * @param f The features array. * @param v The values array. * @return The value of the feature produced for the input object. **/ public double realValue(int[] f, double[] v) { return featureValue(f, v).getStrength(); } /** * Returns the classification of the given example as a single feature * instead of a {@link FeatureVector}. * * @param f The features array. * @param v The values array. * @return The classification of the example as a feature. **/ public Feature featureValue(int[] f, double[] v) { int[] example = new int[f.length - 1]; double[] values = new double[f.length - 1]; System.arraycopy(f, 1, example, 0, example.length); System.arraycopy(v, 1, values, 0, values.length); int selection = f[0]; Learner l = (Learner) network.get(selection); if (l == null) return defaultFeature; return l.featureValue(example, values); } /** * This method performs the multiplexing and returns the output of the * selected <code>Learner</code>. * * @param exampleFeatures The example's array of feature indices. * @param exampleValues The example's array of feature values. * @return The output of the selected <code>Learner</code>. **/ public FeatureVector classify(int[] exampleFeatures, double[] exampleValues) { return new FeatureVector(featureValue(exampleFeatures, exampleValues)); } /** * Writes the algorithm's internal representation as text. * * @param out The output stream. **/ public void write(PrintStream out) { int N = network.size(); final FVector entries = new FVector(N); final OVector learners = new OVector(N); for (int i = 0; i < N; ++i) { Learner learner = (Learner) network.get(i); if (network.get(i) != null) { entries.add(lexicon.lookupKey(i)); learners.add(learner); } } N = entries.size(); int[] indexes = new int[N]; for (int i = 0; i < N; ++i) indexes[i] = i; Sort.sort(indexes, new Sort.IntComparator() { public int compare(int i1, int i2) { return entries.get(i1).compareTo(entries.get(i2)); } }); for (int i = 0; i < N; ++i) { out.println("select: " + entries.get(indexes[i]).getStringValue()); Learner learner = (Learner) learners.get(indexes[i]); learner.setLexicon(lexicon); learner.write(out); learner.setLexicon(null); } out.println("End of MuxLearner"); } /** * Writes the learned function's internal representation in binary form. * * @param out The output stream. **/ public void write(ExceptionlessOutputStream out) { super.write(out); out.writeString(defaultPrediction); baseLearner.write(out); int N = network.size(); out.writeInt(N); int M = 0; for (int i = 0; i < N; ++i) if (network.get(i) != null) ++M; out.writeInt(M); for (int i = 0; i < N; ++i) { Learner learner = (Learner) network.get(i); if (learner != null) { out.writeInt(i); learner.write(out); } } } /** * Reads the binary representation of a learner with this object's run-time * type, overwriting any and all learned or manually specified parameters * as well as the label lexicon but without modifying the feature lexicon. * * @param in The input stream. **/ public void read(ExceptionlessInputStream in) { super.read(in); defaultPrediction = in.readString(); setDefaultFeature(); baseLearner = Learner.readLearner(in); int N = in.readInt(); network = new OVector(N); int M = in.readInt(); for (int i = 0; i < M; ++i) network.set(in.readInt(), Learner.readLearner(in)); } /** Returns a deep clone of this learning algorithm. */ public Object clone() { MuxLearner clone = null; try { clone = (MuxLearner) super.clone(); } catch (Exception e) { System.err.println("Error cloning MuxLearner: " + e); e.printStackTrace(); System.exit(1); } clone.baseLearner = (Learner) baseLearner.clone(); int N = network.size(); clone.network = new OVector(N); for (int i = 0; i < N; ++i) { Learner learner = (Learner) network.get(i); if (learner != null) clone.network.set(i, learner.clone()); } return clone; } /** * Simply a container for all of {@link MuxLearner}'s configurable * parameters. Using instances of this class should make code more * readable and constructors less complicated. * * @author Nick Rizzolo **/ public static class Parameters extends Learner.Parameters { /** * Instances of this learning algorithm will be multiplexed; default * <code>null</code>. **/ public Learner baseLearner; /** * This string is returned during testing when the multiplexed * <code>Learner</code> doesn't exist; default * {@link MuxLearner#defaultDefaultPrediction}. **/ public String defaultPrediction; /** Sets all the default values. */ public Parameters() { baseLearner = (Learner) defaultBaseLearner.clone(); defaultPrediction = defaultDefaultPrediction; } /** * Sets the parameters from the parent's parameters object, giving * defaults to all parameters declared in this object. **/ public Parameters(Learner.Parameters p) { super(p); baseLearner = (Learner) defaultBaseLearner.clone(); defaultPrediction = defaultDefaultPrediction; } /** Copy constructor. */ public Parameters(Parameters p) { super(p); baseLearner = p.baseLearner; defaultPrediction = p.defaultPrediction; } /** * Calls the appropriate <code>Learner.setParameters(Parameters)</code> * method for this <code>Parameters</code> object. * * @param l The learner whose parameters will be set. **/ public void setParameters(Learner l) { ((MuxLearner) l).setParameters(this); } /** * Creates a string representation of these parameters in which only * those parameters that differ from their default values are mentioned. **/ public String nonDefaultString() { String result = super.nonDefaultString(); String name = baseLearner.getClass().getName(); name = name.substring(name.lastIndexOf('.') + 1); if (!defaultPrediction.equals(MuxLearner.defaultDefaultPrediction)) result += "defaultPrediction = " + defaultPrediction + ", "; result += name + ": " + baseLearner.getParameters().nonDefaultString(); return result; } } }