package LBJ2.learn; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.io.Serializable; import java.util.Arrays; import java.util.Comparator; import java.util.Map; import LBJ2.classify.Feature; import LBJ2.util.ClassUtils; import LBJ2.util.DVector; import LBJ2.util.ExceptionlessInputStream; import LBJ2.util.ExceptionlessOutputStream; /** * This class is used as a weight vector in sparse learning algorithms. * {@link Feature}s are associated with <code>Double</code>s and/or with * <code>double[]</code>s representing the weights of the features they * produce. Features not appearing in the vector are assumed to have the * {@link #defaultWeight}. * * @author Nick Rizzolo **/ public class SparseWeightVector implements Cloneable, Serializable { /** * When a feature appears in an example but not in this vector, it is * assumed to have this weight. **/ protected static final double defaultWeight = 0; /** The initial capacity for {@link #weights} if not specified otherwise. */ protected static final int defaultCapacity = 1 << 10; /** The weights in the vector indexed by their {@link Lexicon} key. */ protected DVector weights; /** Simply instantiates {@link #weights}. */ public SparseWeightVector() { this(new DVector(defaultCapacity)); } /** * Simply initializes {@link #weights}. * * @param w An array of weights. **/ public SparseWeightVector(double[] w) { this(new DVector(w)); } /** * Simply initializes {@link #weights}. * * @param w A vector of weights. **/ public SparseWeightVector(DVector w) { weights = w; } /** * Returns the weight of the given feature. * * @param featureIndex The feature index. * @return The weight of the feature. **/ public double getWeight(int featureIndex) { return getWeight(featureIndex, defaultWeight); } /** * Returns the weight of the given feature. * * @param featureIndex The feature index. * @param defaultW The default weight. * @return The weight of the feature. **/ public double getWeight(int featureIndex, double defaultW) { return weights.get(featureIndex, defaultW); } /** * Sets the weight of the given feature. * * @param featureIndex The feature index. * @param w The new weight. **/ protected void setWeight(int featureIndex, double w) { setWeight(featureIndex, w, defaultWeight); } /** * Sets the weight of the given feature. * * @param featureIndex The feature index. * @param w The new weight. * @param defaultW The default weight. **/ protected void setWeight(int featureIndex, double w, double defaultW) { weights.set(featureIndex, w, defaultW); } /** * Takes the dot product of this <code>SparseWeightVector</code> with the * argument vector, using the hard coded default weight. * * @param exampleFeatures The example's feature indices. * @param exampleValues The example's feature values. * @return The computed dot product. **/ public double dot(int[] exampleFeatures, double[] exampleValues) { return dot(exampleFeatures, exampleValues, defaultWeight); } /** * Takes the dot product of this <code>SparseWeightVector</code> with the * argument vector, using the specified default weight when one is not yet * present in this vector. * * @param exampleFeatures The example's feature indices. * @param exampleValues The example's feature values. * @param defaultW The default weight. * @return The computed dot product. **/ public double dot(int[] exampleFeatures, double[] exampleValues, double defaultW) { double sum = 0; for (int i = 0; i < exampleFeatures.length; i++) { double w = getWeight(exampleFeatures[i], defaultW); sum += w * exampleValues[i]; } return sum; } /** * Self-modifying vector addition. * * @param exampleFeatures The example's feature indices. * @param exampleValues The example's feature values. **/ public void scaledAdd(int[] exampleFeatures, double[] exampleValues) { scaledAdd(exampleFeatures, exampleValues, 1, defaultWeight); } /** * Self-modifying vector addition where the argument vector is first scaled * by the given factor. The default weight is used to initialize new * feature weights. * * @param exampleFeatures The example's feature indices. * @param exampleValues The example's feature values. * @param factor The scaling factor. **/ public void scaledAdd(int[] exampleFeatures, double[] exampleValues, double factor) { scaledAdd(exampleFeatures, exampleValues, factor, defaultWeight); } /** * Self-modifying vector addition where the argument vector is first scaled * by the given factor. * * @param exampleFeatures The example's feature indices. * @param exampleValues The example's feature values. * @param factor The scaling factor. * @param defaultW An initial weight for previously unseen * features. **/ public void scaledAdd(int[] exampleFeatures, double[] exampleValues, double factor, double defaultW) { for (int i = 0; i < exampleFeatures.length; i++) { int featureIndex = exampleFeatures[i]; double w = getWeight(featureIndex, defaultW) + factor*exampleValues[i]; setWeight(featureIndex, w, defaultW); } } /** * Self-modifying vector multiplication where the argument vector is first * scaled by the given factor. The default weight is used to initialize * new feature weights. * * @param exampleFeatures The example's feature indices. * @param exampleValues The example's feature values. * @param factor The scaling factor. **/ public void scaledMultiply(int[] exampleFeatures, double[] exampleValues, double factor) { scaledMultiply(exampleFeatures, exampleValues, factor, defaultWeight); } /** * Self-modifying vector multiplication where the argument vector is first * scaled by the given factor. * * @param exampleFeatures The example's feature indices. * @param exampleValues The example's feature values. * @param factor The scaling factor. * @param defaultW An initial weight for previously unseen * features. **/ public void scaledMultiply(int[] exampleFeatures, double[] exampleValues, double factor, double defaultW) { for (int i = 0; i < exampleFeatures.length; i++) { int featureIndex = exampleFeatures[i]; double s = exampleValues[i]; double multiplier = factor; if (s == 0) multiplier = 1; else if (s != 1) multiplier = Math.pow(factor, s); double w = getWeight(featureIndex, defaultW) * multiplier; setWeight(featureIndex, w, defaultW); } } /** * The strength of each feature in the argument vector is multiplied by the * corresponding weight in this weight vector and the result is returned as * an array of arrays. The first array contains the integer keys of the * example's features, as indexed in the lexicon. The second array gives * the double values corresponding to the product of the pairwise * multiplication of the strengths of that feature. * * @param exampleFeatures The example's feature indices. * @param exampleValues The example's feature values. * @param defaultW An initial weight for previously unseen * features. * @param inverse When set to <code>true</code> the weight in this * vector is inverted before the multiplication * takes place. * @return A new example vector representing the pairwise multiplication. **/ public Object[] pairwiseMultiply(int[] exampleFeatures, double[] exampleValues, double defaultW, boolean inverse) { int resultFeatures[] = new int[exampleFeatures.length]; double resultValues[] = new double[exampleFeatures.length]; for (int i = 0; i < exampleFeatures.length; i++) { int featureIndex = exampleFeatures[i]; double w = getWeight(featureIndex, defaultW); if (inverse) w = 1 / w; resultFeatures[i] = exampleFeatures[i]; resultValues[i] = w * exampleValues[i]; } return new Object[] {resultFeatures, resultValues}; } /** Empties the weight map. */ public void clear() { weights = new DVector(defaultCapacity); } /** Returns the length of the weight vector. */ public int size() { return weights.size(); } /** * Outputs the contents of this <code>SparseWeightVector</code> into the * specified <code>PrintStream</code>. The string representation starts * with a <code>"Begin"</code> annotation, ends with an <code>"End"</code> * annotation, and without a <code>Lexicon</code> passed as a parameter, * the weights are simply printed in the order of their integer indices. * * @param out The stream to write to. **/ public void write(PrintStream out) { out.println("Begin SparseWeightVector"); toStringJustWeights(out); out.println("End SparseWeightVector"); } /** * Outputs the contents of this <code>SparseWeightVector</code> into the * specified <code>PrintStream</code>. The string representation starts * with a <code>"Begin"</code> annotation, ends with an <code>"End"</code> * annotation, and lists each feature with its corresponding weight on the * same, separate line in between. * * @param out The stream to write to. * @param lex The feature lexicon. **/ public void write(PrintStream out, Lexicon lex) { out.println("Begin SparseWeightVector"); toStringJustWeights(out, 0, lex); out.println("End SparseWeightVector"); } /** * Outputs a textual representation of this <code>SparseWeightVector</code> * to a stream just like {@link #write(PrintStream)}, but without the * <code>"Begin"</code> and <code>"End"</code> annotations. Without a * <code>Lexicon</code> passed as a parameter, the weights are simply * printed in the order of their integer indices. * * @param out The stream to write to. **/ public void toStringJustWeights(PrintStream out) { for (int i = 0; i < weights.size(); i++) out.println(weights.get(i)); } /** * Outputs a textual representation of this <code>SparseWeightVector</code> * to a stream just like {@link #write(PrintStream)}, but without the * <code>"Begin"</code> and <code>"End"</code> annotations. With a * <code>Lexicon</code> passed as a parameter, the feature is printed along * with each weight. * * @param out The stream to write to. * @param min Sets the minimum width for the textual representation of all * features. * @param lex The feature lexicon. **/ public void toStringJustWeights(PrintStream out, int min, Lexicon lex) { Map map = lex.getMap(); Map.Entry[] entries = (Map.Entry[]) map.entrySet().toArray(new Map.Entry[map.size()]); Arrays.sort(entries, new Comparator() { public int compare(Object o1, Object o2) { Map.Entry e1 = (Map.Entry) o1; Map.Entry e2 = (Map.Entry) o2; int i1 = ((Integer) e1.getValue()).intValue(); int i2 = ((Integer) e2.getValue()).intValue(); if ((i1 < weights.size()) != (i2 < weights.size())) return i1 - i2; return ((Feature) e1.getKey()).compareTo(e2.getKey()); } }); int i, biggest = min; for (i = 0; i < entries.length; ++i) { //for (i = 0; i < weights.size(); ++i) String key = entries[i].getKey().toString() + (((Integer) entries[i].getValue()).intValue() < weights.size() ? "" : " (pruned)"); biggest = Math.max(biggest, key.length()); } if (biggest % 2 == 0) biggest += 2; else ++biggest; for (i = 0; i < entries.length; ++i) { //for (i = 0; i < weights.size(); ++i) String key = entries[i].getKey().toString() + (((Integer) entries[i].getValue()).intValue() < weights.size() ? "" : " (pruned)"); out.print(key); for (int j = 0; key.length() + j < biggest; ++j) out.print(" "); int index = ((Integer) entries[i].getValue()).intValue(); out.println(weights.get(index)); } } /** * Creates a string representation of this <code>SparseWeightVector</code>. * This method merely returns the data computed by * {@link #write(PrintStream)}. * * @return A textual representation of this vector. **/ public String toString() { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream out = new PrintStream(baos); write(out); return baos.toString(); } /** * Creates a string representation of this <code>SparseWeightVector</code>. * This method merely returns the data computed by * {@link #write(PrintStream,Lexicon)}. * * @param lex The feature lexicon. * @return A textual representation of this vector. **/ public String toString(Lexicon lex) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream out = new PrintStream(baos); write(out, lex); return baos.toString(); } /** * Writes the weight vector's internal representation in binary form. * * @param out The output stream. **/ public void write(ExceptionlessOutputStream out) { out.writeString(getClass().getName()); weights.write(out); } /** * Reads the binary representation of a weight vector of any type from the * given stream. The stream is expected to first return a string * containing the fully qualified class name of the weight vector. If the * <i>short</i> value <code>-1</code> appears instead, this method returns * <code>null</code>. * * <p> This method is appropriate for reading weight vectors as written by * {@link #write(ExceptionlessOutputStream)}. * * @param in The input stream. * @return The weight vector read from the stream. **/ public static SparseWeightVector readWeightVector( ExceptionlessInputStream in) { String name = in.readString(); if (name == null) return null; Class c = ClassUtils.getClass(name); SparseWeightVector result = null; try { result = (SparseWeightVector) c.newInstance(); } catch (Exception e) { System.err.println("Error instantiating weight vector '" + name + "':"); e.printStackTrace(); in.close(); System.exit(1); } result.read(in); return result; } /** * Reads the representation of a weight vector with this object's run-time * type from the given stream, overwriting the data in this object. * * <p> This method is appropriate for reading weight vectors as written by * {@link #write(ExceptionlessOutputStream)}. * * @param in The input stream. **/ public void read(ExceptionlessInputStream in) { weights.read(in); } /** * Returns a copy of this <code>SparseWeightVector</code> in which the * {@link #weights} variable has been cloned deeply. * * @return A copy of this <code>SparseWeightVector</code>. **/ public Object clone() { SparseWeightVector clone = null; try { clone = (SparseWeightVector) super.clone(); } catch (Exception e) { System.err.println("Error cloning " + getClass().getName() + ":"); e.printStackTrace(); System.exit(1); } clone.weights = (DVector) weights.clone(); return clone; } /** * Returns a new, empty weight vector with the same parameter settings as * this one. * * @return An empty weight vector. **/ public SparseWeightVector emptyClone() { return new SparseWeightVector(); } }