package LBJ2.learn;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.io.Serializable;
import java.net.URL;
import LBJ2.classify.Classifier;
import LBJ2.classify.DiscreteFeature;
import LBJ2.classify.DiscretePrimitiveStringFeature;
import LBJ2.classify.Feature;
import LBJ2.classify.FeatureVector;
import LBJ2.classify.FeatureVectorReturner;
import LBJ2.classify.LabelVectorReturner;
import LBJ2.classify.RealFeature;
import LBJ2.classify.ScoreSet;
import LBJ2.util.ClassUtils;
import LBJ2.util.ExceptionlessInputStream;
import LBJ2.util.ExceptionlessOutputStream;
import LBJ2.util.FVector;
/**
* Extend this class to create a new {@link Classifier} that learns to mimic
* one an oracle classifier given a feature extracting classifier and example
* objects.
*
* @author Nick Rizzolo
**/
public abstract class Learner extends Classifier
{
/** Stores the classifier used to produce labels. */
protected Classifier labeler;
/** Stores the classifiers used to produce features. */
protected Classifier extractor;
/** Stores the feature {@link Lexicon}. */
protected Lexicon lexicon;
/** Stores the label {@link Lexicon}. */
protected Lexicon labelLexicon;
/** The encoding used by this learner's feature lexicon. */
protected String encoding;
/**
* Stores the set of predictions that this learner will choose from when
* classifying a new example.
**/
protected FVector predictions;
/** Caches the location of this learner's offline binary representation. */
protected URL lcFilePath;
/** Caches the location of this learner's offline lexicon. */
protected URL lexFilePath;
/**
* Informs this learner that it can and should read its feature lexicon on
* demand.
**/
protected boolean readLexiconOnDemand;
/**
* This constructor is used by the LBJ2 compiler; it should never be called
* by a programmer.
**/
protected Learner() { }
/**
* Initializes the name.
*
* @param n The name of the classifier.
**/
protected Learner(String n) {
super(n);
lexicon = new Lexicon();
labelLexicon = new Lexicon();
predictions = new FVector();
}
/**
* Constructor for unsupervised learning.
*
* @param n The name of the classifier.
* @param e The feature extracting classifier.
**/
protected Learner(String n, Classifier e) { this(n, null, e); }
/**
* Constructor for supervised learning.
*
* @param n The name of the classifier.
* @param l The labeling classifier.
* @param e The feature extracting classifier.
**/
protected Learner(String n, Classifier l, Classifier e) {
super(n);
setLabeler(l);
setExtractor(e);
lexicon = new Lexicon();
labelLexicon = new Lexicon();
predictions = new FVector();
}
/**
* Sets the values of parameters that control the behavior of this learning
* algorithm.
*
* @param p The parameters.
**/
public void setParameters(Parameters p) { p.setParameters(this); }
/** Retrieves the parameters that are set in this learner. */
public Parameters getParameters() { return new Parameters(); }
/**
* Sets the labeler.
*
* @param l A labeling classifier.
**/
public void setLabeler(Classifier l) { labeler = l; }
/** Returns the labeler. */
public Classifier getLabeler() { return labeler; }
/**
* Sets the extractor.
*
* @param e A feature extracting classifier.
**/
public void setExtractor(Classifier e) { extractor = e; }
/** Returns the extractor. */
public Classifier getExtractor() { return extractor; }
/**
* Sets the feature lexicon. If set to <code>null</code>, the JVM's
* garbage collector is invoked.
*
* @param l A feature lexicon.
**/
public void setLexicon(Lexicon l) {
lexicon = l;
if (l == null) System.gc();
else l.setEncoding(encoding);
}
/** Returns the feature lexicon. */
public Lexicon getLexicon() {
demandLexicon();
return lexicon;
}
/**
* Sets the label lexicon.
*
* @param l A feature lexicon.
**/
public void setLabelLexicon(Lexicon l) {
labelLexicon = l;
if (labelLexicon == null) {
predictions = null;
return;
}
int N = labelLexicon.size();
predictions = new FVector(N);
for (int i = 0; i < N; ++i) createPrediction(i);
}
/** Returns the label lexicon. */
public Lexicon getLabelLexicon() { return labelLexicon; }
/**
* Sets the encoding to use in this learner's feature lexicon.
*
* @param e The encoding.
**/
public void setEncoding(String e) {
encoding = e;
lexicon.setEncoding(e);
}
/**
* Sets the location of the model as a regular file on this file system.
*
* @param p The file's path.
**/
public void setModelLocation(String p) {
try { lcFilePath = new URL("file:" + p); }
catch (Exception e) {
System.err.println("ERROR: Can't create URL for file '" + p + "':");
e.printStackTrace();
System.exit(1);
}
}
/**
* Sets the location of the model as a <code>URL</code>.
*
* @param u The model's location.
**/
public void setModelLocation(URL u) { lcFilePath = u; }
/** Returns the model's location. */
public URL getModelLocation() { return lcFilePath; }
/**
* Sets the location of the lexicon as a regular file on this file system.
*
* @param p The file's path.
**/
public void setLexiconLocation(String p) {
try { lexFilePath = new URL("file:" + p); }
catch (Exception e) {
System.err.println("ERROR: Can't create URL for file '" + p + "':");
e.printStackTrace();
System.exit(1);
}
}
/**
* Sets the location of the model as a <code>URL</code>.
*
* @param u The model's location.
**/
public void setLexiconLocation(URL u) { lexFilePath = u; }
/** Returns the lexicon's location. */
public URL getLexiconLocation() { return lexFilePath; }
/**
* Establishes a new feature counting policy for this learner's lexicon.
*
* @param policy The new feature counting policy.
**/
public void countFeatures(Lexicon.CountPolicy policy) {
if (policy == Lexicon.CountPolicy.perClass
&& !getOutputType().equals("discrete"))
throw new IllegalArgumentException(
"LBJ ERROR: Learner.countFeatures: Can't do 'per class' feature "
+ "counting unless the learner is discrete.");
demandLexicon();
lexicon.countFeatures(policy);
}
/**
* Returns this learner's feature lexicon after discarding any feature
* counts it may have been storing. This method is likely only useful when
* the lexicon and its counts are currently stored on disk and
* {@link #readLexiconOnDemand(String)} or
* {@link #readLexiconOnDemand(URL)} has already been called, in which case
* the lexicon is read from disk without wasting time loading the counts.
**/
public Lexicon getLexiconDiscardCounts() {
if (readLexiconOnDemand && (lexicon == null || lexicon.size() == 0))
lexicon = Lexicon.readLexicon(lexFilePath, false);
else lexicon.countFeatures(Lexicon.CountPolicy.none);
return lexicon;
}
/**
* Returns a new, emtpy learner into which all of the parameters that
* control the behavior of the algorithm have been copied. Here, "emtpy"
* means no learning has taken place.
**/
public Learner emptyClone() {
Learner clone = (Learner) super.clone();
clone.forget();
return clone;
}
/**
* Trains the learning algorithm given an object as an example.
* By default, this simply converts the example object into arrays
* and passes it to {@link #learn(int[],double[],int[],double[])}.
*
* @param example An example of the desired learned classifier's behavior.
**/
public void learn(Object example) {
Object[] exampleArray = getExampleArray(example);
learn((int[]) exampleArray[0], (double[]) exampleArray[1],
(int[]) exampleArray[2], (double[]) exampleArray[3]);
}
/**
* Trains the learning algorithm given a feature vector as an example.
* This simply converts the example object into arrays and passes it to
* {@link #learn(int[],double[],int[],double[])}.
*
* @param vector An example of the desired learned classifier's behavior.
**/
public void learn(FeatureVector vector) {
Classifier saveExtractor = getExtractor();
Classifier saveLabeler = getLabeler();
setExtractor(new FeatureVectorReturner());
setLabeler(new LabelVectorReturner());
learn((Object) vector);
setExtractor(saveExtractor);
setLabeler(saveLabeler);
}
/**
* Trains the learning algorithm given an example formatted as
* arrays of feature indices, their values, and the example labels.
*
* @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 values of the labels.
**/
abstract public void learn(int[] exampleFeatures, double[] exampleValues,
int[] exampleLabels, double[] labelValues);
/**
* Trains the learning algorithm given many objects as examples. This
* implementation simply calls {@link #learn(Object)} on each of the
* objects in the input array and finishes by calling
* {@link #doneLearning()}. It should be overridden if there is a more
* efficient implementation.
*
* @param examples Examples of the desired learned classifier's behavior.
**/
public void learn(Object[] examples) {
for (int i = 0; i < examples.length; ++i)
learn(examples[i]);
doneLearning();
}
/**
* Trains the learning algorithm given many feature vectors as examples.
* This implementation simply calls {@link #learn(FeatureVector)} on each
* of the vectors in the input array and finishes by calling
* {@link #doneLearning()}. It should be overridden if there is a more
* efficient implementation.
*
* @param examples Examples of the desired learned classifier's behavior.
**/
public void learn(FeatureVector[] examples) {
for (int i = 0; i < examples.length; ++i)
learn(examples[i]);
doneLearning();
}
/**
* This method makes one or more decisions about a single object, returning
* those decisions as {@link Feature}s in a vector.
*
* @param example The object to make decisions about.
* @return A vector of {@link Feature}s about the input object.
**/
public FeatureVector classify(Object example) {
Object[] exampleArray = getExampleArray(example, false);
return classify((int[]) exampleArray[0], (double[]) exampleArray[1]);
}
/**
* This method makes one or more decisions about a single feature vector,
* returning those decisions as {@link Feature}s in a vector.
*
* @param vector The vector to make decisions about.
* @return A vector of {@link Feature}s about the input vector.
**/
public FeatureVector classify(FeatureVector vector) {
Classifier saveExtractor = getExtractor();
Classifier saveLabeler = getLabeler();
setExtractor(new FeatureVectorReturner());
setLabeler(new LabelVectorReturner());
FeatureVector result = classify((Object) vector);
setExtractor(saveExtractor);
setLabeler(saveLabeler);
return result;
}
/**
* This method makes one or more decisions about a single object, returning
* those decisions as {@link Feature}s in a vector.
*
* @param exampleFeatures The example's array of feature indices.
* @param exampleValues The example's array of feature values.
* @return A vector of {@link Feature}s about the input object.
**/
abstract public FeatureVector classify(int[] exampleFeatures,
double[] exampleValues);
/**
* Use this method to make a batch of classification decisions about
* several objects. This function is implemented in the most naive way
* (simply calling {@link #classify(FeatureVector)} repeatedly) and should
* be overridden if there is a more efficient implementation.
*
* @param vectors The vectors to make decisions about.
* @return An array of feature vectors, one per input vector.
**/
public FeatureVector[] classify(FeatureVector[] vectors) {
FeatureVector[] result = new FeatureVector[vectors.length];
for (int i = 0; i < vectors.length; ++i)
result[i] = classify(vectors[i]);
return result;
}
/**
* Use this method to make a batch of classification decisions about
* several examples. This function is implemented in the most naive way
* (simply calling {@link #classify(int[],double[])} repeatedly) and should
* be overridden if there is a more efficient implementation.
*
* @param e The examples to make decisions about, represented as arrays of
* indices and strengths.
* @return An array of feature vectors, one per input object.
**/
public FeatureVector[] classify(Object[][] e) {
FeatureVector[] result = new FeatureVector[e.length];
for (int i = 0; i < e.length; ++i)
result[i] = classify((int[]) e[i][0], (double[]) e[i][1]);
return result;
}
/**
* Returns the classification of the given example object as a single
* feature instead of a {@link FeatureVector}.
*
* @param example The object to classify.
* @return The classification of <code>example</code> as a feature.
**/
public Feature featureValue(Object example) {
Object[] exampleArray = getExampleArray(example, false);
return featureValue((int[]) exampleArray[0], (double[]) exampleArray[1]);
}
/**
* Returns the classification of the given feature vector as a single
* feature instead of a {@link FeatureVector}.
*
* @param vector The vector to classify.
* @return The classification of <code>vector</code> as a feature.
**/
public Feature featureValue(FeatureVector vector) {
Classifier saveExtractor = getExtractor();
Classifier saveLabeler = getLabeler();
setExtractor(new FeatureVectorReturner());
setLabeler(new LabelVectorReturner());
Feature result = featureValue((Object) vector);
setExtractor(saveExtractor);
setLabeler(saveLabeler);
return result;
}
/**
* 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 <code>o</code> as a feature.
**/
public Feature featureValue(int[] f, double[] v) {
throw
new UnsupportedOperationException(
"The featureValue(int[], double[]) method has not been overridden in "
+ "class '" + getClass().getName() + "'.");
}
/**
* Returns the value of the discrete prediction that this learner would
* make, given an example.
*
* @param example The example object.
* @return The discrete value.
**/
public String discreteValue(Object example) {
Object[] exampleArray = getExampleArray(example, false);
return
discreteValue((int[]) exampleArray[0], (double[]) exampleArray[1]);
}
/**
* Returns the value of the discrete prediction that this learner would
* make, given a feature vector.
*
* @param vector The example vector.
* @return The discrete value.
**/
public String discreteValue(FeatureVector vector) {
Classifier saveExtractor = getExtractor();
Classifier saveLabeler = getLabeler();
setExtractor(new FeatureVectorReturner());
setLabeler(new LabelVectorReturner());
String result = discreteValue((Object) vector);
setExtractor(saveExtractor);
setLabeler(saveLabeler);
return result;
}
/**
* Returns the value of the discrete feature that would be returned by this
* classifier. This method should only be called when overridden by a
* classifier returning a single discrete feature.
*
* @param f The features array.
* @param v The values array.
* @return The value of the feature produced for the input object.
**/
public String discreteValue(int[] f, double[] v) {
throw
new UnsupportedOperationException(
"The discreteValue(Object) method has not been overridden in class '"
+ getClass().getName() + "'.");
}
/**
* Returns the value of the real prediction that this learner would
* make, given an example.
*
* @param example The example object.
* @return The real value.
**/
public double realValue(Object example) {
Object[] exampleArray = getExampleArray(example, false);
return realValue((int[])exampleArray[0], (double[])exampleArray[1]);
}
/**
* Returns the value of the real prediction that this learner would
* make, given a feature vector.
*
* @param vector The example vector.
* @return The real value.
**/
public double realValue(FeatureVector vector) {
Classifier saveExtractor = getExtractor();
Classifier saveLabeler = getLabeler();
setExtractor(new FeatureVectorReturner());
setLabeler(new LabelVectorReturner());
double result = realValue((Object) vector);
setExtractor(saveExtractor);
setLabeler(saveLabeler);
return result;
}
/**
* Returns the value of the real feature that would be returned by this
* classifier. This method should only be called when overridden by a
* classifier returning a single real feature.
*
* @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) {
throw
new UnsupportedOperationException(
"The realValue(Object) method has not been overridden in class '"
+ getClass().getName() + "'.");
}
/**
* Overridden by subclasses to perform any required post-processing
* computations after all training examples have been observed through
* {@link #learn(Object)} and {@link #learn(Object[])}. By default this
* method does nothing.
**/
public void doneLearning() {
}
/**
* This method is sometimes called before training begins, although it is
* not guaranteed to be called at all. It allows the number of examples
* and number of features to be passed to the Learner, in case this
* information is available, such as after pre-extraction. By default this
* method does nothing.
*
* @param numExamples The number of examples that will be observed during
* training.
* @param numFeatures The number of features that will be observed during
* training.
**/
public void initialize(int numExamples, int numFeatures) {
}
/** Called after each round of training. Does nothing by default. */
public void doneWithRound() {
}
/**
* Converts an example object into an array of arrays representing the
* example including its labels. 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 strengths of the features
* in the first array. The third and fourth arrays play the same roles as
* the first and second arrays respectively, except they describe the
* labels.
*
* @param example The example object.
* @return The converted example array.
**/
public Object[] getExampleArray(Object example) {
return getExampleArray(example, true);
}
/**
* Converts an example object into an array of arrays representing the
* example. 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 strengths of the features in the first
* array. The third and fourth arrays will only be present if
* <code>training</code> is set to <code>true</code>. They play the same
* roles as the first and second arrays respectively, except they describe
* the labels.
*
* @param example The example object.
* @param training Whether or not labels should be extracted.
* @return The converted example array.
**/
public Object[] getExampleArray(Object example, boolean training) {
if (example instanceof Object[]
&& ((Object[]) example)[0] instanceof int[]
&& ((Object[]) example)[1] instanceof double[])
return (Object[]) example;
if (readLexiconOnDemand && (lexicon == null || lexicon.size() == 0)) {
readLexicon(lexFilePath);
readLexiconOnDemand = false;
}
Object[] exampleArray = null;
Lexicon.CountPolicy countPolicy = lexicon.getCountPolicy();
int labelIndex = -1;
// Get example labels
if (training) {
FeatureVector labelVector = labeler.classify(example);
int F = labelVector.featuresSize();
int[] labelArray = new int[F];
double[] labelValues = new double[F];
for (int f = 0; f < F; ++f) {
Feature label = labelVector.getFeature(f);
if (label.isDiscrete())
labelArray[f] = labelLexicon.lookup(label, true);
else
labelArray[f] =
labelLexicon.lookup(label.getFeatureKey(labelLexicon), true);
labelValues[f] += label.getStrength();
createPrediction(labelArray[f]);
}
exampleArray = new Object[]{ null, null, labelArray, labelValues };
if (countPolicy == Lexicon.CountPolicy.perClass)
//&& labeler.getOutputType().equals("discrete") && F == 1)
// Don't really want to do this comparison for every example; we'll
// trust the user not to do per class feature counting when it isn't
// true. Plus, the countFeatures(CountPolicy) method in this class
// checks for it.
labelIndex = labelArray[0];
}
else exampleArray = new Object[2];
// Get example features.
FeatureVector featureVector = extractor.classify(example);
int F = featureVector.featuresSize();
int[] exampleArrayFeatures = new int[F];
double[] exampleArrayValues = new double[F];
exampleArray[0] = exampleArrayFeatures;
exampleArray[1] = exampleArrayValues;
for (int f = 0; f < F; ++f) {
Feature feature = featureVector.getFeature(f);
exampleArrayFeatures[f] =
lexicon.lookup(feature.getFeatureKey(lexicon, training, labelIndex),
training, labelIndex);
exampleArrayValues[f] += feature.getStrength();
}
return exampleArray;
}
/**
* If it hasn't been created already, this method will create the
* prediction feature in {@link #predictions} associated with the label
* feature at the given index of {@link #labelLexicon}. This method does
* not create {@link RealFeature}s in {@link #predictions} since their
* strengths cannot be modified. In association with
* {@link DiscreteFeature}s it creates a
* {@link DiscretePrimitiveStringFeature} with an empty identifier. Its
* <code>value</code>, <code>valueIndex</code>, and
* <code>totalValues</code> fields are filled by calling the label
* feature's {@link Feature#getStringValue() getStringValue()},
* {@link Feature#getValueIndex() getValueIndex()}, and
* {@link Feature#totalValues() totalValues()} methods respectively.
*
* @param index The index of a label feature in {@link #labelLexicon}.
**/
protected void createPrediction(int index) {
createPrediction(labelLexicon, index);
}
/**
* If it hasn't been created already, this method will create the
* prediction feature in {@link #predictions} associated with the label
* feature at the given index of <code>lex</code>. This method does
* not create {@link RealFeature}s in {@link #predictions} since their
* strengths cannot be modified. In association with
* {@link DiscreteFeature}s it creates a
* {@link DiscretePrimitiveStringFeature} with an empty identifier. Its
* <code>value</code>, <code>valueIndex</code>, and
* <code>totalValues</code> fields are filled by calling the label
* feature's {@link Feature#getStringValue() getStringValue()},
* {@link Feature#getValueIndex() getValueIndex()}, and
* {@link Feature#totalValues() totalValues()} methods respectively.
*
* @param lex The label lexicon to associate prediction features with.
* @param index The index of a label feature in <code>lex</code>.
**/
protected void createPrediction(Lexicon lex, int index) {
if (predictions.get(index) != null
|| !getOutputType().equals("discrete"))
return;
Feature label = lex.lookupKey(index);
predictions.set(index,
new DiscretePrimitiveStringFeature(
containingPackage, name, "", label.getStringValue(),
label.getValueIndex(), label.totalValues()));
}
/**
* Reinitializes the learner to the state it started at before any learning
* was performed. By default, this sets the lexicons to blank Lexicon
* objects and calls {@link #initialize(int,int)} to reset the number of
* examples and features to 0, for learners that use this.
**/
public void forget() {
lexicon = new Lexicon(encoding);
labelLexicon = new Lexicon();
predictions = new FVector();
initialize(0, 0);
readLexiconOnDemand = false;
}
/**
* Produces a set of scores indicating the degree to which each possible
* discrete classification value is associated with the given example
* object. Learners that return a <code>real</code> feature or more than
* one feature may implement this method by simply returning
* <code>null</code>.
*
* @param example The object to make decisions about.
* @return A set of scores indicating the degree to which each possible
* discrete classification value is associated with the given
* example object.
**/
public ScoreSet scores(Object example) {
Object[] exampleArray = getExampleArray(example, false);
return scores((int[])exampleArray[0], (double[])exampleArray[1]);
}
/**
* Produces a set of scores indicating the degree to which each possible
* discrete classification value is associated with the given feature
* vector. Learners that return a <code>real</code> feature or more than
* one feature may implement this method by simply returning
* <code>null</code>.
*
* @param vector The vector to make decisions about.
* @return A set of scores indicating the degree to which each possible
* discrete classification value is associated with the given
* example vector.
**/
public ScoreSet scores(FeatureVector vector) {
Classifier saveExtractor = getExtractor();
Classifier saveLabeler = getLabeler();
setExtractor(new FeatureVectorReturner());
setLabeler(new LabelVectorReturner());
ScoreSet result = scores((Object) vector);
setExtractor(saveExtractor);
setLabeler(saveLabeler);
return result;
}
/**
* Produces a set of scores indicating the degree to which each possible
* discrete classification value is associated with the given example
* object. Learners that return a <code>real</code> feature or more than
* one feature may implement this method by simply returning
* <code>null</code>.
*
* @param exampleFeatures The example's array of feature indices
* @param exampleValues The example's array of values
* @return A set of scores indicating the degree to which each possible
* discrete classification value is associated with the given
* example object.
**/
abstract public ScoreSet scores(int[] exampleFeatures,
double[] exampleValues);
/**
* Writes the learned function's internal representation as text.
*
* @param out The output stream.
**/
abstract public void write(PrintStream out);
/**
* Automatically generated code will override this method to set their
* <code>isClone</code> field to <code>false</code>. This then allows a
* pure java program to read a learner's representation into any instance
* of the learner's class. By default, this method does nothing.
**/
public void unclone() { }
/**
* Returns the size of the lexicon after any pruning that may have taken
* place or 0 if the lexicon's location isn't known.
**/
public int getPrunedLexiconSize() {
if ((lexicon == null || lexicon.size() == 0) && readLexiconOnDemand) {
ExceptionlessInputStream in =
ExceptionlessInputStream.openCompressedStream(lexFilePath);
int result = Lexicon.readPrunedSize(in);
in.close();
return result;
}
return lexicon == null ? 0 : lexicon.getCutoff();
}
/**
* Returns a deep (enough) clone of this learner. The following fields are
* cloned themselves: {@link #lexicon}, {@link #labelLexicon}, and
* {@link #predictions}.
*
* <p> Note that this is an overriding implementation of
* <code>Object</code>'s <code>clone()</code> method, and its functionality
* is completely separate from and unrelated to that of this class's
* {@link #unclone()} method.
**/
public Object clone() {
Learner result = (Learner) super.clone();
if (lexicon != null) result.lexicon = (Lexicon) lexicon.clone();
if (labelLexicon != null)
result.labelLexicon = (Lexicon) labelLexicon.clone();
if (predictions != null)
result.predictions = (FVector) predictions.clone();
return result;
}
/**
* Writes the binary representation of this learned function if there is a
* location cached in {@link #lcFilePath}, and writes the binary
* representation of the feature lexicon if there is a location cached in
* {@link #lexFilePath}.
**/
public void save() {
if (lcFilePath != null) saveModel();
if (lexFilePath != null && lexicon != null && lexicon.size() > 0)
saveLexicon();
}
/**
* Writes the binary representation of this learned function to the
* location specified by {@link #lcFilePath}. If {@link #lcFilePath} is
* not set, this method will produce an error message and exit the program.
**/
public void saveModel() {
if (lcFilePath == null) {
System.err.println(
"LBJ ERROR: saveModel() called without a cached location");
new Exception().printStackTrace();
System.exit(1);
}
ExceptionlessOutputStream out =
ExceptionlessOutputStream.openCompressedStream(lcFilePath);
write(out);
out.close();
}
/**
* Writes the binary representation of the feature lexicon to the location
* specified by {@link #lexFilePath}. If {@link #lexFilePath} is not set,
* this method will produce an error message and exit the program.
**/
public void saveLexicon() {
if (lexFilePath == null) {
System.err.println(
"LBJ ERROR: saveLexicon() called without a cached location");
new Exception().printStackTrace();
System.exit(1);
}
ExceptionlessOutputStream out =
ExceptionlessOutputStream.openCompressedStream(lexFilePath);
if (lexicon == null) out.writeInt(0);
else lexicon.write(out);
out.close();
}
/**
* Writes the learned function's binary internal represetation including
* both its model and lexicons to the specified files. These files are
* then cached in {@link #lcFilePath} and {@link #lexFilePath}.
*
* @param modelFile The name of the file in which to write the model.
* @param lexFile The name of the file in which to write the feature
* lexicon.
**/
public void write(String modelFile, String lexFile) {
writeModel(modelFile);
if (lexicon != null && lexicon.size() > 0) writeLexicon(lexFile);
}
/**
* Writes only the learned function's model (which includes the label
* lexicon) to the specified file in binary form. This file is then cached
* in {@link #lcFilePath}.
*
* @param filename The name of the file in which to write the model.
**/
public void writeModel(String filename) {
ExceptionlessOutputStream out =
ExceptionlessOutputStream.openCompressedStream(filename);
write(out);
out.close();
try { lcFilePath = new URL("file:" + filename); }
catch (Exception e) {
System.err.println("Error constructing URL:");
e.printStackTrace();
System.exit(1);
}
}
/**
* Writes the learned function's feature lexicon to the specified file.
* This file is then cached in {@link #lexFilePath}.
*
* @param filename The name of the file in which to write the feature
* lexicon.
**/
public void writeLexicon(String filename) {
ExceptionlessOutputStream out =
ExceptionlessOutputStream.openCompressedStream(filename);
if (lexicon == null) out.writeInt(0);
else lexicon.write(out);
out.close();
try { lexFilePath = new URL("file:" + filename); }
catch (Exception e) {
System.err.println("Error constructing URL:");
e.printStackTrace();
System.exit(1);
}
}
/**
* Writes the learned function's internal representation in binary form.
*
* @param out The output stream.
**/
public void write(ExceptionlessOutputStream out) {
out.writeString(getClass().getName());
out.writeString(containingPackage);
out.writeString(name);
out.writeString(encoding);
if (labeler == null) out.writeString(null);
else out.writeString(labeler.getClass().getName());
if (extractor == null) out.writeString(null);
else out.writeString(extractor.getClass().getName());
if (labelLexicon == null) out.writeInt(0);
else labelLexicon.write(out);
if (predictions == null) out.writeInt(0);
else predictions.write(out);
}
/**
* Reads the learned function's binary internal represetation including
* both its model and lexicons from the specified files, overwriting any
* and all data this object may have already contained. These files are
* then cached in {@link #lcFilePath} and {@link #lexFilePath}.
*
* @param modelFile The name of the file from which to read the model.
* @param lexFile The name of the file from which to read the feature
* lexicon.
**/
public void read(String modelFile, String lexFile) {
readModel(modelFile);
readLexicon(lexFile);
}
/**
* Reads only the learned function's model and label lexicon from the
* specified file in binary form, overwriting whatever model data may have
* already existed in this object. This file is then cached in
* {@link #lcFilePath}.
*
* @param filename The name of the file from which to read the model.
**/
public void readModel(String filename) {
try { readModel(new URL("file:" + filename)); }
catch (Exception e) {
System.err.println("Error constructing URL:");
e.printStackTrace();
System.exit(1);
}
}
/**
* Reads only the learned function's model and label lexicon from the
* specified location in binary form, overwriting whatever model data may
* have already existed in this object. This location is then cached in
* {@link #lcFilePath}.
*
* @param url The location from which to read the model.
**/
public void readModel(URL url) {
ExceptionlessInputStream in =
ExceptionlessInputStream.openCompressedStream(url);
String s = in.readString();
String expected = getClass().getName();
if (!s.equals(expected)) {
System.err.println("Error reading model from '" + url + "':");
System.err.println(" Expected '" + expected + "' but received '" + s
+ "'");
new Exception().printStackTrace();
in.close();
System.exit(1);
}
read(in);
in.close();
lcFilePath = url;
}
/**
* Reads the learned function's feature lexicon from the specified file,
* overwriting the lexicon present in this object, if any. This file is
* then cached in {@link #lexFilePath}.
*
* @param filename The name of the file from which to read the feature
* lexicon.
**/
public void readLexicon(String filename) {
try { readLexicon(new URL("file:" + filename)); }
catch (Exception e) {
System.err.println("Error constructing URL:");
e.printStackTrace();
System.exit(1);
}
}
/**
* Reads the learned function's feature lexicon from the specified
* location, overwriting the lexicon present in this object, if any. This
* location is then cached in {@link #lexFilePath}.
*
* @param url The location from which to read the feature lexicon.
**/
public void readLexicon(URL url) {
lexicon = Lexicon.readLexicon(url);
lexFilePath = url;
}
/**
* Reads the binary representation of any type of learner (including the
* label lexicon, but not including the feature lexicon) from the given
* file. In that file, there should first be stored a string containing
* the fully qualified class name of the learner. If the <i>short</i>
* value <code>-1</code> appears instead, this method returns
* <code>null</code>.
*
* <p> This method is appropriate for reading learners as written by
* {@link #write(ExceptionlessOutputStream)}.
*
* @param filename The name of the file from which to read the learner.
* @return The learner read from the file.
**/
public static Learner readLearner(String filename) {
return readLearner(filename, true);
}
/**
* Reads the binary representation of any type of learner (including the
* label lexicon, but not including the feature lexicon), with the option
* of cutting off the reading process after the label lexicon and before
* any learned parameters. When <code>whole</code> is <code>false</code>,
* the reading process is cut off in this way.
*
* <p> This method is appropriate for reading learners as written by
* {@link #write(ExceptionlessOutputStream)}.
*
* @param filename The name of the file from which to read the learner.
* @param whole Whether or not to read the whole model.
* @return The learner read from the file.
**/
public static Learner readLearner(String filename, boolean whole) {
URL url = null;
try { url = new URL("file:" + filename); }
catch (Exception e) {
System.err.println("Error constructing URL:");
e.printStackTrace();
System.exit(1);
}
return readLearner(url, whole);
}
/**
* Reads the binary representation of any type of learner (including the
* label lexicon, but not including the feature lexicon) from the given
* location. At that location, there should first be stored a string
* containing the fully qualified class name of the learner. If the
* <i>short</i> value <code>-1</code> appears instead, this method returns
* <code>null</code>. Finally, the location is cached in
* {@link #lcFilePath}.
*
* <p> This method is appropriate for reading learners as written by
* {@link #write(ExceptionlessOutputStream)}.
*
* @param url The location from which to read the learner.
* @return The learner read from the location.
**/
public static Learner readLearner(URL url) {
return readLearner(url, true);
}
/**
* Reads the binary representation of any type of learner (including the
* label lexicon, but not including the feature lexicon), with the option
* of cutting off the reading process after the label lexicon and before
* any learned parameters. When <code>whole</code> is <code>false</code>,
* the reading process is cut off in this way. Finally, the location is
* cached in {@link #lcFilePath}.
*
* <p> This method is appropriate for reading learners as written by
* {@link #write(ExceptionlessOutputStream)}.
*
* @param url The location from which to read the learner.
* @param whole Whether or not to read the whole model.
* @return The learner read from the location.
**/
public static Learner readLearner(URL url, boolean whole) {
ExceptionlessInputStream in =
ExceptionlessInputStream.openCompressedStream(url);
Learner result = readLearner(in, whole);
in.close();
result.lcFilePath = url;
return result;
}
/**
* Reads the binary representation of any type of learner (including the
* label lexicon, but not including the feature lexicon) from the given
* stream. The stream is expected to first return a string containing the
* fully qualified class name of the learner. If the <i>short</i> value
* <code>-1</code> appears instead, this method returns <code>null</code>.
*
* <p> This method is appropriate for reading learners as written by
* {@link #write(ExceptionlessOutputStream)}.
*
* @param in The input stream.
* @return The learner read from the stream.
**/
public static Learner readLearner(ExceptionlessInputStream in) {
return readLearner(in, true);
}
/**
* Reads the binary representation of any type of learner (including the
* label lexicon, but not including the feature lexicon), with the option
* of cutting off the reading process after the label lexicon and before
* any learned parameters. When <code>whole</code> is <code>false</code>,
* the reading process is cut off in this way.
*
* <p> This method is appropriate for reading learners as written by
* {@link #write(ExceptionlessOutputStream)}.
*
* @param in The input stream.
* @param whole Whether or not to read the whole model.
* @return The learner read from the stream.
**/
public static Learner readLearner(ExceptionlessInputStream in,
boolean whole) {
String name = in.readString();
if (name == null) return null;
Learner result = ClassUtils.getLearner(name);
result.unclone();
if (whole) result.read(in); // Overridden by decendents
else {
result.readLabelLexicon(in); // Should not be overridden by decendents
Lexicon labelLexicon = result.getLabelLexicon();
result.forget();
result.setLabelLexicon(labelLexicon);
}
return result;
}
/**
* 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) { readLabelLexicon(in); }
/**
* Reads the initial portion of the model file, including the containing
* package and name strings, the names of the labeler and extractor, and
* finally the label lexicon. This method will not read any further model
* parameters, however.
*
* @param in The input stream.
**/
public void readLabelLexicon(ExceptionlessInputStream in) {
containingPackage = in.readString().intern();
name = in.readString().intern();
encoding = in.readString();
if (encoding != null) encoding = encoding.intern();
String s = in.readString();
labeler = s == null ? null : ClassUtils.getClassifier(s);
s = in.readString();
extractor = s == null ? null : ClassUtils.getClassifier(s);
labelLexicon = Lexicon.readLexicon(in);
if (predictions == null) predictions = new FVector();
predictions.read(in);
}
/**
* Prepares this learner to read in its feature lexicon from the specified
* location on demand; has no effect if this learner already has a
* non-empty lexicon.
*
* @param file The file from which to read the feature lexicon.
**/
public void readLexiconOnDemand(String file) {
URL url = null;
try { url = new URL("file:" + file); }
catch (Exception e) {
System.err.println("Error constructing URL:");
e.printStackTrace();
System.exit(1);
}
readLexiconOnDemand(url);
}
/**
* Prepares this learner to read in its feature lexicon from the specified
* location on demand; has no effect if this learner already has a
* non-empty lexicon.
*
* @param url The location from which to read the feature lexicon.
**/
public void readLexiconOnDemand(URL url) {
lexFilePath = url;
readLexiconOnDemand = true;
}
/**
* Forces this learner to read in its lexicon representation, but only if
* the lexicon currently available in this object is empty and the learner
* has been scheduled to read its lexicon on demand with
* {@link #readLexiconOnDemand(URL)}.
*
* @see #readLexiconOnDemand
* @return The lexicon just read into {@link #lexicon}.
**/
public Lexicon demandLexicon() {
if (readLexiconOnDemand && (lexicon == null || lexicon.size() == 0)) {
readLexicon(lexFilePath);
readLexiconOnDemand = false;
}
return lexicon;
}
/**
* Serializes a {@link Learner.Parameters} object to the specified file.
*
* @param p The parameters to serialize.
* @param file The file in which to serialize them.
**/
public static void writeParameters(Parameters p, String file) {
ObjectOutputStream oos = null;
try {
oos =
new ObjectOutputStream(
new BufferedOutputStream(
new FileOutputStream(file)));
}
catch (Exception e) {
System.err.println(
"Can't create object output stream in '" + file + "': " + e);
System.exit(1);
}
try { oos.writeObject(p); }
catch (Exception e) {
System.err.println(
"Can't write to object output stream in '" + file + "': " + e);
System.exit(1);
}
try { oos.close(); }
catch (Exception e) {
System.err.println("Can't close object stream in '" + file + "': " + e);
System.exit(1);
}
}
/**
* Deserializes a {@link Learner.Parameters} object out of the specified
* locaiton.
*
* @param url The location from which to read the object.
* @return The parameters object.
**/
public static Parameters readParameters(URL url) {
ObjectInputStream ois = null;
try {
ois =
new ObjectInputStream(
new BufferedInputStream(url.openStream()));
}
catch (Exception e) {
System.err.println("Can't open '" + url + "' for input: " + e);
System.exit(1);
}
Parameters result = null;
try { result = (Parameters) ois.readObject(); }
catch (Exception e) {
System.err.println("Can't read from '" + url + "': " + e);
System.exit(1);
}
try { ois.close(); }
catch (Exception e) {
System.err.println("Can't close '" + url + "': " + e);
System.exit(1);
}
return result;
}
/**
* <code>Parameters</code> classes are used to hold values for learning
* algorithm parameters, and all learning algorithm implementations must
* provide a constructor that takes such an object as input. All algorithm
* specific <code>Parameters</code> classes extend this class.
*
* @author Nick Rizzolo
**/
public static class Parameters implements Serializable
{
/**
* The number of rounds of training; but wait; this parameter doesn't
* actually affect the behavior of any learners as the number of training
* rounds is specified via other mechanisms. Nonetheless, it comes in
* handy to have it here as a communication vehicle when tuning
* parameters.
**/
public int rounds;
/** Sets all the default values. */
public Parameters() { }
/** Copy constructor. */
public Parameters(Parameters p) { }
/**
* 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) {
Class c = getClass();
if (Learner.class.equals(c))
throw new UnsupportedOperationException(
"LBJ ERROR: Learner.Parameters.setParameters should never be "
+ "called.");
else
throw new UnsupportedOperationException(
"LBJ ERROR: " + c.getName() + ".Parameters.setParameters has not "
+ "been implemented.");
}
/**
* Creates a string representation of these parameters in which only
* those parameters that differ from their default values are mentioned.
**/
public String nonDefaultString() { return ""; }
}
}