blob: 7c5475e5d6c6ce2738012d5251f2117df441da75 [file] [log] [blame]
package joshua.decoder.ff.tm;
import java.util.Arrays;
import java.util.List;
import joshua.corpus.Vocabulary;
import joshua.decoder.ff.FeatureFunction;
import joshua.decoder.ff.FeatureVector;
/**
* Normally, the feature score in the rule should be *cost* (i.e., -LogP), so that the feature
* weight should be positive
*
* @author Zhifei Li, <zhifei.work@gmail.com>
* @author Matt Post <post@cs.jhu.edu>
*/
public class BilingualRule extends Rule {
/*
* The string format of Rule is: [Phrase] ||| french ||| english ||| feature scores
*/
private int lhs; // tag of this rule
private int[] pFrench; // pointer to the RuleCollection, as all the rules under it share the same
// Source side
protected int arity;
// And a string containing the sparse ones
protected String sparseFeatures;
/*
* a feature function will be fired for this rule only if the owner of the rule matches the owner
* of the feature function
*/
private int owner = -1;
/**
* This is the cost computed only from the features present with the grammar rule. This cost is
* needed to sort the rules in the grammar for cube pruning, but isn't the full cost of applying
* the rule (which will include contextual features that can't be computed until the rule is
* applied).
*/
private float estimatedCost = Float.NEGATIVE_INFINITY;
private float precomputableCost = Float.NEGATIVE_INFINITY;
private int[] english;
private byte [] alignment;
// ===============================================================
// Constructors
// ===============================================================
/**
* Constructs a new rule using the provided parameters. The owner and rule id for this rule are
* undefined. Note that some of the sparse features may be unlabeled, but they cannot be mapped to
* their default names ("tm_OWNER_INDEX") until later, when we know the owner of the rule. This is
* not known until the rule is actually added to a grammar in Grammar::addRule().
*
* @param lhs Left-hand side of the rule.
* @param sourceRhs Source language right-hand side of the rule.
* @param targetRhs Target language right-hand side of the rule.
* @param sparseFeatures Feature value scores for the rule.
* @param arity Number of nonterminals in the source language right-hand side.
* @param owner
*/
public BilingualRule(int lhs, int[] sourceRhs, int[] targetRhs, String sparseFeatures, int arity,
int owner) {
this.lhs = lhs;
this.pFrench = sourceRhs;
this.sparseFeatures = sparseFeatures;
this.arity = arity;
this.owner = owner;
this.english = targetRhs;
}
// Sparse feature version
public BilingualRule(int lhs, int[] sourceRhs, int[] targetRhs, String sparseFeatures, int arity) {
this.lhs = lhs;
this.pFrench = sourceRhs;
this.sparseFeatures = sparseFeatures;
this.arity = arity;
this.owner = -1;
this.english = targetRhs;
}
public BilingualRule(int lhs, int[] sourceRhs, int[] targetRhs, String sparseFeatures, int arity, byte [] alignment) {
this(lhs, sourceRhs, targetRhs, sparseFeatures, arity);
this.alignment = alignment;
}
// ===============================================================
// Attributes
// ===============================================================
public final void setEnglish(int[] eng) {
this.english = eng;
}
public final int[] getEnglish() {
return this.english;
}
/**
* Two BilingualRules are equal of they have the same LHS, the same source RHS and the same target
* RHS.
*
* @param o the object to check for equality
* @return true if o is the same BilingualRule as this rule, false otherwise
*/
public boolean equals(Object o) {
if (!(o instanceof BilingualRule)) {
return false;
}
BilingualRule other = (BilingualRule) o;
if (getLHS() != other.getLHS()) {
return false;
}
if (!Arrays.equals(getFrench(), other.getFrench())) {
return false;
}
if (!Arrays.equals(english, other.getEnglish())) {
return false;
}
return true;
}
public int hashCode() {
// I just made this up. If two rules are equal they'll have the
// same hashcode. Maybe someone else can do a better job though?
int frHash = Arrays.hashCode(getFrench());
int enHash = Arrays.hashCode(english);
return frHash ^ enHash ^ getLHS();
}
// ===============================================================
// Attributes
// ===============================================================
public final void setArity(int arity) {
this.arity = arity;
}
public final int getArity() {
return this.arity;
}
public final void setOwner(int owner) {
this.owner = owner;
}
public final int getOwner() {
return this.owner;
}
public final void setLHS(int lhs) {
this.lhs = lhs;
}
public final int getLHS() {
return this.lhs;
}
@Override
public final void setFrench(int[] french) {
this.pFrench = french;
}
@Override
public final int[] getFrench() {
return this.pFrench;
}
/**
* This function does the work of turning the string version of the sparse features (passed in
* when the rule was created) into an actual set of features. This is a bit complicated because we
* support intermingled labeled and unlabeled features, where the unlabeled features are mapped to
* a default name template of the form "tm_OWNER_INDEX".
*/
@Override
public final FeatureVector getFeatureVector() {
/*
* Now read the feature scores, which can be any number of dense features and sparse features.
* Any unlabeled feature becomes a dense feature. By convention, dense features should precede
* sparse (labeled) ones, but it's not required.
*/
FeatureVector features = (owner != -1)
? new FeatureVector(sparseFeatures, "tm_" + Vocabulary.word(owner) + "_")
: new FeatureVector();
return features;
}
/**
* This function returns the estimated cost of a rule, which should have been computed when the
* grammar was first sorted via a call to Rule::estimateRuleCost(). This function is a getter
* only; it will not compute the value if it has not already been set. It is necessary in addition
* to estimateRuleCost(models) because sometimes the value needs to be retrieved from contexts
* that do not have access to the feature functions.
*/
@Override
public final float getEstimatedCost() {
return estimatedCost;
}
@Override
public final float getPrecomputableCost() {
return precomputableCost;
}
@Override
public final void setPrecomputableCost(float cost) {
this.precomputableCost = cost;
}
/**
* This function estimates the cost of a rule, which is used for sorting the rules for cube
* pruning. The estimated cost is basically the set of precomputable features (features listed
* along with the rule in the grammar file) along with any other estimates that other features
* would like to contribute (e.g., a language model estimate). This cost will be a lower bound on
* the rule's actual cost.
*
* The value of this function is used only for sorting the rules. When the rule is later applied
* in context to particular hypernodes, the rule's actual cost is computed.
*
* @param models the list of models available to the decoder
* @return estimated cost of the rule
*/
@Override
public final float estimateRuleCost(List<FeatureFunction> models) {
if (null == models)
return 0.0f;
if (this.estimatedCost <= Float.NEGATIVE_INFINITY) {
this.estimatedCost = 0.0f; // weights.innerProduct(computeFeatures());
for (FeatureFunction ff : models) {
this.estimatedCost += ff.estimateCost(this, -1);
// System.err.println(" -> FEATURE " + ff.getName() + " -> " + ff.estimateCost(this, -1));
}
}
return estimatedCost;
}
// ===============================================================
// Methods
// ===============================================================
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(Vocabulary.word(this.getLHS()));
sb.append(" ||| ");
sb.append(getFrenchWords());
sb.append(" ||| ");
sb.append(getEnglishWords());
sb.append(" |||");
sb.append(" " + getFeatureVector());
sb.append(String.format(" ||| %.3f", getEstimatedCost()));
sb.append(String.format(" ||| %.3f", getPrecomputableCost()));
return sb.toString();
}
/**
* Returns a version of the rule suitable for reading in from a text file.
*
* @return
*/
public String textFormat() {
StringBuffer sb = new StringBuffer();
sb.append(Vocabulary.word(this.getLHS()));
sb.append(" |||");
int nt = 1;
for (int i = 0; i < getFrench().length; i++) {
if (getFrench()[i] < 0)
sb.append(" " + Vocabulary.word(getFrench()[i]).replaceFirst("\\]", String.format(",%d]", nt++)));
else
sb.append(" " + Vocabulary.word(getFrench()[i]));
}
sb.append(" |||");
nt = 1;
for (int i = 0; i < getEnglish().length; i++) {
if (getEnglish()[i] < 0)
sb.append(" " + Vocabulary.word(getEnglish()[i]).replaceFirst("\\]", String.format(",%d]", nt++)));
else
sb.append(" " + Vocabulary.word(getEnglish()[i]));
}
sb.append(" |||");
sb.append(" " + getFeatureString());
if (getAlignment() != null)
sb.append(" ||| " + getAlignmentString());
return sb.toString();
}
public final String getFeatureString() {
return sparseFeatures;
}
@Override
public byte[] getAlignment() {
return alignment;
}
public String getAlignmentString() {
StringBuffer sb = new StringBuffer();
if (getAlignment() != null)
for (int i = 0; i < getAlignment().length; i += 2)
sb.append(String.format("%d-%d ", getAlignment()[i], getAlignment()[i+1]));
return sb.toString().trim();
}
}