diff --git a/build.xml b/build.xml index d033b05..ac72a0a 100644 --- a/build.xml +++ b/build.xml @@ -25,7 +25,7 @@ - + @@ -78,6 +78,16 @@ + + + + + + + + + + diff --git a/run b/run index c513830..9a98f0f 100755 --- a/run +++ b/run @@ -634,7 +634,7 @@ addMode('tables', 'QA on HTML tables', lambda { |e| l( # Add @cldir=1 to use CodaLab's directory paths letDefault(:cldir, 0), # Usual header - header('core,tables,corenlp'), + header('core,tables,corenlp,cprune'), # Select class letDefault(:class, 'main'), sel(:class, { @@ -674,35 +674,38 @@ addMode('tables', 'QA on HTML tables', lambda { |e| l( }), # Parser letDefault(:parser, 'floatsize'), + o('useSizeInsteadOfDepth'), sel(:parser, { 'floatsize' => l( o('Builder.parser', 'FloatingParser'), - o('useSizeInsteadOfDepth'), o('FloatingParser.maxDepth', 15), nil), - 'baseline' => l( - o('Builder.parser', 'tables.baseline.TableBaselineParser'), - nil), + 'baseline' => o('Builder.parser', 'tables.baseline.TableBaselineParser'), 'serialized' => o('Builder.parser', 'tables.serialize.SerializedParser'), # ACL 2016 'grow-dpd' => l( o('Builder.parser', 'tables.dpd.DPDParser'), - o('useSizeInsteadOfDepth'), o('FloatingParser.maxDepth', 8), nil), 'grow-float' => l( o('Builder.parser', 'FloatingParser'), - o('useSizeInsteadOfDepth'), o('FloatingParser.maxDepth', 8), o('FloatingParser.betaReduce'), o('initialFloatingHasZeroDepth'), nil), 'grow-mix' => l( o('Builder.parser', 'MixParser'), o('MixParser.parsers', 'FloatingParser', 'tables.serialize.SerializedParser:train-0xc'), - o('useSizeInsteadOfDepth'), o('FloatingParser.maxDepth', 8), o('FloatingParser.betaReduce'), o('initialFloatingHasZeroDepth'), nil), + # EMNLP 2017 + 'cprune' => l( + o('Builder.parser', 'cprune.CPruneFloatingParser'), + o('FloatingParser.maxDepth', 15), + o('beamSize', 100), + o('maxNumNeighbors', 40), + o('maxPredictedPatterns', 1000), + nil), }), o('Parser.verbose', 0), letDefault(:pruning, 1), @@ -731,7 +734,7 @@ addMode('tables', 'QA on HTML tables', lambda { |e| l( letDefault(:normalize, 1), sel(:normalize, l(), - l(o('genericDateValue'), o('numberCanStartAnywhere'), o('num2CanStartAnywhere'), o('NumberFn.alsoTestByIsolatedNER')), + l(o('genericDateValue'), o('numberCanStartAnywhere'), o('num2CanStartAnywhere')), nil), letDefault(:anchor, 1), sel(:anchor, { @@ -786,6 +789,7 @@ addMode('tables', 'QA on HTML tables', lambda { |e| l( o('combineFromFloatingParser'), o('maxTrainIters', 3), o('showValues', false), o('showFirstValue'), + o('customExpectedCounts', 'TOP'), nil), l( # for dumping derivations (@class=dump) @@ -886,6 +890,11 @@ def tablesGrammarPaths o('Grammar.tags', *'scoped merge-and arithmetic comparison alternative neq yearrange part closedclass-generic scoped-2args-merge-and'.split), let(:anchor, 2), nil), + # EMNLP 2017 + 'extended' => l( + o('Grammar.inPaths', "#{baseDir}extended.grammar"), + o('Grammar.tags', *'alternative movement comparison count aggregate superlative arithmetic merge v-superlative'.split), + nil), }), nil) } @@ -895,13 +904,17 @@ def tablesDataPaths lambda { |e| baseDir = ['lib/data/WikiTableQuestions/data/', 'WikiTableQuestions/data/'][e[:cldir]] csvDir = ['lib/data/WikiTableQuestions/', 'WikiTableQuestions/'][e[:cldir]] + nnDir = ['lib/data/nn_0/', 'nn_0/'][e[:cldir]] datasets = { 'none' => l(), 'train' => o('Dataset.inPaths', "train,#{baseDir}training.examples"), # Pristine test test - 'test' => o('Dataset.inPaths', - "train,#{baseDir}training.examples", - "test,#{baseDir}pristine-unseen-tables.examples"), + 'test' => l( + o('Dataset.inPaths', + "train,#{baseDir}training.examples", + "test,#{baseDir}pristine-unseen-tables.examples"), + o('neighborFilePath', "#{nnDir}/exact_nearest_neighbors.all"), + nil), # @data=annotated can be used with @class=check only 'annotated' => o('Dataset.inPaths', "train,#{baseDir}annotated-all.examples"), 'before300' => o('Dataset.inPaths', "train,#{baseDir}training-before300.examples"), @@ -913,6 +926,7 @@ def tablesDataPaths "train,#{baseDir}random-split-#{x}-train.examples", "dev,#{baseDir}random-split-#{x}-dev.examples", nil), + o('neighborFilePath', "#{nnDir}/exact_nearest_neighbors.seed-#{x}.train"), nil) end # That's it! diff --git a/src/edu/stanford/nlp/sempre/FloatingParser.java b/src/edu/stanford/nlp/sempre/FloatingParser.java index 4f433f2..827b190 100644 --- a/src/edu/stanford/nlp/sempre/FloatingParser.java +++ b/src/edu/stanford/nlp/sempre/FloatingParser.java @@ -84,14 +84,11 @@ public class FloatingParser extends Parser { @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 int maxFloatingParsingTime = Integer.MAX_VALUE; } public static Options opts = new Options(); - protected List orderedFloatingRules; - public List getOrderedFloatingRules() { return orderedFloatingRules; } - public FloatingParser(Spec spec) { super(spec); } diff --git a/src/edu/stanford/nlp/sempre/Grammar.java b/src/edu/stanford/nlp/sempre/Grammar.java index bb94b36..72cf0c8 100644 --- a/src/edu/stanford/nlp/sempre/Grammar.java +++ b/src/edu/stanford/nlp/sempre/Grammar.java @@ -44,7 +44,7 @@ public class Grammar { // All the rules in the grammar. Each parser can read these and transform // them however the parser wishes. // This contains binarized rules - ArrayList rules = new ArrayList<>(); + protected ArrayList rules = new ArrayList<>(); public List getRules() { return rules; } Map macros = new HashMap<>(); // Map from macro name to its replacement value @@ -260,7 +260,7 @@ public class Grammar { return cat; } - private void interpretRule(LispTree tree) { + protected void interpretRule(LispTree tree) { if (tree.children.size() < 4) throw new RuntimeException("Invalid rule: " + tree); @@ -360,7 +360,7 @@ public class Grammar { // Generate intermediate categories for binarization. public static final String INTERMEDIATE_PREFIX = "$Intermediate"; - private int freshCatIndex = 0; + protected int freshCatIndex = 0; private String generateFreshCat() { freshCatIndex++; return INTERMEDIATE_PREFIX + freshCatIndex; @@ -368,6 +368,9 @@ public class Grammar { public static boolean isIntermediate(String cat) { return cat.startsWith(INTERMEDIATE_PREFIX); } + public int getFreshCatIndex() { + return freshCatIndex; + } // Create multiple versions of this rule if there are optional RHS. // Restriction: must be able to split the RHS into two halves, each of diff --git a/src/edu/stanford/nlp/sempre/Parser.java b/src/edu/stanford/nlp/sempre/Parser.java index c3a7fa3..9afa080 100644 --- a/src/edu/stanford/nlp/sempre/Parser.java +++ b/src/edu/stanford/nlp/sempre/Parser.java @@ -56,7 +56,7 @@ public abstract class Parser { @Option(gloss = "Dump all features (for debugging)") public boolean dumpAllFeatures = false; - + @Option(gloss = "Call SetEvaluation during parsing") public boolean callSetEvaluation = true; } @@ -100,8 +100,8 @@ public abstract class Parser { this.valueEvaluator = spec.valueEvaluator; computeCatUnaryRules(); - LogInfo.logs("Parser: %d catUnaryRules (sorted), %d nonCatUnaryRules (in trie)", - catUnaryRules.size(), grammar.rules.size() - catUnaryRules.size()); + LogInfo.logs("%s: %d catUnaryRules (sorted), %d nonCatUnaryRules (in trie)", + this.getClass().getSimpleName(), catUnaryRules.size(), grammar.rules.size() - catUnaryRules.size()); } // If grammar changes, then we might need to update aspects of the parser. diff --git a/src/edu/stanford/nlp/sempre/cprune/CPruneDerivInfo.java b/src/edu/stanford/nlp/sempre/cprune/CPruneDerivInfo.java new file mode 100644 index 0000000..3c487bb --- /dev/null +++ b/src/edu/stanford/nlp/sempre/cprune/CPruneDerivInfo.java @@ -0,0 +1,13 @@ +package edu.stanford.nlp.sempre.cprune; + +import java.util.Map; +import java.util.List; + +public class CPruneDerivInfo { + + public Map treeSymbols; + public Map ruleSymbols; + public List customRuleStrings; + public boolean containsCrossReference; + +} diff --git a/src/edu/stanford/nlp/sempre/cprune/CPruneFloatingParser.java b/src/edu/stanford/nlp/sempre/cprune/CPruneFloatingParser.java new file mode 100644 index 0000000..e580359 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/cprune/CPruneFloatingParser.java @@ -0,0 +1,102 @@ +package edu.stanford.nlp.sempre.cprune; + +import java.util.List; + +import edu.stanford.nlp.sempre.*; +import fig.basic.LogInfo; + +public class CPruneFloatingParser extends FloatingParser { + + FloatingParser exploreParser; + + public CPruneFloatingParser(Spec spec) { + super(spec); + exploreParser = new FloatingParser(spec); + } + + @Override + public void onBeginDataGroup(int iter, int numIters, String group) { + if (CollaborativePruner.neighbors == null) { + CollaborativePruner.customGrammar.init(grammar); + CollaborativePruner.loadNeighbors(); + } + CollaborativePruner.stats.reset(iter + "." + group); + } + + @Override + public ParserState newParserState(Params params, Example ex, boolean computeExpectedCounts) { + return new CPruneFloatingParserState(this, params, ex, computeExpectedCounts); + } + +} + +class CPruneFloatingParserState extends ParserState { + + public CPruneFloatingParserState(Parser parser, Params params, Example ex, boolean computeExpectedCounts) { + super(parser, params, ex, computeExpectedCounts); + } + + @Override + public void infer() { + LogInfo.begin_track("CPruneFloatingParser.infer()"); + boolean exploitSucceeds = exploit(); + if (computeExpectedCounts) { + LogInfo.begin_track("Summary of Collaborative Pruning"); + LogInfo.logs("Exploit succeeds: " + exploitSucceeds); + LogInfo.logs("Exploit success rate: " + CollaborativePruner.stats.successfulExploit + "/" + CollaborativePruner.stats.totalExploit); + LogInfo.end_track(); + } + // Explore only on the first training iteration + if (CollaborativePruner.stats.iter.equals("0.train") && computeExpectedCounts && !exploitSucceeds + && (CollaborativePruner.stats.totalExplore <= CollaborativePruner.opts.maxExplorationIters)) { + explore(); + LogInfo.logs("Consistent pattern: " + CollaborativePruner.getConsistentPattern(ex)); + LogInfo.logs("Explore success rate: " + CollaborativePruner.stats.successfulExplore + "/" + CollaborativePruner.stats.totalExplore); + } + LogInfo.end_track(); + } + + public void explore() { + LogInfo.begin_track("Explore"); + CollaborativePruner.initialize(ex, CollaborativePruner.Mode.EXPLORE); + ParserState exploreParserState = ((CPruneFloatingParser) parser).exploreParser.newParserState(params, ex, computeExpectedCounts); + exploreParserState.infer(); + predDerivations.clear(); + predDerivations.addAll(exploreParserState.predDerivations); + expectedCounts = exploreParserState.expectedCounts; + CollaborativePruner.stats.totalExplore += 1; + if (CollaborativePruner.foundConsistentDerivation) + CollaborativePruner.stats.successfulExplore += 1; + LogInfo.end_track(); + } + + public boolean exploit() { + LogInfo.begin_track("Exploit"); + CollaborativePruner.initialize(ex, CollaborativePruner.Mode.EXPLOIT); + Parser exploitParser = new FloatingParser( + new Parser.Spec(new MiniGrammar(CollaborativePruner.predictedRules), parser.extractor, parser.executor, parser.valueEvaluator)); + ParserState exploitParserState = exploitParser.newParserState(params, ex, computeExpectedCounts); + exploitParserState.infer(); + predDerivations.clear(); + predDerivations.addAll(exploitParserState.predDerivations); + expectedCounts = exploitParserState.expectedCounts; + boolean succeeds = CollaborativePruner.foundConsistentDerivation; + CollaborativePruner.stats.totalExploit += 1; + if (succeeds) + CollaborativePruner.stats.successfulExploit += 1; + LogInfo.end_track(); + return succeeds; + } +} + +// ============================================================ +// Helper classes +// ============================================================ + +class MiniGrammar extends Grammar { + + public MiniGrammar(List rules) { + this.rules.addAll(rules); + } + +} diff --git a/src/edu/stanford/nlp/sempre/cprune/CPruneStats.java b/src/edu/stanford/nlp/sempre/cprune/CPruneStats.java new file mode 100644 index 0000000..6892bc3 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/cprune/CPruneStats.java @@ -0,0 +1,20 @@ +package edu.stanford.nlp.sempre.cprune; + +/** + * Stores various statistic. + */ +public class CPruneStats { + public String iter; + public int totalExplore = 0; + public int successfulExplore = 0; + public int totalExploit = 0; + public int successfulExploit = 0; + + public void reset(String iter) { + this.iter = iter; + this.totalExplore = 0; + this.successfulExplore = 0; + this.totalExploit = 0; + this.successfulExploit = 0; + } +} diff --git a/src/edu/stanford/nlp/sempre/cprune/CollaborativePruner.java b/src/edu/stanford/nlp/sempre/cprune/CollaborativePruner.java new file mode 100644 index 0000000..cf91014 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/cprune/CollaborativePruner.java @@ -0,0 +1,208 @@ +package edu.stanford.nlp.sempre.cprune; + +import java.io.*; +import java.util.*; + +import fig.basic.*; +import edu.stanford.nlp.sempre.*; +import fig.basic.LogInfo; + +/** + * Static class for collaborative pruning. + */ +public class CollaborativePruner { + public static class Options { + @Option(gloss = "K = Maximum number of nearest-neighbor examples to consider (-1 to use all examples so far)") + public int maxNumNeighbors = -1; + @Option(gloss = "Load cached neighbors from this file") + public String neighborFilePath = null; + @Option(gloss = "Maximum number of matching patterns (default = use all patterns)") + public int maxPredictedPatterns = Integer.MAX_VALUE; + @Option(gloss = "Maximum number of derivations per example") + public int maxDerivations = 5000; + @Option(gloss = "Maximum number of exporation iterations") + public int maxExplorationIters = Integer.MAX_VALUE; + } + + public static Options opts = new Options(); + + public enum Mode { EXPLORE, EXPLOIT, NONE } + + public static Mode mode = Mode.NONE; + public static CPruneStats stats = new CPruneStats(); + public static CustomGrammar customGrammar = new CustomGrammar(); + + // Static class; do not instantiate + private CollaborativePruner() { throw new RuntimeException("Cannot instantiate CollaborativePruner"); } + + // Global variables + // Nearest neighbors + static Map> neighbors; + // uid => pattern + static Map consistentPattern = new HashMap<>(); + // patternString => customRuleString + static Map> customRules = new HashMap<>(); + // set of patternStrings + static Set allConsistentPatterns = new HashSet<>(); + + // Example-level variables + public static boolean foundConsistentDerivation = false; + public static Map predictedPatterns; + public static List predictedRules; + + /** + * Read the cached neighbors file. + * Line Format: ex_id [tab] neighbor_id1,neighbor_id2,... + */ + public static void loadNeighbors() { + if (opts.neighborFilePath == null) { + LogInfo.logs("neighborFilePath is null."); + return; + } + LogInfo.logs("loading Neighbors " + opts.neighborFilePath); + Map> tmpMap = new HashMap<>(); + try { + BufferedReader reader = IOUtils.openIn(opts.neighborFilePath); + String line; + while ((line = reader.readLine()) != null) { + String[] tokens = line.split("\t"); + String uid = tokens[0]; + String[] nids = tokens[1].split(","); + tmpMap.put(uid, Arrays.asList(nids)); + } + reader.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + neighbors = tmpMap; + } + + public static > Map sortByValue(Map map) { + List> list = new LinkedList>(map.entrySet()); + Collections.sort(list, new Comparator>() { + public int compare(Map.Entry o1, Map.Entry o2) { + return (o1.getValue()).compareTo(o2.getValue()); + } + }); + + Map result = new LinkedHashMap(); + for (Map.Entry entry : list) { + result.put(entry.getKey(), entry.getValue()); + } + return result; + } + + public static void initialize(Example ex, Mode mode) { + CollaborativePruner.mode = mode; + predictedRules = null; + predictedPatterns = null; + foundConsistentDerivation = false; + if (mode == Mode.EXPLOIT) { + preprocessExample(ex); + } + } + + static void preprocessExample(Example ex) { + Map patternFreqMap = new HashMap<>(); + List cachedNeighbors = neighbors.get(ex.id); + int total = 0; + + // Gather the neighbors + if (opts.maxNumNeighbors > 0) { + for (String nid : cachedNeighbors) { + // Only get examples that have been previously processed + if (!consistentPattern.containsKey(nid)) + continue; + + String neighborPattern = consistentPattern.get(nid).pattern; + if (!patternFreqMap.containsKey(neighborPattern)) + patternFreqMap.put(neighborPattern, new Pattern(neighborPattern, 0, 0)); + patternFreqMap.get(neighborPattern).frequency += 1; + total++; + if (total >= opts.maxNumNeighbors) + break; + } + } else { + for (String patternString : allConsistentPatterns) { + patternFreqMap.put(patternString, new Pattern(patternString, 0, 1)); + } + } + + // Gather the patterns + patternFreqMap = sortByValue(patternFreqMap); + int rank = 0; + Set predictedRulesStrings = new HashSet<>(); + predictedPatterns = new HashMap<>(); + LogInfo.begin_track("Predicted patterns"); + for (Map.Entry entry : patternFreqMap.entrySet()) { + Pattern newPattern = entry.getValue(); + predictedPatterns.put(newPattern.pattern, newPattern); + predictedRulesStrings.addAll(customRules.get(newPattern.pattern)); + LogInfo.logs((rank + 1) + ". " + newPattern.pattern + " (" + newPattern.frequency + ")"); + rank++; + if (rank >= opts.maxPredictedPatterns) + break; + } + + // Gather the rules + predictedRules = customGrammar.getRules(predictedRulesStrings); + LogInfo.end_track(); + } + + public static String getPatternString(Derivation deriv) { + if (deriv.cat.equals("$TOKEN") || deriv.cat.equals("$PHRASE") + || deriv.cat.equals("$LEMMA_TOKEN") || deriv.cat.equals("$LEMMA_PHRASE")) { + return deriv.cat; + } else { + return PatternInfo.convertToIndexedPattern(deriv); + } + } + + public static void addRules(String patternString, Derivation deriv, Example ex) { + if (!customRules.containsKey(patternString)) { + customRules.put(patternString, new HashSet()); + } + Set parsedCustomRules = customGrammar.addCustomRule(deriv, ex); + customRules.get(patternString).addAll(parsedCustomRules); + } + + public static void updateConsistentPattern(ValueEvaluator evaluator, Example ex, Derivation deriv) { + String uid = ex.id; + if (ex.targetValue != null) + deriv.compatibility = evaluator.getCompatibility(ex.targetValue, deriv.value); + + if (deriv.isRootCat() && deriv.compatibility == 1) { + foundConsistentDerivation = true; + LogInfo.logs("Found consistent deriv: %s", deriv); + + String patternString = getPatternString(deriv); + Pattern newConsistentPattern = new Pattern(patternString, 0, 0); + newConsistentPattern.score = deriv.getScore(); + + if (!consistentPattern.containsKey(uid)) { + addRules(patternString, deriv, ex); + consistentPattern.put(uid, newConsistentPattern); + allConsistentPatterns.add(patternString); + } else { + Pattern oldConsistentPattern = consistentPattern.get(uid); + if (newConsistentPattern.score > oldConsistentPattern.score) { + addRules(patternString, deriv, ex); + consistentPattern.put(uid, newConsistentPattern); + allConsistentPatterns.add(patternString); + } + } + } + } + + public static Pattern getConsistentPattern(Example ex) { + return consistentPattern.get(ex.id); + } + + public static Pattern getPattern(Example ex, Derivation deriv) { + if (mode == Mode.EXPLORE) + return null; + if (!deriv.isRootCat()) + return null; + return predictedPatterns.get(getPatternString(deriv)); + } +} diff --git a/src/edu/stanford/nlp/sempre/cprune/CustomGrammar.java b/src/edu/stanford/nlp/sempre/cprune/CustomGrammar.java new file mode 100644 index 0000000..d7b7697 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/cprune/CustomGrammar.java @@ -0,0 +1,256 @@ +package edu.stanford.nlp.sempre.cprune; + +import java.util.*; + +import edu.stanford.nlp.sempre.*; +import fig.basic.*; + +public class CustomGrammar extends Grammar { + public static class Options { + @Option + public boolean enableTemplateDecomposition = true; + } + + public static Options opts = new Options(); + + public static final Set baseCategories = new HashSet(Arrays.asList("$Unary", "$Binary", "$Entity", "$Property")); + + ArrayList baseRules = new ArrayList<>(); + + // symbolicFormulas => symbolicFormula ID + Map symbolicFormulas = new HashMap<>(); + // indexedSymbolicFormula => customRuleString + Map> customRules = new HashMap<>(); + // customRuleString => Binarized rules + Map> customBinarizedRules = new HashMap<>(); + + public void init(Grammar initGrammar) { + baseCategories.add(Rule.tokenCat); + baseCategories.add(Rule.phraseCat); + baseCategories.add(Rule.lemmaTokenCat); + baseCategories.add(Rule.lemmaPhraseCat); + + baseRules = new ArrayList<>(); + for (Rule rule : initGrammar.getRules()) { + if (baseCategories.contains(rule.lhs)) { + baseRules.add(rule); + } + } + this.freshCatIndex = initGrammar.getFreshCatIndex(); + } + + public List getRules(Collection customRuleStrings) { + Set ruleSet = new LinkedHashSet<>(); + ruleSet.addAll(baseRules); + for (String ruleString : customRuleStrings) { + ruleSet.addAll(customBinarizedRules.get(ruleString)); + } + return new ArrayList(ruleSet); + } + + public Set addCustomRule(Derivation deriv, Example ex) { + String indexedSymbolicFormula = getIndexedSymbolicFormula(deriv); + if (customRules.containsKey(indexedSymbolicFormula)) { + return customRules.get(indexedSymbolicFormula); + } + + CPruneDerivInfo derivInfo = aggregateSymbols(deriv); + Set crossReferences = new HashSet<>(); + for (Symbol symbol : derivInfo.treeSymbols.values()) { + if (symbol.frequency > 1) { + crossReferences.add(symbol.formula); + } + } + computeCustomRules(deriv, crossReferences); + customRules.put(indexedSymbolicFormula, new HashSet(derivInfo.customRuleStrings)); + + LogInfo.begin_track("Add custom rules for formula: " + indexedSymbolicFormula); + for (String customRuleString : derivInfo.customRuleStrings) { + if (customBinarizedRules.containsKey(customRuleString)) { + LogInfo.log("Custom rule exists: " + customRuleString); + continue; + } + + rules = new ArrayList<>(); + LispTree tree = LispTree.proto.parseFromString(customRuleString); + interpretRule(tree); + customBinarizedRules.put(customRuleString, new HashSet(rules)); + + // Debug + LogInfo.begin_track("Add custom rule: " + customRuleString); + for (Rule rule : rules) { + LogInfo.log(rule.toString()); + } + LogInfo.end_track(); + } + LogInfo.end_track(); + + // Debug + System.out.println("consistent_lf\t" + ex.id + "\t" + deriv.formula.toString()); + + return customRules.get(indexedSymbolicFormula); + } + + public static String getIndexedSymbolicFormula(Derivation deriv) { + return getIndexedSymbolicFormula(deriv, deriv.formula.toString()); + } + + public static String getIndexedSymbolicFormula(Derivation deriv, String formula) { + CPruneDerivInfo derivInfo = aggregateSymbols(deriv); + int index = 1; + List symbolList = new ArrayList<>(derivInfo.treeSymbols.values()); + for (Symbol symbol : symbolList) + symbol.computeIndex(formula); + Collections.sort(symbolList); + for (Symbol symbol : symbolList) { + if (formula.equals(symbol.formula)) + formula = symbol.category + "#" + index; + formula = safeReplace(formula, symbol.formula, symbol.category + "#" + index); + index += 1; + } + return formula; + } + + // ============================================================ + // Private methods + // ============================================================ + + private static String safeReplace(String formula, String target, String replacement) { + formula = formula.replace(target + ")", replacement + ")"); + formula = formula.replace(target + " ", replacement + " "); + return formula; + } + + private static CPruneDerivInfo aggregateSymbols(Derivation deriv) { + Map tempState = deriv.getTempState(); + if (tempState.containsKey("cprune")) { + return (CPruneDerivInfo) tempState.get("cprune"); + } + CPruneDerivInfo derivInfo = new CPruneDerivInfo(); + tempState.put("cprune", derivInfo); + + Map treeSymbols = new LinkedHashMap<>(); + derivInfo.treeSymbols = treeSymbols; + if (baseCategories.contains(deriv.cat)) { + String formula = deriv.formula.toString(); + treeSymbols.put(formula, new Symbol(deriv.cat, formula, 1)); + } else { + for (Derivation child : deriv.children) { + CPruneDerivInfo childInfo = aggregateSymbols(child); + for (Symbol symbol : childInfo.treeSymbols.values()) { + if (derivInfo.treeSymbols.containsKey(symbol.formula)) { + treeSymbols.get(symbol.formula).frequency += symbol.frequency; + } else { + treeSymbols.put(symbol.formula, symbol); + } + } + } + } + return derivInfo; + } + + private CPruneDerivInfo computeCustomRules(Derivation deriv, Set crossReferences) { + CPruneDerivInfo derivInfo = (CPruneDerivInfo) deriv.getTempState().get("cprune"); + Map ruleSymbols = new LinkedHashMap<>(); + derivInfo.ruleSymbols = ruleSymbols; + derivInfo.customRuleStrings = new ArrayList<>(); + String formula = deriv.formula.toString(); + + if (baseCategories.contains(deriv.cat)) { + // Leaf node induces no custom rule + derivInfo.containsCrossReference = crossReferences.contains(formula); + // Propagate the symbol of this derivation to the parent + ruleSymbols.putAll(derivInfo.treeSymbols); + } else { + derivInfo.containsCrossReference = false; + for (Derivation child : deriv.children) { + CPruneDerivInfo childInfo = computeCustomRules(child, crossReferences); + derivInfo.containsCrossReference = derivInfo.containsCrossReference || childInfo.containsCrossReference; + } + + for (Derivation child : deriv.children) { + CPruneDerivInfo childInfo = (CPruneDerivInfo) child.getTempState().get("cprune"); + ruleSymbols.putAll(childInfo.ruleSymbols); + derivInfo.customRuleStrings.addAll(childInfo.customRuleStrings); + } + + if (opts.enableTemplateDecomposition == false || derivInfo.containsCrossReference) { + // If this node contains a cross reference + if (deriv.isRootCat()) { + // If this is the root node, then generate a custom rule + derivInfo.customRuleStrings.add(getCustomRuleString(deriv, derivInfo)); + } + } else { + if (!deriv.cat.startsWith("$Intermediate")) { + // Generate a custom rule for this node + derivInfo.customRuleStrings.add(getCustomRuleString(deriv, derivInfo)); + + // Propagate this derivation as a category to the parent + ruleSymbols.clear(); + ruleSymbols.put(formula, new Symbol(hash(deriv), deriv.formula.toString(), 1)); + } + } + } + return derivInfo; + } + + private String getCustomRuleString(Derivation deriv, CPruneDerivInfo derivInfo) { + String formula = deriv.formula.toString(); + List rhsSymbols = new ArrayList<>(derivInfo.ruleSymbols.values()); + for (Symbol symbol : rhsSymbols) + symbol.computeIndex(formula); + Collections.sort(rhsSymbols); + + String lhs = null; + if (derivInfo.containsCrossReference) + lhs = deriv.cat; + else + lhs = deriv.isRootCat() ? "$ROOT" : hash(deriv); + + LinkedList rhsList = new LinkedList<>(); + int index = 1; + for (Symbol symbol : rhsSymbols) { + if (formula.equals(symbol.formula)) { + formula = "(IdentityFn)"; + } else { + formula = safeReplace(formula, symbol.formula, "(var s" + index + ")"); + formula = "(lambda s" + index + " " + formula + ")"; + } + rhsList.addFirst(symbol.category); + index += 1; + } + String rhs = null; + if (rhsList.size() > 0) { + rhs = "(" + String.join(" ", rhsList) + ")"; + } else { + rhs = "(nothing)"; + formula = "(ConstantFn " + formula + ")"; + } + return "(rule " + lhs + " " + rhs + " " + formula + ")"; + } + + private String hash(Derivation deriv) { + if (baseCategories.contains(deriv.cat)) + return deriv.cat; + + String formula = getSymbolicFormula(deriv); + if (!symbolicFormulas.containsKey(formula)) { + symbolicFormulas.put(formula, symbolicFormulas.size() + 1); + String hashString = "$Formula" + symbolicFormulas.get(formula); + LogInfo.log("Add symbolic formula: " + hashString + " = " + formula + " (" + deriv.cat + ")"); + } + return "$Formula" + symbolicFormulas.get(formula); + } + + private static String getSymbolicFormula(Derivation deriv) { + CPruneDerivInfo derivInfo = aggregateSymbols(deriv); + String formula = deriv.formula.toString(); + for (Symbol symbol : derivInfo.treeSymbols.values()) { + if (formula.equals(symbol.formula)) + formula = symbol.category; + formula = safeReplace(formula, symbol.formula, symbol.category); + } + return formula; + } + +} diff --git a/src/edu/stanford/nlp/sempre/cprune/Pattern.java b/src/edu/stanford/nlp/sempre/cprune/Pattern.java new file mode 100644 index 0000000..485573d --- /dev/null +++ b/src/edu/stanford/nlp/sempre/cprune/Pattern.java @@ -0,0 +1,34 @@ +package edu.stanford.nlp.sempre.cprune; + +public class Pattern implements Comparable { + public String pattern; + public Integer rank; + public Integer frequency; + public Double score; + + public Pattern(String pattern, Integer rank, Integer frequency) { + this.pattern = pattern; + this.rank = rank; + this.frequency = frequency; + } + + public Double complexity() { + return (double) (pattern.length() - pattern.replace("(@R", "***").replace("(", "").length()); + } + + @Override + public String toString() { + return "(" + pattern + ", " + rank + ", " + frequency + ")"; + } + + @Override + public int compareTo(Pattern that) { + if (this.frequency > that.frequency) { + return -1; + } else if (this.frequency < that.frequency) { + return 1; + } else { + return this.complexity().compareTo(that.complexity()); + } + } +} diff --git a/src/edu/stanford/nlp/sempre/cprune/PatternInfo.java b/src/edu/stanford/nlp/sempre/cprune/PatternInfo.java new file mode 100644 index 0000000..b9d8377 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/cprune/PatternInfo.java @@ -0,0 +1,92 @@ +package edu.stanford.nlp.sempre.cprune; + +import java.util.*; +import edu.stanford.nlp.sempre.*; + +public class PatternInfo { + Set baseCategories = new HashSet(Arrays.asList("$Unary", "$Binary", "$Entity", "$Property")); + + public static String removePropertyPredicates(String formula) { + formula = RegexReplaceManager.replace(formula, "fb:cell\\.cell\\.[_0-9a-z]+", "@PPT"); + formula = formula.replace("(reverse @PPT)", "@PPT"); + + while (formula.contains("@PPT")) { + int begin = formula.indexOf("(@PPT"); + if (begin == -1) { + formula = formula.replace("@PPT", ""); + ; + break; + } + int count = 1; + for (int i = begin + 1; i < formula.length(); i++) { + if (formula.charAt(i) == '(') { + count += 1; + } else if (formula.charAt(i) == ')') { + count -= 1; + } + if (count == 0) { + int end = i; + formula = formula.substring(0, begin) + formula.substring(begin + 6, end) + formula.substring(end + 1, formula.length()); + break; + } + if (i == formula.length() - 1) { + System.out.println(formula); + } + } + } + return formula; + } + + public static String convertToPattern(Derivation deriv) { + String formula = deriv.formula.toString(); + + // These can interfere with (number 1) + formula = formula.replace("argmax (number 1) (number 1)", "argmax"); + formula = formula.replace("argmin (number 1) (number 1)", "argmin"); + + formula = removePropertyPredicates(formula); + + formula = formula.replace("fb:type.object.type fb:type.row", "@type @row"); + + formula = RegexReplaceManager.replace(formula, "!fb:[._0-9a-z]+", "(reverse $0)").replace("reverse !fb", "reverse fb"); + formula = formula.replace("fb:row.row.index", "(reverse (lambda x ((reverse @index) (var x))))"); + formula = formula.replace("fb:row.row.next", "@next"); + formula = RegexReplaceManager.replace(formula, "\\(lambda [a-z]", "\\(lambda x"); + formula = RegexReplaceManager.replace(formula, "\\(var [a-z]\\)", "\\(var x\\)"); + + formula = RegexReplaceManager.replace(formula, "fb:row\\.row\\.[_0-9a-z]+", "\\$Binary"); + formula = RegexReplaceManager.replace(formula, "fb:cell\\.cell\\.[_0-9a-z]+", "\\$Property"); + formula = RegexReplaceManager.replace(formula, "fb:cell_[_0-9a-z]+\\.[_0-9a-z]+", "\\$Entity"); + formula = RegexReplaceManager.replace(formula, "\\(number [0-9]+[.]?[0-9]+\\)", "\\$Entity"); + formula = RegexReplaceManager.replace(formula, "\\(number [0-9]+\\)", "\\$Entity"); + formula = RegexReplaceManager.replace(formula, "\\(date [-]?[0-9]+ [-]?[0-9]+ [-]?[0-9]+\\)", "\\$Entity"); + + formula = RegexReplaceManager.replace(formula, "[ ]+", " "); + formula = formula.replace("reverse", "@R"); + formula = RegexReplaceManager.replace(formula, "(<=|>=|>|<)", "@compare"); + return formula; + } + + public static String convertToIndexedPattern(Derivation deriv) { + String formula = deriv.formula.toString(); + + // These can interfere with (number 1) + formula = formula.replace("argmax (number 1) (number 1)", "argmax"); + formula = formula.replace("argmin (number 1) (number 1)", "argmin"); + + formula = removePropertyPredicates(formula); + formula = CustomGrammar.getIndexedSymbolicFormula(deriv, formula); + + formula = formula.replace("fb:type.object.type fb:type.row", "@type @row"); + + formula = RegexReplaceManager.replace(formula, "!fb:[._0-9a-z]+", "(reverse $0)").replace("reverse !fb", "reverse fb"); + formula = formula.replace("fb:row.row.index", "(reverse (lambda x ((reverse @index) (var x))))"); + formula = formula.replace("fb:row.row.next", "@next"); + formula = RegexReplaceManager.replace(formula, "\\(lambda [a-z]", "\\(lambda x"); + formula = RegexReplaceManager.replace(formula, "\\(var [a-z]\\)", "\\(var x\\)"); + formula = RegexReplaceManager.replace(formula, "[ ]+", " "); + formula = formula.replace("reverse", "@R"); + formula = RegexReplaceManager.replace(formula, "(<=|>=|>|<)", "@compare"); + return formula; + } +} diff --git a/src/edu/stanford/nlp/sempre/cprune/RegexReplaceManager.java b/src/edu/stanford/nlp/sempre/cprune/RegexReplaceManager.java new file mode 100644 index 0000000..c6493dd --- /dev/null +++ b/src/edu/stanford/nlp/sempre/cprune/RegexReplaceManager.java @@ -0,0 +1,15 @@ +package edu.stanford.nlp.sempre.cprune; + +import java.util.*; + +public class RegexReplaceManager { + static Map dict = new HashMap<>(); + + public static String replace(String source, String regex, String replacement) { + if (!dict.containsKey(regex)) { + dict.put(regex, java.util.regex.Pattern.compile(regex)); + } + java.util.regex.Pattern regexPattern = dict.get(regex); + return regexPattern.matcher(source).replaceAll(replacement); + } +} diff --git a/src/edu/stanford/nlp/sempre/cprune/Symbol.java b/src/edu/stanford/nlp/sempre/cprune/Symbol.java new file mode 100644 index 0000000..11634be --- /dev/null +++ b/src/edu/stanford/nlp/sempre/cprune/Symbol.java @@ -0,0 +1,26 @@ +package edu.stanford.nlp.sempre.cprune; + +public class Symbol implements Comparable { + String category; + String formula; + Integer frequency; + Integer index; + + public Symbol(String category, String formula, int frequency) { + this.category = category; + this.formula = formula; + this.frequency = frequency; + } + + public void computeIndex(String referenceString) { + index = referenceString.indexOf(formula); + if (index < 0) { + index = Integer.MAX_VALUE; + } + } + + @Override + public int compareTo(Symbol that) { + return index.compareTo(that.index); + } +} diff --git a/src/edu/stanford/nlp/sempre/tables/lambdadcs/DenotationUtils.java b/src/edu/stanford/nlp/sempre/tables/lambdadcs/DenotationUtils.java index afa098a..b3f63d1 100644 --- a/src/edu/stanford/nlp/sempre/tables/lambdadcs/DenotationUtils.java +++ b/src/edu/stanford/nlp/sempre/tables/lambdadcs/DenotationUtils.java @@ -238,6 +238,8 @@ public final class DenotationUtils { */ public static UnaryDenotation superlativeUnary(int rank, int count, List> pairs, SuperlativeFormula.Mode mode, TypeProcessor processor) { + if (rank <= 0 || count <= 0) + LogInfo.fails("Invalid superlative (rank = %d, count = %d)", rank, count); if (pairs.isEmpty()) { if (LambdaDCSExecutor.opts.superlativesFailOnEmptyLists) throw new LambdaDCSException(Type.emptyList, "Cannot call %s on an empty list.", mode); @@ -343,7 +345,7 @@ public final class DenotationUtils { public boolean isCompatible(Value v) { return v instanceof NumberValue; } - + @Override public boolean isSortable(Collection values) { return true; @@ -387,19 +389,19 @@ public final class DenotationUtils { public boolean isCompatible(Value v) { return v instanceof DateValue; } - + @Override public boolean isSortable(Collection values) { DateValue firstDate = null; for (Value value : values) { - DateValue date = (DateValue) value; + DateValue date = (DateValue) value; if (firstDate == null) { firstDate = date; } else { if ((firstDate.year == -1) != (date.year == -1)) return false; if ((firstDate.month == -1) != (date.month == -1)) return false; if ((firstDate.day == -1) != (date.day == -1)) return false; - } + } } return true; } @@ -465,5 +467,5 @@ public final class DenotationUtils { throw new LambdaDCSException(Type.typeMismatch, "Cannot compare values"); } } - + } diff --git a/tables/grammars/extended.grammar b/tables/grammars/extended.grammar new file mode 100644 index 0000000..d45e1b5 --- /dev/null +++ b/tables/grammars/extended.grammar @@ -0,0 +1,169 @@ +# (Extended) Generic Grammar +# Use more generic compositional patterns. + +################################################################ +# Macros + +(def @R reverse) +(def @type fb:type.object.type) +(def @row fb:type.row) + +(def @next fb:row.row.next) +(def @!next !fb:row.row.next) +(def @index fb:row.row.index) +(def @!index !fb:row.row.index) + +(def @p.num fb:cell.cell.number) +(def @!p.num !fb:cell.cell.number) +(def @p.date fb:cell.cell.date) +(def @!p.date !fb:cell.cell.date) +(def @p.second fb:cell.cell.second) +(def @!p.second !fb:cell.cell.second) + +################################################################ +# Lexicon + +################################ +# Anchored Rules: Entity, Unary, Binary +(rule $Entity ($PHRASE) (FuzzyMatchFn entity) (anchored 1)) +#(rule $Binary ($PHRASE) (FuzzyMatchFn binary) (anchored 1)) +(rule $Entity ($PHRASE) (NumberFn) (anchored 1)) +(rule $Entity ($PHRASE) (DateFn) (anchored 1)) + +################################ +# Create binary from thin air +(rule $Binary (nothing) (FuzzyMatchFn any binary)) +(rule $Unary (nothing) (FuzzyMatchFn any unary)) + +################################ +# Property +(for @property (@p.num @p.date) + (rule $Property (nothing) (ConstantFn @property)) +) +(when second + (rule $Property (nothing) (ConstantFn @p.second)) +) + +################################ +# Generic RowSet +(rule $RowSet (nothing) (ConstantFn (@type @row))) + +################################ +# Anchored ValueSet +(rule $ValueSet ($Entity) (IdentityFn)) + +# [TAG] alternative: "X or Y" questions +(when alternative + (rule $ValueSet ($Entity $Entity) + (lambda e1 (lambda e2 (or (var e1) (var e2)))) + ) +) + +################################ +# Join + +(rule $RowSet ($Binary $ValueSet) (lambda b (lambda v ((var b) (var v))))) + +(rule $ValueSet ($Binary $RowSet) (lambda b (lambda r ((@R (var b)) (var r))))) + +(rule $RowSet ($Binary $Property $ValueSet) + (lambda b (lambda p (lambda v ((var b) ((var p) (var v)))))) +) + +(rule $ValueSet ($Binary $Property $RowSet) + (lambda b (lambda p (lambda r ((@R (var p)) ((@R (var b)) (var r)))))) +) + +# [TAG] movement: "next" / "previous" +(when movement + (for @movement (@next @!next) + (rule $RowSet ($RowSet) (lambda r (@movement (var r)))) + ) +) + +# [TAG] comparison: "at least" / "more than" +(when comparison + (for @comparison (< > <= >=) + (rule $RowSet ($Binary $Property $Entity) + (lambda b (lambda p (lambda e ((var b) ((var p) (@comparison (var e))))))) + ) + ) +) + +# [TAG] != : "not zero" / "same" +(when neq + (rule $RowSet ($Binary $Entity) (lambda b (lambda e ((var b) (!= (var e)))))) + (rule $RowSet ($Binary $Property $Entity) + (lambda b (lambda p (lambda e ((var b) ((var p) (!= (var e))))))) + ) +) + +################################ +# Aggregate + +(when count + (rule $SingleValue ($RowSet) (lambda r (count (var r)))) +) + +(when aggregate + (rule $SingleValue ($ValueSet) (lambda r (min (var r)))) + (rule $SingleValue ($ValueSet) (lambda r (max (var r)))) + (rule $SingleValue ($ValueSet) (lambda r (sum (var r)))) + (rule $SingleValue ($ValueSet) (lambda r (avg (var r)))) +) + +################################ +# Superlative + +(rule $FnOnRow ($Binary $Property) + (lambda b (lambda p (lambda x ((@R (var p)) ((@R (var b)) (var x)))))) +) + +(when superlative + (for @argm (argmax argmin) + (rule $RowSet ($RowSet) (lambda r (@argm 1 1 (var r) @index))) + (rule $RowSet ($RowSet $FnOnRow) (lambda r (lambda f (@argm 1 1 (var r) (@R (var f)))))) + ) +) + +################################ +# Merge + +(when merge + (rule $RowSet ($RowSet $RowSet) + (lambda r1 (lambda r2 (and (var r1) (var r2)))) + ) +) + +################################ +# Arithmatic + +(rule $FnOnValue ($Binary $Binary $Property) + (lambda b1 (lambda b2 (lambda p (lambda x ((@R (var p)) ((@R (var b2)) ((var b1) (var x)))))))) +) +(rule $FnOnValue ($Binary) + (lambda b (lambda x (count ((var b) (var x))))) +) +(rule $FnOnValue ($Binary $Property) + (lambda b (lambda p (lambda x (count ((var b) ((var p) (var x))))))) +) + +(when arithmetic + (rule $SingleValue ($FnOnValue $Entity $Entity) + (lambda f (lambda e1 (lambda e2 (- ((var f) (var e1)) ((var f) (var e2)))))) + ) +) + +################################ +# V-superlative + +(when v-superlative + (for @argm (argmax argmin) + (rule $ValueSet ($ValueSet $FnOnValue) (lambda v (lambda f (@argm 1 1 (var v) (reverse (var f)))))) + ) +) + +################################ +# ROOT +(rule $ROOT ($ValueSet) (IdentityFn)) +(rule $ROOT ($SingleValue) (IdentityFn))