package edu.stanford.nlp.sempre;
import java.util.*;
import fig.basic.*;
/**
* Actually does the parsing. Main method is infer(), whose job is to fill in
*
* @author Roy Frostig
* @author Percy Liang
*/
public abstract class ParserState {
public static class Options {
@Option(gloss = "Use a custom distribution for computing expected counts")
public CustomExpectedCount customExpectedCounts = CustomExpectedCount.NONE;
@Option(gloss = "For customExpectedCounts = TOP, only update if good < bad + margin")
public double contrastiveMargin = 1e6; // default = always update
@Option(gloss = "Whether to prune based on probability difference")
public boolean pruneByProbDiff = false;
@Option(gloss = "Difference in probability for pruning by prob diff")
public double probDiffPruningThresh = 100;
@Option(gloss = "Throw features away after scoring to save memory")
public boolean throwFeaturesAway = false;
}
public static Options opts = new Options();
public enum CustomExpectedCount { NONE, UNIFORM, TOP, RANDOM, }
//// Input: specification of how to parse
public final Parser parser;
public final Params params;
public final Example ex;
public final boolean computeExpectedCounts; // Whether we're learning
//// Output
public final List<Derivation> predDerivations = new ArrayList<Derivation>();
public final Evaluation evaluation = new Evaluation();
// If computeExpectedCounts is true (for learning), then fill this out.
public Map<String, Double> expectedCounts;
public double objectiveValue;
// Statistics generated while parsing
public final int numTokens;
public long parseTime; // Number of milliseconds to parse this example
public int maxCellSize; // Maximum number of derivations in any chart cell prior to pruning.
public String maxCellDescription; // Description of that cell (for debugging)
public boolean fallOffBeam; // Did any hypotheses fall off the beam?
public int totalGeneratedDerivs; // Total number of derivations produced
public int numOfFeaturizedDerivs = 0; // Number of derivations featured
public ParserState(Parser parser, Params params, Example ex, boolean computeExpectedCounts) {
this.parser = parser;
this.params = params;
this.ex = ex;
this.computeExpectedCounts = computeExpectedCounts;
this.numTokens = ex.numTokens();
}
protected int getBeamSize() { return Parser.opts.beamSize; }
// Main entry point. Should set all the output variables.
public abstract void infer();
protected void featurizeAndScoreDerivation(Derivation deriv) {
if (deriv.isFeaturizedAndScored()) {
LogInfo.warnings("Derivation already featurized: %s", deriv);
return;
}
// Compute features
parser.extractor.extractLocal(ex, deriv);
// Compute score
deriv.computeScoreLocal(params);
if (opts.throwFeaturesAway)
deriv.clearFeatures();
if (parser.verbose(5)) {
LogInfo.logs("featurizeAndScoreDerivation(score=%s) %s %s: %s [rule: %s]",
Fmt.D(deriv.score), deriv.cat, ex.spanString(deriv.start, deriv.end), deriv, deriv.rule);
}
numOfFeaturizedDerivs++;
}
/**
* Prune down the number of derivations in |derivations| to the beam size.
* Sort the beam by score.
* Update beam statistics.
*/
protected void pruneCell(String cellDescription, List<Derivation> derivations) {
if (derivations == null) return;
// Update stats about cell size.
if (derivations.size() > maxCellSize) {
maxCellSize = derivations.size();
maxCellDescription = cellDescription;
if (maxCellSize > 5000)
LogInfo.logs("ParserState.pruneCell %s: maxCellSize = %s entries (not pruned yet)",
maxCellDescription, maxCellSize);
}
// The extra code blocks in here that set |deriv.maxXBeamPosition|
// are there to track, over the course of parsing, the lowest
// position at which any of a derivation's constituents ever
// placed on any of the relevant beams.
// Max beam position (before sorting)
int i = 0;
for (Derivation deriv : derivations) {
deriv.maxUnsortedBeamPosition = i;
if (deriv.children != null) {
for (Derivation child : deriv.children)
deriv.maxUnsortedBeamPosition = Math.max(deriv.maxUnsortedBeamPosition, child.maxUnsortedBeamPosition);
}
if (deriv.preSortBeamPosition == -1) {
// Need to be careful to only do this once since |pruneCell()|
// might be called several times for the same beam and the
// second time around we have already sorted once.
deriv.preSortBeamPosition = i;
}
i++;
}
// Inject noise into the noise (to simulate sampling); ideally would add Gumbel noise
if (Parser.opts.derivationScoreNoise > 0) {
for (Derivation deriv : derivations)
deriv.score += Parser.opts.derivationScoreRandom.nextDouble() * Parser.opts.derivationScoreNoise;
}
Derivation.sortByScore(derivations);
// Print out information
if (Parser.opts.verbose >= 3) {
LogInfo.begin_track("ParserState.pruneCell(%s): %d derivations", cellDescription, derivations.size());
for (Derivation deriv : derivations) {
LogInfo.logs("%s(%s,%s): %s %s, [score=%s] allAnchored: %s", deriv.cat, deriv.start, deriv.end, deriv.formula,
deriv.canonicalUtterance, deriv.score, deriv.allAnchored());
}
LogInfo.end_track();
}
// Max beam position (after sorting)
i = 0;
for (Derivation deriv : derivations) {
deriv.maxBeamPosition = i;
if (deriv.children != null) {
for (Derivation child : deriv.children)
deriv.maxBeamPosition = Math.max(deriv.maxBeamPosition, child.maxBeamPosition);
}
deriv.postSortBeamPosition = i;
i++;
}
//prune all d_i s.t p(d_1) > CONST \cdot p(d_i)
if(ChartParserState.opts.pruneByProbDiff) {
double highestScore = derivations.get(0).score;
while (highestScore - derivations.get(derivations.size()-1).score > Math.log(opts.probDiffPruningThresh)) {
derivations.remove(derivations.size() - 1);
fallOffBeam = true;
}
}
//prune by beam size
else {
// Keep only the top hypotheses
int beamSize = getBeamSize();
if (derivations.size() > beamSize && Parser.opts.verbose >= 1) {
LogInfo.logs("ParserState.pruneCell %s: Pruning %d -> %d derivations", cellDescription, derivations.size(), beamSize);
}
while (derivations.size() > beamSize) {
derivations.remove(derivations.size() - 1);
fallOffBeam = true;
}
}
}
// -- Base case --
public List<Derivation> gatherTokenAndPhraseDerivations() {
List<Derivation> derivs = new ArrayList<>();
// All tokens (length 1)
for (int i = 0; i < numTokens; i++) {
derivs.add(
new Derivation.Builder()
.cat(Rule.tokenCat).start(i).end(i + 1)
.rule(Rule.nullRule)
.children(Derivation.emptyList)
.withStringFormulaFrom(ex.token(i))
.canonicalUtterance(ex.token(i))
.createDerivation());
// Lemmatized version
derivs.add(
new Derivation.Builder()
.cat(Rule.lemmaTokenCat).start(i).end(i + 1)
.rule(Rule.nullRule)
.children(Derivation.emptyList)
.withStringFormulaFrom(ex.lemmaToken(i))
.canonicalUtterance(ex.token(i))
.createDerivation());
}
// All phrases (any length)
for (int i = 0; i < numTokens; i++) {
for (int j = i + 1; j <= numTokens; j++) {
derivs.add(
new Derivation.Builder()
.cat(Rule.phraseCat).start(i).end(j)
.rule(Rule.nullRule)
.children(Derivation.emptyList)
.withStringFormulaFrom(ex.phrase(i, j))
.canonicalUtterance(ex.phrase(i, j))
.createDerivation());
// Lemmatized version
derivs.add(
new Derivation.Builder()
.cat(Rule.lemmaPhraseCat).start(i).end(j)
.rule(Rule.nullRule)
.children(Derivation.emptyList)
.withStringFormulaFrom(ex.lemmaPhrase(i, j))
.canonicalUtterance(ex.phrase(i, j))
.createDerivation());
}
}
return derivs;
}
// Ensure that all the logical forms are executed and compatibilities are computed.
public void ensureExecuted() {
LogInfo.begin_track("Parser.ensureExecuted");
// Execute predicted derivations to get value.
for (Derivation deriv : predDerivations) {
deriv.ensureExecuted(parser.executor, ex.context);
if (ex.targetValue != null)
deriv.compatibility = parser.valueEvaluator.getCompatibility(ex.targetValue, deriv.value);
if (!computeExpectedCounts && Parser.opts.executeTopFormulaOnly) break;
}
LogInfo.end_track();
}
// Add statistics to |evaluation|.
// Override if we have more statistics.
protected void setEvaluation() {
evaluation.add("numTokens", numTokens);
evaluation.add("parseTime", parseTime);
evaluation.add("maxCellSize", maxCellDescription, maxCellSize);
evaluation.add("fallOffBeam", fallOffBeam);
evaluation.add("totalDerivs", totalGeneratedDerivs);
evaluation.add("numOfFeaturizedDerivs", numOfFeaturizedDerivs);
}
public static double compatibilityToReward(double compatibility) {
if (Parser.opts.partialReward)
return compatibility;
return compatibility == 1 ? 1 : 0; // All or nothing
}
/**
* Fill |counts| with the gradient with respect to the derivations
* according to a standard exponential family model over a finite set of derivations.
* Assume that everything has been executed, and compatibility has been computed.
*/
public static void computeExpectedCounts(List<Derivation> derivations, Map<String, Double> counts) {
double[] trueScores;
double[] predScores;
int n = derivations.size();
if (n == 0) return;
trueScores = new double[n];
predScores = new double[n];
// For update schemas that choose one good and one bad candidate to update
int[] goodAndBad = null;
if (opts.customExpectedCounts == CustomExpectedCount.TOP) {
goodAndBad = getTopDerivations(derivations);
if (goodAndBad == null) return;
} else if (opts.customExpectedCounts == CustomExpectedCount.RANDOM) {
goodAndBad = getRandomDerivations(derivations);
if (goodAndBad == null) return;
}
for (int i = 0; i < n; i++) {
Derivation deriv = derivations.get(i);
double logReward = Math.log(compatibilityToReward(deriv.compatibility));
switch (opts.customExpectedCounts) {
case NONE:
trueScores[i] = deriv.score + logReward;
predScores[i] = deriv.score;
break;
case UNIFORM:
trueScores[i] = logReward;
predScores[i] = 0;
break;
case TOP: case RANDOM:
trueScores[i] = (i == goodAndBad[0]) ? 0 : Double.NEGATIVE_INFINITY;
predScores[i] = (i == goodAndBad[1]) ? 0 : Double.NEGATIVE_INFINITY;
break;
default:
throw new RuntimeException("Unknown customExpectedCounts: " + opts.customExpectedCounts);
}
}
// Usually this happens when there are no derivations.
if (!NumUtils.expNormalize(trueScores)) return;
if (!NumUtils.expNormalize(predScores)) return;
// Update parameters
for (int i = 0; i < n; i++) {
Derivation deriv = derivations.get(i);
double incr = trueScores[i] - predScores[i];
if (incr == 0) continue;
deriv.incrementAllFeatureVector(incr, counts);
}
}
private static int[] getTopDerivations(List<Derivation> derivations) {
int chosenGood = -1, chosenBad = -1;
double chosenGoodScore = Double.NEGATIVE_INFINITY, chosenBadScore = Double.NEGATIVE_INFINITY;
for (int i = 0; i < derivations.size(); i++) {
Derivation deriv = derivations.get(i);
if (deriv.compatibility == 1) { // good
if (deriv.score > chosenGoodScore) {
chosenGood = i; chosenGoodScore = deriv.score;
}
} else { // bad
if (deriv.score > chosenBadScore) {
chosenBad = i; chosenBadScore = deriv.score;
}
}
}
if (chosenGood == -1 || chosenBad == -1 || chosenGoodScore >= chosenBadScore + opts.contrastiveMargin)
return null;
return new int[] {chosenGood, chosenBad};
}
private static int[] getRandomDerivations(List<Derivation> derivations) {
int chosenGood = -1, chosenBad = -1, numGoodSoFar = 0, numBadSoFar = 0;
// Get a uniform random sample from the stream
for (int i = 0; i < derivations.size(); i++) {
Derivation deriv = derivations.get(i);
if (deriv.compatibility == 1) {
numGoodSoFar++;
if (Math.random() <= 1.0 / numGoodSoFar) {
chosenGood = i;
}
} else { // bad
numBadSoFar++;
if (Math.random() <= 1.0 / numBadSoFar) {
chosenBad = i;
}
}
}
return (chosenGood == -1 || chosenBad == -1) ? null : new int[] {chosenGood, chosenBad};
}
}