package LBJ2.learn; import java.io.PrintStream; import LBJ2.classify.Classifier; import LBJ2.classify.FeatureVector; import LBJ2.util.ExceptionlessInputStream; import LBJ2.util.ExceptionlessOutputStream; /** * The Binary MIRA learning algorithm implementation. This algorithm * operates very similarly to {@link SparsePerceptron} with a thick * separator, except the learning rate is a function of each training * example's margin. When the weight vector has made a mistake, the full * {@link LinearThresholdUnit#learningRate} will be used. When the weight * vector did not make a mistake, {@link LinearThresholdUnit#learningRate} is * multiplied by the following value before the update takes place. * * <p> * <blockquote> * <i>(beta/2 - y(w*x)) / ||x||<sup>2</sup></i> * </blockquote> * * <p> In the expression above, <i>w</i> is the weight vector, <i>y</i> * represents the label of the example vector <i>x</i>, <i>*</i> stands for * inner product, and <i>beta</i> is a user supplied parameter. If this * expression turns out to be non-positive (i.e., if <i>y(w*x) >= * beta/2</i>), no update is made for that example. * * <p> It is assumed that {@link Learner#labeler} is a single discrete * classifier that produces the same feature for every example object and * that the values that feature may take are available through the * {@link Classifier#allowableValues()} method. The second value returned * from {@link Classifier#allowableValues()} is treated as "positive", and it * is assumed there are exactly 2 allowable values. Assertions will produce * error messages if these assumptions do not hold. * * <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.BinaryMIRA.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.BinaryMIRA.Parameters Parameters} class * indicates the default value of the parameter when using the latter type of * constructor. * * @author Arindam Saha **/ public class BinaryMIRA extends SparsePerceptron { /** * Used to decide if two values are nearly equal to each other. * @see #nearlyEqualTo(double,double) **/ public static final double TOLERANCE = 0.000000001; /** Default value for {@link #beta}. */ public static final double defaultBeta = 2; /** Default value for {@link #learningRate}. */ public static final double defaultLearningRate = 1; /** * The user supplied learning algorithm parameter; default * {@link #defaultBeta}. The learning rate changes as a function of * <code>beta</code>. **/ protected double beta; /** * The learning rate and beta take default values while the name of * the classifier takes the empty string. **/ public BinaryMIRA() { this(""); } /** * Sets the learning rate to the specified value, and beta to the * default, while the name of the classifier takes the empty string. * * @param r The desired learning rate value. **/ public BinaryMIRA(double r) { this("", r); } /** * Sets the learning rate and beta to the specified values, while * the name of the classifier takes the empty string. * * @param r The desired learning rate value. * @param B the desired beta value. **/ public BinaryMIRA(double r, double B) { this("", r, B); } /** * Sets the learning rate, beta and the weight vector to the specified * values. * * @param r The desired learning rate. * @param B The desired beta value. * @param v The desired weight vector. **/ public BinaryMIRA(double r, double B, SparseWeightVector v) { this("", r, B, v); } /** * Initializing constructor. Sets all member variables to their associated * settings in the {@link BinaryMIRA.Parameters} object. * * @param p The settings of all parameters. **/ public BinaryMIRA(Parameters p) { this("", p); } /** * Sets the name of the classifier to the specified value, while the * learning rate and beta take default values. * * @param n The name of the classifier. **/ public BinaryMIRA(String n) { this(n, defaultLearningRate); } /** * Sets the name of the classifier and learning rate to the specified * values, while beta takes the default value. * * @param n The name of the classifier. * @param r The desired learning rate. **/ public BinaryMIRA(String n, double r) { this(n, r, defaultBeta); } /** * Sets the name of the classifier, the learning rate and beta to * the specified values. * * @param n The name of the classifier. * @param r The desired learning rate. * @param B The desired beta value. */ public BinaryMIRA(String n, double r, double B) { this(n, r, B, (SparseWeightVector) LinearThresholdUnit.defaultWeightVector.clone()); } /** * Sets the name of the classifier, the learning rate, beta and the * weight vector to the specified values. Use this constructor to specify * an alternative subclass of {@link SparseWeightVector}. * * @param n The name of the classifier. * @param r The desired learning rate. * @param B The desired beta value. * @param v The desired weight vector. */ public BinaryMIRA(String n, double r, double B, SparseWeightVector v) { super(n); Parameters p = new Parameters(); p.learningRate = r; p.weightVector = v; p.beta = B; setParameters(p); } /** * Initializing constructor. Sets all member variables to their associated * settings in the {@link BinaryMIRA.Parameters} object. * * @param n The name of the classifier. * @param p The settings of all parameters. **/ public BinaryMIRA(String n, Parameters p) { super(n); setParameters(p); } /** * Sets the values of parameters that control the behavior of this learning * algorithm. * * @param p The parameters. **/ public void setParameters(Parameters p) { super.setParameters(p); beta = p.beta; } /** * 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((SparsePerceptron.Parameters) super.getParameters()); p.beta = beta; return p; } /** * Returns the current value of the {@link #beta} member variable. * * @return The value of the {@link #beta} variable. **/ public double getBeta() { return beta; } /** * Sets the {@link #beta} member variable to the specified value. * * @param B The new value for {@link #beta}. **/ public void setBeta(double B) { beta = B; } /** * Determines if the weights should be promoted. * * @param label The label of the example object. * @param s The score of the example object. * @param threshold The LTU threshold. * @param positiveThickness The thickness of the hyperplane on the * positive side. * @return <code>true</code> iff the weights should be promoted. **/ public boolean shouldPromote(boolean label, double s, double threshold, double positiveThickness) { return label; } /** * Determines if the weights should be promoted. * * @param label The label of the example object. * @param s The score of the example object. * @param threshold The LTU threshold. * @param negativeThickness The thickness of the hyperplane on the * negative side. * @return <code>true</code> iff the weights should be demoted. **/ public boolean shouldDemote(boolean label, double s, double threshold, double negativeThickness) { return !label; } /** * Scales the feature vector produced by the extractor by the learning rate * and adds it to the weight vector. * * @param exampleFeatures The example's array of feature indices. * @param exampleValues The example's array of feature values. * @param rate The learning rate at which the weights are * updated. **/ public void promote(int[] exampleFeatures, double[] exampleValues, double rate) { if (!nearlyEqualTo(rate, 0.0)) { super.promote(exampleFeatures, exampleValues, rate); } } /** * Scales the feature vector produced by the extractor by the learning rate * and subtracts it from the weight vector. * * @param exampleFeatures The example's array of feature indices. * @param exampleValues The example's array of feature values. * @param rate The learning rate at which the weights are * updated. **/ public void demote(int[] exampleFeatures, double[] exampleValues, double rate) { if (!nearlyEqualTo(rate, 0.0)) { super.demote(exampleFeatures, exampleValues, rate); } } /** * Determines if <code>a</code> is nearly equal to <code>b</code> based on * the value of the <code>TOLERANCE</code> member variable. * * @param a The first value. * @param b The second value. * @return <code>true</code> iff they are nearly equal. **/ private static boolean nearlyEqualTo(double a, double b) { return -TOLERANCE < a - b && a - b < TOLERANCE; } /** * Computes the learning rate for this example. * * @param exampleFeatures The example's array of feature indices. * @param exampleValues The example's array of feature values. * @param s The score for the example object. * @param label The label. * @return The new learning rate. **/ public double computeLearningRate(int[] exampleFeatures, double[] exampleValues, double s, boolean label) { double labelVal = label? 1: -1; double x = (beta / 2 - labelVal * s) / (FeatureVector.L2NormSquared(exampleValues) + 1); double rate = 1; if (x < 0) rate = 0; else if (x < 1) rate = x; rate *= learningRate; return rate; } /** * Returns the original value of the {@link #learningRate} variable. * * @return The value of the {@link #learningRate} variable. **/ public double getLearningRate() { return learningRate; } /** * Writes the algorithm's internal representation as text. In the first * line of output, the name of the classifier is printed, followed by * {@link #learningRate}, {@link #beta}, * {@link LinearThresholdUnit#initialWeight}, * {@link LinearThresholdUnit#threshold}, * {@link LinearThresholdUnit#positiveThickness}, * {@link LinearThresholdUnit#negativeThickness}, * and finally {@link LinearThresholdUnit#bias}. * * @param out The output stream. **/ public void write(PrintStream out) { out.println(name + ": " + learningRate + ", " + beta + ", " + initialWeight + ", " + threshold + ", " + positiveThickness + ", " + negativeThickness + ", " + bias); if (lexicon.size() == 0) weightVector.write(out); else weightVector.write(out, lexicon); } /** * Writes the learned function's internal representation in binary form. * * @param out The output stream. **/ public void write(ExceptionlessOutputStream out) { super.write(out); out.writeDouble(beta); } /** * 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); beta = in.readDouble(); } /** * Simply a container for all of {@link BinaryMIRA}'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 SparsePerceptron.Parameters { /** * The user supplied learning algorithm parameter; default * {@link #defaultBeta}. The learning rate changes as a function of * <code>beta</code>. */ public double beta; /** Sets all the default values. */ public Parameters() { beta = defaultBeta; learningRate = defaultLearningRate; } /** * Sets the parameters from the parent's parameters object, giving * defaults to all parameters declared in this object. **/ public Parameters(SparsePerceptron.Parameters p) { super(p); beta = defaultBeta; } /** Copy constructor. */ public Parameters(Parameters p) { super(p); beta = p.beta; } /** * 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) { ((BinaryMIRA) 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(); if (beta != BinaryMIRA.defaultBeta) result += ", beta = " + beta; if (result.startsWith(", ")) result = result.substring(2); return result; } } }