/* Copyright 2003, Carnegie Mellon, All Rights Reserved */
package edu.cmu.minorthird.classify;
import java.io.Serializable;
import java.util.Set;
/**
* A label which is associated with an instance---either by a classifier,
* or in training data.
*
*<p>
* ClassLabels should be weighted to that the weight for a class name
* is (approximately) the log-odds having that class name, ie if
* the probability of class "POS" is p, the getWeight("POS") should
* return Math.log( p/(1-p) ).
*
* The POS and NEG class labels (as defined in
* ExampleSchema.POS_CLASS_NAME and ExampleSchema.NEG_CLASS_NAME) are
* special. Binary class labels should be created with the
* positiveLabel(posWeight) and negativeLabel(negWeight) routines, or
* else the binaryLabel routine. The numericLabel() returns +1 or -1
* for binary classLabels. The posWeight() method returns the score
* of the positive class.
* The classLabel.numericLabel() method ignores the underlying score.
* For testing binary examples, classLabel.isPositive(),
* classLabel.isNegative(), and classLabel.bestWeight() should be
* used.
*
* @author William Cohen
*/
public class ClassLabel implements Serializable{
static private final long serialVersionUID = 1;
private WeightedSet<String> wset=new WeightedSet<String>();
private String bestLabel=null;
private double bestWeight=Double.NEGATIVE_INFINITY;
public ClassLabel(String label,double weight){
add(label,weight);
}
public ClassLabel(String label){
this(label,1.0);
}
public ClassLabel(){}
/** Create a positive binary label, with the associated score (in logits). */
static public ClassLabel multiPosLabel(String label, double score)
{
ClassLabel result = new ClassLabel(label,score);
//String negLabel = "NOT" + label;
return result;
}
/** Create a positive binary label, with the associated score (in logits). */
static public ClassLabel multiNegLabel(String label, double score)
{
ClassLabel result = new ClassLabel(label,score);
return result;
}
/** Create a binary label, either positive or negative, as appropriate, with the associated score (in logits). */
static public ClassLabel multiLabel(String name, double score)
{
return (score>=0?multiPosLabel(name, score):multiNegLabel(name, score));
}
/** Create a positive binary label, with the associated score (in logits). */
static public ClassLabel positiveLabel(double score)
{
if (score<0) throw new IllegalArgumentException("positiveLabel should have positive score");
ClassLabel result = new ClassLabel(ExampleSchema.POS_CLASS_NAME,score);
result.add(ExampleSchema.NEG_CLASS_NAME,-score);
return result;
}
/** Create a negative binary label, with the associated score (in logits). */
static public ClassLabel negativeLabel(double score)
{
if (score>0) throw new IllegalArgumentException("negativeLabel should have negative score");
ClassLabel result = new ClassLabel(ExampleSchema.POS_CLASS_NAME,score);
result.add(ExampleSchema.NEG_CLASS_NAME,-score);
return result;
}
/** Create a binary label, either positive or negative, as appropriate, with the associated score (in logits). */
static public ClassLabel binaryLabel(double score)
{
return (score>=0?positiveLabel(score):negativeLabel(score));
}
/** See if this is one of the distinguished binary labels. */
public boolean isBinary() { return ExampleSchema.BINARY_EXAMPLE_SCHEMA.isValid(this); }
/** See if this is the distinguished positive label. */
public boolean isPositive() { return ExampleSchema.POS_CLASS_NAME.equals(this.bestLabel); }
/** See if this is the distinguished negative label. */
public boolean isNegative() { return ExampleSchema.NEG_CLASS_NAME.equals(this.bestLabel); }
/** Return a numeric score of +1, or -1 for a binary example */
public double numericLabel()
{
if (isPositive()) return +1;
else if (isNegative()) return -1;
else throw new IllegalArgumentException("not binary label");
}
/** Returns the highest-ranking label. */
public String bestClassName() { return bestLabel; }
/** Returns the weight of the highest-ranking label. */
public double bestWeight() { return bestWeight; }
/** Returns the weight of the positive class name */
public double posWeight() { return getWeight(ExampleSchema.POS_CLASS_NAME); }
/** Returns the probability of the positive class name */
public double posProbability() { return getProbability(ExampleSchema.POS_CLASS_NAME); }
/** Returns the weight of the label. */
public double getWeight(String label) { return wset.getWeight(label,-Double.MAX_VALUE); }
/** Returns the probability of a label. */
public double getProbability(String label)
{
// same as MathUtil.logistic, I think
double expOdds = Math.exp( getWeight(label) );
return expOdds/(1.0 + expOdds);
}
/** Returns the set of labels that appear in the ranking. */
public Set<String> possibleLabels() { return wset.asSet(); }
/** Is this label correct, relative to another label? */
public boolean isCorrect(ClassLabel otherLabel)
{
if (otherLabel==null) throw new IllegalArgumentException("null otherLabel?");
if (bestClassName()==null) throw new IllegalArgumentException("null bestClassName?");
return this.bestClassName().equals(otherLabel.bestClassName());
}
/** Is this label correct, relative to a numeric label? */
public boolean isCorrect(double otherLabel)
{
if (isBinary()) return (isPositive() && otherLabel>=0);
else throw new IllegalArgumentException("not a binary label");
}
/** Add a label with the given weight to this ClassLabel. */
public void add(String label, double weight) {
if (weight>bestWeight) {
bestWeight = weight;
bestLabel = label;
}
wset.add( label, weight );
}
@Override
public String toString()
{
return "[Class: "+bestLabel+" "+bestWeight+"]";
}
public String toDetails()
{
return "[ClassLabel: "+wset+"]";
}
}