From 8a987add98231a1172b862a993013e28a2a36361 Mon Sep 17 00:00:00 2001 From: Panupong Pasupat Date: Wed, 30 Aug 2017 18:21:22 -0700 Subject: [PATCH] Added more features --- run | 12 ++- src/edu/stanford/nlp/sempre/FuzzyMatchFn.java | 3 + .../sempre/tables/TableKnowledgeGraph.java | 12 +++ .../features/AnchorFeatureComputer.java | 68 +++++++++++++++ .../tables/features/ColumnCategoryInfo.java | 84 +++++++++++++++++++ .../PhraseDenotationFeatureComputer.java | 1 + .../sempre/tables/features/PhraseInfo.java | 16 ++++ .../PhrasePredicateFeatureComputer.java | 32 ++++++- .../tables/lambdadcs/DenotationUtils.java | 4 +- 9 files changed, 222 insertions(+), 10 deletions(-) create mode 100644 src/edu/stanford/nlp/sempre/tables/features/AnchorFeatureComputer.java create mode 100644 src/edu/stanford/nlp/sempre/tables/features/ColumnCategoryInfo.java diff --git a/run b/run index e55c140..18e944e 100755 --- a/run +++ b/run @@ -675,7 +675,7 @@ addMode('tables', 'QA on HTML tables', lambda { |e| l( }), # Parser letDefault(:parser, 'floatsize'), - o('beamSize', 100), + o('beamSize', 50), o('useSizeInsteadOfDepth'), sel(:parser, { 'floatsize' => l( @@ -728,8 +728,8 @@ addMode('tables', 'QA on HTML tables', lambda { |e| l( nil), 'editdist-fuzzy' => l( o('FuzzyMatcher.fuzzyMatcher', 'tables.match.EditDistanceFuzzyMatcher'), - o('fuzzyMatchSubstring'), o('fuzzyMatchMaxEditDistanceRatio', 0.3), - o('alsoReturnUnion'), o('alsoMatchPart'), + o('fuzzyMatchSubstring'), o('fuzzyMatchMaxEditDistanceRatio', 0.15), + o('alsoMatchPart'), nil), }), letDefault(:normalize, 1), @@ -814,10 +814,14 @@ addMode('tables', 'QA on HTML tables', lambda { |e| l( 'some' => l( # Add your own features! (only set up the feature computers) o('FeatureExtractor.featureComputers', 'tables.features.PhrasePredicateFeatureComputer tables.features.PhraseDenotationFeatureComputer'.split), nil), - 'all' => l( # All features + 'all' => l( # All ACL 2015 features o('FeatureExtractor.featureDomains', 'custom-denotation phrase-predicate phrase-denotation headword-denotation missing-predicate'.split), o('FeatureExtractor.featureComputers', 'tables.features.PhrasePredicateFeatureComputer tables.features.PhraseDenotationFeatureComputer'.split), nil), + 'more' => l( # All ACL 2015 features + more experimental features + o('FeatureExtractor.featureDomains', 'custom-denotation phrase-predicate phrase-denotation headword-denotation missing-predicate anchored-entity'.split), + o('FeatureExtractor.featureComputers', 'tables.features.PhrasePredicateFeatureComputer tables.features.PhraseDenotationFeatureComputer tables.features.AnchorFeatureComputer'.split), + nil), 'baseline' => l( # For the baseline classifier o('FeatureExtractor.featureDomains', 'custom-denotation phrase-denotation headword-denotation table-baseline'.split), o('FeatureExtractor.featureComputers', 'tables.baseline.TableBaselineFeatureComputer tables.features.PhraseDenotationFeatureComputer'.split), diff --git a/src/edu/stanford/nlp/sempre/FuzzyMatchFn.java b/src/edu/stanford/nlp/sempre/FuzzyMatchFn.java index 6c98126..9f427c5 100644 --- a/src/edu/stanford/nlp/sempre/FuzzyMatchFn.java +++ b/src/edu/stanford/nlp/sempre/FuzzyMatchFn.java @@ -39,6 +39,9 @@ public class FuzzyMatchFn extends SemanticFn { } } + public FuzzyMatchFnMode getMode() { return mode; } + public boolean getMatchAny() { return matchAny; } + @Override public DerivationStream call(Example ex, Callable c) { return new LazyFuzzyMatchFnDerivs(ex, c, mode, matchAny); diff --git a/src/edu/stanford/nlp/sempre/tables/TableKnowledgeGraph.java b/src/edu/stanford/nlp/sempre/tables/TableKnowledgeGraph.java index 01b31ad..eca3bc3 100644 --- a/src/edu/stanford/nlp/sempre/tables/TableKnowledgeGraph.java +++ b/src/edu/stanford/nlp/sempre/tables/TableKnowledgeGraph.java @@ -660,6 +660,18 @@ public class TableKnowledgeGraph extends KnowledgeGraph implements FuzzyMatchabl return answer; } + /** + * Return the index of the column with the specified ID. Return -1 if not found. + */ + public int getColumnIndex(String nameValueId) { + if (nameValueId.startsWith("!")) + nameValueId = nameValueId.substring(1); + for (int j = 0; j < columns.size(); j++) { + if (columns.get(j).relationNameValue.id.equals(nameValueId)) return j; + } + return -1; + } + // ============================================================ // Test // ============================================================ diff --git a/src/edu/stanford/nlp/sempre/tables/features/AnchorFeatureComputer.java b/src/edu/stanford/nlp/sempre/tables/features/AnchorFeatureComputer.java new file mode 100644 index 0000000..a3dd20c --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/features/AnchorFeatureComputer.java @@ -0,0 +1,68 @@ +package edu.stanford.nlp.sempre.tables.features; + +import java.util.*; + +import edu.stanford.nlp.sempre.*; +import edu.stanford.nlp.sempre.tables.StringNormalizationUtils; +import edu.stanford.nlp.sempre.tables.TableKnowledgeGraph; +import edu.stanford.nlp.sempre.tables.TableCell; +import edu.stanford.nlp.sempre.tables.TableColumn; +import fig.basic.LogInfo; + +public class AnchorFeatureComputer implements FeatureComputer { + + @Override + public void extractLocal(Example ex, Derivation deriv) { + if (!(FeatureExtractor.containsDomain("anchored-entity"))) return; + if (!(deriv.rule.sem instanceof FuzzyMatchFn)) return; + FuzzyMatchFn sem = (FuzzyMatchFn) deriv.rule.sem; + if (sem.getMatchAny() || sem.getMode() != FuzzyMatchFn.FuzzyMatchFnMode.ENTITY) return; + String phrase = ((StringValue) ((ValueFormula) deriv.child(0).formula).value).value; + NameValue predicate = (NameValue) ((ValueFormula) deriv.formula).value; + TableKnowledgeGraph graph = (TableKnowledgeGraph) ex.context.graph; + extractMatchingFeatures(graph, deriv, phrase, predicate); + } + + private void extractMatchingFeatures(TableKnowledgeGraph graph, + Derivation deriv, String phrase, NameValue predicate) { + LogInfo.logs("%s -> %s", phrase, predicate); + // Type of match + String predicateString = graph.getOriginalString(predicate); + predicateString = StringNormalizationUtils.simpleNormalize(predicateString).toLowerCase(); + if (predicateString.equals(phrase)) { + deriv.addFeature("a-e", "exact"); + LogInfo.logs("%s %s exact", phrase, predicateString); + } else if (predicateString.startsWith(phrase + " ")) { + deriv.addFeature("a-e", "prefix"); + LogInfo.logs("%s %s prefix", phrase, predicateString); + } else if (predicateString.endsWith(" " + phrase)) { + deriv.addFeature("a-e", "suffix"); + LogInfo.logs("%s %s suffix", phrase, predicateString); + } else if (predicateString.contains(" " + phrase + " ")){ + deriv.addFeature("a-e", "substring"); + LogInfo.logs("%s %s substring", phrase, predicateString); + } else { + deriv.addFeature("a-e", "other"); + LogInfo.logs("%s %s other", phrase, predicateString); + } + // Does the phrase match other cells? + Set matches = new HashSet<>(); + for (TableColumn column : graph.columns) { + for (TableCell cell : column.children) { + String s = StringNormalizationUtils.simpleNormalize(cell.properties.originalString).toLowerCase(); + if (s.contains(phrase) && !cell.properties.id.equals(predicate.id)) { + matches.add(s); + } + } + } + LogInfo.logs(">> %s", matches); + if (matches.size() == 0) { + deriv.addFeature("a-e", "unique"); + } else if (matches.size() < 3) { + deriv.addFeature("a-e", "multiple;" + matches.size()); + } else { + deriv.addFeature("a-e", "multiple;>=3"); + } + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/features/ColumnCategoryInfo.java b/src/edu/stanford/nlp/sempre/tables/features/ColumnCategoryInfo.java new file mode 100644 index 0000000..d776fc2 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/features/ColumnCategoryInfo.java @@ -0,0 +1,84 @@ +package edu.stanford.nlp.sempre.tables.features; + +import java.io.*; +import java.util.*; + +import edu.stanford.nlp.sempre.Example; +import edu.stanford.nlp.sempre.tables.TableKnowledgeGraph; +import fig.basic.*; + +public class ColumnCategoryInfo { + public static class Options { + @Option(gloss = "Read category information from this file") + public String tableCategoryInfo = null; + } + public static Options opts = new Options(); + + // ============================================================ + // Singleton access + // ============================================================ + + private static ColumnCategoryInfo singleton; + + public static ColumnCategoryInfo getSingleton() { + if (opts.tableCategoryInfo == null) + return null; + else if (singleton == null) + singleton = new ColumnCategoryInfo(); + return singleton; + } + + // ============================================================ + // Read data from file + // ============================================================ + + // tableId -> columnIndex -> list of (category, weight) + protected static Map>>> allCategoryInfo = null; + + private ColumnCategoryInfo() { + LogInfo.begin_track("Loading category information from %s", opts.tableCategoryInfo); + allCategoryInfo = new HashMap<>(); + try { + BufferedReader reader = IOUtils.openIn(opts.tableCategoryInfo); + String line; + while ((line = reader.readLine()) != null) { + String[] tokens = line.split("\t"); + String tableId = tokens[0]; + List>> categoryInfoForTable = allCategoryInfo.get(tableId); + if (categoryInfoForTable == null) + allCategoryInfo.put(tableId, categoryInfoForTable = new ArrayList<>()); + int columnIndex = Integer.parseInt(tokens[1]); + // Assume that the columns are ordered + assert categoryInfoForTable.size() == columnIndex; + // Read the category-weight pairs + List> categories = new ArrayList<>(); + for (int i = 2; i < tokens.length; i++) { + String[] pair = tokens[i].split(":"); + categories.add(new Pair<>(pair[0], Double.parseDouble(pair[1]))); + } + categoryInfoForTable.add(categories); + } + reader.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + LogInfo.end_track(); + } + + // ============================================================ + // Getters + // ============================================================ + + public List> get(String tableId, int columnIndex) { + return allCategoryInfo.get(tableId).get(columnIndex); + } + + public List> get(Example ex, String columnId) { + TableKnowledgeGraph graph = (TableKnowledgeGraph) ex.context.graph; + String tableId = graph.filename; + int columnIndex = graph.getColumnIndex(columnId); + if (columnIndex == -1) return null; + return allCategoryInfo.get(tableId).get(columnIndex); + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/features/PhraseDenotationFeatureComputer.java b/src/edu/stanford/nlp/sempre/tables/features/PhraseDenotationFeatureComputer.java index 5f33941..895ef39 100644 --- a/src/edu/stanford/nlp/sempre/tables/features/PhraseDenotationFeatureComputer.java +++ b/src/edu/stanford/nlp/sempre/tables/features/PhraseDenotationFeatureComputer.java @@ -113,6 +113,7 @@ public class PhraseDenotationFeatureComputer implements FeatureComputer { LogInfo.logs("%s %s %s", deriv.value, deriv.type, denotationTypes); for (String denotationType : denotationTypes) { for (PhraseInfo phraseInfo : phraseInfos) { + if (PhraseInfo.opts.forbidBorderStopWordInLexicalizedFeatures && phraseInfo.isBorderStopWord) continue; deriv.addFeature("p-d", phraseInfo.lemmaText + ";" + denotationType); } // Check original column text diff --git a/src/edu/stanford/nlp/sempre/tables/features/PhraseInfo.java b/src/edu/stanford/nlp/sempre/tables/features/PhraseInfo.java index 1ba0cd5..4c1c82c 100644 --- a/src/edu/stanford/nlp/sempre/tables/features/PhraseInfo.java +++ b/src/edu/stanford/nlp/sempre/tables/features/PhraseInfo.java @@ -2,6 +2,7 @@ package edu.stanford.nlp.sempre.tables.features; import java.util.*; import java.util.concurrent.ExecutionException; +import java.util.regex.Pattern; import com.google.common.cache.*; @@ -23,6 +24,8 @@ public class PhraseInfo { public int maxPhraseLength = 3; @Option(gloss = "Fuzzy match predicates") public boolean computeFuzzyMatchPredicates = false; + @Option(gloss = "Do not produce lexicalized features if the phrase begins or ends with a stop word") + public boolean forbidBorderStopWordInLexicalizedFeatures = false; } public static Options opts = new Options(); @@ -35,6 +38,7 @@ public class PhraseInfo { public final List nerTags; public final String canonicalPosSeq; public final List fuzzyMatchedPredicates; + public final boolean isBorderStopWord; // true if the first or last word is a stop word public PhraseInfo(Example ex, int start, int end) { this.start = start; @@ -49,6 +53,7 @@ public class PhraseInfo { lemmaText = languageInfo.lemmaPhrase(start, end).toLowerCase(); canonicalPosSeq = languageInfo.canonicalPosSeq(start, end); fuzzyMatchedPredicates = opts.computeFuzzyMatchPredicates ? getFuzzyMatchedPredicates(ex.context) : null; + isBorderStopWord = isStopWord(languageInfo.lemmaTokens.get(start)) || isStopWord(languageInfo.lemmaTokens.get(end - 1)); } private List getFuzzyMatchedPredicates(ContextValue context) { @@ -71,6 +76,17 @@ public class PhraseInfo { return matchedPredicates; } + static final Pattern ALL_PUNCT = Pattern.compile("^[^A-Za-z0-9]*$"); + static final Set STOP_WORDS = new HashSet<>(Arrays.asList( + "a", "an", "the", "be", "of", "in", "on", "do" + )); + + static boolean isStopWord(String x) { + if (ALL_PUNCT.matcher(x).matches()) return true; + if (STOP_WORDS.contains(x)) return true; + return false; + } + @Override public String toString() { return "\"" + text + "\""; diff --git a/src/edu/stanford/nlp/sempre/tables/features/PhrasePredicateFeatureComputer.java b/src/edu/stanford/nlp/sempre/tables/features/PhrasePredicateFeatureComputer.java index d7b5f71..e61c2d2 100644 --- a/src/edu/stanford/nlp/sempre/tables/features/PhrasePredicateFeatureComputer.java +++ b/src/edu/stanford/nlp/sempre/tables/features/PhrasePredicateFeatureComputer.java @@ -35,6 +35,11 @@ public class PhrasePredicateFeatureComputer implements FeatureComputer { public boolean lexicalizedPhrasePredicate = true; @Option(gloss = "Maximum ngram length for lexicalize all pair features") public int maxNforLexicalizeAllPairs = Integer.MAX_VALUE; + @Option(gloss = "phrase-category: Weight threshold") + public double phraseCategoryWeightThreshold = 0.8; + @Option(gloss = "phrase-category: Use binary features instead of continuous ones") + public boolean phraseCategoryBinary = true; + } public static Options opts = new Options(); @@ -47,7 +52,8 @@ public class PhrasePredicateFeatureComputer implements FeatureComputer { @Override public void extractLocal(Example ex, Derivation deriv) { if (!(FeatureExtractor.containsDomain("phrase-predicate") - || FeatureExtractor.containsDomain("missing-predicate"))) return; + || FeatureExtractor.containsDomain("phrase-formula") + || FeatureExtractor.containsDomain("phrase-category"))) return; // Only compute features at the root, except when the partial option is set. if (!opts.defineOnPartialDerivs && !deriv.isRoot(ex.numTokens())) return; List phraseInfos = PhraseInfo.getPhraseInfos(ex); @@ -58,7 +64,8 @@ public class PhrasePredicateFeatureComputer implements FeatureComputer { LogInfo.logs("Derivation: %s", deriv); LogInfo.logs("Predicates: %s", predicateInfos); } - if (FeatureExtractor.containsDomain("phrase-predicate")) { + if (FeatureExtractor.containsDomain("phrase-predicate") + || FeatureExtractor.containsDomain("phrase-category")) { if (opts.defineOnPartialDerivs) { deriv.getTempState().put("p-p", new ArrayList<>(predicateInfos)); // Subtract predicates from children @@ -109,7 +116,7 @@ public class PhrasePredicateFeatureComputer implements FeatureComputer { private void extractMatch(Example ex, Derivation deriv, PhraseInfo phraseInfo, String phraseString, String phraseType, PredicateInfo predicateInfo, String predicateString, String predicateType, double factor) { - if (opts.unlexicalizedPhrasePredicate) { + if (FeatureExtractor.containsDomain("phrase-predicate") && opts.unlexicalizedPhrasePredicate) { if (phraseString.equals(predicateString)) { defineFeatures(ex, deriv, phraseInfo, predicateInfo, phraseType + "=" + predicateType, phraseString, predicateString, factor); @@ -132,10 +139,27 @@ public class PhrasePredicateFeatureComputer implements FeatureComputer { } } } - if (opts.lexicalizedPhrasePredicate && phraseInfo.end - phraseInfo.start <= maxNforLexicalizeAllPairs) { + if (FeatureExtractor.containsDomain("phrase-predicate") && opts.lexicalizedPhrasePredicate + && phraseInfo.end - phraseInfo.start <= maxNforLexicalizeAllPairs + && (!PhraseInfo.opts.forbidBorderStopWordInLexicalizedFeatures || !phraseInfo.isBorderStopWord)) { deriv.addFeature("p-p", phraseType + phraseString + ";" + predicateType + predicateString, factor); } + if (FeatureExtractor.containsDomain("phrase-category") && predicateInfo.type == PredicateType.BINARY + && (!PhraseInfo.opts.forbidBorderStopWordInLexicalizedFeatures || !phraseInfo.isBorderStopWord)) { + ColumnCategoryInfo catInfo = ColumnCategoryInfo.getSingleton(); + List> categories = catInfo.get(ex, predicateInfo.predicate); + if (categories != null) { + for (Pair pair : categories) { + if (pair.getSecond() >= opts.phraseCategoryWeightThreshold) { + if (opts.phraseCategoryBinary) + deriv.addFeature("p-c", phraseType + phraseString + ";" + pair.getFirst()); + else + deriv.addFeature("p-c", phraseType + phraseString + ";" + pair.getFirst(), pair.getSecond()); + } + } + } + } } private void defineFeatures(Example ex, Derivation deriv, PhraseInfo phraseInfo, PredicateInfo predicateInfo, diff --git a/src/edu/stanford/nlp/sempre/tables/lambdadcs/DenotationUtils.java b/src/edu/stanford/nlp/sempre/tables/lambdadcs/DenotationUtils.java index 9bd6c04..f586b3b 100644 --- a/src/edu/stanford/nlp/sempre/tables/lambdadcs/DenotationUtils.java +++ b/src/edu/stanford/nlp/sempre/tables/lambdadcs/DenotationUtils.java @@ -238,7 +238,7 @@ public final class DenotationUtils { */ public static UnaryDenotation superlativeUnary(int rank, int count, List> pairs, SuperlativeFormula.Mode mode, TypeProcessor processor) { - if (rank <= 0 || count <= 0) + if (rank <= 0 || count <= 0 || rank >= 1000000 || count >= 100000) LogInfo.fails("Invalid superlative (rank = %d, count = %d)", rank, count); if (pairs.isEmpty()) { if (LambdaDCSExecutor.opts.superlativesFailOnEmptyLists) @@ -269,7 +269,7 @@ public final class DenotationUtils { default: throw new LambdaDCSException(Type.invalidFormula, "Unknown superlative mode: %s", mode); } int from = Math.min(rank - 1, indices.size()), - to = Math.min(Math.max(from, from + count), indices.size()); + to = Math.min(from + count, indices.size()); for (int index : indices.subList(from, to)) answer.add(pairs.get(index).getSecond()); }