package edu.stanford.nlp.sempre;
import fig.basic.*;
import fig.exec.Execution;
import java.io.PrintWriter;
import java.util.*;
import static fig.basic.LogInfo.logs;
/**
* A FloatingParser builds Derivations according to a Grammar without having to
* generate the input utterance. In contrast, a conventional chart parser (e.g.,
* BeamParser) constructs parses for each span of the utterance. This is very
* inefficient when we're performing a more extractive semantic parsing task
* where many of the words are unaccounted for.
*
* Assume the Grammar is binarized and only has rules of the following form:
*
* $Cat => token
* $Cat => $Cat
* $Cat => token token
* $Cat => token $Cat
* $Cat => $Cat token
* $Cat => $Cat $Cat
*
* Each rule can be either anchored or floating (or technically, both).
* For floating rules, tokens on the RHS are ignored.
*
* Chart cells are either:
* - anchored: (cat, start, end) [these are effectively at depth 0]
* - floating: (cat, depth or size) [depends on anchored cells as base cases]
*
* With rules:
* cat => cat1 cat2 [binary]
* cat => cat1 [unary]
*
* Anchored Combinations:
* (cat1, start, end) => (cat, start, end)
* (cat1, start, mid), (cat2, mid, end) => (cat, start, end)
* (cat, start, end) => (cat, 0) [anchored => floating]
*
* Floating Combinations:
* [nothing] => (cat, 1) [from $Cat => token]
* (cat1, depth) => (cat, depth + 1)
* (cat1, depth1), (cat2, depth2) => (cat, max(depth1, depth2) + 1)
*
* If --useSizeInsteadOfDepth is turned on, the floating combinations become:
* [nothing] => (cat, 1) [from $Cat => token]
* (cat1, size) => (cat, size + 1)
* (cat1, size1), (cat2, size2) => (cat, size1 + size2 + 1)
*
* @author Percy Liang
*/
public class FloatingParser extends Parser {
public static class Options {
// Floating rules
@Option(gloss = "Whether rules without the (anchored 1) or (floating 1) tag should be anchored or floating")
public boolean defaultIsFloating = true;
@Option(gloss = "Limit on formula depth (or formula size when --useSizeInsteadOfDepth is true)")
public int maxDepth = 10;
@Option(gloss = "Put a limit on formula size instead of formula depth")
public boolean useSizeInsteadOfDepth = false;
@Option(gloss = "Whether floating rules are allowed to be applied consecutively")
public boolean consecutiveRules = true;
@Option(gloss = "Whether floating rule (rule $A (a)) should have depth 0 or 1")
public boolean initialFloatingHasZeroDepth = false;
@Option(gloss = "Filter child derivations using the type information from SemanticFn")
public boolean filterChildDerivations = true;
// Anchored rules
@Option(gloss = "Whether anchored spans/tokens can only be used once in a derivation")
public boolean useAnchorsOnce = false;
@Option(gloss = "Each span can be anchored this number of times (unused if useAnchorsOnce is active)")
public int useMaxAnchors = -1;
// Other options
@Option(gloss = "Whether to always execute the derivation")
public boolean executeAllDerivations = false;
@Option(gloss = "Whether to output a file with all utterances predicted")
public boolean printPredictedUtterances = false;
@Option(gloss = "Custom beam size at training time (default = Parser.beamSize)")
public int trainBeamSize = -1;
@Option(gloss = "Whether to beta reduce the formula")
public boolean betaReduce = false;
@Option(gloss = "DEBUG: Print amount of time spent on each rule")
public boolean summarizeRuleTime = false;
@Option(gloss = "Stop the parser if it has used more than this amount of time (in seconds)")
public int maxFloatingParsingTime = 600;
}
public static Options opts = new Options();
protected List<Rule> orderedFloatingRules;
public List<Rule> getOrderedFloatingRules() { return orderedFloatingRules; }
public FloatingParser(Spec spec) {
super(spec);
}
/**
* computeCatUnaryRules, but do not topologically sort floating rules
*/
@Override
protected void computeCatUnaryRules() {
// Handle anchored catUnaryRules
catUnaryRules = new ArrayList<>();
Map<String, List<Rule>> graph = new HashMap<>(); // Node from LHS to list of rules
for (Rule rule : grammar.rules)
if (rule.isCatUnary() && rule.isAnchored())
MapUtils.addToList(graph, rule.lhs, rule);
// Topologically sort catUnaryRules so that B->C occurs before A->B
Map<String, Boolean> done = new HashMap<>();
for (String node : graph.keySet())
traverse(catUnaryRules, node, graph, done);
// Add floating catUnaryRules
for (Rule rule : grammar.rules)
if (rule.isCatUnary() && rule.isFloating())
catUnaryRules.add(rule);
}
// Helper function for transitive closure of floating rules.
protected void traverseFloatingRules(List<Rule> orderedFloatingRules,
String node, Map<String, List<Rule>> graph, Map<String, Boolean> done) {
Boolean d = done.get(node);
if (Boolean.TRUE.equals(d)) return;
if (Boolean.FALSE.equals(d))
throw new RuntimeException("Found cycle of floating rules involving " + node);
done.put(node, false);
for (Rule rule : MapUtils.getList(graph, node)) {
for (String rhsCat : rule.rhs) {
if (Grammar.isIntermediate(rhsCat))
traverseFloatingRules(orderedFloatingRules, rhsCat, graph, done);
}
orderedFloatingRules.add(rule);
}
done.put(node, true);
}
public ParserState newParserState(Params params, Example ex, boolean computeExpectedCounts) {
return new FloatingParserState(this, params, ex, computeExpectedCounts);
}
}
/**
* Stores FloatingParser information about parsing a particular example. The actual
* parsing code lives here.
*
* Currently, many of the fields in ParserState are not used (chart).
* Those should be refactored out.
*
* @author Percy Liang
*/
class FloatingParserState extends ParserState {
// cell => list of derivations
// Anchored cells: cat[start,end]
// Floating cells: cat:depth
private final Map<Object, List<Derivation>> chart = new HashMap<>();
private final DerivationPruner pruner;
private final CatSizeBound catSizeBound;
private Map<Rule, Long> ruleTime;
private boolean timeout = false;
public FloatingParserState(FloatingParser parser, Params params, Example ex, boolean computeExpectedCounts) {
super(parser, params, ex, computeExpectedCounts);
pruner = new DerivationPruner(this);
catSizeBound = new CatSizeBound(FloatingParser.opts.maxDepth, parser.grammar);
}
@Override
protected int getBeamSize() {
if (computeExpectedCounts && FloatingParser.opts.trainBeamSize > 0)
return FloatingParser.opts.trainBeamSize;
return Parser.opts.beamSize;
}
// Construct state names.
private Object floatingCell(String cat, int depth) {
return (cat + ":" + depth).intern();
}
private Object anchoredCell(String cat, int start, int end) {
return (cat + "[" + start + "," + end + "]").intern();
}
private Object cell(String cat, int start, int end, int depth) {
return (start != -1) ? anchoredCell(cat, start, end) : floatingCell(cat, depth);
}
private void addToChart(Object cell, Derivation deriv) {
if (!deriv.isFeaturizedAndScored()) // A derivation could be belong in multiple cells.
featurizeAndScoreDerivation(deriv);
if (Parser.opts.pruneErrorValues && deriv.value instanceof ErrorValue) return;
if (Parser.opts.verbose >= 4)
LogInfo.logs("addToChart %s: %s", cell, deriv);
MapUtils.addToList(chart, cell, deriv);
}
private boolean isRootRule(Rule rule) {
return Rule.rootCat.equals(rule.lhs);
}
private boolean applyRule(Rule rule, int start, int end, int depth,
Derivation child1, Derivation child2, String canonicalUtterance) {
if (timeout && !isRootRule(rule)) return false;
applyRuleActual(rule, start, end, depth, child1, child2, canonicalUtterance);
return true;
}
private void applyRuleActual(Rule rule, int start, int end, int depth, Derivation child1, Derivation child2, String canonicalUtterance) {
if (Parser.opts.verbose >= 5) logs("applyRule %s [%s:%s] depth=%s, %s %s", rule, start, end, depth, child1, child2);
List<Derivation> children;
if (child1 == null) // 0-ary
children = Collections.emptyList();
else if (child2 == null) // 1-ary
children = Collections.singletonList(child1);
else {
// Optional: ensure that each anchor is only used once per derivation.
if (FloatingParser.opts.useAnchorsOnce) {
if (FloatingRuleUtils.derivationAnchorsOverlap(child1, child2))
return;
} else if (FloatingParser.opts.useMaxAnchors >= 0) {
if (FloatingRuleUtils.maxNumAnchorOverlaps(child1, child2)
> FloatingParser.opts.useMaxAnchors)
return;
}
children = ListUtils.newList(child1, child2);
}
// optionally: ensure that rule being applied is not the same as one of the children's
if (!FloatingParser.opts.consecutiveRules) {
for (Derivation child : children) {
if (child.rule.equals(rule)) return;
}
}
DerivationStream results = rule.sem.call(ex,
new SemanticFn.CallInfo(rule.lhs, start, end, rule, children));
while (results.hasNext()) {
Derivation newDeriv = results.next();
if (FloatingParser.opts.betaReduce) newDeriv = newDeriv.betaReduction();
newDeriv.canonicalUtterance = canonicalUtterance;
// make sure we execute
if (FloatingParser.opts.executeAllDerivations && !(newDeriv.type instanceof FuncSemType))
newDeriv.ensureExecuted(parser.executor, ex.context);
if (pruner.isPruned(newDeriv)) continue;
// Avoid repetitive floating cells
addToChart(cell(rule.lhs, start, end, depth), newDeriv);
if (depth == -1) // In addition, anchored cells become floating at level 0
addToChart(floatingCell(rule.lhs, 0), newDeriv);
}
}
private boolean applyAnchoredRule(Rule rule, int start, int end, Derivation child1, Derivation child2, String canonicalUtterance) {
return applyRule(rule, start, end, -1, child1, child2, canonicalUtterance);
}
private boolean applyFloatingRule(Rule rule, int depth, Derivation child1, Derivation child2, String canonicalUtterance) {
return applyRule(rule, -1, -1, depth, child1, child2, canonicalUtterance);
}
/**
* Return a collection of Derivation.
*/
private List<Derivation> getDerivations(Object cell) {
List<Derivation> derivations = chart.get(cell);
// logs("getDerivations %s => %s", cell, derivations);
if (derivations == null) return Derivation.emptyList;
return derivations;
}
/**
* Return a collection of DerivationGroup.
*
* The rule should be applied on all derivations (or all pairs of derivations) in each DerivationGroup.
*/
private Collection<ChildDerivationsGroup> getFilteredDerivations(Rule rule, Object cell1, Object cell2) {
List<Derivation> derivations1 = getDerivations(cell1),
derivations2 = (cell2 == null) ? null : getDerivations(cell2);
if (!FloatingParser.opts.filterChildDerivations)
return Collections.singleton(new ChildDerivationsGroup(derivations1, derivations2));
// Try to filter down the number of partial logical forms
if (rule.getSem().supportFilteringOnTypeData())
return rule.getSem().getFilteredDerivations(derivations1, derivations2);
return Collections.singleton(new ChildDerivationsGroup(derivations1, derivations2));
}
private Collection<ChildDerivationsGroup> getFilteredDerivations(Rule rule, Object cell) {
return getFilteredDerivations(rule, cell, null);
}
// Build derivations over span |start|, |end|.
private void buildAnchored(int start, int end) {
// Apply unary tokens on spans (rule $A (a))
for (Rule rule : parser.grammar.rules) {
if (!rule.isAnchored()) continue;
if (rule.rhs.size() != 1 || rule.isCatUnary()) continue;
boolean match = (end - start == 1) && ex.token(start).equals(rule.rhs.get(0));
if (!match) continue;
StopWatch stopWatch = new StopWatch().start();
applyAnchoredRule(rule, start, end, null, null, rule.rhs.get(0));
ruleTime.put(rule, ruleTime.getOrDefault(rule, 0L) + stopWatch.stop().ms);
}
// Apply binaries on spans (rule $A ($B $C)), ...
for (int mid = start + 1; mid < end; mid++) {
for (Rule rule : parser.grammar.rules) {
if (!rule.isAnchored()) continue;
if (rule.rhs.size() != 2) continue;
StopWatch stopWatch = new StopWatch().start();
String rhs1 = rule.rhs.get(0);
String rhs2 = rule.rhs.get(1);
boolean match1 = (mid - start == 1) && ex.token(start).equals(rhs1);
boolean match2 = (end - mid == 1) && ex.token(mid).equals(rhs2);
if (!Rule.isCat(rhs1) && Rule.isCat(rhs2)) { // token $Cat
if (match1) {
List<Derivation> derivations = getDerivations(anchoredCell(rhs2, mid, end));
for (Derivation deriv : derivations)
applyAnchoredRule(rule, start, end, deriv, null, rhs1 + " " + deriv.canonicalUtterance);
}
} else if (Rule.isCat(rhs1) && !Rule.isCat(rhs2)) { // $Cat token
if (match2) {
List<Derivation> derivations = getDerivations(anchoredCell(rhs1, start, mid));
for (Derivation deriv : derivations)
applyAnchoredRule(rule, start, end, deriv, null, deriv.canonicalUtterance + " " + rhs2);
}
} else if (!Rule.isCat(rhs1) && !Rule.isCat(rhs2)) { // token token
if (match1 && match2)
applyAnchoredRule(rule, start, end, null, null, rhs1 + " " + rhs2);
} else { // $Cat $Cat
List<Derivation> derivations1 = getDerivations(anchoredCell(rhs1, start, mid));
List<Derivation> derivations2 = getDerivations(anchoredCell(rhs2, mid, end));
for (Derivation deriv1 : derivations1)
for (Derivation deriv2 : derivations2)
applyAnchoredRule(rule, start, end, deriv1, deriv2, deriv1.canonicalUtterance + " " + deriv2.canonicalUtterance);
}
ruleTime.put(rule, ruleTime.getOrDefault(rule, 0L) + stopWatch.stop().ms);
}
}
// Apply unary categories on spans (rule $A ($B))
// Important: do this in topologically sorted order and after all the binaries are done.
for (Rule rule : parser.catUnaryRules) {
if (!rule.isAnchored()) continue;
StopWatch stopWatch = new StopWatch().start();
List<Derivation> derivations = getDerivations(anchoredCell(rule.rhs.get(0), start, end));
for (Derivation deriv : derivations)
applyAnchoredRule(rule, start, end, deriv, null, deriv.canonicalUtterance);
ruleTime.put(rule, ruleTime.getOrDefault(rule, 0L) + stopWatch.stop().ms);
}
}
// Build floating derivations of exactly depth |depth|.
private void buildFloating(int depth) {
// Build a floating predicate from thin air
// (rule $A (a)); note that "a" is ignored
if (depth == (FloatingParser.opts.initialFloatingHasZeroDepth ? 0 : 1)) {
for (Rule rule : parser.grammar.rules) {
if (timeout && !isRootRule(rule)) continue;
if (!rule.isFloating()) continue;
if (rule.rhs.size() != 1 || rule.isCatUnary()) continue;
StopWatch stopWatch = new StopWatch().start();
applyFloatingRule(rule, depth, null, null, rule.rhs.get(0));
ruleTime.put(rule, ruleTime.getOrDefault(rule, 0L) + stopWatch.stop().ms);
}
}
// Apply binaries on spans (rule $A ($B $C)), ...
for (Rule rule : parser.grammar.rules) {
if (timeout && !isRootRule(rule)) continue;
if (!rule.isFloating()) continue;
if (rule.rhs.size() != 2) continue;
if (catSizeBound.getBound(rule.lhs) < depth) continue;
StopWatch stopWatch = new StopWatch().start();
String rhs1 = rule.rhs.get(0);
String rhs2 = rule.rhs.get(1);
if (!Rule.isCat(rhs1) || !Rule.isCat(rhs2))
throw new RuntimeException("Floating rules with > 1 arguments cannot have tokens on the RHS: " + rule);
if (FloatingParser.opts.useSizeInsteadOfDepth) {
derivLoop:
for (int depth1 = 0; depth1 < depth; depth1++) { // sizes must add up to depth-1 (actually size-1)
int depth2 = depth - 1 - depth1;
for (ChildDerivationsGroup group : getFilteredDerivations(rule, floatingCell(rhs1, depth1), floatingCell(rhs2, depth2)))
for (Derivation deriv1 : group.derivations1)
for (Derivation deriv2 : group.derivations2)
if (!applyFloatingRule(rule, depth, deriv1, deriv2, deriv1.canonicalUtterance + " " + deriv2.canonicalUtterance))
break derivLoop;
}
} else {
{
derivLoop:
for (int subDepth = 0; subDepth < depth; subDepth++) { // depth-1 <=depth-1
for (ChildDerivationsGroup group : getFilteredDerivations(rule, floatingCell(rhs1, depth - 1), floatingCell(rhs2, subDepth)))
for (Derivation deriv1 : group.derivations1)
for (Derivation deriv2 : group.derivations2)
if (!applyFloatingRule(rule, depth, deriv1, deriv2, deriv1.canonicalUtterance + " " + deriv2.canonicalUtterance))
break derivLoop;
}
}
{
derivLoop:
for (int subDepth = 0; subDepth < depth - 1; subDepth++) { // <depth-1 depth-1
for (ChildDerivationsGroup group : getFilteredDerivations(rule, floatingCell(rhs1, subDepth), floatingCell(rhs2, depth - 1)))
for (Derivation deriv1 : group.derivations1)
for (Derivation deriv2 : group.derivations2)
if (!applyFloatingRule(rule, depth, deriv1, deriv2, deriv1.canonicalUtterance + " " + deriv2.canonicalUtterance))
break derivLoop;
}
}
}
ruleTime.put(rule, ruleTime.getOrDefault(rule, 0L) + stopWatch.stop().ms);
}
// Apply unary categories on spans (rule $A ($B))
for (Rule rule : parser.catUnaryRules) {
if (timeout && !isRootRule(rule)) continue;
if (!rule.isFloating()) continue;
if (catSizeBound.getBound(rule.lhs) < depth) continue;
StopWatch stopWatch = new StopWatch().start();
derivLoop:
for (ChildDerivationsGroup group : getFilteredDerivations(rule, floatingCell(rule.rhs.get(0), depth - 1)))
for (Derivation deriv : group.derivations1)
if (!applyFloatingRule(rule, depth, deriv, null, deriv.canonicalUtterance))
break derivLoop;
ruleTime.put(rule, ruleTime.getOrDefault(rule, 0L) + stopWatch.stop().ms);
}
}
void addToDerivations(Object cell, List<Derivation> derivations) {
List<Derivation> myDerivations = chart.get(cell);
if (myDerivations != null)
derivations.addAll(myDerivations);
}
@Override public void infer() {
LogInfo.begin_track_printAll("FloatingParser.infer()");
ruleTime = new HashMap<>();
// Base case ($TOKEN, $PHRASE)
for (Derivation deriv : gatherTokenAndPhraseDerivations()) {
addToChart(anchoredCell(deriv.cat, deriv.start, deriv.end), deriv);
addToChart(floatingCell(deriv.cat, 0), deriv);
}
Set<String> categories = new HashSet<>();
for (Rule rule : parser.grammar.rules)
categories.add(rule.lhs);
if (Parser.opts.verbose >= 1)
LogInfo.begin_track_printAll("Anchored");
// Build up anchored derivations (like the BeamParser)
int numTokens = ex.numTokens();
for (int len = 1; len <= numTokens; len++) {
for (int i = 0; i + len <= numTokens; i++) {
buildAnchored(i, i + len);
for (String cat : categories) {
String cell = anchoredCell(cat, i, i + len).toString();
pruneCell(cell, chart.get(cell));
}
}
}
if (Parser.opts.verbose >= 1)
LogInfo.end_track();
// Build up floating derivations
// Timeout if taking too long
timeout = false;
Thread parsingThread = new Thread(new Runnable() {
@Override
public void run() {
for (int depth = (FloatingParser.opts.initialFloatingHasZeroDepth ? 0 : 1); depth <= FloatingParser.opts.maxDepth; depth++) {
if (Parser.opts.verbose >= 1)
LogInfo.begin_track_printAll("%s = %d", FloatingParser.opts.useSizeInsteadOfDepth ? "SIZE" : "DEPTH", depth);
buildFloating(depth);
for (String cat : categories) {
String cell = floatingCell(cat, depth).toString();
pruneCell(cell, chart.get(cell));
}
if (Parser.opts.verbose >= 1)
LogInfo.end_track();
}
}
});
parsingThread.start();
try {
parsingThread.join(FloatingParser.opts.maxFloatingParsingTime * 1000);
if (parsingThread.isAlive()) {
// This will only interrupt first or second passes, not the final candidate collection.
LogInfo.warnings("Parsing time exceeded %d seconds. Will now interrupt ...", FloatingParser.opts.maxFloatingParsingTime);
timeout = true;
parsingThread.interrupt();
parsingThread.join();
}
} catch (InterruptedException e) {
e.printStackTrace();
LogInfo.fails("DPParser error: %s", e);
}
evaluation.add("timeout", timeout);
if (FloatingParser.opts.summarizeRuleTime) summarizeRuleTime();
// Collect final predicted derivations
addToDerivations(anchoredCell(Rule.rootCat, 0, numTokens), predDerivations);
for (int depth = 0; depth <= FloatingParser.opts.maxDepth; depth++)
addToDerivations(floatingCell(Rule.rootCat, depth), predDerivations);
// Compute gradient with respect to the predicted derivations
ensureExecuted();
if (computeExpectedCounts) {
expectedCounts = new HashMap<>();
ParserState.computeExpectedCounts(predDerivations, expectedCounts);
}
// Example summary
if (Parser.opts.verbose >= 2) {
LogInfo.begin_track_printAll("Summary of Example %s", ex.getUtterance());
for (Derivation deriv : predDerivations)
LogInfo.logs("Generated: canonicalUtterance=%s, value=%s", deriv.canonicalUtterance, deriv.value);
LogInfo.end_track();
}
if (FloatingParser.opts.printPredictedUtterances) {
PrintWriter writer = IOUtils.openOutAppendEasy(Execution.getFile("canonical_utterances"));
PrintWriter fWriter = IOUtils.openOutAppendEasy(Execution.getFile("utterances_formula.tsv"));
Derivation.sortByScore(predDerivations);
for (Derivation deriv: predDerivations) {
if (deriv.score > -10) {
writer.println(String.format("%s\t%s", deriv.canonicalUtterance, deriv.score));
fWriter.println(String.format("%s\t%s", deriv.canonicalUtterance, deriv.formula.toString()));
}
}
writer.close();
fWriter.close();
}
LogInfo.end_track();
}
@Override
protected void setEvaluation() {
super.setEvaluation();
evaluation.add("numCells", chart.size());
}
@SuppressWarnings("unused")
private void visualizeAnchoredChart(Set<String> categories) {
for (String cat : categories) {
for (int len = 1; len <= numTokens; ++len) {
for (int i = 0; i + len <= numTokens; ++i) {
List<Derivation> derivations = getDerivations(anchoredCell(cat, i, i + len));
for (Derivation deriv : derivations) {
LogInfo.logs("ParserState.visualize: %s(%s:%s): %s", cat, i, i + len, deriv);
}
}
}
}
}
private void summarizeRuleTime() {
List<Map.Entry<Rule, Long>> entries = new ArrayList<>(ruleTime.entrySet());
entries.sort(new ValueComparator<>(true));
LogInfo.begin_track_printAll("Rule time");
for (Map.Entry<Rule, Long> entry : entries) {
LogInfo.logs("%9d : %s", entry.getValue(), entry.getKey());
}
LogInfo.end_track();
}
}