From 9178eb73226b75c0b209cdcf078927b83677042f Mon Sep 17 00:00:00 2001 From: Panupong Pasupat Date: Thu, 4 Jun 2015 18:01:24 -0700 Subject: [PATCH] Added tables and overnight modules --- .gitignore | 23 + Makefile | 38 - README.md | 2 +- TUTORIAL.md | 6 +- build.xml | 101 +++ overnight/README.md | 25 + overnight/basketball.grammar | 38 + overnight/blocks.grammar | 28 + overnight/calendar-canonical.examples | 6 + overnight/calendar.grammar | 33 + overnight/calendar.turk.examples | 428 +++++++++ overnight/general.grammar | 416 +++++++++ overnight/geo880-unittest.examples | 49 ++ overnight/geo880.grammar | 69 ++ overnight/housing-unittest.examples | 12 + overnight/housing.grammar | 42 + overnight/null.examples | 2 + overnight/publications.grammar | 27 + overnight/recipes.grammar | 33 + overnight/restaurants-unittest.examples | 19 + overnight/restaurants.grammar | 46 + overnight/socialnetwork-unittest.examples | 59 ++ overnight/socialnetwork.grammar | 72 ++ overnight/templates.grammar | 68 ++ overnight/unittest.db | 5 + overnight/unittest.grammar | 4 + pull-dependencies | 46 + run | 440 +++++++++- scripts/checkstyle.sh | 3 +- scripts/checkstyle.xml | 13 +- scripts/create-geo-simple-lexicon.py | 68 ++ scripts/filterGeneratedNegations.py | 29 + scripts/generate-prediction-file.sh | 7 + scripts/tunnel | 8 + scripts/verify-code-loop.rb | 4 +- .../stanford/nlp/sempre/AggregateFormula.java | 1 + .../nlp/sempre/ArithmeticFormula.java | 1 + src/edu/stanford/nlp/sempre/Builder.java | 1 + src/edu/stanford/nlp/sempre/CallFormula.java | 1 + .../stanford/nlp/sempre/CanonicalNames.java | 21 + .../stanford/nlp/sempre/ChartParserState.java | 2 + src/edu/stanford/nlp/sempre/Dataset.java | 26 +- src/edu/stanford/nlp/sempre/Derivation.java | 75 +- .../stanford/nlp/sempre/DerivationPruner.java | 214 +++++ .../nlp/sempre/DerivationPruningComputer.java | 25 + src/edu/stanford/nlp/sempre/Example.java | 33 +- src/edu/stanford/nlp/sempre/ExampleUtils.java | 79 +- .../stanford/nlp/sempre/FeatureExtractor.java | 103 +-- .../stanford/nlp/sempre/FeatureVector.java | 51 +- .../stanford/nlp/sempre/FilterTokenFn.java | 52 ++ .../stanford/nlp/sempre/FloatingParser.java | 135 ++- .../nlp/sempre/FloatingRuleUtils.java | 6 + src/edu/stanford/nlp/sempre/Formula.java | 1 + src/edu/stanford/nlp/sempre/FuzzyMatchFn.java | 13 +- src/edu/stanford/nlp/sempre/Grammar.java | 2 + src/edu/stanford/nlp/sempre/JavaExecutor.java | 25 +- src/edu/stanford/nlp/sempre/JoinFn.java | 2 + src/edu/stanford/nlp/sempre/JoinFormula.java | 1 + .../stanford/nlp/sempre/KnowledgeGraph.java | 9 +- .../stanford/nlp/sempre/LambdaFormula.java | 1 + src/edu/stanford/nlp/sempre/LanguageInfo.java | 45 +- src/edu/stanford/nlp/sempre/Learner.java | 17 +- src/edu/stanford/nlp/sempre/MarkFormula.java | 1 + src/edu/stanford/nlp/sempre/Master.java | 19 +- src/edu/stanford/nlp/sempre/MergeFormula.java | 1 + .../nlp/sempre/NaiveKnowledgeGraph.java | 5 + src/edu/stanford/nlp/sempre/NotFormula.java | 1 + src/edu/stanford/nlp/sempre/NumberFn.java | 69 +- src/edu/stanford/nlp/sempre/Params.java | 34 +- .../stanford/nlp/sempre/ParaphraseModel.java | 75 -- src/edu/stanford/nlp/sempre/Parser.java | 45 +- src/edu/stanford/nlp/sempre/ParserState.java | 153 +++- .../nlp/sempre/ReinforcementParser.java | 4 +- .../stanford/nlp/sempre/ReverseFormula.java | 1 + src/edu/stanford/nlp/sempre/Rule.java | 10 + src/edu/stanford/nlp/sempre/SemType.java | 1 + .../stanford/nlp/sempre/SimpleAnalyzer.java | 43 +- .../nlp/sempre/SuperlativeFormula.java | 1 + .../nlp/sempre/TargetValuePreprocessor.java | 36 + src/edu/stanford/nlp/sempre/TimeValue.java | 51 ++ .../stanford/nlp/sempre/TypeInference.java | 25 +- src/edu/stanford/nlp/sempre/Value.java | 9 + src/edu/stanford/nlp/sempre/ValueFormula.java | 1 + src/edu/stanford/nlp/sempre/Values.java | 1 + .../stanford/nlp/sempre/VariableFormula.java | 1 + src/edu/stanford/nlp/sempre/build.xml | 17 - src/edu/stanford/nlp/sempre/cache/build.xml | 14 - .../nlp/sempre/corenlp/CoreNLPAnalyzer.java | 29 +- src/edu/stanford/nlp/sempre/corenlp/build.xml | 14 - .../nlp/sempre/freebase/Free917Converter.java | 4 +- .../freebase/LambdaCalculusConverter.java | 114 +-- .../stanford/nlp/sempre/freebase/build.xml | 14 - .../freebase/test/SparqlExecutorTest.java | 4 +- .../nlp/sempre/freebase/utils/FileUtils.java | 56 ++ .../nlp/sempre/overnight/Aligner.java | 146 ++++ .../ConvertTargetValueFromListToString.java | 59 ++ ...reateBerkeleyAlignerInputFromLispTree.java | 53 ++ .../nlp/sempre/overnight/GenerationMain.java | 50 ++ .../OvernightDerivationPruningComputer.java | 57 ++ .../overnight/OvernightFeatureComputer.java | 609 +++++++++++++ .../nlp/sempre/overnight/PPDBModel.java | 90 ++ .../nlp/sempre/overnight/SimpleWorld.java | 827 ++++++++++++++++++ .../overnight/test/SimpleWorldTest.java | 30 + .../nlp/sempre/tables/ConvertCsvToTtl.java | 285 ++++++ .../stanford/nlp/sempre/tables/DPParser.java | 720 +++++++++++++++ .../nlp/sempre/tables/FuzzyMatcher.java | 127 +++ .../tables/StringNormalizationUtils.java | 339 +++++++ .../TableDerivationPruningComputer.java | 72 ++ .../sempre/tables/TableKnowledgeGraph.java | 600 +++++++++++++ .../nlp/sempre/tables/TableTypeLookup.java | 40 + .../nlp/sempre/tables/TableTypeSystem.java | 175 ++++ .../sempre/tables/TableValueEvaluator.java | 121 +++ .../sempre/tables/TableValuePreprocessor.java | 69 ++ .../alignment/AgreementAlignmentComputer.java | 122 +++ .../tables/alignment/AlignmentComputer.java | 6 + .../sempre/tables/alignment/BitextData.java | 45 + .../tables/alignment/BitextDataGroup.java | 18 + .../sempre/tables/alignment/BitextDatum.java | 21 + .../sempre/tables/alignment/DoubleMap.java | 79 ++ .../alignment/GroupAlignmentComputer.java | 102 +++ .../alignment/IBM1AlignmentComputer.java | 87 ++ .../sempre/tables/alignment/IBMAligner.java | 115 +++ .../alignment/ProductAlignmentComputer.java | 45 + .../TableBaselineFeatureComputer.java | 111 +++ .../tables/baseline/TableBaselineParser.java | 75 ++ .../sempre/tables/features/HeadwordInfo.java | 60 ++ .../PhraseDenotationFeatureComputer.java | 182 ++++ .../sempre/tables/features/PhraseInfo.java | 100 +++ .../PhrasePredicateFeatureComputer.java | 225 +++++ .../sempre/tables/features/PredicateInfo.java | 395 +++++++++ .../tables/lambdadcs/BinaryDenotation.java | 36 + .../tables/lambdadcs/BinaryTypeHint.java | 47 + .../tables/lambdadcs/DenotationUtils.java | 378 ++++++++ .../tables/lambdadcs/ExecutorCache.java | 60 ++ .../lambdadcs/ExplicitBinaryDenotation.java | 101 +++ .../lambdadcs/ExplicitUnaryDenotation.java | 109 +++ .../lambdadcs/InfiniteUnaryDenotation.java | 175 ++++ .../tables/lambdadcs/LambdaDCSException.java | 40 + .../tables/lambdadcs/LambdaDCSExecutor.java | 361 ++++++++ .../lambdadcs/PredicateBinaryDenotation.java | 93 ++ .../lambdadcs/SpecialBinaryDenotation.java | 83 ++ .../nlp/sempre/tables/lambdadcs/TypeHint.java | 85 ++ .../tables/lambdadcs/UnaryDenotation.java | 56 ++ .../tables/lambdadcs/UnaryTypeHint.java | 61 ++ .../sempre/tables/serialize/DummyParser.java | 40 + .../tables/serialize/LoadedExampleList.java | 185 ++++ .../tables/serialize/SerializedDataset.java | 59 ++ .../tables/serialize/SerializedDumper.java | 183 ++++ .../tables/serialize/SerializedLoader.java | 26 + .../nlp/sempre/tables/test/CustomExample.java | 245 ++++++ .../sempre/tables/test/DPParserChecker.java | 224 +++++ .../tables/test/LambdaDCSExecutorTest.java | 211 +++++ .../tables/test/TableStatsComputer.java | 134 +++ .../nlp/sempre/test/GrammarValidityTest.java | 2 +- .../stanford/nlp/sempre/test/JsonTest.java | 4 +- .../nlp/sempre/test/L1RegularizationTest.java | 183 ++++ .../stanford/nlp/sempre/test/ParserTest.java | 2 +- .../stanford/nlp/sempre/test/TestUtils.java | 2 +- tables/README.md | 1 + tables/grammars/combined.grammar | 311 +++++++ tables/grammars/generic.grammar | 174 ++++ tables/grammars/restrict.grammar | 126 +++ tables/grammars/simple.grammar | 106 +++ tables/grammars/tiny.grammar | 107 +++ tables/toy-examples/random/nikos_machlas.csv | 19 + tables/view | 11 + 166 files changed, 13834 insertions(+), 527 deletions(-) create mode 100644 .gitignore delete mode 100644 Makefile create mode 100644 build.xml create mode 100644 overnight/README.md create mode 100644 overnight/basketball.grammar create mode 100644 overnight/blocks.grammar create mode 100644 overnight/calendar-canonical.examples create mode 100644 overnight/calendar.grammar create mode 100644 overnight/calendar.turk.examples create mode 100644 overnight/general.grammar create mode 100644 overnight/geo880-unittest.examples create mode 100644 overnight/geo880.grammar create mode 100644 overnight/housing-unittest.examples create mode 100644 overnight/housing.grammar create mode 100644 overnight/null.examples create mode 100644 overnight/publications.grammar create mode 100644 overnight/recipes.grammar create mode 100644 overnight/restaurants-unittest.examples create mode 100644 overnight/restaurants.grammar create mode 100644 overnight/socialnetwork-unittest.examples create mode 100644 overnight/socialnetwork.grammar create mode 100644 overnight/templates.grammar create mode 100644 overnight/unittest.db create mode 100644 overnight/unittest.grammar create mode 100755 scripts/create-geo-simple-lexicon.py create mode 100755 scripts/filterGeneratedNegations.py create mode 100755 scripts/generate-prediction-file.sh create mode 100644 src/edu/stanford/nlp/sempre/DerivationPruner.java create mode 100644 src/edu/stanford/nlp/sempre/DerivationPruningComputer.java create mode 100644 src/edu/stanford/nlp/sempre/FilterTokenFn.java delete mode 100644 src/edu/stanford/nlp/sempre/ParaphraseModel.java create mode 100644 src/edu/stanford/nlp/sempre/TargetValuePreprocessor.java create mode 100644 src/edu/stanford/nlp/sempre/TimeValue.java delete mode 100644 src/edu/stanford/nlp/sempre/build.xml delete mode 100644 src/edu/stanford/nlp/sempre/cache/build.xml delete mode 100644 src/edu/stanford/nlp/sempre/corenlp/build.xml delete mode 100644 src/edu/stanford/nlp/sempre/freebase/build.xml create mode 100644 src/edu/stanford/nlp/sempre/overnight/Aligner.java create mode 100644 src/edu/stanford/nlp/sempre/overnight/ConvertTargetValueFromListToString.java create mode 100644 src/edu/stanford/nlp/sempre/overnight/CreateBerkeleyAlignerInputFromLispTree.java create mode 100644 src/edu/stanford/nlp/sempre/overnight/GenerationMain.java create mode 100644 src/edu/stanford/nlp/sempre/overnight/OvernightDerivationPruningComputer.java create mode 100644 src/edu/stanford/nlp/sempre/overnight/OvernightFeatureComputer.java create mode 100644 src/edu/stanford/nlp/sempre/overnight/PPDBModel.java create mode 100644 src/edu/stanford/nlp/sempre/overnight/SimpleWorld.java create mode 100644 src/edu/stanford/nlp/sempre/overnight/test/SimpleWorldTest.java create mode 100644 src/edu/stanford/nlp/sempre/tables/ConvertCsvToTtl.java create mode 100644 src/edu/stanford/nlp/sempre/tables/DPParser.java create mode 100644 src/edu/stanford/nlp/sempre/tables/FuzzyMatcher.java create mode 100644 src/edu/stanford/nlp/sempre/tables/StringNormalizationUtils.java create mode 100644 src/edu/stanford/nlp/sempre/tables/TableDerivationPruningComputer.java create mode 100644 src/edu/stanford/nlp/sempre/tables/TableKnowledgeGraph.java create mode 100644 src/edu/stanford/nlp/sempre/tables/TableTypeLookup.java create mode 100644 src/edu/stanford/nlp/sempre/tables/TableTypeSystem.java create mode 100644 src/edu/stanford/nlp/sempre/tables/TableValueEvaluator.java create mode 100644 src/edu/stanford/nlp/sempre/tables/TableValuePreprocessor.java create mode 100644 src/edu/stanford/nlp/sempre/tables/alignment/AgreementAlignmentComputer.java create mode 100644 src/edu/stanford/nlp/sempre/tables/alignment/AlignmentComputer.java create mode 100644 src/edu/stanford/nlp/sempre/tables/alignment/BitextData.java create mode 100644 src/edu/stanford/nlp/sempre/tables/alignment/BitextDataGroup.java create mode 100644 src/edu/stanford/nlp/sempre/tables/alignment/BitextDatum.java create mode 100644 src/edu/stanford/nlp/sempre/tables/alignment/DoubleMap.java create mode 100644 src/edu/stanford/nlp/sempre/tables/alignment/GroupAlignmentComputer.java create mode 100644 src/edu/stanford/nlp/sempre/tables/alignment/IBM1AlignmentComputer.java create mode 100644 src/edu/stanford/nlp/sempre/tables/alignment/IBMAligner.java create mode 100644 src/edu/stanford/nlp/sempre/tables/alignment/ProductAlignmentComputer.java create mode 100644 src/edu/stanford/nlp/sempre/tables/baseline/TableBaselineFeatureComputer.java create mode 100644 src/edu/stanford/nlp/sempre/tables/baseline/TableBaselineParser.java create mode 100644 src/edu/stanford/nlp/sempre/tables/features/HeadwordInfo.java create mode 100644 src/edu/stanford/nlp/sempre/tables/features/PhraseDenotationFeatureComputer.java create mode 100644 src/edu/stanford/nlp/sempre/tables/features/PhraseInfo.java create mode 100644 src/edu/stanford/nlp/sempre/tables/features/PhrasePredicateFeatureComputer.java create mode 100644 src/edu/stanford/nlp/sempre/tables/features/PredicateInfo.java create mode 100644 src/edu/stanford/nlp/sempre/tables/lambdadcs/BinaryDenotation.java create mode 100644 src/edu/stanford/nlp/sempre/tables/lambdadcs/BinaryTypeHint.java create mode 100644 src/edu/stanford/nlp/sempre/tables/lambdadcs/DenotationUtils.java create mode 100644 src/edu/stanford/nlp/sempre/tables/lambdadcs/ExecutorCache.java create mode 100644 src/edu/stanford/nlp/sempre/tables/lambdadcs/ExplicitBinaryDenotation.java create mode 100644 src/edu/stanford/nlp/sempre/tables/lambdadcs/ExplicitUnaryDenotation.java create mode 100644 src/edu/stanford/nlp/sempre/tables/lambdadcs/InfiniteUnaryDenotation.java create mode 100644 src/edu/stanford/nlp/sempre/tables/lambdadcs/LambdaDCSException.java create mode 100644 src/edu/stanford/nlp/sempre/tables/lambdadcs/LambdaDCSExecutor.java create mode 100644 src/edu/stanford/nlp/sempre/tables/lambdadcs/PredicateBinaryDenotation.java create mode 100644 src/edu/stanford/nlp/sempre/tables/lambdadcs/SpecialBinaryDenotation.java create mode 100644 src/edu/stanford/nlp/sempre/tables/lambdadcs/TypeHint.java create mode 100644 src/edu/stanford/nlp/sempre/tables/lambdadcs/UnaryDenotation.java create mode 100644 src/edu/stanford/nlp/sempre/tables/lambdadcs/UnaryTypeHint.java create mode 100644 src/edu/stanford/nlp/sempre/tables/serialize/DummyParser.java create mode 100644 src/edu/stanford/nlp/sempre/tables/serialize/LoadedExampleList.java create mode 100644 src/edu/stanford/nlp/sempre/tables/serialize/SerializedDataset.java create mode 100644 src/edu/stanford/nlp/sempre/tables/serialize/SerializedDumper.java create mode 100644 src/edu/stanford/nlp/sempre/tables/serialize/SerializedLoader.java create mode 100644 src/edu/stanford/nlp/sempre/tables/test/CustomExample.java create mode 100644 src/edu/stanford/nlp/sempre/tables/test/DPParserChecker.java create mode 100644 src/edu/stanford/nlp/sempre/tables/test/LambdaDCSExecutorTest.java create mode 100644 src/edu/stanford/nlp/sempre/tables/test/TableStatsComputer.java create mode 100644 src/edu/stanford/nlp/sempre/test/L1RegularizationTest.java create mode 100644 tables/README.md create mode 100644 tables/grammars/combined.grammar create mode 100644 tables/grammars/generic.grammar create mode 100644 tables/grammars/restrict.grammar create mode 100644 tables/grammars/simple.grammar create mode 100644 tables/grammars/tiny.grammar create mode 100644 tables/toy-examples/random/nikos_machlas.csv create mode 100755 tables/view diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3fa725e --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +lib +fig +virtuoso-opensource +module-classes.txt +classes +libsempre + +state +out +test-output + +.project +.classpath +.settings +.idea +semparse.iml +*~ +*.swp +*.bak +*.pyc +*.cache +*.DS_Store +java.hprof.txt diff --git a/Makefile b/Makefile deleted file mode 100644 index a4fc941..0000000 --- a/Makefile +++ /dev/null @@ -1,38 +0,0 @@ -BUILD_DEPS = libsempre/sempre-core.jar \ - libsempre/sempre-cache.jar \ - libsempre/sempre-freebase.jar \ - libsempre/sempre-fbalignment.jar \ - libsempre/sempre-paraphrase.jar \ - libsempre/sempre-corenlp.jar \ - libsempre/sempre-jungle.jar - - -default: module-classes $(BUILD_DEPS) - -module-classes: - scripts/extract-module-classes.rb - -core: libsempre/sempre-core.jar -libsempre/sempre-core.jar: \ - $(shell ls src/edu/stanford/nlp/sempre/*.java) \ - $(shell ls src/edu/stanford/nlp/sempre/test/*.java) - cd src/edu/stanford/nlp/sempre && ant compile - -cache: libsempre/sempre-cache.jar -libsempre/sempre-cache.jar: \ - $(shell find src/edu/stanford/nlp/sempre/cache -name "*.java") - cd src/edu/stanford/nlp/sempre/cache && ant compile - -corenlp: libsempre/sempre-corenlp.jar -libsempre/sempre-corenlp.jar: libsempre/sempre-core.jar libsempre/sempre-cache.jar \ - $(shell find src/edu/stanford/nlp/sempre/corenlp -name "*.java") - cd src/edu/stanford/nlp/sempre/corenlp && ant compile - -freebase: libsempre/sempre-freebase.jar -libsempre/sempre-freebase.jar: libsempre/sempre-core.jar libsempre/sempre-cache.jar \ - $(shell find src/edu/stanford/nlp/sempre/freebase -name "*.java") - cd src/edu/stanford/nlp/sempre/freebase && ant compile - - -clean: - rm -rf classes libsempre diff --git a/README.md b/README.md index 6757c4d..aa73ffe 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# SEMPRE 2.0: Semantic Parsing with Execution +# SEMPRE 2.1: Semantic Parsing with Execution ## What is semantic parsing? diff --git a/TUTORIAL.md b/TUTORIAL.md index 0d6c6ab..b9c27ad 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -175,7 +175,7 @@ derivation representing a number. Now, you can parse the following: four - 2.718 + 20 Note: if you now type in `three`, you should get two derivations that yield the same answer, one coming from each rule. Note that `twenty-five million` will @@ -334,7 +334,7 @@ The complete derivation for *three plus four* is illustrated here: three plus four -**Exercise 2.1**: write rules can parse the following utterances into +**Exercise 2.1**: write rules that can parse the following utterances into into the category `$Expr`: length of hello world # 11 @@ -375,7 +375,7 @@ You can put a set of grammar rules in a file (e.g., ./run @mode=simple -Grammar.inPaths data/tutorial-arithmetic.grammar -If you make edit the grammar, you can reload the grammar without exiting the +If you edit the grammar, you can reload the grammar without exiting the prompt by typing: (reload) diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..fa80275 --- /dev/null +++ b/build.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/overnight/README.md b/overnight/README.md new file mode 100644 index 0000000..00625a1 --- /dev/null +++ b/overnight/README.md @@ -0,0 +1,25 @@ +# Files for Building a Semantic Parser Overnight + +## Setting up datasets + +To generate canonical utterances, run + + ./pull-dependencies freebase overnight + make corenlp freebase overnight + ./run @mode=genovernight-wrapper + +Or, to generate each individual domain + ./run @mode=genovernight @gen=1 @domain= + +After turking and setting up the approprate example files, run + + ./run @mode=overnight @domain= + +to run with all features. + +To run with partial features, use the following commands: + + Baseline - ./run @mode=overnight @domain= -OvernightFeatureComputer.featureDomains match skip-bigram root lf simpleworld + No Lexical features - ./run @mode=overnight @domain= -OvernightFeatureComputer.featureDomains match ppdb skip-bigram root lf simpleworld + No PPDB - ./run @mode=overnight @domain= -OvernightFeatureComputer.featureDomains match skip-bigram root lf alignment lexical root_lexical simpleworld + Full system - ./run @mode=overnight @domain= -OvernightFeatureComputer.featureDomains match ppdb skip-bigram root alignment lexical root_lexical lf simpleworld diff --git a/overnight/basketball.grammar b/overnight/basketball.grammar new file mode 100644 index 0000000..06048f1 --- /dev/null +++ b/overnight/basketball.grammar @@ -0,0 +1,38 @@ +(include general.grammar) + +# Types +(rule $TypeNP (player) (ConstantFn en.player)) +(rule $EntityNP1 (kobe bryant) (ConstantFn en.player.kobe_bryant)) +(rule $EntityNP2 (lebron james) (ConstantFn en.player.lebron_james)) + +(rule $TypeNP (team) (ConstantFn en.team)) +(rule $EntityNP1 (los angeles lakers) (ConstantFn en.team.lakers)) +(rule $EntityNP2 (cleveland cavaliers) (ConstantFn en.team.cavaliers)) + +(rule $TypeNP (position) (ConstantFn en.position)) +(rule $EntityNP1 (point guard) (ConstantFn en.position.point_guard)) +(rule $EntityNP2 (forward) (ConstantFn en.position.forward)) + +(rule $EntityNP1 (2004) (ConstantFn (date 2004 -1 -1))) +(rule $EntityNP2 (2010) (ConstantFn (date 2010 -1 -1))) + +(for @x (point assist steal turnover rebound block foul game fg ft) + (rule $EntityNP1 (3) (ConstantFn (number 3 @x))) + #(rule $EntityNP2 (6) (ConstantFn (number 6 @x))) +) + +# Season statistics + +(rule $Rel0NP (player) (ConstantFn (string player))) +(rule $RelNP (position) (ConstantFn (string position))) +(rule $RelNP (team) (ConstantFn (string team))) +(rule $RelNP (season) (ConstantFn (string season))) + +(rule $RelNP (number of points "(over a season)") (ConstantFn (string num_points))) +(rule $RelNP (number of assists "(over a season)") (ConstantFn (string num_assists))) +(rule $RelNP (number of steals "(over a season)") (ConstantFn (string num_steals))) +(rule $RelNP (number of turnovers "(over a season)") (ConstantFn (string num_turnovers))) +(rule $RelNP (number of rebounds "(over a season)") (ConstantFn (string num_rebounds))) +(rule $RelNP (number of blocks "(over a season)") (ConstantFn (string num_blocks))) +(rule $RelNP (number of fouls "(over a season)") (ConstantFn (string num_fouls))) +(rule $RelNP (number of played games "(over a season)") (ConstantFn (string num_games_played))) diff --git a/overnight/blocks.grammar b/overnight/blocks.grammar new file mode 100644 index 0000000..e29d39d --- /dev/null +++ b/overnight/blocks.grammar @@ -0,0 +1,28 @@ +(include general.grammar) + +# Types +(rule $TypeNP (block) (ConstantFn en.block)) +(rule $EntityNP1 (block 1) (ConstantFn en.block.block1)) +(rule $EntityNP2 (block 2) (ConstantFn en.block.block2)) + +# Properties +(rule $RelNP (shape) (ConstantFn (string shape))) +(rule $EntityNP1 (a pyramid) (ConstantFn en.shape.pyramid)) +(rule $EntityNP2 (a cube) (ConstantFn en.shape.cube)) + +(rule $RelNP (color) (ConstantFn (string color))) +(rule $EntityNP1 (red) (ConstantFn en.color.red)) +(rule $EntityNP2 (green) (ConstantFn en.color.green)) + +(rule $RelNP (length) (ConstantFn (string length))) +(rule $RelNP (width) (ConstantFn (string width))) +(rule $RelNP (height) (ConstantFn (string height))) +(rule $EntityNP1 (3 inches) (ConstantFn (number 3 en.inch))) +(rule $EntityNP2 (6 inches) (ConstantFn (number 6 en.inch))) + +(rule $VP/NP (is left of) (ConstantFn (string left))) +(rule $VP/NP (is right of) (ConstantFn (string right))) +(rule $VP/NP (is above) (ConstantFn (string above))) +(rule $VP/NP (is below) (ConstantFn (string below))) + +(rule $VP (is special) (ConstantFn (string is_special))) diff --git a/overnight/calendar-canonical.examples b/overnight/calendar-canonical.examples new file mode 100644 index 0000000..f05e529 --- /dev/null +++ b/overnight/calendar-canonical.examples @@ -0,0 +1,6 @@ +# Make sure these test cases always parse +(example (utterance "create a meeting with start time 3pm")) +(example (utterance "what is the meeting whose start time is 9am or 3pm")) +(example (utterance "remove the meeting whose date is day 2")) +(example (utterance "what is the location of the meeting whose start time is 9am")) +(example (utterance "what is the length of the free block whose date is day 1 with the largest end time")) diff --git a/overnight/calendar.grammar b/overnight/calendar.grammar new file mode 100644 index 0000000..847893e --- /dev/null +++ b/overnight/calendar.grammar @@ -0,0 +1,33 @@ +(include general.grammar) + +# Types +(rule $TypeNP (meeting) (ConstantFn en.meeting)) +(rule $EntityNP1 (weekly standup) (ConstantFn en.meeting.weekly_standup)) +(rule $EntityNP2 (annual review) (ConstantFn en.meeting.annual_review)) + +# Properties +(rule $RelNP (date) (ConstantFn (string date))) +(rule $EntityNP1 (jan 2) (ConstantFn (date 2015 1 2))) +(rule $EntityNP2 (jan 3) (ConstantFn (date 2015 1 3))) + +(rule $RelNP (start time) (ConstantFn (string start_time))) +(rule $RelNP (end time) (ConstantFn (string end_time))) +(rule $EntityNP1 (10am) (ConstantFn (time 10 0))) +(rule $EntityNP2 (3pm) (ConstantFn (time 15 0))) + +(rule $RelNP (length) (ConstantFn (string length))) +(rule $EntityNP1 (three hours) (ConstantFn (number 3 en.hour))) +(rule $EntityNP2 (one hour) (ConstantFn (number 1 en.hour))) + +(rule $RelNP (attendee) (ConstantFn (string attendee))) +(rule $TypeNP (person) (ConstantFn en.person)) +(rule $EntityNP1 (alice) (ConstantFn en.person.alice)) +(rule $EntityNP2 (bob) (ConstantFn en.person.bob)) + +(rule $RelNP (location) (ConstantFn (string location))) +(rule $TypeNP (location) (ConstantFn en.location)) +(rule $EntityNP1 (greenberg cafe) (ConstantFn en.location.greenberg_cafe)) +(rule $EntityNP2 (central office) (ConstantFn en.location.central_office)) + +# Unaries +(rule $VP (is important) (ConstantFn (string is_important))) diff --git a/overnight/calendar.turk.examples b/overnight/calendar.turk.examples new file mode 100644 index 0000000..c2ec0dd --- /dev/null +++ b/overnight/calendar.turk.examples @@ -0,0 +1,428 @@ +(example + (utterance "What meeting is on Jan 3?") + (original "meeting whose date is jan 3") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string date) (string =) (date 2015 1 3))) + ) +) + +(example + (utterance "How many meetings after 1pm?") + (original "number of meeting whose start time is larger than 1pm") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call .size (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (call edu.stanford.nlp.sempre.agile.SimpleWorld.ensureNumericProperty (string start_time)) (string >) (call edu.stanford.nlp.sempre.agile.SimpleWorld.ensureNumericEntity (time 13 0))))) + ) +) + +(example + (utterance "What meeting is on Wed Jan 5") + (original "meeting whose date is jan 5") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string date) (string =) (date 2015 1 5))) + ) +) + +(example + (utterance "Am I free on mar 6?") + (original "number of meeting whose date is mar 6") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call .size (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (call edu.stanford.nlp.sempre.agile.SimpleWorld.ensureNumericProperty (string date)) (string =) (date 2015 3 6)))) + ) +) + +(example + (utterance "Which meetings are about the office chair?") + (original "meeting whose subject is office chair") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string subject) (string =) (en.subject.office_chair))) + ) +) + +(example + (utterance "What time does the office meeting start?") + (original "start time of meeting whose location is office") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call @getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string location) (string =) en.location.office) (string start_time))) + ) +) + +(example + (utterance "Who is attending the meeting on June 9?") + (original "attendee of meeting whose date is jun 9") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call @getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string date) (string =) (date 2015 6 9)) (string attendee))) + ) +) + +(example + (utterance "How long is the meeting on May 23?") + (original "length of meeting whose date is may 23") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call @getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string date) (string =) (date 2015 5 23)) (string length))) + ) +) + +(example + (utterance "Where is the meeting on Jun 12?") + (original "location of meeting whose date is jun 12") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call @getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string date) (string =) (date 2015 6 12)) (string location))) + ) +) + +(example + (utterance "What time is my weekly standup?") + (original "start time of weekly standup") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty en.meeting.weekly_standup (string start_time))) + ) +) + +(example + (utterance "How many times have I visited greenburg cafe at noon?") + (original "Number of meetings whose location is greenburg cafe and whose start time is 12pm") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call .size (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string location) (string =) en.location.greenburg_cafe) (string start_time) (string =) (time 12 0)))) + ) +) + +(example + (utterance "Are there any meetings in June?") + (original "number of meeting whose date is at least jun 1 and whose date is at most jun 30") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call .size (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string date) (string >=) (date 2015 6 1)) (string date) (string <=) (date 2015 6 30)))) + ) +) + +(example + (utterance "Do I have meetings on May 8?") + (original "number of meeting whose date is may 8") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call .size (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld. getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (call edu.stanford.nlp.sempre.agile.SimpleWorld.ensureNumericProperty (string date)) (string =) (date 2015 5 8)))) + ) +) + +(example + (utterance "What do I have scheduled on May 7?") + (original "meeting whose date may 7") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string date) (string =) (date 2015 5 7))) + ) +) + +(example + (utterance "How many meetings does Alice have on May 12?") + (original "Number of meetings whose attendee is alice and whose date is may 12") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call .size (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string attendee) (string =) en.person.alice) (string date) (string =) (date 2015 5 12)))) + ) +) + +(example + (utterance "Who is attending the same meetings as me on May 7th?") + (original "attendee of meeting whose date is may 7") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call @getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string date) (string =) (date 2015 5 7)) (string attendee))) + ) +) + +(example + (utterance "Who is attending the 2:00 pm meeting on January 3rd?") + (original "attendee of meeting whose start time is 2pm and whose date is jan 3) + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call @getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string start_time) (string =) (time 14 0)) (string date) (string =) (date 2015 1 3)) (string attendee))) + ) +) + +(example + (utterance "What time does my first meeting start on May 17?") + (original "start time of meeting whose date is may 17 that has smallest start time") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call @getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.superlative (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string date) (string =) (date 2015 5 17)) (string min) (call edu.stanford.nlp.sempre.agile.SimpleWorld.ensureNumericProperty (string start_time))) (string start_time))) + ) +) + +(example + (utterance "How many people will be attending the meeting on December 23?") + (original "number of attendee of meeting whose date is dec 23") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call .size (call @getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string date) (string =) (date 2015 12 23)) (string attendee)))) + ) +) + +(example + (utterance "Where are my meetings on Feb 3?") + (original "location of meeting whose date is feb 3") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call @getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string date) (string =) (date 2015 2 3)) (string location))) + ) +) + +(example + (utterance "Who will be at the meeting on Feb 3?") + (original "attendee of meeting whose date is feb 3") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call @getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string date) (string =) (date 2015 2 3)) (string attendee))) + ) +) + +(example + (utterance "When is my first meeting on Feb 3?") + (original "start time of meeting whose date is feb 3 that has smallest start time") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call @getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.superlative (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string date) (string =) (date 2015 2 3)) (string min) (call edu.stanford.nlp.sempre.agile.SimpleWorld.ensureNumericProperty (string start_time))) (string start_time))) + ) +) + +(example + (utterance "When is my last meeting on Feb 3?") + (original "start time of meeting whose date is feb 3 that has largest start time") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call @getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.superlative (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string date) (string =) (date 2015 2 3)) (string max) (call edu.stanford.nlp.sempre.agile.SimpleWorld.ensureNumericProperty (string start_time))) (string start_time))) + ) +) + +(example + (utterance "Who is attending the meeting on My6?") + (original "attendee of meeting whose date is may 6") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call @getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string date) (string =) (date 2015 5 6)) (string attendee))) + ) +) + +(example + (utterance "Where is the location of the meeting on May 7?") + (original "location of meeting whose date is may 7") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call @getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string date) (string =) (date 2015 5 7)) (string location))) + ) +) + +(example + (utterance "What is the subject of my June 6 meeting?") + (original "subject of meeting whose date is jun 6") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call @getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string date) (string =) (date 2015 6 6)) (string subject))) + ) +) + +(example + (utterance "Where is my June 6 meeting?") + (original "location of meeting whose date is jun 6") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call @getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string date) (string =) (date 2015 6 6)) (string location))) + ) +) + +(example + (utterance Who is attending my June 6 meeting?") + (original "attendee of meeting whose date is jun 6") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call @getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string date) (string =) (date 2015 6 6)) (string attendee))) + ) +) + +(example + (utterance "What is the subject of my June 6 meeting?") + (original "subject of meeting whose date is jun 6") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call @getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string date) (string =) (date 2015 6 6)) (string subject))) + ) +) + +(example + (utterance "What time does the June 6 meeting start?") + (original "start time of meeting whose date is jun 6") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call @getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string date) (string =) (date 2015 6 6)) (string start_time))) + ) +) + +(example + (utterance "Which meetings are in the afternoon?) + (original "meeting whose start time is at least 12pm") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call .size (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (call edu.stanford.nlp.sempre.agile.SimpleWorld.ensureNumericProperty (string start_time)) (string >=) (call edu.stanford.nlp.sempre.agile.SimpleWorld.ensureNumericEntity (time 12 0))))) + ) +) + +(example + (utterance "How many meetings are on Dec 12?") + (original "number of meeting whose date is dec 12") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call .size (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld. getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (call edu.stanford.nlp.sempre.agile.SimpleWorld.ensureNumericProperty (string date)) (string =) (date 2015 12 12)))) + ) +) + +(example + (utterance "How many meetings is Alice going to in April?") + (original "number of meeting whose attendee is alice and whose date is at least apr 1 and whose date is at most apr 30") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call .size (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string attendee) (string =) en.person.alice) (string date) (string >=) (date 2015 4 1)) (string date) (string <=) (date 2015 4 30)))) + ) +) + +(example + (utterance "What is the longest meeting scheduled for the week of May 4-8?") + (original "meeting whose date is at least may 4 and whose date is at most may 8 that has the largest length") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call edu.stanford.nlp.sempre.agile.SimpleWorld.superlative (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string date) (string >=) (date 2015 5 4)) (string date) (string <=) (date 2015 5 8)) (string max) (call edu.stanford.nlp.sempre.agile.SimpleWorld.ensureNumericProperty (string length)))) + ) +) + +(example + (utterance "Do I have any meetings on May 8th?") + (original "number of meeting whose date is may 8") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call .size (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld. getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (call edu.stanford.nlp.sempre.agile.SimpleWorld.ensureNumericProperty (string date)) (string =) (date 2015 5 8)))) + ) +) + +(example + (utterance "When does my meeting on June 3rd end?") + (original "end time of meeting whose date is jun 3") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call @getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string date) (string =) (date 2015 6 3)) (string end_time))) + ) +) + +(example + (utterance "What time is my meeting on May 7th?") + (original "start time of meeting whose date is may 7") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call @getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string date) (string =) (date 2015 5 7)) (string start_time))) + ) +) + +(example + (utterance "How many people will be in the May 10th meeting?") + (original "number of attendee of meeting whose date is may 10") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call .size (call @getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string date) (string =) (date 2015 5 10)) (string attendee)))) + ) +) + +(example + (utterance "Do I have any important meetings in April?") + (original "number of meeting that is important and whose date is at least apr 1 and whose date is at most april 30") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call .size (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (cal l edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string is_important)) (string date) (string >=) (date 2015 4 1)) (string date) (string <=) (date 2015 4 30)))) + ) +) + +(example + (utterance "How many important meetings do I have in April?") + (original "number of meeting that is important and whose date is at least apr 1 and whose date is at most april 30") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call .size (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (cal l edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string is_important)) (string date) (string >=) (date 2015 4 1)) (string date) (string <=) (date 2015 4 30)))) + ) +) + +(example + (utterance "Will Alice be at the meeting on May 10th?") + (original "meeting whose attendee is alice and whose date is may 10") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call .size (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string attendee) (string =) en.person.alice) (string date) (string =) (date 2015 5 10)))) + ) +) + +(example + (utterance "Is there a meeting on February 3?") + (original "meeting whose date is feb 3") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string date) (string =) (date 2015 2 3))) + ) +) + +(example + (utterance "Which meetings are scheduled for August 1?") + (original "meeting whose date is aug 1") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (call edu.stanford.nlp.sempre.agile.SimpleWorld.getProperty (call edu.stanford.nlp.sempre.agile.SimpleWorld.singleton en.meeting) (string !type)) (string date) (string =) (date 2015 8 1))) + ) +) + +(example + (utterance "how many meetings do I have to attend in July?") + (original "number of meeting whose date is at least aug 1 and whose date is at most aug 31") + (targetFormula + (call edu.stanford.nlp.sempre.agile.SimpleWorld.listValue (call.size (call edu.stanford.nlp.sempre.agile.SimpleWorld.filter (edu.stanford.nlp.sempre.agile.SimpleWorld.) + ) +) +how many meetings do I have to attend in July? +where is the 2pm meeting being held? + + +Which meetings in May 1-7 are in the morning? +Who is attending the May 19th meeting? +which is my next day without a meeting? +When is my next meeting with Alice? +What is happening on the third? +when does my last meeting end on feb 14? +how many people will be at the march 4 meeting? +What time is my meeting on Monday May 11th? +Do I have anything scheduled on Wednesday May 13th? +where is the last meeting of the year? +how many meeting are in February? +How many meetings are after noon? +How many meetings are after noon on May 9th? +How many meetings are before 4 pm on May 10th? +Which meeting is Jami attending on May 11th? +What is the agenda of the meeting? +How long will the meeting prolong? +How many important meetings do I have before noon on May 9th? +What meetings are in March? +how many meetings on jul 23 are longer than 1 hour? +how many meetings have more than 2 attendees? +what is the subject of the meeting on nov 23 at 4 pm? +do i have any plans for 15th march +can you check my appointments for the evening of the 5th +What meetings are scheduled for May 5th? +Where is the meeting scheduled for May 5th? +How many will be attending the May 5th meeting? +What time does the May 5th meeting start? +what time is my first meeting on February 22? +What meetings are scheduled with the CEO, Bob? +What time does my last meeting end on June 22? +Which meetings are about the annual budget review? +what time is my next meeting with bob? + +==== Requires "now"/"today"/"tomorrow" ==== +How many meetings do I have next week? +Is Alice going to the meeting tomorrow? +Where are the meetings for tomorrow? +How many meetings do I have today? +When do I have an important meeting in the next three days? +What meetings are scheduled for next week? +What time is tomorrow's meeting? +How many meetings are this month? +What is the earliest meeting tomorrow? +What is the latest meeting tomorrow? +What time is my next meeting? +How many people will attend the meeting tomorrow? +What time does today's meeting start? +Where is my first meeting of the day at? +When does my last meeting of the day end? +Find the address for my next meeting? +How much time do I have until my next meeting with Sam? +How many meetings are today? +Am I free at 2 pm today? +How many meetings do I have this week? +What is on today's calendar? +When is my next standup? +How many meetings are this month? + +==== Implicit reference ==== +How many attendees are planning on going to the meeting? +What time do I have to be at the meeting on May 9? + +==== Not in grammar ===== +Is it possible to schedule a meeting before 2 pm? +When do I call my boss Bob? +When is my date with Alice? +What meetings are with my Client Adam? +Which month is most popular for meetings? +When is the annual report due? + +==== yes/no ==== +Do I have more than 2 meetings on April 5? diff --git a/overnight/general.grammar b/overnight/general.grammar new file mode 100644 index 0000000..06d657f --- /dev/null +++ b/overnight/general.grammar @@ -0,0 +1,416 @@ +############################################################ +# General grammar +# There are two types of grammars +# - generate: used to generate canonical utterances +# - parse: used to actually parse + +(def @domain edu.stanford.nlp.sempre.overnight.SimpleWorld.domain) +(def @singleton edu.stanford.nlp.sempre.overnight.SimpleWorld.singleton) +(def @filter edu.stanford.nlp.sempre.overnight.SimpleWorld.filter) +(def @getProperty edu.stanford.nlp.sempre.overnight.SimpleWorld.getProperty) +(def @superlative edu.stanford.nlp.sempre.overnight.SimpleWorld.superlative) +(def @countSuperlative edu.stanford.nlp.sempre.overnight.SimpleWorld.countSuperlative) +(def @countComparative edu.stanford.nlp.sempre.overnight.SimpleWorld.countComparative) +(def @aggregate edu.stanford.nlp.sempre.overnight.SimpleWorld.aggregate) +(def @concat edu.stanford.nlp.sempre.overnight.SimpleWorld.concat) +(def @reverse edu.stanford.nlp.sempre.overnight.SimpleWorld.reverse) +(def @arithOp edu.stanford.nlp.sempre.overnight.SimpleWorld.arithOp) +(def @sortAndToString edu.stanford.nlp.sempre.overnight.SimpleWorld.sortAndToString) +(def @ensureNumericProperty edu.stanford.nlp.sempre.overnight.SimpleWorld.ensureNumericProperty) +(def @ensureNumericEntity edu.stanford.nlp.sempre.overnight.SimpleWorld.ensureNumericEntity) +(def @listValue edu.stanford.nlp.sempre.overnight.SimpleWorld.listValue) + +############################################################ +# Base cases + +(when parse + # G1 + # Generic values + (rule $EntityNP ($PHRASE) (NumberFn) (anchored 1)) + (rule $EntityNP ($PHRASE) (DateFn) (anchored 1)) + + # Currently, just cheat and use the entities defined in the base grammar. + # In the future, want actually NER. + (rule $EntityNP ($EntityNP1) (IdentityFn)) + (rule $EntityNP ($EntityNP2) (IdentityFn)) + #(rule $EntityNP ($PHRASE) (FilterNerSpanFn PERSON ORGANIZATION LOCATION MISC) (anchored 1)) + #(rule $EntityNP ($PHRASE) (FilterPosTagFn span NNP) (anchored 1)) + + (rule $Num ($PHRASE) (NumberFn) (anchored 1)) +) +(when generate + (rule $Num (two) (ConstantFn (number 2))) +) + +(when (and parse general) + # G1 + (rule $NP ($EntityNP) (IdentityFn)) + # G2 + (rule $NP ($TypeNP) (lambda t (call @getProperty (call @singleton (var t)) (string !type)))) # Unary +) +(when (and generate general) + (rule $UnaryNP ($TypeNP) (lambda t (call @getProperty (call @singleton (var t)) (string !type)))) # Unary +) + +(when (and parse regex) + (rule $NP ($EntityNP) (IdentityFn)) + (rule $NP ($TypeNP) (IdentityFn)) +) +(when (and generate regex) + (rule $UnaryNP ($TypeNP) (IdentityFn)) +) + +(when (and parse general) + (rule $NumberRelNP ($RelNP) (lambda r (call @ensureNumericProperty (var r)))) + (rule $NumberNP ($NP) (lambda r (call @ensureNumericEntity (var r)))) +) +(when (and generate general) + (rule $NumberRelNP ($RelNP) (lambda r (call @ensureNumericProperty (var r)))) + (rule $NumberEntityNP ($EntityNP1) (lambda r (call @ensureNumericEntity (var r)))) + (rule $NumberNP0 ($NP0) (lambda r (call @ensureNumericEntity (var r)))) +) + +############################################################ +# Complementizer phrase (filtering) + +(when (and parse general) + # R0 + (rule $CP (that $VP) (lambda r (lambda s (call @filter (var s) (var r))))) # Vunary + # R1 + (rule $CP (whose $RelNP is $NP) (lambda r (lambda n (lambda s (call @filter (var s) (var r) (string =) (var n)))))) # Req + (rule $CP (whose $RelNP is not $NP) (lambda r (lambda n (lambda s (call @filter (var s) (var r) (string !=) (var n)))))) # Rnot + (rule $CP (whose $NumberRelNP is smaller than $NumberNP) (lambda r (lambda n (lambda s (call @filter (var s) (var r) (string <) (var n)))))) # Rl + (rule $CP (whose $NumberRelNP is larger than $NumberNP) (lambda r (lambda n (lambda s (call @filter (var s) (var r) (string >) (var n)))))) # Rg + (rule $CP (whose $NumberRelNP is at most $NumberNP) (lambda r (lambda n (lambda s (call @filter (var s) (var r) (string <=) (var n)))))) # Rle + (rule $CP (whose $NumberRelNP is at least $NumberNP) (lambda r (lambda n (lambda s (call @filter (var s) (var r) (string >=) (var n)))))) # Rge + # R2 + (rule $CP (that $VP/NP $NP) (lambda r (lambda n (lambda s (call @filter (var s) (var r) (string =) (var n)))))) # Vobj + (rule $CP (that not $VP/NP $NP) (lambda r (lambda n (lambda s (call @filter (var s) (var r) (string !=) (var n)))))) # Vobj-not + # R3 + (rule $CP (that is $RelNP of $NP) (lambda r (lambda n (lambda s (call @filter (var s) (call @reverse (var r)) (string =) (var n)))))) # Reqrev + (rule $CP (that is not $RelNP of $NP) (lambda r (lambda n (lambda s (call @filter (var s) (call @reverse (var r)) (string !=) (var n)))))) # Reqrev-not + # R4 + (rule $CP (that $NP $VP/NP) (lambda n (lambda r (lambda s (call @filter (var s) (call @reverse (var r)) (string =) (var n)))))) # Vsubj + (rule $CP (that $NP not $VP/NP) (lambda n (lambda r (lambda s (call @filter (var s) (call @reverse (var r)) (string !=) (var n)))))) # Vsubj-not +) +(when (and generate general) + # R0 + (rule $CP00 (that $VP) (lambda r (lambda s (call @filter (var s) (var r))))) # Vunary + # R1 + (rule $CP00 (whose $RelNP is $EntityNP1) (lambda r (lambda n (lambda s (call @filter (var s) (var r) (string =) (var n)))))) # Req + (rule $CP1 (whose $RelNP is $NP0) (lambda r (lambda n (lambda s (call @filter (var s) (var r) (string =) (var n)))))) # Req + (rule $CP0 (whose $RelNP is not $EntityNP1) (lambda r (lambda n (lambda s (call @filter (var s) (var r) (string !=) (var n)))))) # Rnot + (rule $CP0 (whose $NumberRelNP is smaller than $NumberEntityNP) (lambda r (lambda n (lambda s (call @filter (var s) (var r) (string <) (var n)))))) # Rl + (rule $CP1 (whose $NumberRelNP is smaller than $NumberNP0) (lambda r (lambda n (lambda s (call @filter (var s) (var r) (string <) (var n)))))) # Rl + (rule $CP0 (whose $NumberRelNP is larger than $NumberEntityNP) (lambda r (lambda n (lambda s (call @filter (var s) (var r) (string >) (var n)))))) # Rg + (rule $CP1 (whose $NumberRelNP is larger than $NumberNP0) (lambda r (lambda n (lambda s (call @filter (var s) (var r) (string >) (var n)))))) # Rg + (rule $CP0 (whose $NumberRelNP is at most $NumberEntityNP) (lambda r (lambda n (lambda s (call @filter (var s) (var r) (string <=) (var n)))))) #Rle + (rule $CP1 (whose $NumberRelNP is at most $NumberNP0) (lambda r (lambda n (lambda s (call @filter (var s) (var r) (string <=) (var n)))))) # Rle + (rule $CP0 (whose $NumberRelNP is at least $NumberEntityNP) (lambda r (lambda n (lambda s (call @filter (var s) (var r) (string >=) (var n)))))) # Rge + (rule $CP1 (whose $NumberRelNP is at least $NumberNP0) (lambda r (lambda n (lambda s (call @filter (var s) (var r) (string >=) (var n)))))) # Rge + # R2 + (rule $CP00 (that $VP/NP $EntityNP1) (lambda r (lambda n (lambda s (call @filter (var s) (var r) (string =) (var n)))))) # Vobj + (rule $CP0 (that not $VP/NP $EntityNP1) (lambda r (lambda n (lambda s (call @filter (var s) (var r) (string !=) (var n)))))) # Vobj-neg + (rule $CP1 (that $VP/NP $NP0) (lambda r (lambda n (lambda s (call @filter (var s) (var r) (string =) (var n)))))) # Vobj + # R3 + (rule $CP00 (that is $RelNP of $EntityNP1) (lambda r (lambda n (lambda s (call @filter (var s) (call @reverse (var r)) (string =) (var n)))))) # Req2 + (rule $CP0 (that is not $RelNP of $EntityNP1) (lambda r (lambda n (lambda s (call @filter (var s) (call @reverse (var r)) (string !=) (var n)))))) # Req2 + (rule $CP1 (that is $RelNP of $NP0) (lambda r (lambda n (lambda s (call @filter (var s) (call @reverse (var r)) (string =) (var n)))))) # Req2 + # R4 + (rule $CP00 (that $EntityNP1 $VP/NP) (lambda n (lambda r (lambda s (call @filter (var s) (call @reverse (var r)) (string =) (var n)))))) # Vsubj + (rule $CP0 (that $EntityNP1 not $VP/NP) (lambda n (lambda r (lambda s (call @filter (var s) (call @reverse (var r)) (string !=) (var n)))))) # Vsubj + (rule $CP1 (that $NP0 $VP/NP) (lambda n (lambda r (lambda s (call @filter (var s) (call @reverse (var r)) (string =) (var n)))))) # Vsubj +) +(when geo880 + (rule $CP2 (that $NP1 $VP/NP) (lambda n (lambda r (lambda s (call @filter (var s) (call @reverse (var r)) (string =) (var n)))))) # Vsubj + (rule $CP2 (that $VP/NP $NP1) (lambda r (lambda n (lambda s (call @filter (var s) (var r) (string =) (var n)))))) # Vobj + (rule $CP2 (that is $RelNP of $NP1) (lambda r (lambda n (lambda s (call @filter (var s) (call @reverse (var r)) (string =) (var n)))))) # Req2 + (rule $CP2 (whose $RelNP is $NP1) (lambda r (lambda n (lambda s (call @filter (var s) (var r) (string =) (var n)))))) # Req +) +(when geo440 + (rule $CP2 (that $VP/NP $NP1) (lambda r (lambda n (lambda s (call @filter (var s) (var r) (string =) (var n)))))) # Vobj + (rule $CP2 (whose $RelNP is $NP1) (lambda r (lambda n (lambda s (call @filter (var s) (var r) (string =) (var n)))))) # Req +) + +(when (and parse regex) + # I use modifier for negation, we did not have this negation in previous grammars + (rule $Modifier ($VP/NP $NP) (JoinFn forward betaReduce)) + (rule $Modifier ($VP/NP1 $NP) (JoinFn forward betaReduce)) + (rule $CP (that $Modifier) (IdentityFn)) + (rule $CP (that not $Modifier) (lambda m (call + (string "~\(") (var m) (string "\)")))) +) +(when (and generate regex) + (rule $Modifier0 ($VP/NP $EntityNP1) (JoinFn forward betaReduce)) + (rule $Modifier1 ($VP/NP $NP0) (JoinFn forward betaReduce)) + (rule $Modifier0 ($VP/NP1 $EntityNP1) (JoinFn forward betaReduce)) + (rule $Modifier1 ($VP/NP1 $NP0) (JoinFn forward betaReduce)) + (rule $CP00 (that $Modifier0) (IdentityFn)) + (rule $CP1 (that $Modifier1) (IdentityFn)) + (rule $CP1 (that not $Modifier0) (lambda m (call + (string "~\(") (var m) (string "\)")))) +) + +############################################################ +# Complementizer phrase (superlatives and comparatives) + +(when (and parse general) + # S0 + (rule $CP (that has the smallest $NumberRelNP) (lambda r (lambda s (call @superlative (var s) (string min) (var r))))) # Smin + (rule $CP (that has the largest $NumberRelNP) (lambda r (lambda s (call @superlative (var s) (string max) (var r))))) # Smax + # S1 + (rule $CP (that has the least number of $RelNP) (lambda r (lambda s (call @countSuperlative (var s) (string min) (var r))))) # Scmin + (rule $CP (that has the most number of $RelNP) (lambda r (lambda s (call @countSuperlative (var s) (string max) (var r))))) # Scmax + # S2 + (rule $CP (that $VP/NP the least number of $NP) (lambda r (lambda s2 (lambda s1 (call @countSuperlative (var s1) (string min) (var r) (var s2)))))) # Scvmin + (rule $CP (that $VP/NP the most number of $NP) (lambda r (lambda s2 (lambda s1 (call @countSuperlative (var s1) (string max) (var r) (var s2)))))) # Scvmax + # S3 + (rule $CP (that is $RelNP of the least number of $NP) (lambda r (lambda np (lambda s (call @countSuperlative (var s) (string min) (call @reverse (var r)) (var np)))))) # Scmin + (rule $CP (that is $RelNP of the most number of $NP) (lambda r (lambda np (lambda s (call @countSuperlative (var s) (string max) (call @reverse (var r)) (var np)))))) # Scmax + # S4 + (rule $CP (that the least number of $NP $VP/NP) (lambda np (lambda r (lambda s (call @countSuperlative (var s) (string min) (call @reverse (var r)) (var np)))))) # Scvmin + (rule $CP (that the most number of $NP $VP/NP) (lambda np (lambda r (lambda s (call @countSuperlative (var s) (string max) (call @reverse (var r)) (var np)))))) # Scvmax + # C1 + (rule $CP (that has $Num $RelNP) (lambda num (lambda r (lambda s (call @countComparative (var s) (var r) (string =) (var num)))))) # Ceq + (rule $CP (that has less than $Num $RelNP) (lambda num (lambda r (lambda s (call @countComparative (var s) (var r) (string <) (var num)))))) # Cl + (rule $CP (that has more than $Num $RelNP) (lambda num (lambda r (lambda s (call @countComparative (var s) (var r) (string >) (var num)))))) # Cg + (rule $CP (that has at most $Num $RelNP) (lambda num (lambda r (lambda s (call @countComparative (var s) (var r) (string <=) (var num)))))) # Cleq + (rule $CP (that has at least $Num $RelNP) (lambda num (lambda r (lambda s (call @countComparative (var s) (var r) (string >=) (var num)))))) # Cgeq + # C2 + (rule $CP (that $VP/NP $Num $NP) (lambda r (lambda num (lambda np (lambda s (call @countComparative (var s) (var r) (string =) (var num) (var np))))))) # Ceq + (rule $CP (that $VP/NP less than $Num $NP) (lambda r (lambda num (lambda np (lambda s (call @countComparative (var s) (var r) (string <) (var num) (var np))))))) # Cl + (rule $CP (that $VP/NP more than $Num $NP) (lambda r (lambda num (lambda np (lambda s (call @countComparative (var s) (var r) (string >) (var num) (var np))))))) # Cg + (rule $CP (that $VP/NP at most $Num $NP) (lambda r (lambda num (lambda np (lambda s (call @countComparative (var s) (var r) (string <=) (var num) (var np))))))) # Cleq + (rule $CP (that $VP/NP at least $Num $NP) (lambda r (lambda num (lambda np (lambda s (call @countComparative (var s) (var r) (string >=) (var num) (var np))))))) # Cgeq + # C3 + (rule $CP (that is $RelNP of $Num $NP) (lambda r (lambda num (lambda np (lambda s (call @countComparative (var s) (call @reverse (var r)) (string =) (var num) (var np))))))) # Ceq + (rule $CP (that is $RelNP of less than $Num $NP) (lambda r (lambda num (lambda np (lambda s (call @countComparative (var s) (call @reverse (var r)) (string <) (var num) (var np))))))) # Cl + (rule $CP (that is $RelNP of more than $Num $NP) (lambda r (lambda num (lambda np (lambda s (call @countComparative (var s) (call @reverse (var r)) (string >) (var num) (var np))))))) # Cg + (rule $CP (that is $RelNP of at most $Num $NP) (lambda r (lambda num (lambda np (lambda s (call @countComparative (var s) (call @reverse (var r)) (string <=) (var num) (var np))))))) # Cleq + (rule $CP (that is $RelNP of at least $Num $NP) (lambda r (lambda num (lambda np (lambda s (call @countComparative (var s) (call @reverse (var r)) (string >=) (var num) (var np))))))) # Cgeq + # C4 + (rule $CP (that $Num $NP $VP/NP) (lambda num (lambda np (lambda r (lambda s (call @countComparative (var s) (call @reverse (var r)) (string =) (var num) (var np))))))) # Ceq + (rule $CP (that less than $Num $NP $VP/NP) (lambda num (lambda np (lambda r (lambda s (call @countComparative (var s) (call @reverse (var r)) (string <) (var num) (var np))))))) # Cl + (rule $CP (that more than $Num $NP $VP/NP) (lambda num (lambda np (lambda r (lambda s (call @countComparative (var s) (call @reverse (var r)) (string >) (var num) (var np))))))) # Cg + (rule $CP (that at most $Num $NP $VP/NP) (lambda num (lambda np (lambda r (lambda s (call @countComparative (var s) (call @reverse (var r)) (string <=) (var num) (var np))))))) # Cleq + (rule $CP (that at least $Num $NP $VP/NP) (lambda num (lambda np (lambda r (lambda s (call @countComparative (var s) (call @reverse (var r)) (string >=) (var num) (var np))))))) # Cgeq +) +(when (and generate general) # $CP => $CP1, $NP => $UnaryNP + # S1 + (rule $CP1 (that has the smallest $NumberRelNP) (lambda r (lambda s (call @superlative (var s) (string min) (var r))))) # Smin + (rule $CP1 (that has the largest $NumberRelNP) (lambda r (lambda s (call @superlative (var s) (string max) (var r))))) # Smax + # S2 + (rule $CP1 (that has the least number of $RelNP) (lambda r (lambda s (call @countSuperlative (var s) (string min) (var r))))) # Scmin + (rule $CP1 (that has the most number of $RelNP) (lambda r (lambda s (call @countSuperlative (var s) (string max) (var r))))) # Scmax + # S3 + (rule $CP1 (that $VP/NP the least number of $UnaryNP) (lambda r (lambda s2 (lambda s1 (call @countSuperlative (var s1) (string min) (var r) (var s2)))))) # Scvmin + (rule $CP1 (that $VP/NP the most number of $UnaryNP) (lambda r (lambda s2 (lambda s1 (call @countSuperlative (var s1) (string max) (var r) (var s2)))))) # Scvmax + # S4 + (rule $CP1 (that is $RelNP of the least number of $UnaryNP) (lambda r (lambda np (lambda s (call @countSuperlative (var s) (string min) (call @reverse (var r)) (var np)))))) # Scmin + (rule $CP1 (that is $RelNP of the most number of $UnaryNP) (lambda r (lambda np (lambda s (call @countSuperlative (var s) (string max) (call @reverse (var r)) (var np)))))) # Scmax + # S5 + (rule $CP1 (that the least number of $UnaryNP $VP/NP) (lambda np (lambda r (lambda s (call @countSuperlative (var s) (string min) (call @reverse (var r)) (var np)))))) # Scvmin + (rule $CP1 (that the most number of $UnaryNP $VP/NP) (lambda np (lambda r (lambda s (call @countSuperlative (var s) (string max) (call @reverse (var r)) (var np)))))) # Scvmax + # C1 + (rule $CP1 (that has $Num $RelNP) (lambda num (lambda r (lambda s (call @countComparative (var s) (var r) (string =) (var num)))))) # Ceq + (rule $CP1 (that has less than $Num $RelNP) (lambda num (lambda r (lambda s (call @countComparative (var s) (var r) (string <) (var num)))))) # Cl + (rule $CP1 (that has more than $Num $RelNP) (lambda num (lambda r (lambda s (call @countComparative (var s) (var r) (string >) (var num)))))) # Cg + (rule $CP1 (that has at most $Num $RelNP) (lambda num (lambda r (lambda s (call @countComparative (var s) (var r) (string <=) (var num)))))) # Cleq + (rule $CP1 (that has at least $Num $RelNP) (lambda num (lambda r (lambda s (call @countComparative (var s) (var r) (string >=) (var num)))))) # Cgeq + # C2 + (rule $CP1 (that $VP/NP $Num $UnaryNP) (lambda r (lambda num (lambda np (lambda s (call @countComparative (var s) (var r) (string =) (var num) (var np))))))) # Ceq + (rule $CP1 (that $VP/NP less than $Num $UnaryNP) (lambda r (lambda num (lambda np (lambda s (call @countComparative (var s) (var r) (string <) (var num) (var np))))))) # Cl + (rule $CP1 (that $VP/NP more than $Num $UnaryNP) (lambda r (lambda num (lambda np (lambda s (call @countComparative (var s) (var r) (string >) (var num) (var np))))))) # Cg + (rule $CP1 (that $VP/NP at most $Num $UnaryNP) (lambda r (lambda num (lambda np (lambda s (call @countComparative (var s) (var r) (string <=) (var num) (var np))))))) # Cleq + (rule $CP1 (that $VP/NP at least $Num $UnaryNP) (lambda r (lambda num (lambda np (lambda s (call @countComparative (var s) (var r) (string >=) (var num) (var np))))))) # Cgeq + # C3 + (rule $CP1 (that is $RelNP of $Num $UnaryNP) (lambda r (lambda num (lambda np (lambda s (call @countComparative (var s) (call @reverse (var r)) (string =) (var num) (var np))))))) # Ceq + (rule $CP1 (that is $RelNP of less than $Num $UnaryNP) (lambda r (lambda num (lambda np (lambda s (call @countComparative (var s) (call @reverse (var r)) (string <) (var num) (var np))))))) # Cl + (rule $CP1 (that is $RelNP of more than $Num $UnaryNP) (lambda r (lambda num (lambda np (lambda s (call @countComparative (var s) (call @reverse (var r)) (string >) (var num) (var np))))))) # Cg + (rule $CP1 (that is $RelNP of at most $Num $UnaryNP) (lambda r (lambda num (lambda np (lambda s (call @countComparative (var s) (call @reverse (var r)) (string <=) (var num) (var np))))))) # Cleq + (rule $CP1 (that is $RelNP of at least $Num $UnaryNP) (lambda r (lambda num (lambda np (lambda s (call @countComparative (var s) (call @reverse (var r)) (string >=) (var num) (var np))))))) # Cgeq + # C4 + (rule $CP1 (that $Num $UnaryNP $VP/NP) (lambda num (lambda np (lambda r (lambda s (call @countComparative (var s) (call @reverse (var r)) (string =) (var num) (var np))))))) # Ceq + (rule $CP1 (that less than $Num $UnaryNP $VP/NP) (lambda num (lambda np (lambda r (lambda s (call @countComparative (var s) (call @reverse (var r)) (string <) (var num) (var np))))))) # Cl + (rule $CP1 (that more than $Num $UnaryNP $VP/NP) (lambda num (lambda np (lambda r (lambda s (call @countComparative (var s) (call @reverse (var r)) (string >) (var num) (var np))))))) # Cg + (rule $CP1 (that at most $Num $UnaryNP $VP/NP) (lambda num (lambda np (lambda r (lambda s (call @countComparative (var s) (call @reverse (var r)) (string <=) (var num) (var np))))))) # Cleq + (rule $CP1 (that at least $Num $UnaryNP $VP/NP) (lambda num (lambda np (lambda r (lambda s (call @countComparative (var s) (call @reverse (var r)) (string >=) (var num) (var np))))))) # Cgeq +) + +# Regex +(when (and parse regex) + (rule $CP (that $VP/NP1 at least $Num $NP) (lambda v (lambda num (lambda n (call + (string "\(") ((var v) (var n)) (string "\)") (string "{") (var num) (string ",}")))))) + (rule $CP (that $VP/NP1 at most $Num $NP) (lambda v (lambda num (lambda n (call + (string "\(") ((var v) (var n)) (string "\)") (string "{0,") (var num) (string "}")))))) + (rule $CP (that $VP/NP1 $Num $NP) (lambda v (lambda num (lambda n (call + (string "\(") ((var v) (var n)) (string "\)") (string "{") (var num) (string "}")))))) + (rule $CP (that $VP/NP at least $Num $NP) (lambda v (lambda num (lambda n ((var v) (call + (string "\(") (var n) (string "\)") (string "{") (var num) (string ",}"))))))) + (rule $CP (that $VP/NP at most $Num $NP) (lambda v (lambda num (lambda n ((var v) (call + (string "\(") (var n) (string "\)") (string "{0,") (var num) (string "}"))))))) + (rule $CP (that $VP/NP $Num $NP) (lambda v (lambda num (lambda n ((var v) (call + (string "\(") (var n) (string "\)") (string "{") (var num) (string "}"))))))) +) +(when (and generate regex) + (rule $CP1 (that $VP/NP1 at least $Num $UnaryNP) (lambda v (lambda num (lambda n (call + (string "\(") ((var v) (var n)) (string "\)") (string "{") (var num) (string ",}")))))) + (rule $CP1 (that $VP/NP1 at most $Num $UnaryNP) (lambda v (lambda num (lambda n (call + (string "\(") ((var v) (var n)) (string "\)") (string "{0,") (var num) (string "}")))))) + (rule $CP1 (that $VP/NP1 $Num $UnaryNP) (lambda v (lambda num (lambda n (call + (string "\(") ((var v) (var n)) (string "\)") (string "{") (var num) (string "}")))))) + (rule $CP1 (that $VP/NP at least $Num $UnaryNP) (lambda v (lambda num (lambda n ((var v) (call + (string "\(") (var n) (string "\)") (string "{") (var num) (string ",}"))))))) + (rule $CP1 (that $VP/NP at most $Num $UnaryNP) (lambda v (lambda num (lambda n ((var v) (call + (string "\(") (var n) (string "\)") (string "{0,") (var num) (string "}"))))))) + (rule $CP1 (that $VP/NP $Num $UnaryNP) (lambda v (lambda num (lambda n ((var v) (call + (string "\(") (var n) (string "\)") (string "{") (var num) (string "}"))))))) +) + +############################################################ +# Construct NPs from CPs + +(when (and parse general) + # G3 + (rule $NPCP ($NP $CP) (JoinFn backward betaReduce)) + (rule $NPCP ($NPCP and $CP) (JoinFn backward betaReduce)) + (rule $NP ($NPCP) (IdentityFn)) +) +(when (and generate general) + # G3 + (rule $NP0 ($UnaryNP $CP00) (JoinFn backward betaReduce)) + (rule $NP1 ($UnaryNP $CP0) (JoinFn backward betaReduce)) + (rule $NP1 ($UnaryNP $CP1) (JoinFn backward betaReduce)) + (rule $NPCP1 ($UnaryNP $CP00) (JoinFn backward betaReduce)) + (rule $NP1 ($NPCP1 and $CP00) (JoinFn backward betaReduce)) +) +(when geo880 + (rule $NP2 ($NP0 $CP00) (JoinFn backward betaReduce)) + (rule $NP2 ($NP0 $CP0) (JoinFn backward betaReduce)) + (rule $NP2 ($NP0 $CP1) (JoinFn backward betaReduce)) + (rule $NP2 ($UnaryNP $CP2) (JoinFn backward betaReduce)) +) +(when geo440 + (rule $NP2 ($NP0 $CP00) (JoinFn backward betaReduce)) + (rule $NP2 ($NP0 $CP0) (JoinFn backward betaReduce)) + (rule $NP2 ($UnaryNP $CP2) (JoinFn backward betaReduce)) +) + +# Regex +(when (and parse regex) + (rule $CP ($CP and $CP) (lambda c1 (lambda c2 (call + (string "\(\(") (var c1) (string "\)&\(") (var c2) (string "\)\)"))))) + (rule $NP ($NP $CP) (JoinFn betaReduce forward)) +) +(when (and generate regex) + (rule $CP1 ($CP00 and $CP00) (lambda c1 (lambda c2 (call + (string "\(\(") (var c1) (string "\)&\(") (var c2) (string "\)\)"))))) + (rule $NP1 ($UnaryNP $CP0) (JoinFn betaReduce forward)) + (rule $NP1 ($UnaryNP $CP1) (JoinFn betaReduce forward)) +) + +############################################################ +# Transformations + +(when (and parse general) + # T1 + (rule $NP ($RelNP of $NP) (lambda r (lambda s (call @getProperty (var s) (var r))))) # Tr + # T3 + (rule $NP ($NP or $NP) (lambda n1 (lambda n2 (call @concat (var n1) (var n2))))) # Tdisj +) +(when (and generate general) + # T1 + (rule $NP0 ($RelNP of $EntityNP1) (lambda r (lambda s (call @getProperty (var s) (var r))))) # Tr + # T3 + (rule $NP0 ($EntityNP1 or $EntityNP2) (lambda n1 (lambda n2 (call @concat (var n1) (var n2))))) # Tdisj +) +(when (or geo880 geo440) + (rule $NP2 ($RelNP of $NP0) (lambda r (lambda s (call @getProperty (var s) (var r))))) # Tr + (rule $NP2 ($RelNP of $NP1) (lambda r (lambda s (call @getProperty (var s) (var r))))) # Tr +) + +(when (and parse regex) + (rule $NP ($NP or $NP) (lambda n1 (lambda n2 (call + (string "\(") (var n1) (string "|") (var n2) (string "\)"))))) +) +(when (and generate regex) + # TODO - do we want "location of meeting"? I think not, probably only "area of california"?? + (rule $NP0 ($EntityNP1 or $EntityNP2) (lambda n1 (lambda n2 (call + (string "\(") (var n1) (string "|") (var n2) (string "\)"))))) +) + +############################################################ +# Transformations: events + +(when parse + # T2 + (rule $EventNP ($Rel0NP $NP) (lambda r0 (lambda e (call @getProperty (var e) (call @reverse (var r0)))))) # student John + (rule $EventNPCP ($EventNP $CP) (JoinFn backward betaReduce)) # ... whose field of study is history + (rule $EventNPCP ($EventNPCP and $CP) (JoinFn backward betaReduce)) + (rule $EventNP ($EventNPCP) (IdentityFn)) + (rule $NP ($RelNP of $EventNP) (lambda r (lambda x (call @getProperty (var x) (var r))))) # university of ... + # T2' + (rule $NP ($Rel0NP $CP) (lambda r (lambda cp (call @getProperty ((var cp) (call @domain (var r))) (var r))))) # student whose field of study is history +) +(when generate + # T2 + (rule $EventNP0 ($Rel0NP $EntityNP1) (lambda r0 (lambda e (call @getProperty (var e) (call @reverse (var r0)))))) # student John + (rule $EventNP1 ($EventNP0 $CP00) (JoinFn backward betaReduce)) # ... whose field of study is history + (rule $NP0 ($RelNP of $EventNP0) (lambda r (lambda x (call @getProperty (var x) (var r))))) # university of ... + (rule $NP1 ($RelNP of $EventNP1) (lambda r (lambda x (call @getProperty (var x) (var r))))) # university of ... + # T2' + (rule $NP0 ($Rel0NP $CP0) (lambda r (lambda cp (call @getProperty ((var cp) (call @domain (var r))) (var r))))) # student whose field of study is history + (rule $NP1 ($Rel0NP $CP1) (lambda r (lambda cp (call @getProperty ((var cp) (call @domain (var r))) (var r))))) # student whose field of study is history +) + +############################################################ +# Transformations: binary operators + +(when parse + # T4 + (rule $BinaryOpRight ($BinaryOp $NP) (JoinFn betaReduce forward)) + (rule $NP ($NP $BinaryOpRight) (JoinFn betaReduce backward)) +) +(when generate + # T4 + (rule $BinaryOpRight ($BinaryOp $EntityNP2) (JoinFn betaReduce forward)) + (rule $NP0 ($EntityNP1 $BinaryOpRight) (JoinFn betaReduce backward)) +) + +############################################################ +# Aggregation + +(when (and parse general) + # A1 + (rule $NP (number of $NP) (lambda x (call .size (var x)))) # An + # A2 + (rule $NP (total $RelNP of $NP) (lambda r (lambda n (call @aggregate (string sum) (call @getProperty (var n) (var r)))))) # At + # A3 + (rule $NP (average $RelNP of $NP) (lambda r (lambda n (call @aggregate (string avg) (call @getProperty (var n) (var r)))))) # Am + # TODO: add 'all' before $NP's +) +(when (and generate general) + # A1 + (rule $NP1 (number of $UnaryNP) (lambda x (call .size (var x)))) # An + # A2 + (rule $NP1 (total $RelNP of $UnaryNP) (lambda r (lambda n (call @aggregate (string sum) (call @getProperty (var n) (var r)))))) # At + # A3 + (rule $NP1 (average $RelNP of $UnaryNP) (lambda r (lambda n (call @aggregate (string avg) (call @getProperty (var n) (var r)))))) # Am +) +# Supporting more for geo880 +(when (or geo880 geo440) + # A1 + (rule $NP2 (number of $NP0) (lambda x (call .size (var x)))) # An + (rule $NP2 (number of $NP1) (lambda x (call .size (var x)))) # An + # A2 + (rule $NP2 (total $RelNP of $NP0) (lambda r (lambda n (call @aggregate (string sum) (call @getProperty (var n) (var r)))))) # At + (rule $NP2 (total $RelNP of $NP1) (lambda r (lambda n (call @aggregate (string sum) (call @getProperty (var n) (var r)))))) # At + # A3 + (rule $NP2 (average $RelNP of $NP0) (lambda r (lambda n (call @aggregate (string avg) (call @getProperty (var n) (var r)))))) # Am + (rule $NP2 (average $RelNP of $NP1) (lambda r (lambda n (call @aggregate (string avg) (call @getProperty (var n) (var r)))))) # Am +) + +############################################################ +# Top-level + +(when (and parse general) + (rule $ROOT ($NP) (lambda x (call @listValue (var x)))) +) +(when (and generate general) + (rule $ROOT ($NP0) (lambda x (call @listValue (var x)))) + (rule $ROOT ($NP1) (lambda x (call @listValue (var x)))) +) +(when (or geo880 geo440) + (rule $ROOT ($NP2) (lambda x (call @listValue (var x)))) + (rule $ROOT ($UnaryNP) (lambda x (call @listValue (var x)))) +) + +(when (and parse regex) + (rule $ROOT ($NP) (IdentityFn)) +) +(when (and generate regex) + (rule $ROOT ($NP0) (IdentityFn)) + (rule $ROOT ($NP1) (IdentityFn)) +) diff --git a/overnight/geo880-unittest.examples b/overnight/geo880-unittest.examples new file mode 100644 index 0000000..55b8e03 --- /dev/null +++ b/overnight/geo880-unittest.examples @@ -0,0 +1,49 @@ +# Unittest + +# R1 +(example (utterance "state whose capital is sacramento")) +(example (utterance "state whose capital is not sacramento")) +(example (utterance "state whose capital is capital of california")) +(example (utterance "mountain whose elevation is smaller than elevation of mount whitney")) +(example (utterance "mountain whose elevation is larger than elevation of mount whitney")) +(example (utterance "mountain whose elevation is at most elevation of mount whitney")) +(example (utterance "mountain whose elevation is at least elevation of mount whitney")) +(example (utterance "city that is in california and that is capital of california")) + +# R2 +(example (utterance "city that is in california")) +(example (utterance "city that not is in california")) +# R3 +# R4 +(example (utterance "state that california borders")) +(example (utterance "state that california not borders")) +(example (utterance "river that traverses california")) +# R5 +(example (utterance "city that is major")) + +# S1 +(example (utterance "state that has the largest area")) +(example (utterance "state that has the smallest area")) +# S2 +# S3 +(example (utterance "state that borders the most number of state")) +(example (utterance "state that borders the least number of state")) + +# C1 +(example (utterance "river that is in two state")) +(example (utterance "river that is in more than two state")) +(example (utterance "river that is in less than two state")) +(example (utterance "river that is in at most two state")) +(example (utterance "river that is in at least two state")) + +# T1 +(example (utterance "population of sacramento")) +# T2: TODO +# T3 +(example (utterance "california or texas")) +# T4 +# A1 +(example (utterance "number of lake")) +# A2 +(example (utterance "total elevation of mountain")) +(example (utterance "average elevation of mountain")) diff --git a/overnight/geo880.grammar b/overnight/geo880.grammar new file mode 100644 index 0000000..330fd28 --- /dev/null +++ b/overnight/geo880.grammar @@ -0,0 +1,69 @@ +(include general.grammar) + +# Types +(rule $TypeNP (city) (ConstantFn fb:en.city)) +(rule $EntityNP1 (sacramento) (ConstantFn fb:en.city.sacramento_ca)) +(rule $EntityNP2 (austin) (ConstantFn fb:en.city.austin_tx)) + +(rule $TypeNP (state) (ConstantFn fb:en.state)) +(rule $EntityNP1 (california) (ConstantFn fb:en.state.california)) +(rule $EntityNP2 (texas) (ConstantFn fb:en.state.texas)) + +(rule $TypeNP (river) (ConstantFn fb:en.river)) +(rule $EntityNP1 (colorado river) (ConstantFn fb:en.river.colorado)) +(rule $EntityNP2 (red river) (ConstantFn fb:en.river.red)) + +(rule $TypeNP (lake) (ConstantFn fb:en.lake)) +(rule $EntityNP1 (lake tahoe) (ConstantFn fb:en.lake.tahoe)) +(rule $EntityNP2 (lake huron) (ConstantFn fb:en.lake.huron)) + +(rule $TypeNP (mountain) (ConstantFn fb:en.mountain)) +(rule $EntityNP1 (mount whitney) (ConstantFn fb:en.mountain.whitney)) +(rule $EntityNP2 (mount rainier) (ConstantFn fb:en.mountain.rainier)) + +(rule $TypeNP (place) (ConstantFn fb:en.place)) +(rule $EntityNP1 (death valley) (ConstantFn fb:en.place.death_valley)) +(rule $EntityNP2 (pacific ocean) (ConstantFn fb:en.place.pacific_ocean)) + +# Unaries +(rule $VP (is major) (ConstantFn (string major_city))) +(rule $VP (is major) (ConstantFn (string major_river))) +(rule $VP (is major) (ConstantFn (string major_lake))) +(rule $VP (is a capital) (ConstantFn (string capital_city))) + +# Properties +(rule $VP/NP (is contained by) (ConstantFn (string loc_city_state))) +(rule $VP/NP (is contained by) (ConstantFn (string loc_lake_state))) +(rule $VP/NP (is contained by) (ConstantFn (string loc_mountain_state))) +(rule $VP/NP (is contained by) (ConstantFn (string loc_state_country))) +(rule $VP/NP (is contained by) (ConstantFn (string loc_place_state))) + +(rule $VP/NP (traverses) (ConstantFn (string traverse_river_state))) +(rule $VP/NP (borders) (ConstantFn (string next_to_state_state))) + +(rule $RelNP (capital) (ConstantFn (string capital_state_city))) +(rule $RelNP (area) (ConstantFn (string area_state_length^2))) +(rule $RelNP (area) (ConstantFn (string area_city_length^2))) +(rule $RelNP (area) (ConstantFn (string area_country_length^2))) +(rule $RelNP (area) (ConstantFn (string area_lake_length^2))) +(rule $RelNP (length) (ConstantFn (string len_river_length))) +(rule $RelNP (elevation) (ConstantFn (string elevation_mountain_length))) +(rule $RelNP (elevation) (ConstantFn (string elevation_place_length))) +(rule $RelNP (population) (ConstantFn (string population_city_count))) +(rule $RelNP (population) (ConstantFn (string population_state_count))) +(rule $RelNP (population) (ConstantFn (string population_country_count))) +(rule $RelNP (density) (ConstantFn (string density_state_count))) +(rule $RelNP (density) (ConstantFn (string density_city_count))) +(rule $RelNP (density) (ConstantFn (string density_country_count))) + +# simple lexicon +(rule $EntityNP1 ($PHRASE) (SimpleLexiconFn)) +(rule $EntityNP2 ($PHRASE) (SimpleLexiconFn)) + +# TODO: named is a funny relation: do we really want to include it? +# I estimate less than 2% requires the 'name' property, so let's punt +#(rule $EntityNP1 (sacramento) (ConstantFn (string Sacramento))) +#(rule $EntityNP1 (california) (ConstantFn (string California))) +#(rule $EntityNP1 (carson river) (ConstantFn (string Carson River))) +#(rule $EntityNP1 (lake austin) (ConstantFn (string Lake Austin))) +#(rule $EntityNP1 (mount whitney) (ConstantFn (string Mount Whitney))) diff --git a/overnight/housing-unittest.examples b/overnight/housing-unittest.examples new file mode 100644 index 0000000..d981464 --- /dev/null +++ b/overnight/housing-unittest.examples @@ -0,0 +1,12 @@ +# R1 +(example (utterance "housing unit whose neighborhood is chelsea")) +(example (utterance "housing unit whose housing type is apartment")) + +# R2 +(example (utterance "housing unit that allows cats")) +(example (utterance "housing unit that allows dogs")) + +# R3 +(example (utterance "housing unit whose size is at least 800 square feet")) + +(example (utterance "number of housing type")) diff --git a/overnight/housing.grammar b/overnight/housing.grammar new file mode 100644 index 0000000..238ac35 --- /dev/null +++ b/overnight/housing.grammar @@ -0,0 +1,42 @@ +# Agile grammar for housing (based on craigslist) +# - What is the price of house? +# - How many apartments are there? +# - Create house with price 225000 +# - Move price of house from 225000 to 250000 +# - Is there any apartment whose rent is less than 2000 + +(include general.grammar) + +# Types +(rule $TypeNP (housing unit) (ConstantFn en.housing_unit)) +(rule $EntityNP1 (123 sesame street) (ConstantFn en.housing_unit.123_sesame_street)) +(rule $EntityNP2 (900 mission ave) (ConstantFn en.housing_unit.900_mission_ave)) + +# Properties +(rule $RelNP (monthly rent) (ConstantFn (string rent))) +(rule $EntityNP1 (1500 dollars) (ConstantFn (number 1500 en.dollar))) +(rule $EntityNP2 (2000 dollars) (ConstantFn (number 2000 en.dollar))) + +(rule $RelNP (size) (ConstantFn (string size))) +(rule $EntityNP1 (800 square feet) (ConstantFn (number 800 en.square_feet))) +(rule $EntityNP2 (1000 square feet) (ConstantFn (number 1000 en.square_feet))) + +(rule $RelNP (posting date) (ConstantFn (string posting_date))) +(rule $EntityNP1 (jan 2) (ConstantFn (date 2015 1 2))) +(rule $EntityNP2 (feb 3) (ConstantFn (date 2015 2 3))) + +(rule $RelNP (neighborhood) (ConstantFn (string neighborhood))) +(rule $TypeNP (neighborhood) (ConstantFn en.neighborhood)) +(rule $EntityNP1 (midtown west) (ConstantFn en.neighborhood.midtown_west)) +(rule $EntityNP2 (chelsea) (ConstantFn en.neighborhood.chelsea)) + +(rule $RelNP (housing type) (ConstantFn (string housing_type))) +(rule $TypeNP (housing type) (ConstantFn en.housing)) +(rule $EntityNP1 (apartment) (ConstantFn en.housing.apartment)) +(rule $EntityNP2 (condo) (ConstantFn en.housing.condo)) + +# Unaries +(rule $VP (allows cats) (ConstantFn (string allows_cats))) +(rule $VP (allows dogs) (ConstantFn (string allows_dogs))) +(rule $VP (has a private bath) (ConstantFn (string has_private_bath))) +(rule $VP (has a private room) (ConstantFn (string has_private_room))) diff --git a/overnight/null.examples b/overnight/null.examples new file mode 100644 index 0000000..b53773a --- /dev/null +++ b/overnight/null.examples @@ -0,0 +1,2 @@ +# Used to trigger generation (without looking at utterance). +(example (utterance null)) diff --git a/overnight/publications.grammar b/overnight/publications.grammar new file mode 100644 index 0000000..90b782d --- /dev/null +++ b/overnight/publications.grammar @@ -0,0 +1,27 @@ +# Agile grammar for publications + +(include general.grammar) + +# Types +(rule $TypeNP (article) (ConstantFn en.article)) +(rule $EntityNP1 (multivariate data analysis) (ConstantFn en.article.multivariate_data_analysis)) + +# Properties +(rule $RelNP (author) (ConstantFn (string author))) +(rule $TypeNP (person) (ConstantFn en.person)) +(rule $EntityNP1 (efron) (ConstantFn en.person.efron)) +(rule $EntityNP2 (lakoff) (ConstantFn en.person.lakoff)) + +(rule $RelNP (venue) (ConstantFn (string venue))) +(rule $TypeNP (venue) (ConstantFn en.venue)) +(rule $EntityNP1 (annals of statistics) (ConstantFn en.venue.annals_of_statistics)) +(rule $EntityNP2 (computational linguistics) (ConstantFn en.venue.computational_linguistics)) + +(rule $RelNP (publication date) (ConstantFn (string publication_date))) +(rule $EntityNP1 (2004) (ConstantFn (date 2004 -1 -1))) +(rule $EntityNP2 (2010) (ConstantFn (date 2010 -1 -1))) + +(rule $VP/NP (cites) (ConstantFn (string cites))) + +# Unaries +(rule $VP (won an award) (ConstantFn (string won_award))) diff --git a/overnight/recipes.grammar b/overnight/recipes.grammar new file mode 100644 index 0000000..94a49c3 --- /dev/null +++ b/overnight/recipes.grammar @@ -0,0 +1,33 @@ +# Agile grammar for recipes (based on allrecipes.com) + +(include general.grammar) + +# Types +(rule $TypeNP (recipe) (ConstantFn en.recipe)) +(rule $EntityNP1 (rice pudding) (ConstantFn en.recipe.rice_pudding)) +(rule $EntityNP2 (quiche) (ConstantFn en.recipe.quiche)) + +# Properties +(rule $RelNP (preparation time) (ConstantFn (string preparation_time))) +(rule $RelNP (cooking time) (ConstantFn (string cooking_time))) +(rule $Value (10) (ConstantFn (number 10 en.minute))) +(rule $Value (30) (ConstantFn (number 300 en.minute))) + +(rule $RelNP (cuisine) (ConstantFn (string cuisine))) +(rule $TypeNP (cuisine) (ConstantFn fb:en.cuisine)) +(rule $EntityNP1 (chinese) (ConstantFn (string chinese))) +(rule $EntityNP2 (french) (ConstantFn (string french))) + +(rule $VP/NP (requires) (ConstantFn (string requires))) +(rule $TypeNP (ingredient) (ConstantFn en.ingredient)) +(rule $EntityNP1 (milk) (ConstantFn en.ingredient.milk)) +(rule $EntityNP2 (spinach) (ConstantFn en.ingredient.spinach)) + +(rule $VP/NP (is for) (ConstantFn (string meal))) +(rule $TypeNP (meal) (ConstantFn en.meal)) +(rule $EntityNP1 (lunch) (ConstantFn en.meal.lunch)) +(rule $EntityNP2 (dinner) (ConstantFn en.meal.dinner)) + +(rule $RelNP (posting date) (ConstantFn (string posting_date))) +(rule $EntityNP1 (2004) (ConstantFn (date 2004 -1 -1))) +(rule $EntityNP2 (2010) (ConstantFn (date 2010 -1 -1))) diff --git a/overnight/restaurants-unittest.examples b/overnight/restaurants-unittest.examples new file mode 100644 index 0000000..bcf3670 --- /dev/null +++ b/overnight/restaurants-unittest.examples @@ -0,0 +1,19 @@ +# Unittest + +# Type +(example (utterance "restaurant")) + +# R1 +(example (utterance "restaurant that takes reservations")) +(example (utterance "restaurant that has outdoor seating")) + +# R2 +(example (utterance "restaurant whose cuisine is chinese")) +(example (utterance "restaurant whose neighborhood is tribeca")) + +# R3 +(example (utterance "restaurant whose price rating is 2 dollar signs")) +(example (utterance "restaurant whose star rating is at least 3 stars")) + +# R4 +(example (utterance "restaurant that serves lunch")) diff --git a/overnight/restaurants.grammar b/overnight/restaurants.grammar new file mode 100644 index 0000000..f4e1843 --- /dev/null +++ b/overnight/restaurants.grammar @@ -0,0 +1,46 @@ +# Agile grammar for restaurants (based on yelp) + +(include general.grammar) + +# Types +(rule $TypeNP (restaurant) (ConstantFn en.restaurant)) +(rule $EntityNP1 (thai cafe) (ConstantFn en.restaurant.thai_cafe)) +(rule $EntityNP2 (pizzeria juno) (ConstantFn en.restaurant.pizzeria_juno)) + +# Properties +(rule $RelNP (star rating) (ConstantFn (string star_rating))) +(rule $EntityNP1 (3 stars) (ConstantFn (number 3 en.star))) +(rule $EntityNP2 (5 stars) (ConstantFn (number 5 en.star))) + +(rule $RelNP (price rating) (ConstantFn (string price_rating))) +(rule $EntityNP1 (2 dollar signs) (ConstantFn (number 2 en.dollar_sign))) +(rule $EntityNP2 (3 dollar signs) (ConstantFn (number 3 en.dollar_sign))) + +(rule $RelNP (number of reviews) (ConstantFn (string reviews))) +(rule $EntityNP1 (30 reviews) (ConstantFn (number 30 en.review))) +(rule $EntityNP2 (40 reviews) (ConstantFn (number 40 en.review))) + +(rule $RelNP (neighborhood) (ConstantFn (string neighborhood))) +(rule $TypeNP (neighborhood) (ConstantFn en.neighborhood)) +(rule $EntityNP1 (midtown west) (ConstantFn en.neighborhood.midtown_west)) +(rule $EntityNP2 (chelsea) (ConstantFn en.neighborhood.chelsea)) + +(rule $RelNP (cuisine) (ConstantFn (string cuisine))) +(rule $TypeNP (cuisine) (ConstantFn en.cuisine)) +(rule $EntityNP1 (thai) (ConstantFn en.cuisine.thai)) +(rule $EntityNP2 (italian) (ConstantFn en.cuisine.italian)) + +(rule $VP/NP (serves) (ConstantFn (string meals))) +(rule $TypeNP (meal) (ConstantFn en.food)) +(rule $EntityNP1 (lunch) (ConstantFn en.food.lunch)) +(rule $EntityNP2 (dinner) (ConstantFn en.food.dinner)) + +# Unaries +(rule $VP (takes reservations) (ConstantFn (string reserve))) +(rule $VP (takes credit cards) (ConstantFn (string credit))) +(rule $VP (has outdoor seating) (ConstantFn (string outdoor))) +(rule $VP (has take-out) (ConstantFn (string takeout))) +(rule $VP (has delivery) (ConstantFn (string delivery))) +(rule $VP (has waiter service) (ConstantFn (string waiter))) +(rule $VP (is good for kids) (ConstantFn (string kids))) +(rule $VP (is good for groups) (ConstantFn (string groups))) diff --git a/overnight/socialnetwork-unittest.examples b/overnight/socialnetwork-unittest.examples new file mode 100644 index 0000000..cfbead6 --- /dev/null +++ b/overnight/socialnetwork-unittest.examples @@ -0,0 +1,59 @@ +# Unittest + +# G1 +(example (utterance "google")) +# G2 +(example (utterance "gender")) +# G3 +(example (utterance "person whose gender is male and that is friends with alice")) + +# R1 +(example (utterance "person whose gender is male")) +(example (utterance "person whose gender is not male")) +(example (utterance "person whose birthdate is smaller than 2010")) +(example (utterance "person whose birthdate is larger than 2010")) +(example (utterance "person whose birthdate is at most 2010")) +(example (utterance "person whose birthdate is at least 2010")) +(example (utterance "person whose height is at least 200 cm")) +# R2 +(example (utterance "person that is friends with alice")) +(example (utterance "person that not is friends with alice")) +# R3 +(example (utterance "city that is birthplace of alice")) +(example (utterance "city that is not birthplace of alice")) +# R4 +(example (utterance "person that is friends with alice")) +(example (utterance "person that not is friends with alice")) +# R5 +(example (utterance "person that is logged in")) + +# S1 +(example (utterance "person that has the smallest birthdate")) +(example (utterance "person that has the largest birthdate")) +# S2 +(example (utterance "person that has the most birthplace")) +(example (utterance "person that has the least birthplace")) +# S3 +(example (utterance "person that is friends with the most person")) +(example (utterance "person that is friends with the least person")) + +# C1 +(example (utterance "person that is friends with 1 person")) +(example (utterance "person that is friends with less than 1 person")) +(example (utterance "person that is friends with more than 1 person")) +(example (utterance "person that is friends with at most 1 person")) +(example (utterance "person that is friends with at least 1 person")) + +# T1 +(example (utterance "birthdate of alice")) +# T2 +(example (utterance "employer of employee bob")) +(example (utterance "employer of employee bob whose start date is 2004")) +# T3 +(example (utterance "alice or bob")) +# T4 (none) + +# A1 +(example (utterance "number of person")) +# A2 +(example (utterance "total height of person")) diff --git a/overnight/socialnetwork.grammar b/overnight/socialnetwork.grammar new file mode 100644 index 0000000..460906f --- /dev/null +++ b/overnight/socialnetwork.grammar @@ -0,0 +1,72 @@ +# Agile grammar for social network (think Facebook Graph Search) + +(include general.grammar) + +# Types +(rule $TypeNP (person) (ConstantFn en.person)) +(rule $EntityNP1 (alice) (ConstantFn en.person.alice)) +(rule $EntityNP2 (bob) (ConstantFn en.person.bob)) + +(rule $VP/NP (is friends with) (ConstantFn (string friend))) + +# Properties +(rule $RelNP (birthdate) (ConstantFn (string birthdate))) +(rule $EntityNP1 (2004) (ConstantFn (date 2004 -1 -1))) +(rule $EntityNP2 (2010) (ConstantFn (date 2010 -1 -1))) + +(rule $RelNP (gender) (ConstantFn (string gender))) +(rule $TypeNP (gender) (ConstantFn en.gender)) +(rule $EntityNP1 (male) (ConstantFn en.gender.male)) +(rule $EntityNP2 (female) (ConstantFn en.gender.female)) + +(rule $RelNP (relationship status) (ConstantFn (string relationship_status))) +(rule $TypeNP (relationship status) (ConstantFn en.relationship_status)) +(rule $EntityNP1 (single) (ConstantFn en.relationship_status.single)) +(rule $EntityNP2 (married) (ConstantFn en.relationship_status.married)) + +(rule $RelNP (birthplace) (ConstantFn (string birthplace))) +(rule $TypeNP (city) (ConstantFn en.city)) +(rule $EntityNP1 (new york) (ConstantFn en.city.new_york)) +(rule $EntityNP2 (beijing) (ConstantFn en.city.bejing)) + +(rule $RelNP (height) (ConstantFn (string height))) +(rule $EntityNP1 (180 cm) (ConstantFn (number 180 en.cm))) +(rule $EntityNP2 (200 cm) (ConstantFn (number 200 en.cm))) + +# Education +#(rule $EventNP (education) (ConstantFn (call @getProperty (call @singleton en.education) (string !type)))) # for debugging + +(rule $Rel0NP (student) (ConstantFn (string student))) + +(rule $RelNP (university) (ConstantFn (string university))) +(rule $TypeNP (university) (ConstantFn en.university)) +(rule $EntityNP1 (brown university) (ConstantFn en.university.brown)) +(rule $EntityNP2 (ucla) (ConstantFn en.university.ucla)) + +(rule $RelNP (field of study) (ConstantFn (string field_of_study))) +(rule $TypeNP (field) (ConstantFn en.field)) +(rule $EntityNP1 (computer science) (ConstantFn en.field.computer_science)) +(rule $EntityNP2 (history) (ConstantFn en.field.history)) + +(rule $RelNP (start date) (ConstantFn (string education_start_date))) +(rule $RelNP (end date) (ConstantFn (string education_end_date))) + +# Employment + +(rule $Rel0NP (employee) (ConstantFn (string employee))) + +(rule $RelNP (employer) (ConstantFn (string employer))) +(rule $TypeNP (company) (ConstantFn en.company)) +(rule $EntityNP1 (mckinsey) (ConstantFn en.company.mckinsey)) +(rule $EntityNP2 (google) (ConstantFn en.company.google)) + +(rule $RelNP (job title) (ConstantFn (string job_title))) +(rule $TypeNP (job title) (ConstantFn en.job_title)) +(rule $EntityNP1 (software engineer) (ConstantFn en.job_title.software_engineer)) +(rule $EntityNP2 (program manager) (ConstantFn en.job_title.program_manager)) + +(rule $RelNP (start date) (ConstantFn (string employment_start_date))) +(rule $RelNP (end date) (ConstantFn (string employment_end_date))) + +# Unaries +(rule $VP (is logged in) (ConstantFn (string logged_in))) diff --git a/overnight/templates.grammar b/overnight/templates.grammar new file mode 100644 index 0000000..c82ea40 --- /dev/null +++ b/overnight/templates.grammar @@ -0,0 +1,68 @@ +############################################################ +# Domain general +# Keep track of coarse types: +# - Entity: "day 1" +# - Value: "3pm" +# - EntitySet: "meeting" +# - ValueSet: "start time of meeting" + +(def @allEntities edu.stanford.nlp.sempre.SimpleWorld.allEntities) +(def @filter edu.stanford.nlp.sempre.SimpleWorld.filter) +(def @merge edu.stanford.nlp.sempre.SimpleWorld.merge) +(def @getProperty edu.stanford.nlp.sempre.SimpleWorld.getProperty) +(def @superlative edu.stanford.nlp.sempre.SimpleWorld.superlative) +(def @sum edu.stanford.nlp.sempre.SimpleWorld.sum) +(def @list edu.stanford.nlp.sempre.SimpleWorld.list) +(def @act edu.stanford.nlp.sempre.SimpleWorld.act) + +# Operations +(def @create edu.stanford.nlp.sempre.SimpleWorld.create) +(def @remove edu.stanford.nlp.sempre.SimpleWorld.remove) +(def @change edu.stanford.nlp.sempre.SimpleWorld.change) + +# General values +(rule $Value ($PHRASE) (NumberFn) (anchored 1)) +(rule $Value ($PHRASE) (DateFn) (anchored 1)) + +(rule $TypeSet ($Type) (lambda t (call @filter (call @allEntities) (string type) (string =) (var t)))) + +# 0. Show me all meetings +(rule $Command (show me all $TypeSet) (IdentityFn)) + +# 1. Get property: "what is the location of meeting" +(rule $Command (what is the $EntityProperty of the $TypeSet) (lambda p (lambda s (call @getProperty (var s) (var p))))) +(rule $Command (what is the $ValueProperty of the $TypeSet) (lambda p (lambda s (call @getProperty (var s) (var p))))) +# what is the block left of block whose color is red +(rule $Command (what is the $TypeSet $BinaryProperty the $TypeSet) (lambda s1 (lambda p (lambda s2 (call @getProperty (var s2) (var p)))))) + +# 2. Filter: "what is the meeting whose location is cafe" +(rule $Command (what is the $TypeSet whose $EntityProperty is $Entity) (lambda s (lambda p (lambda v (call @filter (var s) (var p) (string =) (var v)))))) +(rule $Command (what is the $TypeSet whose $ValueProperty is $Value) (lambda s (lambda p (lambda v (call @filter (var s) (var p) (string =) (var v)))))) +(rule $Command (what is the $TypeSet whose $EntityProperty is not $Entity) (lambda s (lambda p (lambda v (call @filter (var s) (var p) (string !=) (var v)))))) +(rule $Command (what is the $TypeSet whose $ValueProperty is not $Value) (lambda s (lambda p (lambda v (call @filter (var s) (var p) (string !=) (var v)))))) + +# 3. Count: what is the number of meeting whose location is cafe +(rule $Command (what is the number of $TypeSet whose $EntityProperty is $Entity) (lambda s (lambda p (lambda v (call .size (call @filter (var s) (var p) (string =) (var v))))))) + +# 4. Sum: what is the total length of meeting +(rule $Command (what is the total $ValueProperty of all $TypeSet) (lambda p (lambda s (call @sum (call @getProperty (var s) (var p)))))) + +# 5. Comparatives: what is the meeting whose start time is smaller than 10am +(rule $Command (what is the $TypeSet whose $ValueProperty is smaller than $Value) (lambda s (lambda p (lambda v (call @filter (var s) (var p) (string <) (var v)))))) +(rule $Command (what is the $TypeSet whose $ValueProperty is larger than $Value) (lambda s (lambda p (lambda v (call @filter (var s) (var p) (string >) (var v)))))) +(rule $Command (what is the $TypeSet whose $ValueProperty is at most $Value) (lambda s (lambda p (lambda v (call @filter (var s) (var p) (string <=) (var v)))))) +(rule $Command (what is the $TypeSet whose $ValueProperty is at least $Value) (lambda s (lambda p (lambda v (call @filter (var s) (var p) (string >=) (var v)))))) + +# 6. Superlatives - what is the meeting with the smallest start time +(rule $Command (what is the $TypeSet with the smallest $ValueProperty) (lambda s (lambda p (call @superlative (var s) (string min) (var p))))) +(rule $Command (what is the $TypeSet with the largest $ValueProperty) (lambda s (lambda p (call @superlative (var s) (string max) (var p))))) + +# 7. create and remove +(rule $Command (create a $Type with $EntityProperty $Entity) (lambda t (lambda p (lambda v (call @create (var t) (var p) (var v)))))) +(rule $Command (create a $Type with $ValueProperty $Value) (lambda t (lambda p (lambda v (call @create (var t) (var p) (var v)))))) +(rule $Command (remove the $TypeSet with $EntityProperty $Entity) (lambda s (lambda p (lambda v (call @remove (call @filter (var s) (var p) (string =) (var v))))))) + +# 8. Two joins +(rule $Command (what is the $TypeSet whose $ValueProperty is $ValueProperty of $TypeSet) (lambda s1 (lambda p1 (lambda p2 (lambda s2 (call @filter (var s1) (var p1) (string =) (call @getProperty (var s2) (var p2)))))))) + +(rule $ROOT ($Command) (lambda x (call .toString (var x)))) diff --git a/overnight/unittest.db b/overnight/unittest.db new file mode 100644 index 0000000..12f1551 --- /dev/null +++ b/overnight/unittest.db @@ -0,0 +1,5 @@ +friend fb:en.person.alice fb:en.person.bob +enemy fb:en.person.alice fb:en.person.eve +person fb:en.person.alice +person fb:en.person.bob +person fb:en.person.eve diff --git a/overnight/unittest.grammar b/overnight/unittest.grammar new file mode 100644 index 0000000..4dd0c82 --- /dev/null +++ b/overnight/unittest.grammar @@ -0,0 +1,4 @@ +(include general.grammar) + +# Types +(rule $TypeNP (person) (ConstantFn en.person)) diff --git a/pull-dependencies b/pull-dependencies index ceb3a87..dd597a1 100755 --- a/pull-dependencies +++ b/pull-dependencies @@ -143,6 +143,52 @@ addModule('fullfreebase-types', 'Freebase types', lambda { # You need to unzip these yourself and move these files to the right place. }) +addModule('tables', 'Semantic parsing with execution on tables', lambda { + # CSV reader + pull('/u/nlp/data/semparse/resources/opencsv-3.0.jar') +}) + +addModule('tables-data', 'Data for semantic parsing with execution on tables', lambda { + pull('/u/nlp/data/semparse/wikitable/data', 'data/tables/', {:symlink => true}) + pull('/u/nlp/data/semparse/wikitable/mturk-trivia-data/csv', 'data/tables/', {:symlink => true}) +}) + + +addModule('overnight', 'Creating a parser for multiple domains', lambda { + # Geo evaluation + pull('/u/nlp/data/semparse/overnight/geo880.db', 'data/overnight/', {:symlink => true}) + pull('/u/nlp/data/semparse/overnight/geo880/geo880-train.examples', 'data/overnight/', {:symlink => true}) + pull('/u/nlp/data/semparse/overnight/geo880/geo880-test.examples', 'data/overnight/', {:symlink => true}) + + + # Cache for turking + pull('/u/nlp/data/semparse/overnight/cache/', 'data/overnight/', {:symlink => true}) + pull('/u/nlp/data/semparse/overnight/layouts/', 'data/overnight/', {:symlink => true}) + + # Pull testing code + pull('/u/nlp/data/semparse/overnight/test/', 'data/overnight/', {:symlink => true}) + + # Pull geo880 + pull('/u/nlp/data/semparse/overnight/geo880/geo880.paraphrases.train.superlatives.examples', 'data/overnight/', {:symlink => true}) + pull('/u/nlp/data/semparse/overnight/geo880/geo880.paraphrases.train.superlatives2.examples', 'data/overnight/', {:symlink => true}) + pull('/u/nlp/data/semparse/overnight/geo880/geo880.lexicon', 'data/overnight/', {:symlink => true}) + pull('/u/nlp/data/semparse/overnight/geo880/geo880.predicate.dict', 'data/overnight/', {:symlink => true}) + + # Pull dependencies for everything else + domains = ['geo880', 'regex', 'publications', 'socialnetwork', 'restaurants', 'blocks', 'calendar', 'housing', 'basketball', 'recipes', 'calendarplus'] + domains.each do |domain| + pull('/u/nlp/data/semparse/overnight/' + domain + '/' + domain + '.paraphrases.train.examples', 'data/overnight/', {:symlink => true}) + pull('/u/nlp/data/semparse/overnight/' + domain + '/' + domain + '.paraphrases.test.examples', 'data/overnight/', {:symlink => true}) + pull('/u/nlp/data/semparse/overnight/' + domain + '/' + domain + '.paraphrases.groups', 'data/overnight/', {:symlink => true}) + pull('/u/nlp/data/semparse/overnight/' + domain + '/' + domain + '.word_alignments.berkeley', 'data/overnight/', {:symlink => true}) + pull('/u/nlp/data/semparse/overnight/' + domain + '/' + domain + '.phrase_alignments', 'data/overnight/', {:symlink => true}) + pull('/u/nlp/data/semparse/overnight/' + domain + '/' + domain + '-ppdb.txt', 'data/overnight/', {:symlink => true}) + end + + # Pull the independent sets for calendar + pull('/u/nlp/data/semparse/overnight/calendar/eval/calendar.test.turk.examples', 'data/overnight/', {:symlink => true}) +}) + ############################################################ diff --git a/run b/run index 0ed04c9..3ac68cf 100755 --- a/run +++ b/run @@ -13,8 +13,21 @@ def addMode(name, description, func) $modes << [name, description, func] end -def header(modules='core') +def codalab(dependencies=nil) + # Set @cl=1 to run job on CodaLab + dependencies ||= l(':fig', ':lib', ':module-classes.txt', ':libsempre') l( + letDefault(:cl, 0), + sel(:cl, + l(), + l('cl', 'run', dependencies, '---'), + nil), + nil) +end + +def header(modules='core', codalabDependencies=nil) + l( + codalab(codalabDependencies), # Queuing system letDefault(:q, 0), sel(:q, l(), l('fig/bin/q', '-shareWorkingPath', o('mem', '5g'), o('memGrace', 10), '-add', '---')), # Create execution directory @@ -23,7 +36,18 @@ def header(modules='core') 'java', '-ea', '-Dmodules='+modules, - '-Xmx10g', + # Memory size + letDefault(:memsize, 'default'), + sel(:memsize, { + 'tiny' => l('-Xms2G', '-Xmx4G'), + 'low' => l('-Xms5G', '-Xmx7G'), + 'default' => l('-Xms8G', '-Xmx10G'), + 'medium' => l('-Xms12G', '-Xmx14G'), + 'high' => l('-Xms20G', '-Xmx24G'), + 'higher' => l('-Xms40G', '-Xmx50G'), + 'impressive' => l('-Xms75G', '-Xmx90G'), + }), + # Classpath '-cp', 'libsempre/*:lib/*', # Profiling letDefault(:prof, 0), sel(:prof, l(), '-Xrunhprof:cpu=samples,depth=100,file=_OUTPATH_/java.hprof.txt'), @@ -46,7 +70,11 @@ def figOpts; l(o('execDir', '_OUTPATH_'), o('overwriteExecDir'), o('addToView', addMode('test', 'Run unit tests', lambda { |e| l( - 'java', '-ea', '-Xmx12g', '-cp', 'libsempre/*:lib/*', 'org.testng.TestNG', + 'java', '-ea', '-Xmx12g', '-cp', 'libsempre/*:lib/*', + lambda { |e| + e.key?(:sparqlserver) ? "-Dsparqlserver=http://#{e[:sparqlserver]}/sparql" : l() + }, + 'org.testng.TestNG', lambda { |e| if e[:class] l('-testclass', 'edu.stanford.nlp.sempre.' + e[:class]) @@ -131,6 +159,7 @@ def cachePaths(lexiconFnCachePath, sparqlExecutorCachePath) when 'local' then l( # Use files directly - don't run more than one job that does this! o('Lexicon.cachePath', 'LexiconFn.cache'), o('SparqlExecutor.cachePath', 'SparqlExecutor.cache'), + o('FreebaseSearch.cachePath', 'FreebaseSearch.cache'), nil) else l( o('Lexicon.cachePath', cacheserver+':/u/nlp/data/semparse/cache/'+lexiconFnCachePath), @@ -225,7 +254,8 @@ def webquestions nil), # Grammar - o('Grammar.inPaths', 'freebase/data/emnlp2013.grammar'), + letDefault(:grammar, 1), + sel(:grammar, l(), l(o('Grammar.inPaths', 'freebase/data/emnlp2013.grammar'))), o('Parser.beamSize', 200), # {07/03/13}: WebQuestions is too slow to run with default 500, so set to 200 for now... @@ -350,9 +380,19 @@ nil) }) addMode('sparqlserver', '(2) Start the SPARQL server [do this every time]', lambda { |e| l( scrOptions, required(:exec), - 'freebase/scripts/virtuoso', 'start', - lambda{|e| e[:scr]+'/state/execs/'+e[:exec].to_s+'.exec/vdb'}, # DB directory - lambda{|e| 3000+e[:exec]}, # port + sel(nil, + l( + 'freebase/scripts/virtuoso', 'start', + lambda{|e| e[:scr]+'/state/execs/'+e[:exec].to_s+'.exec/vdb'}, # DB directory + lambda{|e| 3000+e[:exec]}, # port + nil), + # Give everyone permissions so that anyone can kill the server if needed. + l( + 'chmod', '-R', 'og=u', + lambda{|e| e[:scr]+'/state/execs/'+e[:exec].to_s+'.exec/vdb'}, # DB directory + nil), + # To stop the server: freebase/scripts/virtuoso stop 3093 + nil), nil) }) # (3) Index the filtered RDF dump [takes 48 hours] @@ -387,6 +427,7 @@ addMode('convertfree917', 'Convert the Free917 dataset', lambda { |e| l( nil) }) addMode('query', 'Query a single logical form or SPARQL', lambda { |e| l( + codalab, 'java', '-ea', '-cp', 'libsempre/*:lib/*', 'edu.stanford.nlp.sempre.freebase.SparqlExecutor', @@ -395,17 +436,18 @@ nil) }) ############################################################ + # Just start a simple interactive shell to try out SEMPRE commands addMode('simple', 'Simple shell', lambda { |e| l( - rlwrap, 'java', '-cp', 'libsempre/*:lib/*', '-ea', 'edu.stanford.nlp.sempre.Main', - o('interactive'), + codalab, rlwrap, 'java', '-cp', 'libsempre/*:lib/*', '-ea', 'edu.stanford.nlp.sempre.Main', + o('Main.interactive'), nil) }) addMode('simple-sparql', 'Simple shell for querying SPARQL', lambda { |e| l( - rlwrap, 'java', '-Dmodules=core,freebase', '-cp', 'libsempre/*:lib/*', '-ea', 'edu.stanford.nlp.sempre.Main', + codalab, rlwrap, 'java', '-Dmodules=core,freebase', '-cp', 'libsempre/*:lib/*', '-ea', 'edu.stanford.nlp.sempre.Main', o('executor', 'freebase.SparqlExecutor'), sparqlOpts, - o('interactive'), + o('Main.interactive'), nil) }) addMode('simple-freebase', 'Simple shell for using Freebase', lambda { |e| l( @@ -435,6 +477,382 @@ addMode('simple-freebase', 'Simple shell for using Freebase', lambda { |e| l( nil) }) +############################################################ +# {2014-12-27} [Percy]: Overnight semantic parsing +addMode('overnight', 'Overnight semantic parsing', lambda { |e| l( + 'rlwrap', + header('core,freebase,overnight'), + 'edu.stanford.nlp.sempre.Main', + figOpts, + o('JavaExecutor.convertNumberValues', false), + o('useAnchorsOnce', true), + o('trackLocalChoices'), + o('JoinFn.typeInference', true), + o('Builder.parser', 'FloatingParser'), + o('FloatingParser.executeAllDerivations', 'true'), + o('LanguageAnalyzer', 'corenlp.CoreNLPAnalyzer'), + o('Learner.maxTrainIters', 1), + #o('printAllPredictions'), + o('Derivation.showUtterance'), + letDefault(:domain, 'publications'), + letDefault(:debug, 0), + + selo(1, 'maxExamples', 'train:10', 'train:MAX'), + + # Exact matching is needed on most simple domains + # o('executor', 'FormulaMatchExecutor'), + # o('Builder.valueEvaluator', 'ExactValueEvaluator'), + + # Features + o('FeatureExtractor.featureDomains', 'denotation'), # denotation features from general feature extractor + o('FeatureExtractor.featureComputers', 'overnight.OvernightFeatureComputer'), # + o('OvernightFeatureComputer.featureDomains', *overnightFeatureDomains), + #o('initialization', 'paraphrase :: match,1', 'paraphrase :: size,-0.1', 'paraphrase :: ppdb,0.3', + # 'paraphrase :: skip-bigram,0.8', 'paraphrase :: skip-ppdb,0.2','denotation :: error,-1000'), + o('coarsePrune'), + o('OvernightDerivationPruningComputer.applyHardConstraints'), + sel(2, + l(), # no reg + l(o('Params.l1Reg','lazy'),o('Params.l1RegCoeff',0)), + l(o('Params.l1Reg','lazy'),o('Params.l1RegCoeff',0.001)), + nil), + # Set up the domain + required(:domain), + o('Grammar.inPaths', lambda { |e| 'overnight/' + e[:domain] + '.grammar' }), + o('SimpleWorld.domain', lambda { |e| e[:domain] }), + o('PPDBModel.ppdbModelPath', lambda { |e| 'lib/data/overnight/' + e[:domain] + '-ppdb.txt' }), + #o('PPDBModel.ppdbModelPath', lambda { |e| 'overnightData/' + e[:domain] + '-ppdb.txt' }), # codalab + o('Dataset.trainFrac', 0.8), o('Dataset.devFrac', 0.2), + o('FloatingParser.maxDepth', 11), + o('Parser.beamSize', 20), + letDefault(:alignment,1), + sel(:alignment, + o('wordAlignmentPath', 'lib/data/overnight/' + e[:domain] + '.word_alignments.heuristic'), + o('wordAlignmentPath', 'lib/data/overnight/' + e[:domain] + '.word_alignments.berkeley'), + #o('wordAlignmentPath', 'overnightData/' + e[:domain] + '.word_alignments.heuristic'), # codalab + #o('wordAlignmentPath', 'overnightData/' + e[:domain] + '.word_alignments.berkeley'), + nil), + o('phraseAlignmentPath', 'lib/data/overnight/' + e[:domain] + '.phrase_alignments'), + o('PPDBModel.ppdbModelPath', 'lib/data/overnight/' + e[:domain] + '-ppdb.txt'), + #o('phraseAlignmentPath', 'overnightData/' + e[:domain] + '.phrase_alignments'), # codalab + #o('PPDBModel.ppdbModelPath', 'overnightData/' + e[:domain] + '-ppdb.txt'), + o('DerivationPruner.pruningComputers', ['overnight.OvernightDerivationPruningComputer']), + o('Dataset.inPaths', + 'train:lib/data/overnight/' + e[:domain] + '.paraphrases.train.examples', + 'test:lib/data/overnight/' + e[:domain] + '.paraphrases.test.examples'), + #'train:overnightData/' + e[:domain] + '.paraphrases.train.examples', # codalab + #'test:overnightData/' + e[:domain] + '.paraphrases.test.examples'), + sel(:domain, { + 'regex' => l( + letDefault(:data,0), + sel(:data, + l(o('Dataset.inPaths', 'train:lib/data/overnight/regex.paraphrases.train.examples')), + l(o('Dataset.inPaths', 'train:lib/data/overnight/regex.paraphrases.train.examples', 'test:lib/data/overnight/regex.examples.train.json')), + l(o('Dataset.inPaths', 'train:lib/data/overnight/regex.paraphrases.train.examples', 'test:lib/data/overnight/regex.examples.test.json')), + nil), + # selo(:debug, 'Dataset.inPaths', 'train:lib/data/overnight/regex.paraphrases.train.examples', 'train:regex/regex-debug.examples'), + o('executor', 'JavaExecutor'), + o('Builder.valueEvaluator', 'regex.RegexValueEvaluator'), + o('OvernightFeatureComputer.approxEmptyDenotation', 'true'), + # o('OvernightFeatureComputer.featureDomains', *regexFeatureDomains), + o('JavaExecutor.convertNumberValues', true), + o('Grammar.tags','generate','regex'), + o('lowerCaseTokens', false), # case sensitivity is important for correctness of regular expressions + o('initialization', 'paraphrase :: match,1', 'paraphrase :: size,-0.1', 'paraphrase :: ppdb,0.3', + 'paraphrase :: skip-bigram,0.8', 'paraphrase :: skip-ppdb,0.2','denotation :: error,-1000'), + nil), + 'geo880' => l( + letDefault(:data,0), + sel(:data, + l(o('Dataset.inPaths', 'train:lib/data/overnight/geo880.paraphrases.train.superlatives.examples')), + l(o('Dataset.inPaths', 'train:lib/data/overnight/geo880.paraphrases.train.superlatives.examples', 'test:lib/data/overnight/geo880-train.examples')), + l(o('Dataset.inPaths', 'train:lib/data/overnight/geo880.paraphrases.train.superlatives2.examples', 'test:lib/data/overnight/geo880-train.examples')), + l(o('Dataset.inPaths', 'train:lib/data/overnight/geo880.paraphrases.train.superlatives.examples', 'test:lib/data/overnight/geo880-test.examples')), + l(o('Dataset.inPaths', 'train:lib/data/overnight/geo880.paraphrases.train.superlatives2.examples', 'test:lib/data/overnight/geo880-test.examples')), + nil), + o('Parser.beamSize', 20), + o('initialization', 'paraphrase :: match,1', 'paraphrase :: size,-0.1', 'paraphrase :: ppdb,0.3', + 'lf :: edu.stanford.nlp.sempre.SimpleWorld.superlative& superlative,10', + 'root :: pos0=WRB&returnType=class edu.stanford.nlp.sempre.NumberValue,10'), + o('FloatingParser.maxDepth', 11), + o('Grammar.tags','generate','general', 'geo880'), + o('SimpleLexicon.inPaths', 'lib/data/overnight/geo880.lexicon'), + #o('Params.l1Reg','lazy'), + #o('Params.l1RegCoeff',0.00001), + o('OvernightDerivationPruningComputer.usePredicateDict'), + o('OvernightDerivationPruningComputer.predicateDictPath','lib/data/overnight/geo880.predicate.dict'), + nil), + 'calendar' => l( + o('Grammar.tags','generate','general'), + nil), + 'calendarplus' => l( + o('Grammar.tags','generate','general','geo440'), + o('Grammar.inPaths','overnight/calendar.grammar'), + o('SimpleWorld.domain', 'calendar'), + nil), + 'blocks' => l( + o('Grammar.tags','generate','general'), + nil), + 'restaurants' => l( + o('Grammar.tags','generate','general'), + nil), + 'housing' => l( + o('Grammar.tags','generate','general'), + nil), + 'socialnetwork' => l( + o('Grammar.tags','generate','general'), + nil), + 'publications' => l( + o('Grammar.tags','generate','general'), + nil), + 'basketball' => l( + o('Grammar.tags','generate','general'), + nil), + 'recipes' => l( + o('Grammar.tags','generate','general'), + nil), + }), +nil) }) + +def overnightFeatureDomains + [ + 'match', + 'ppdb', + 'skip-bigram', + 'root', + 'alignment', + 'lexical', + 'root_lexical', + 'lf', + 'simpleworld', + nil].compact +end + +############################################################ +# {5/27/15} [Ice] +addMode('tables', 'QA on HTML tables', lambda { |e| l( + # Add @cldir=1 to use CodaLab's directory paths + letDefault(:cldir, 0), + # Usual header + rlwrap, header('core,tables,corenlp'), + # Select class + letDefault(:class, 'main'), + sel(:class, { + 'main' => 'edu.stanford.nlp.sempre.Main', + 'check' => 'edu.stanford.nlp.sempre.tables.test.DPParserChecker', + 'align' => 'edu.stanford.nlp.sempre.tables.alignment.IBMAligner', + 'dump' => 'edu.stanford.nlp.sempre.tables.serialize.SerializedDumper', + 'load' => 'edu.stanford.nlp.sempre.tables.serialize.SerializedLoader', + 'stats' => 'edu.stanford.nlp.sempre.tables.test.TableStatsComputer', + }), + # Fig parameters + selo(:cldir, 'execDir', '_OUTPATH_', '.'), + o('overwriteExecDir'), o('addToView', 0), o('jarFiles', 'libsempre/*'), + sel(:cldir, l(), '>/dev/null'), + # Set environment for table execution + o('executor', 'tables.lambdadcs.LambdaDCSExecutor'), + o('Builder.valueEvaluator', 'tables.TableValueEvaluator'), + o('TargetValuePreprocessor.targetValuePreprocessor', 'tables.TableValuePreprocessor'), + o('NumberFn.unitless'), o('NumberFn.alsoTestByConversion'), + o('TypeInference.typeLookup', 'tables.TableTypeLookup'), + o('JoinFn.specializedTypeCheck', false), o('JoinFn.typeInference', true), + # Parser + letDefault(:parser, 'floatsize'), + sel(:parser, { + 'floatsize' => l( + o('Builder.parser', 'FloatingParser'), + o('useSizeInsteadOfDepth'), + o('FloatingParser.maxDepth', 15), + nil), + 'baseline' => l( + o('Builder.parser', 'tables.baseline.TableBaselineParser'), + nil), + 'dummy' => o('Builder.parser', 'tables.serialize.DummyParser'), + }), + o('FloatingParser.useAnchorsOnce', true), + letDefault(:pruning, 1), + sel(:pruning, + l(), + l( + o('DerivationPruner.pruningStrategies', *tablesPruningStrategies), + o('DerivationPruner.pruningComputers', 'tables.TableDerivationPruningComputer'), + nil), + nil), + # Grammar + tablesGrammarPaths, + # Dataset + letDefault(:data, 'none'), + letDefault(:unseen, 0), + tablesDataPaths, + # Verbosity + o('FeatureVector.ignoreZeroWeight'), + o('maxPrintedPredictions', 10), o('maxPrintedTrue', 10), o('logFeaturesLimit', 10), + letDefault(:verbose, 0), + sel(:verbose, + l(), + l( + o('showRules'), + o('Parser.verbose', 2), + o('JoinFn.verbose', 3), + o('JoinFn.showTypeCheckFailures'), + nil), + nil), + # Language Analyzer + letDefault(:lang, 'corenlp'), + sel(:lang, { + 'simple' => o('LanguageAnalyzer', 'SimpleAnalyzer'), + 'corenlp' => l(o('LanguageAnalyzer', 'corenlp.CoreNLPAnalyzer'), o('annotators', *'tokenize ssplit pos lemma ner'.split)), + 'fullcorenlp' => l(o('LanguageAnalyzer', 'corenlp.CoreNLPAnalyzer'), o('annotators', *'tokenize ssplit pos lemma ner parse'.split)), + }), + # Training + letDefault(:train, 0), + sel(:train, + l(), + l( + o('combineFromFloatingParser'), + sel(:unseen, unbalancedTrainDevSplit, l()), + o('maxTrainIters', 3), + o('showValues', false), o('showFirstValue'), + nil), + l( + # for dumping derivations (@class=dump) + # force unbalancedTrainDevSplit + combine from floating parser + o('combineFromFloatingParser'), o('DPParser.cheat'), + sel(:unseen, unbalancedTrainDevSplit, l()), + nil), + nil), + # Regularization + letDefault(:l1, 1), + sel(:l1, + l(), + l(o('Params.l1Reg','lazy'), o('Params.l1RegCoeff', '3e-5')), + l(o('Params.l1Reg','lazy'), selo(nil, 'Params.l1RegCoeff', 0, 0.00001, 0.0001, 0.001, 0.01)), + l(o('Params.l1Reg','lazy'), selo(nil, 'Params.l1RegCoeff', 0.00001, 0.00003, 0.0001, 0.0003)), + l(o('Params.l1Reg','lazy'), selo(nil, 'Params.l1RegCoeff', 0.00001, 0.00003, 0.0005)), + nil), + # Features + letDefault(:feat, 'none'), + sel(:feat, { + 'none' => l(), # No features (random) + '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 + 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), + '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), + nil), + 'ablate' => l( + o('FeatureExtractor.featureComputers', 'tables.features.PhrasePredicateFeatureComputer tables.features.PhraseDenotationFeatureComputer'.split), + selo(nil, + 'FeatureExtractor.featureDomains', + 'phrase-predicate phrase-denotation headword-denotation missing-predicate'.split, + 'custom-denotation phrase-denotation headword-denotation missing-predicate'.split, + 'custom-denotation phrase-predicate headword-denotation missing-predicate'.split, + 'custom-denotation phrase-predicate phrase-denotation missing-predicate'.split, + 'custom-denotation phrase-predicate phrase-denotation headword-denotation'.split, + nil), + nil), + }), + letDefault(:featOp, 'careful'), + sel(:featOp, { + 'none' => l(), + 'base' => l( + o('usePredicateLemma'), o('usePhraseLemmaOnly'), + nil), + 'careful' => l( + o('usePredicateLemma'), o('usePhraseLemmaOnly'), + o('maxNforLexicalizeAllPairs', 2), + o('traverseWithFormulaTypes'), o('reverseNameValueConversion', 'allBang'), + o('lookUnderCellProperty'), o('useGenericCellType'), + o('computeFuzzyMatchPredicates'), + nil), + }), +nil) }) + +def tablesGrammarPaths + lambda { |e| + baseDir = ['tables/grammars/', 'grammars/'][e[:cldir]] + l( + letDefault(:grammar, 'combined-all'), + sel(:grammar, { + 'restrict' => o('Grammar.inPaths', "#{baseDir}restrict.grammar"), + 'simple' => o('Grammar.inPaths', "#{baseDir}simple.grammar"), + 'combined' => o('Grammar.inPaths', "#{baseDir}combined.grammar"), + 'combined-jnc' => l( # WQ baseline + o('Grammar.inPaths', "#{baseDir}combined.grammar"), + o('Grammar.tags', *'movement count'.split), + nil), + 'combined-cut' => l( # No intersection / union + o('Grammar.inPaths', "#{baseDir}combined.grammar"), + o('Grammar.tags', *'movement comparison count aggregate superlative arithmetic'.split), + nil), + 'combined-all' => l( # Default + o('Grammar.inPaths', "#{baseDir}combined.grammar"), + o('Grammar.tags', *'alternative movement comparison count aggregate superlative arithmetic merge'.split), + nil), + 'combined-trigger' => l( # Use trigger words for operations + o('Grammar.inPaths', "#{baseDir}combined.grammar"), + o('Grammar.tags', *'t-alternative t-movement t-comparison t-count t-aggregate t-superlative t-arithmetic merge'.split), + nil), + }), + nil) + } +end + +def tablesDataPaths + lambda { |e| + baseDir = ['lib/data/tables/data/', 'WikiTableQuestions/data/'][e[:cldir]] + csvDir = ['lib/data/tables/', 'WikiTableQuestions/'][e[:cldir]] + datasets = { + 'none' => l(), + # Pristine test test + 'test' => l( + o('Dataset.inPaths', + "train,#{baseDir}training.examples", + "test,#{baseDir}pristine-unseen-tables.examples"), + o('Dataset.trainFrac', 1), o('Dataset.devFrac', 0), + let(:unseen, 1), + nil), + } + # Development sets: 80:20 random split of training data + ['1', '2', '3', '4', '5'].each do |x| + datasets['u-' + x] = l( + o('Dataset.inPaths', + "train,#{baseDir}random-split-seed-#{x}-train.examples", + "dev,#{baseDir}random-split-seed-#{x}-test.examples", + nil), + let(:unseen, 1), + nil) + end + # That's it! + l( + o('TableKnowledgeGraph.baseCSVDir', csvDir), + sel(:data, datasets), + nil) + } +end + +def tablesPruningStrategies + [ + # Formula + "singleton", + "multipleSuperlatives", + "sameMerge", + "forwardBackward", + "doubleNext", + # Denotation + "emptyDenotation", + "nonLambdaError", + "tooManyValues", + "badSuperlativeHead", + "mistypedMerge", + nil].compact +end + diff --git a/scripts/checkstyle.sh b/scripts/checkstyle.sh index d605fd6..bd68d0a 100755 --- a/scripts/checkstyle.sh +++ b/scripts/checkstyle.sh @@ -7,4 +7,5 @@ else fi d=`dirname $0` -java -cp $d/../lib/checkstyle/checkstyle-6.1.1-all.jar com.puppycrawl.tools.checkstyle.Main -c `dirname $0`/checkstyle.xml $files +prog="$d/../lib/checkstyle/checkstyle-6.6-all.jar" +java -cp $prog com.puppycrawl.tools.checkstyle.Main -c `dirname $0`/checkstyle.xml $files diff --git a/scripts/checkstyle.xml b/scripts/checkstyle.xml index 446e998..06eda0a 100644 --- a/scripts/checkstyle.xml +++ b/scripts/checkstyle.xml @@ -39,6 +39,8 @@ --> + + @@ -76,8 +78,16 @@ + + + + + + + + @@ -167,9 +177,6 @@ - - - diff --git a/scripts/create-geo-simple-lexicon.py b/scripts/create-geo-simple-lexicon.py new file mode 100755 index 0000000..ce2f817 --- /dev/null +++ b/scripts/create-geo-simple-lexicon.py @@ -0,0 +1,68 @@ +#!/usr/bin/python + +import sys +import json + +class LexicalEntry: + def __init__(self, l, f, t): + self.lexeme=l.strip() + self.formula=f.strip() + self.type=t.strip() + +out = open(sys.argv[2],'w') + +with open(sys.argv[1]) as f: + for line in f: + tokens = line.split("\t") + if len(tokens) > 2: + continue + if(tokens[0] == "loc_city"): + index = tokens[1].rfind('.') + citystate = tokens[1][index+1:] + city = citystate[0:citystate.rfind('_')] + city = city.replace('_',' ').strip() + entry = LexicalEntry(city, tokens[1], "fb:en.city") + out.write(json.dumps(entry.__dict__)+'\n') + elif (tokens[0] == "loc_state"): + index = tokens[1].rfind('.') + state = tokens[1][index+1:].strip() + state = state.replace('_',' ').strip() + entry = LexicalEntry(state, tokens[1], "fb:en.state") + out.write(json.dumps(entry.__dict__)+'\n') + elif tokens[0] == "loc_river": + index = tokens[1].rfind('.') + river = tokens[1][index+1:].strip() + river = river.replace('_',' ').strip() + entry = LexicalEntry(river+" river", tokens[1], "fb:en.river") + out.write(json.dumps(entry.__dict__)+'\n') + elif (tokens[0] == "loc_place"): + index = tokens[1].rfind('.') + place = tokens[1][index+1:].strip() + place = place.replace('_',' ').strip() + entry = LexicalEntry(place, tokens[1], "fb:en.place") + out.write(json.dumps(entry.__dict__)+'\n') + elif (tokens[0] == "loc_lake"): + index = tokens[1].rfind('.') + lake = tokens[1][index+1:].strip() + lake = lake.replace('_',' ').strip() + if not 'lake' in lake: + lake = lake + " lake" + entry = LexicalEntry(lake, tokens[1], "fb:en.lake") + out.write(json.dumps(entry.__dict__)+'\n') + elif (tokens[0] == "loc_mountain"): + index = tokens[1].rfind('.') + mountain = tokens[1][index+1:].strip() + mountain = mountain.replace('_',' ').strip() + entry = LexicalEntry("mount " + mountain, tokens[1], "fb:en.mountain") + out.write(json.dumps(entry.__dict__)+'\n') + elif (tokens[0] == "loc_country"): + index = tokens[1].rfind('.') + country = tokens[1][index+1:].strip() + country = country.replace('_',' ').strip() + entry = LexicalEntry(country, tokens[1], "fb:en.country") + out.write(json.dumps(entry.__dict__)+'\n') + + +out.close() + + diff --git a/scripts/filterGeneratedNegations.py b/scripts/filterGeneratedNegations.py new file mode 100755 index 0000000..a7da009 --- /dev/null +++ b/scripts/filterGeneratedNegations.py @@ -0,0 +1,29 @@ +#!/usr/bin/python + +import sys +import json + +# Official evaluation script used to evaluate Freebase question answering +# systems. Used for EMNLP 2013, ACL 2014 papers, etc. + +if len(sys.argv) != 3: + sys.exit("Usage: %s " % sys.argv[0]) + +generated = set() +with open(sys.argv[1]) as f: + for line in f: + generated.add(line) + +out = open(sys.argv[2],'w') + + +with open(sys.argv[1]) as f: + for line in f: + index = line.find(" not") + if index != -1: + newStr = line[0:index] + line[index+4:len(line)] + if newStr in generated: + out.write(line) + else: + out.write(line) +out.close() diff --git a/scripts/generate-prediction-file.sh b/scripts/generate-prediction-file.sh new file mode 100755 index 0000000..b176c5a --- /dev/null +++ b/scripts/generate-prediction-file.sh @@ -0,0 +1,7 @@ +#!/bin/sh +#Generate predictions file for evaluation script +#arguments: (1) execution numebr (2) iteration number (3) final output file + +fig/bin/tab e/$1.exec/learner.events iter group utterance targetValue predValue | grep -P "$2\ttest" | cut -f3,4,5 > pred_temp +java -cp "libsempre/*:lib/*" edu.stanford.nlp.sempre.freebase.utils.FileUtils pred_temp $3 +rm pred_temp diff --git a/scripts/tunnel b/scripts/tunnel index 8ed2aa8..12dcc69 100755 --- a/scripts/tunnel +++ b/scripts/tunnel @@ -20,6 +20,14 @@ pid=$(ps ax | grep ssh.*:$port | grep -v grep | awk '{print $1}') if [ -n "$pid" ]; then kill $pid; fi ssh -N -n -L $port:$host:$port jacob.stanford.edu & +# Sparql server for geo +host=jonsson +port=3094 +echo "Tunnel localhost:$port => $host:$port" +pid=$(ps ax | grep ssh.*:$port | grep -v grep | awk '{print $1}') +if [ -n "$pid" ]; then kill $pid; fi +ssh -N -n -L $port:$host:$port jacob.stanford.edu & + # Sparql server for Paleo host=jonsson port=3021 diff --git a/scripts/verify-code-loop.rb b/scripts/verify-code-loop.rb index cae18b6..ce61593 100755 --- a/scripts/verify-code-loop.rb +++ b/scripts/verify-code-loop.rb @@ -92,8 +92,8 @@ while true log("Testing...") run('git log -3', true) or restart # Print out last commit messages run('./pull-dependencies', true) or restart - run('make clean', true) or restart - run('make', true) or restart + run('ant clean', true) or restart + run('ant', true) or restart run('scripts/find-hard-coded-paths.rb', true) or restart diff --git a/src/edu/stanford/nlp/sempre/AggregateFormula.java b/src/edu/stanford/nlp/sempre/AggregateFormula.java index 32b875b..2ba0bce 100644 --- a/src/edu/stanford/nlp/sempre/AggregateFormula.java +++ b/src/edu/stanford/nlp/sempre/AggregateFormula.java @@ -49,6 +49,7 @@ public class AggregateFormula extends Formula { return res; } + @SuppressWarnings({"equalshashcode"}) @Override public boolean equals(Object thatObj) { if (!(thatObj instanceof AggregateFormula)) return false; diff --git a/src/edu/stanford/nlp/sempre/ArithmeticFormula.java b/src/edu/stanford/nlp/sempre/ArithmeticFormula.java index 9b28331..1bcac69 100644 --- a/src/edu/stanford/nlp/sempre/ArithmeticFormula.java +++ b/src/edu/stanford/nlp/sempre/ArithmeticFormula.java @@ -65,6 +65,7 @@ public class ArithmeticFormula extends Formula { } } + @SuppressWarnings({"equalshashcode"}) @Override public boolean equals(Object thatObj) { if (!(thatObj instanceof ArithmeticFormula)) return false; diff --git a/src/edu/stanford/nlp/sempre/Builder.java b/src/edu/stanford/nlp/sempre/Builder.java index 2308713..edfd898 100644 --- a/src/edu/stanford/nlp/sempre/Builder.java +++ b/src/edu/stanford/nlp/sempre/Builder.java @@ -86,6 +86,7 @@ public class Builder { } catch (ClassNotFoundException e1) { throw new RuntimeException("Illegal parser: " + opts.parser); } catch (Exception e) { + e.printStackTrace(); throw new RuntimeException("Error while instantiating parser: " + opts.parser + "\n" + e); } } diff --git a/src/edu/stanford/nlp/sempre/CallFormula.java b/src/edu/stanford/nlp/sempre/CallFormula.java index 5fba752..8de5d46 100644 --- a/src/edu/stanford/nlp/sempre/CallFormula.java +++ b/src/edu/stanford/nlp/sempre/CallFormula.java @@ -56,6 +56,7 @@ public class CallFormula extends Formula { return res; } + @SuppressWarnings({"equalshashcode"}) @Override public boolean equals(Object thatObj) { if (!(thatObj instanceof CallFormula)) return false; diff --git a/src/edu/stanford/nlp/sempre/CanonicalNames.java b/src/edu/stanford/nlp/sempre/CanonicalNames.java index 87a3e03..db14f07 100644 --- a/src/edu/stanford/nlp/sempre/CanonicalNames.java +++ b/src/edu/stanford/nlp/sempre/CanonicalNames.java @@ -13,10 +13,12 @@ public final class CanonicalNames { private CanonicalNames() { } // Standard type names + public static final String PREFIX = "fb:"; public static final String BOOLEAN = "fb:type.boolean"; public static final String INT = "fb:type.int"; public static final String FLOAT = "fb:type.float"; public static final String DATE = "fb:type.datetime"; + public static final String TIME = "fb:type.time"; public static final String TEXT = "fb:type.text"; public static final String NUMBER = "fb:type.number"; public static final String ENTITY = "fb:common.topic"; @@ -29,6 +31,24 @@ public final class CanonicalNames { public static final String TYPE = "fb:type.object.type"; public static final String NAME = "fb:type.object.name"; + // Unary: fb:domain.type [contains exactly one period] + // Binary: fb:domain.type.property, <, >, etc. + public static boolean isUnary(String s) { + int i = s.indexOf('.'); + if (i == -1) return false; + i = s.indexOf('.', i + 1); + if (i == -1) return true; + return false; + } + + public static boolean isBinary(String s) { + int i = s.indexOf('.'); + if (i == -1) return false; + i = s.indexOf('.', i + 1); + if (i == -1) return false; + return true; + } + // Return whether |property| is the name of a reverse property. // Convention: ! is the prefix for reverses. public static boolean isReverseProperty(String property) { @@ -39,4 +59,5 @@ public final class CanonicalNames { else return "!" + property; } + } diff --git a/src/edu/stanford/nlp/sempre/ChartParserState.java b/src/edu/stanford/nlp/sempre/ChartParserState.java index 6e8499d..c8e9037 100644 --- a/src/edu/stanford/nlp/sempre/ChartParserState.java +++ b/src/edu/stanford/nlp/sempre/ChartParserState.java @@ -77,6 +77,8 @@ public abstract class ChartParserState extends ParserState { void addToChart(Derivation deriv) { if (parser.verbose(3)) LogInfo.logs("addToChart %s: %s", deriv.cat, deriv); + if (Parser.opts.pruneErrorValues && deriv.value instanceof ErrorValue) return; + List derivations = chart[deriv.start][deriv.end].get(deriv.cat); if (chart[deriv.start][deriv.end].get(deriv.cat) == null) chart[deriv.start][deriv.end].put(deriv.cat, derivations = new ArrayList<>()); diff --git a/src/edu/stanford/nlp/sempre/Dataset.java b/src/edu/stanford/nlp/sempre/Dataset.java index 69d170a..1efecde 100644 --- a/src/edu/stanford/nlp/sempre/Dataset.java +++ b/src/edu/stanford/nlp/sempre/Dataset.java @@ -33,6 +33,8 @@ public class Dataset { public double devFrac = 0; @Option(gloss = "Used to randomly divide training examples") public Random splitRandom = new Random(1); + @Option(gloss = "whether to split dev from train") + public boolean splitDevFromTrain = true; @Option(gloss = "Only keep examples which have at most this number of tokens") public int maxTokens = Integer.MAX_VALUE; @@ -44,8 +46,8 @@ public class Dataset { private LinkedHashMap> allExamples = new LinkedHashMap>(); // General statistics about the examples. - private HashSet tokenTypes = new HashSet(); - private StatFig numTokensFig = new StatFig(); // For each example, number of tokens + private final HashSet tokenTypes = new HashSet(); + private final StatFig numTokensFig = new StatFig(); // For each example, number of tokens public Set groups() { return allExamples.keySet(); } public List examples(String group) { return allExamples.get(group); } @@ -123,7 +125,7 @@ public class Dataset { allExamples.put(groupInfo.group, examples = new ArrayList()); readHelper(groupInfo.examples, maxExamples, examples, groupInfo.path); } - splitDevFromTrain(); + if (opts.splitDevFromTrain) splitDevFromTrain(); collectStats(); LogInfo.end_track(); @@ -140,8 +142,16 @@ public class Dataset { List trainExamples = new ArrayList(); allExamples.put("train", trainExamples); List devExamples = allExamples.get("dev"); - if (devExamples == null) - allExamples.put("dev", devExamples = new ArrayList()); + if (devExamples == null) { + // Preserve order + LinkedHashMap> newAllExamples = new LinkedHashMap<>(); + for (Map.Entry> entry : allExamples.entrySet()) { + newAllExamples.put(entry.getKey(), entry.getValue()); + if (entry.getKey().equals("train")) + newAllExamples.put("dev", devExamples = new ArrayList<>()); + } + allExamples = newAllExamples; + } for (int i = 0; i < split1; i++) trainExamples.add(origTrainExamples.get(perm[i])); for (int i = split2; i < origTrainExamples.size(); i++) @@ -165,7 +175,7 @@ public class Dataset { ex = new Example.Builder().withExample(ex).setId(id).createExample(); } i++; - ex.preprocess(LanguageAnalyzer.getSingleton()); + ex.preprocess(); // Skip example if too long if (ex.numTokens() > opts.maxTokens) continue; @@ -190,7 +200,7 @@ public class Dataset { allExamples.put(group, examples = new ArrayList()); readLispTreeHelper(path, maxExamples, examples); } - splitDevFromTrain(); + if (opts.splitDevFromTrain) splitDevFromTrain(); LogInfo.end_track(); } @@ -208,7 +218,7 @@ public class Dataset { Example ex = Example.fromLispTree(tree, path + ":" + n); // Specify a default id if it doesn't exist n++; - ex.preprocess(LanguageAnalyzer.getSingleton()); + ex.preprocess(); // Skip example if too long if (ex.numTokens() > opts.maxTokens) continue; diff --git a/src/edu/stanford/nlp/sempre/Derivation.java b/src/edu/stanford/nlp/sempre/Derivation.java index 783484b..1787d85 100644 --- a/src/edu/stanford/nlp/sempre/Derivation.java +++ b/src/edu/stanford/nlp/sempre/Derivation.java @@ -24,6 +24,8 @@ public class Derivation implements SemanticFn.Callable, HasScore { public boolean showRules = false; @Option(gloss = "When printing derivations, to show canonical utterance") public boolean showUtterance = false; + @Option(gloss = "When executing, show formulae (for debugging)") + public boolean showExecutions = false; } public static Options opts = new Options(); @@ -34,7 +36,11 @@ public class Derivation implements SemanticFn.Callable, HasScore { public final String cat; public final int start; public final int end; + + // Floating cell information + // TODO(yushi): make fields final public String canonicalUtterance; + private boolean[] anchoredTokens; // Tokens which anchored rules are defined on // If this derivation is composed of other derivations public final Rule rule; // Which rule was used to produce this derivation? Set to nullRule if not. @@ -58,6 +64,14 @@ public class Derivation implements SemanticFn.Callable, HasScore { private final FeatureVector localFeatureVector; // Features double score = Double.NaN; // Weighted combination of features + // Used during parsing (by FeatureExtractor, SemanticFn) to cache arbitrary + // computation across different sub-Derivations. + // Convention: + // - use the featureDomain, FeatureComputer or SemanticFn as the key. + // - the value is whatever the FeatureExtractor needs. + // This information should be set to null after parsing is done. + private Map tempState; + // What the formula evaluates to (optionally set later; only non-null for the root Derivation) public Value value; public Evaluation executorStats; @@ -141,12 +155,14 @@ public class Derivation implements SemanticFn.Callable, HasScore { public Derivation createDerivation() { return new Derivation( cat, start, end, rule, children, formula, type, - localFeatureVector, score, value, executorStats, compatibility, prob, canonicalUtterance); + localFeatureVector, score, value, executorStats, compatibility, prob, + canonicalUtterance); } } Derivation(String cat, int start, int end, Rule rule, List children, Formula formula, SemType type, - FeatureVector localFeatureVector, double score, Value value, Evaluation executorStats, double compatibility, double prob, String canonicalUtterance) { + FeatureVector localFeatureVector, double score, Value value, Evaluation executorStats, double compatibility, double prob, + String canonicalUtterance) { this.cat = cat; this.start = start; this.end = end; @@ -180,6 +196,7 @@ public class Derivation implements SemanticFn.Callable, HasScore { public boolean containsIndex(int i) { return i < end && i >= start; } public Rule getRule() { return rule; } public Evaluation getExecutorStats() { return executorStats; } + public FeatureVector getLocalFeatureVector() { return localFeatureVector; } public Derivation child(int i) { return children.get(i); } public String childStringValue(int i) { @@ -191,6 +208,11 @@ public class Derivation implements SemanticFn.Callable, HasScore { return cat.equals(Rule.rootCat) && ((start == 0 && end == numTokens) || (start == -1)); } + // Return whether |deriv| has root category (for floating parser) + public boolean isRootCat() { + return cat.equals(Rule.rootCat); + } + // Functions that operate on features. public void addFeature(String domain, String name) { addFeature(domain, name, 1); } public void addFeature(String domain, String name, double value) { this.localFeatureVector.add(domain, name, value); } @@ -234,6 +256,8 @@ public class Derivation implements SemanticFn.Callable, HasScore { public void ensureExecuted(Executor executor, ContextValue context) { if (isExecuted()) return; StopWatchSet.begin("Executor.execute"); + if (opts.showExecutions) + LogInfo.logs("%s - %s", canonicalUtterance, formula); Executor.Response response = executor.execute(formula, context); StopWatchSet.end(); value = response.value; @@ -304,7 +328,7 @@ public class Derivation implements SemanticFn.Callable, HasScore { } public String startEndString(List tokens) { - return start + ":" + end + tokens.subList(start, end); + return start + ":" + end + (start == -1 ? "" : tokens.subList(start, end)); } public String toString() { return toLispTree().toString(); } @@ -319,8 +343,13 @@ public class Derivation implements SemanticFn.Callable, HasScore { for (Derivation child : children) child.incrementAllFeatureVector(factor, map, updateFeatureMatcher); } + public void incrementAllFeatureVector(double factor, FeatureVector fv) { + localFeatureVector.add(factor, fv); + for (Derivation child : children) + child.incrementAllFeatureVector(factor, fv); + } - // recursively renames all features in derivation by adding a prefix + // returns feature vector with renamed features by prefix public FeatureVector addPrefixLocalFeatureVector(String prefix) { return localFeatureVector.addPrefix(prefix); } @@ -404,4 +433,42 @@ public class Derivation implements SemanticFn.Callable, HasScore { NumUtils.expNormalize(probs); return probs; } + + // Manipulation of temporary state used during parsing. + public Map getTempState() { + // Create the tempState if it doesn't exist. + if (tempState == null) + tempState = new HashMap(); + return tempState; + } + public void clearTempState() { + tempState = null; + if (children != null) + for (Derivation child : children) + child.clearTempState(); + } + + // Compute anchoredTokens and return the result + // anchoredTokens[>= anchoredTokens.length] are False by default + public boolean[] getAnchoredTokens() { + if (anchoredTokens == null) { + if (rule.isAnchored()) { + anchoredTokens = new boolean[end]; + for (int i = start; i < end; i++) anchoredTokens[i] = true; + } else { + anchoredTokens = new boolean[0]; + for (Derivation child : children) { + boolean[] childAnchoredTokens = child.getAnchoredTokens(); + if (anchoredTokens.length < childAnchoredTokens.length) { + boolean[] newAnchoredTokens = new boolean[childAnchoredTokens.length]; + for (int i = 0; i < anchoredTokens.length; i++) newAnchoredTokens[i] = anchoredTokens[i]; + anchoredTokens = newAnchoredTokens; + } + for (int i = 0; i < childAnchoredTokens.length; i++) + anchoredTokens[i] = anchoredTokens[i] || childAnchoredTokens[i]; + } + } + } + return anchoredTokens.clone(); + } } diff --git a/src/edu/stanford/nlp/sempre/DerivationPruner.java b/src/edu/stanford/nlp/sempre/DerivationPruner.java new file mode 100644 index 0000000..eeb6c73 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/DerivationPruner.java @@ -0,0 +1,214 @@ +package edu.stanford.nlp.sempre; + +import java.util.*; + +import fig.basic.*; + +/** + * Prune derivations during parsing. + * + * To add custom pruning criteria, implement a DerivationPruningComputer class, + * and put the class name in the |pruningComputers| option. + * + * @author ppasupat + */ + +public class DerivationPruner { + public static class Options { + @Option public List pruningStrategies = new ArrayList<>(); + @Option public List pruningComputers = new ArrayList<>(); + @Option public int pruningVerbosity = 0; + @Option public int maxNumValues = 10; + } + public static Options opts = new Options(); + + public final Parser parser; + public final Example ex; + private List pruningComputers = new ArrayList<>(); + private List customAllowedDomains; + + public DerivationPruner(ParserState parserState) { + this.parser = parserState.parser; + this.ex = parserState.ex; + for (String pruningComputer : opts.pruningComputers) { + try { + Class pruningComputerClass = Class.forName(SempreUtils.resolveClassName(pruningComputer)); + pruningComputers.add((DerivationPruningComputer) pruningComputerClass.getConstructor(this.getClass()).newInstance(this)); + } catch (ClassNotFoundException e1) { + throw new RuntimeException("Illegal pruning computer: " + pruningComputer); + } catch (Exception e) { + e.printStackTrace(); + e.getCause().printStackTrace(); + throw new RuntimeException("Error while instantiating pruning computer: " + pruningComputer); + } + } + } + + public void setCustomAllowedDomains(List customAllowedDomains) { + this.customAllowedDomains = customAllowedDomains; + } + + protected boolean containsStrategy(String name) { + return opts.pruningStrategies.contains(name) && + (customAllowedDomains == null || customAllowedDomains.contains(name)); + } + + public boolean isPruned(Derivation deriv) { + if (opts.pruningStrategies.isEmpty() && pruningComputers.isEmpty()) return false; + if (pruneFormula(deriv)) return true; + if (pruneDenotation(deriv)) return true; + for (DerivationPruningComputer pruningComputer : pruningComputers) { + if (pruningComputer.isPruned(deriv)) return true; + } + return false; + } + + // ============================================================ + // Formula-based Pruning + // ============================================================ + + private boolean pruneFormula(Derivation deriv) { + return pruneSingleton(deriv) || pruneSuperlatives(deriv) || pruneMerges(deriv); + } + + /** + * Prune singleton formula at the root. + */ + private boolean pruneSingleton(Derivation deriv) { + if (!containsStrategy("singleton")) return false; + return deriv.isRoot(ex.numTokens()) && deriv.formula instanceof ValueFormula; + } + + /** + * Prune strings of multiple superlatives. + */ + private boolean pruneSuperlatives(Derivation deriv) { + if (containsStrategy("doubleSuperlatives")) { + // Prune if there is an arg{max|min} whose head has arg{max|min} + if (deriv.formula instanceof SuperlativeFormula) { + SuperlativeFormula superlative = (SuperlativeFormula) deriv.formula; + if (superlative.head instanceof SuperlativeFormula) { + if (opts.pruningVerbosity >= 2) + LogInfo.logs("PRUNED [doubleSuperlatives] %s", deriv.formula); + return true; + } + } + } + if (containsStrategy("multipleSuperlatives")) { + // Prune if there are more than arg{max|min} appearing in the formula (don't need to be adjacent) + List stack = new ArrayList<>(); + int count = 0; + stack.add(deriv.formula.toLispTree()); + while (!stack.isEmpty()) { + LispTree tree = stack.remove(stack.size() - 1); + if (tree.isLeaf()) { + if ("argmax".equals(tree.value) || "argmin".equals(tree.value)) { + count++; + if (count >= 2) { + if (opts.pruningVerbosity >= 2) + LogInfo.logs("PRUNED [multipleSuperlatives] %s", deriv.formula); + return true; + } + } + } else { + for (LispTree subtree : tree.children) + stack.add(subtree); + } + } + } + return false; + } + + /** + * Prune merges. + */ + private boolean pruneMerges(Derivation deriv) { + if (!(deriv.formula instanceof MergeFormula)) return false; + MergeFormula merge = (MergeFormula) deriv.formula; + if (containsStrategy("sameMerge")) { + if (merge.child1.equals(merge.child2)) { + if (opts.pruningVerbosity >= 2) + LogInfo.logs("PRUNED [sameMerge] %s", deriv.formula); + return true; + } + } + return false; + } + + // ============================================================ + // Denotation-based Pruning + // ============================================================ + + /** + * Pruning based on denotations. + */ + private boolean pruneDenotation(Derivation deriv) { + return pruneFinalDenotation(deriv) || prunePartialDenotation(deriv); + } + + private boolean pruneFinalDenotation(Derivation deriv) { + Formula formula = deriv.formula; + // Prune if the denotation is an empty list + if (containsStrategy("emptyDenotation")) { + deriv.ensureExecuted(parser.executor, ex.context); + if (deriv.value instanceof ListValue) { + if (((ListValue) deriv.value).values.isEmpty()) { + if (opts.pruningVerbosity >= 3) + LogInfo.logs("PRUNED [emptyDenotation] %s", formula); + return true; + } + } + } + // Prune if the denotation is an error and the formula is not a partial formula + if (containsStrategy("nonLambdaError") && !(deriv.formula instanceof LambdaFormula)) { + deriv.ensureExecuted(parser.executor, ex.context); + if (deriv.value instanceof ErrorValue) { + if (opts.pruningVerbosity >= 3) + LogInfo.logs("PRUNED [nonLambdaError] %s", formula); + return true; + } + } + // Prune if the denotation has too many values + if (containsStrategy("tooManyValues") && deriv.isRoot(ex.numTokens())) { + deriv.ensureExecuted(parser.executor, ex.context); + if (deriv.value instanceof ListValue) { + if (((ListValue) deriv.value).values.size() > opts.maxNumValues) { + if (opts.pruningVerbosity >= 3) + LogInfo.logs("PRUNED [tooManyValues] %s", formula); + return true; + } + } + } + return false; + } + + private boolean prunePartialDenotation(Derivation deriv) { + Formula formula = deriv.formula; + if (containsStrategy("badSuperlativeHead")) { + Formula head = null; + if (formula instanceof AggregateFormula) + head = ((AggregateFormula) formula).child; + else if (formula instanceof SuperlativeFormula) + head = ((SuperlativeFormula) formula).head; + if (head != null) { + Value headValue = parser.executor.execute(head, ex.context).value; + if (headValue instanceof ListValue && ((ListValue) headValue).values.size() < 2) { + if (opts.pruningVerbosity >= 3) + LogInfo.logs("PRUNED [badSuperlativeHead] %s", formula); + return true; + } + } + } + if (containsStrategy("mistypedMerge") && formula instanceof MergeFormula) { + MergeFormula merge = (MergeFormula) formula; + SemType type1 = TypeInference.inferType(merge.child1); + SemType type2 = TypeInference.inferType(merge.child2); + if (!type1.meet(type2).isValid()) { + if (opts.pruningVerbosity >= 2) + LogInfo.logs("PRUNED [mistypedMerge] %s", deriv.formula); + return true; + } + } + return false; + } +} diff --git a/src/edu/stanford/nlp/sempre/DerivationPruningComputer.java b/src/edu/stanford/nlp/sempre/DerivationPruningComputer.java new file mode 100644 index 0000000..048d263 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/DerivationPruningComputer.java @@ -0,0 +1,25 @@ +package edu.stanford.nlp.sempre; + +/** + * Used to prune formulas during parsing. + * + * Extend this class to add custom pruning criteria, + * then add the class name to the |pruningComputers| options of DerivationPruner. + * + * @author ppasupat + */ +public abstract class DerivationPruningComputer { + + protected final DerivationPruner pruner; + + public DerivationPruningComputer(DerivationPruner pruner) { + this.pruner = pruner; + } + + protected boolean containsStrategy(String name) { + return pruner.containsStrategy(name); + } + + public abstract boolean isPruned(Derivation deriv); + +} diff --git a/src/edu/stanford/nlp/sempre/Example.java b/src/edu/stanford/nlp/sempre/Example.java index 496545b..766f55a 100644 --- a/src/edu/stanford/nlp/sempre/Example.java +++ b/src/edu/stanford/nlp/sempre/Example.java @@ -10,9 +10,7 @@ import fig.basic.Evaluation; import fig.basic.LispTree; import fig.basic.LogInfo; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; +import java.util.*; /** * An example corresponds roughly to an input-output pair, the basic unit which @@ -41,13 +39,16 @@ public class Example { @JsonProperty public Value targetValue; // Denotation (e.g., answer) //// Information after preprocessing (e.g., tokenization, POS tagging, NER, syntactic parsing, etc.). - @JsonProperty public LanguageInfo languageInfo = null; + public LanguageInfo languageInfo = null; //// Output of the parser. // Predicted derivations (sorted by score). public List predDerivations; + // Temporary state while parsing an Example (see Derivation.java for analogous struture). + private Map tempState; + // Statistics relating to processing the example. public Evaluation evaluation; @@ -133,6 +134,8 @@ public class Example { b.setId(arg.child(1).value); } else if ("utterance".equals(label)) { b.setUtterance(arg.child(1).value); + } else if ("canonicalUtterance".equals(label)) { + b.setUtterance(arg.child(1).value); } else if ("targetFormula".equals(label)) { b.setTargetFormula(Formulas.fromLispTree(arg.child(1))); } else if ("targetValue".equals(label) || "targetValues".equals(label)) { @@ -162,7 +165,7 @@ public class Example { ex.predDerivations = new ArrayList<>(); for (int j = 1; j < arg.children.size(); j++) ex.predDerivations.add(derivationFromLispTree(arg.child(j))); - } else if (!Sets.newHashSet("id", "utterance", "targetFormula", "targetValue", "targetValues", "context").contains(label)) { + } else if (!Sets.newHashSet("id", "utterance", "targetFormula", "targetValue", "targetValues", "context", "original").contains(label)) { throw new RuntimeException("Invalid example argument: " + arg); } } @@ -170,8 +173,9 @@ public class Example { return ex; } - public void preprocess(LanguageAnalyzer analyzer) { - this.languageInfo = analyzer.analyze(this.utterance); + public void preprocess() { + this.languageInfo = LanguageAnalyzer.getSingleton().analyze(this.utterance); + this.targetValue = TargetValuePreprocessor.getSingleton().preprocess(this.targetValue); } public void log() { @@ -191,11 +195,6 @@ public class Example { LogInfo.end_track(); } - // To save memory - public void clearPredDerivations() { - predDerivations.clear(); - } - public List getCorrectDerivations() { List res = new ArrayList<>(); for (Derivation deriv : predDerivations) { @@ -290,4 +289,14 @@ public class Example { return item; } + + public Map getTempState() { + // Create the tempState if it doesn't exist. + if (tempState == null) + tempState = new HashMap(); + return tempState; + } + public void clearTempState() { + tempState = null; + } } diff --git a/src/edu/stanford/nlp/sempre/ExampleUtils.java b/src/edu/stanford/nlp/sempre/ExampleUtils.java index c4c79dd..71041de 100644 --- a/src/edu/stanford/nlp/sempre/ExampleUtils.java +++ b/src/edu/stanford/nlp/sempre/ExampleUtils.java @@ -22,6 +22,18 @@ public final class ExampleUtils { out.close(); } + public static void writeJson(List examples, String outPath) throws IOException { + PrintWriter out = edu.stanford.nlp.io.IOUtils.getPrintWriter(outPath); + out.println("["); + for (int i = 0; i < examples.size(); ++i) { + Example ex = examples.get(i); + out.print(ex.toJson()); + out.println(i < examples.size() - 1 ? "," : ""); + } + out.println("]"); + out.close(); + } + private static String escapeSpace(String s) { return s.replaceAll(" ", " "); } @@ -38,6 +50,7 @@ public final class ExampleUtils { PrintWriter out = IOUtils.openOutHard(outPath); LispTree p = LispTree.proto; + out.println("# SDF version 1.1"); out.println("# " + p.L(p.L("iter", iter), p.L("group", group), p.L("numExamples", examples.size()), p.L("evaluation", evaluation.toLispTree()))); for (Example ex : examples) { out.println(""); @@ -47,11 +60,22 @@ public final class ExampleUtils { if (outputPredDerivations) { for (Derivation deriv : ex.predDerivations) { StringBuilder buf = new StringBuilder(); - buf.append("item " + escapeSpace(p.L(p.L("formula", deriv.formula.toLispTree()), p.L("value", deriv.value.toLispTree())).toString())); // Description - buf.append(" " + deriv.compatibility); + buf.append("item"); + LispTree description = p.newList(); + if (deriv.canonicalUtterance != null) + description.addChild(p.L("canonicalUtterance", deriv.canonicalUtterance)); + description.addChild(p.L("formula", deriv.formula.toLispTree())); + description.addChild(p.L("value", deriv.value.toLispTree())); + buf.append("\t" + description); + buf.append("\t" + deriv.compatibility); Map features = deriv.getAllFeatureVector(); + buf.append("\t"); + boolean first = true; for (Map.Entry e : features.entrySet()) { - buf.append(" " + escapeSpace(e.getKey()) + ":" + e.getValue()); + if (!first) + buf.append(' '); + first = false; + buf.append(e.getKey() + ":" + e.getValue()); } out.println(buf.toString()); } @@ -60,4 +84,53 @@ public final class ExampleUtils { out.close(); LogInfo.end_track(); } + + public static void writeParaphraseSDF(int iter, String group, Example ex, + boolean outputPredDerivations) { + String basePath = "preds-iter" + iter + "-" + group + ".examples"; + String outPath = Execution.getFile(basePath); + if (outPath == null) return; + PrintWriter out = IOUtils.openOutAppendHard(outPath); + + out.println("example " + ex.id); + + if (outputPredDerivations) { + int i = 0; + for (Derivation deriv : ex.predDerivations) { + if (deriv.canonicalUtterance != null) + out.println("Pred@" + i + ":\t" + ex.utterance + "\t" + deriv.canonicalUtterance + "\t" + deriv.compatibility + "\t" + deriv.formula + "\t" + deriv.prob); + i++; + } + } + out.close(); + } + + public static void writeEvaluationSDF(int iter, String group, + Evaluation evaluation, int numExamples) { + String basePath = "preds-iter" + iter + "-" + group + ".examples"; + String outPath = Execution.getFile(basePath); + if (outPath == null) return; + PrintWriter out = IOUtils.openOutAppendHard(outPath); + + LispTree p = LispTree.proto; + out.println(""); + out.println("# SDF version 1.1"); + out.println("# " + p.L(p.L("iter", iter), p.L("group", group), p.L("numExamples", numExamples), p.L("evaluation", evaluation.toLispTree()))); + out.close(); + } + + //read lisptree and write json + public static void main(String[] args) { + Dataset dataset = new Dataset(); + Pair pair = Pair.newPair("train", args[0]); + Dataset.opts.splitDevFromTrain = false; + dataset.readFromPathPairs(Collections.singletonList(pair)); + List examples = dataset.examples("train"); + try { + writeJson(examples, args[1]); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } } diff --git a/src/edu/stanford/nlp/sempre/FeatureExtractor.java b/src/edu/stanford/nlp/sempre/FeatureExtractor.java index 8040f9b..791ba3a 100644 --- a/src/edu/stanford/nlp/sempre/FeatureExtractor.java +++ b/src/edu/stanford/nlp/sempre/FeatureExtractor.java @@ -1,10 +1,11 @@ package edu.stanford.nlp.sempre; -import java.util.*; import com.google.common.base.Joiner; import com.google.common.collect.Sets; import fig.basic.*; +import java.util.*; + /** * A FeatureExtractor specifies a mapping from derivations to feature vectors. * @@ -34,6 +35,8 @@ public class FeatureExtractor { public boolean useAllFeatures = false; @Option(gloss = "For bigram features in paraphrased utterances, maximum distance to consider") public int maxBigramDistance = 3; + @Option(gloss = "Whether or not paraphrasing and bigram features should be lexicalized") + public boolean lexicalBigramParaphrase = true; } private Executor executor; @@ -63,7 +66,6 @@ public class FeatureExtractor { extractDependencyFeatures(ex, deriv); extractWhTypeFeatures(ex, deriv); conjoinLemmaAndBinary(ex, deriv); - extractParaphraseFeatures(ex, deriv); extractBigramFeatures(ex, deriv); for (FeatureComputer featureComputer : featureComputers) featureComputer.extractLocal(ex, deriv); @@ -100,21 +102,24 @@ public class FeatureExtractor { return; } + if (deriv.value instanceof StringValue) { + if (((StringValue) deriv.value).value.equals("[]") || ((StringValue) deriv.value).value.equals("[null]")) + deriv.addFeature("denotation", "empty"); + return; + } + if (deriv.value instanceof ListValue) { ListValue list = (ListValue) deriv.value; - // TODO(pliang): this is hacky; don't depend on the logical form - if (Formulas.isCountFormula(deriv.formula)) { - if (list.values.size() != 1) { - deriv.addFeature("denotation", "size", list.values.size()); - } else { - int count = getNumber(list.values.get(0)); - deriv.addFeature("denotation", "count-size" + (count == 0 ? "=0" : ">0")); - } - } else { + if (list.values.size() == 1 && list.values.get(0) instanceof NumberValue) { + int count = getNumber(list.values.get(0)); + deriv.addFeature("denotation", "count-size" + (count <= 1 ? "=" + count : ">1")); + } + else { int size = list.values.size(); deriv.addFeature("denotation", "size" + (size < 3 ? "=" + size : ">=" + 3)); } + } } @@ -137,11 +142,11 @@ public class FeatureExtractor { String containment = deriv.containsIndex(dependency.modifier) ? "internal" : "external"; if (containsDomain("fullDependencyParse")) addAllDependencyFeatures(dependency, direction, containment, - deriv); + deriv); else deriv.addFeature("dependencyParse", - "(" + dependency.label + " " + direction + " " + containment + ") --- " - + deriv.getRule().toString()); + "(" + dependency.label + " " + direction + " " + containment + ") --- " + + deriv.getRule().toString()); } } } @@ -150,7 +155,7 @@ public class FeatureExtractor { } private void addAllDependencyFeatures(LanguageInfo.DependencyEdge dependency, - String direction, String containment, Derivation deriv) { + String direction, String containment, Derivation deriv) { String[] types = {dependency.label, "*"}; String[] directions = {" " + direction, ""}; String[] containments = {" " + containment, ""}; @@ -160,7 +165,7 @@ public class FeatureExtractor { for (String containmentPresent : containments) { for (String rulePresent : rules) { deriv.addFeature("fullDependencyParse", - "(" + typePresent + directionPresent + containmentPresent + ") --- " + rulePresent); + "(" + typePresent + directionPresent + containmentPresent + ") --- " + rulePresent); } } } @@ -175,8 +180,8 @@ public class FeatureExtractor { if (ex.posTag(0).startsWith("W")) { deriv.addFeature("whType", - "token0=" + ex.token(0) + "," + - "type=" + coarseType(deriv.type.toString())); + "token0=" + ex.token(0) + "," + + "type=" + coarseType(deriv.type.toString())); } } @@ -229,7 +234,7 @@ public class FeatureExtractor { //Used in Berant et., EMNLP 2013, and in the agenda RL parser //Extracts all content-word lemmas in the derivation tree not dominated by the category $Entity private void extractNonEntityLemmas(Example ex, Derivation deriv, - List nonEntityLemmas) { + List nonEntityLemmas) { if (deriv.children.size() == 0) { // base case this means it is a word that should be appended for (int i = deriv.start; i < deriv.end; i++) { String pos = ex.languageInfo.posTags.get(i); @@ -259,31 +264,6 @@ public class FeatureExtractor { return res; } - /** - * Add an indicator for each token alignment between a token in the utterance and its - * canonical form in the grammar using the ParaphraseModel, if applicable. - */ - // Compute paraphrasing features. In the future, need to store temporary - // state to make this more efficient. - void extractParaphraseFeatures(Example ex, Derivation deriv) { - if (!containsDomain("paraphrasing")) return; - if (deriv.rule == Rule.nullRule) return; - extractParaphrasePrecision(ex, deriv); - extractParaphraseRecall(ex, deriv); - - // Make sure we have a valid, floating, paraphraseable rule - if (!deriv.rule.isFloating() || !deriv.rule.isRhsTerminals()) return; - String key = join(deriv.rule.rhs, " "); - ParaphraseModel model = ParaphraseModel.getSingleton(); - if (!model.containsKey(key)) return; - for (String token : ex.getTokens()) { - double score = model.get(key, token); - if (score > 0.0) { - deriv.addFeature("paraphrasing", "(" + key + ", " + token + ")"); - } - } - } - /** * Add an indicator for each pair of bigrams that can be aligned from the original * utterance and two (not necessarily contiguous) lemmas in the generated utterance @@ -294,45 +274,26 @@ public class FeatureExtractor { LanguageInfo derivInfo = LanguageAnalyzer.getSingleton().analyze(deriv.canonicalUtterance); List derivLemmas = derivInfo.lemmaTokens; List exLemmas = ex.languageInfo.lemmaTokens; + Map bigramCounts = new HashMap(); for (int i = 0; i < exLemmas.size() - 1; i++) { for (int j = 0; j < derivLemmas.size() - 1; j++) { if (derivLemmas.get(j).equals(exLemmas.get(i))) { // Consider bigrams separated by up to maxBigramDistance in generated utterance for (int k = 1; j + k < derivLemmas.size() && k <= opts.maxBigramDistance; k++) { if (derivLemmas.get(j + k).equals(exLemmas.get(i + 1))) { - deriv.addFeature("bigram", - exLemmas.get(i) + "," + exLemmas.get(i + 1) + " - " + k); + if (opts.lexicalBigramParaphrase) + deriv.addFeature("bigram", + exLemmas.get(i) + "," + exLemmas.get(i + 1) + " - " + k); + else MapUtils.incr(bigramCounts, k, 1); } } } } } - } - - private void extractParaphraseRecall(Example ex, Derivation deriv) { - if (!deriv.cat.equals(Rule.rootCat)) return; - String[] derivTokens = deriv.canonicalUtterance.split("\\s+"); - int n = 0; - for (int i = 0; i < ex.languageInfo.numTokens(); ++i) { - for (int j = 0; j < derivTokens.length; ++j) { - if (derivTokens[j].equals(ex.languageInfo.tokens.get(i))) { - n++; - break; - } - } + if (!opts.lexicalBigramParaphrase) { + for (Integer dist : bigramCounts.keySet()) + deriv.addFeature("bigram", "distance " + dist + " - " + bigramCounts.get(dist)); } - deriv.addFeature("paraphrasing", "deriv_recall", (double) n / ex.languageInfo.numTokens()); - } - - private void extractParaphrasePrecision(Example ex, Derivation deriv) { - //how many of the generated stuff is in the original utterance - int n = 0; - for (String item : deriv.rule.rhs) { - if (Rule.isCat(item)) continue; - if (ex.languageInfo.tokens.contains(item)) - n++; - } - deriv.addFeature("paraphrasing", "deriv_prec", n); } // Joins arrayList of strings into string diff --git a/src/edu/stanford/nlp/sempre/FeatureVector.java b/src/edu/stanford/nlp/sempre/FeatureVector.java index d6e75c6..a2503e6 100644 --- a/src/edu/stanford/nlp/sempre/FeatureVector.java +++ b/src/edu/stanford/nlp/sempre/FeatureVector.java @@ -17,6 +17,14 @@ import java.util.*; * @author Jonathan Berant */ public class FeatureVector { + public static class Options { + @Option(gloss = "When logging, ignore features with zero weight") + public boolean ignoreZeroWeight = false; + @Option(gloss = "Log only this number of top and bottom features") + public int logFeaturesLimit = Integer.MAX_VALUE; + } + public static Options opts = new Options(); + // These features map to the value 1 (most common case in NLP). private ArrayList indicatorFeatures; // General features @@ -90,21 +98,27 @@ public class FeatureVector { } public void add(FeatureVector that) { add(that, AllFeatureMatcher.matcher); } - public void add(FeatureVector that, FeatureMatcher matcher) { + public void add(double scale, FeatureVector that) { add(scale, that, AllFeatureMatcher.matcher); } + public void add(FeatureVector that, FeatureMatcher matcher) { add(1, that, matcher); } + public void add(double scale, FeatureVector that, FeatureMatcher matcher) { if (that.indicatorFeatures != null) { for (String f : that.indicatorFeatures) - if (matcher.matches(f)) - add(f); + if (matcher.matches(f)) { + if (scale == 1) + add(f); + else + add(f, scale); + } } if (that.generalFeatures != null) { for (Pair pair : that.generalFeatures) if (matcher.matches(pair.getFirst())) - add(pair.getFirst(), pair.getSecond()); + add(pair.getFirst(), scale * pair.getSecond()); } // dense features are always added if (that.denseFeatures != null) { for (int i = 0; i < denseFeatures.length; ++i) - denseFeatures[i] = that.denseFeatures[i]; + denseFeatures[i] += scale * that.denseFeatures[i]; } } @@ -229,16 +243,33 @@ public class FeatureVector { String feature = entry.getKey(); if (entry.getValue() == 0) continue; double value = entry.getValue() * params.getWeight(feature); + if (opts.ignoreZeroWeight && value == 0) continue; sumValue += value; entries.add(new java.util.AbstractMap.SimpleEntry(feature, value)); } Collections.sort(entries, new ValueComparator(false)); LogInfo.begin_track_printAll("%s features [sum = %s] (format is feature value * weight)", prefix, Fmt.D(sumValue)); - for (Map.Entry entry : entries) { - String feature = entry.getKey(); - double value = entry.getValue(); - double weight = params.getWeight(feature); - LogInfo.logs("%-50s %6s = %s * %s", "[ " + feature + " ]", Fmt.D(value), Fmt.D(MapUtils.getDouble(features, feature, 0)), Fmt.D(weight)); + if (entries.size() / 2 > opts.logFeaturesLimit) { + for (Map.Entry entry : entries.subList(0, opts.logFeaturesLimit)) { + String feature = entry.getKey(); + double value = entry.getValue(); + double weight = params.getWeight(feature); + LogInfo.logs("%-50s %6s = %s * %s", "[ " + feature + " ]", Fmt.D(value), Fmt.D(MapUtils.getDouble(features, feature, 0)), Fmt.D(weight)); + } + LogInfo.logs("... (%d more features) ...", entries.size() - 2 * opts.logFeaturesLimit); + for (Map.Entry entry : entries.subList(entries.size() - opts.logFeaturesLimit, entries.size())) { + String feature = entry.getKey(); + double value = entry.getValue(); + double weight = params.getWeight(feature); + LogInfo.logs("%-50s %6s = %s * %s", "[ " + feature + " ]", Fmt.D(value), Fmt.D(MapUtils.getDouble(features, feature, 0)), Fmt.D(weight)); + } + } else { + for (Map.Entry entry : entries) { + String feature = entry.getKey(); + double value = entry.getValue(); + double weight = params.getWeight(feature); + LogInfo.logs("%-50s %6s = %s * %s", "[ " + feature + " ]", Fmt.D(value), Fmt.D(MapUtils.getDouble(features, feature, 0)), Fmt.D(weight)); + } } LogInfo.end_track(); } diff --git a/src/edu/stanford/nlp/sempre/FilterTokenFn.java b/src/edu/stanford/nlp/sempre/FilterTokenFn.java new file mode 100644 index 0000000..61b8847 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/FilterTokenFn.java @@ -0,0 +1,52 @@ +package edu.stanford.nlp.sempre; + +import fig.basic.LispTree; + +import java.util.ArrayList; +import java.util.List; + +/** + * Given a token at a particular position, keep it is from a select set. + * + * @author ppasupat + */ +public class FilterTokenFn extends SemanticFn { + List acceptableTokens = new ArrayList<>(); + String mode; + + public void init(LispTree tree) { + super.init(tree); + mode = tree.child(1).value; + if (!mode.equals("token") && !mode.equals("lemma")) + throw new RuntimeException("Illegal description for FilterTokenFn: " + mode); + for (int j = 2; j < tree.children.size(); j++) { + acceptableTokens.add(tree.child(j).value); + } + } + + public DerivationStream call(final Example ex, final Callable c) { + return new SingleDerivationStream() { + @Override + public Derivation createDerivation() { + if (!isValid(ex, c)) + return null; + else { + return new Derivation.Builder() + .withCallable(c) + .withFormulaFrom(c.child(0)) + .createDerivation(); + } + } + }; + } + + private boolean isValid(Example ex, Callable c) { + if (c.getEnd() - c.getStart() != 1) return false; + String token; + if ("token".equals(mode)) + token = ex.token(c.getStart()); + else + token = ex.lemmaToken(c.getStart()); + return acceptableTokens.contains(token); + } +} diff --git a/src/edu/stanford/nlp/sempre/FloatingParser.java b/src/edu/stanford/nlp/sempre/FloatingParser.java index 99cebf8..38436d1 100644 --- a/src/edu/stanford/nlp/sempre/FloatingParser.java +++ b/src/edu/stanford/nlp/sempre/FloatingParser.java @@ -1,7 +1,9 @@ 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; @@ -46,7 +48,18 @@ public class FloatingParser extends Parser { @Option public boolean defaultIsFloating = true; @Option (gloss = "Flag specifying whether anchored spans/tokens can only be used once in a derivation") public boolean useAnchorsOnce = false; + @Option (gloss = "Flag specifying whether floating rules are allowed to be applied consecutively") + public boolean consecutiveRules = true; + @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 = "Put a limit on formula size instead of formula depth") + public boolean useSizeInsteadOfDepth = false; + @Option(gloss = "Custom beam size at training time (default = Parser.beamSize)") + public int trainBeamSize = -1; } + public static Options opts = new Options(); public FloatingParser(Spec spec) { super(spec); } @@ -70,12 +83,25 @@ class FloatingParserState extends ParserState { // Examples of state: // (category, depth) // (category, depth, set of tokens) + private final Map> chart = new HashMap<>(); + private final DerivationPruner pruner; + public FloatingParserState(FloatingParser parser, Params params, Example ex, boolean computeExpectedCounts) { super(parser, params, ex, computeExpectedCounts); + pruner = new DerivationPruner(this); } + @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; @@ -88,10 +114,11 @@ class FloatingParserState extends ParserState { } private void addToChart(Object cell, Derivation deriv) { - if (Parser.opts.verbose >= 3) - LogInfo.logs("addToChart %s: %s", cell, 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); } @@ -103,29 +130,32 @@ class FloatingParserState extends ParserState { else if (child2 == null) // 1-ary children = Collections.singletonList(child1); else { - children = ListUtils.newList(child1, child2); - // optionally: ensure that specific anchors are only used once per final derivation + // Optional: ensure that each anchor is only used once per derivation. if (FloatingParser.opts.useAnchorsOnce && - FloatingRuleUtils.derivationAnchorsOverlap(child1, child2)) + FloatingRuleUtils.derivationAnchorsOverlap(child1, child2)) 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)); + new SemanticFn.CallInfo(rule.lhs, start, end, rule, children)); while (results.hasNext()) { Derivation newDeriv = results.next(); 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 - /* - if (depth > 0) { - if (MapUtils.getSet(setChart, rule.lhs).contains(newDeriv.formula)) { - continue; - } - if (rule.lhs == Rule.rootCat) - LogInfo.logs("adding %s to root for floating", newDeriv); - MapUtils.addToSet(setChart, rule.lhs, newDeriv.formula); - } - */ 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); @@ -236,19 +266,30 @@ class FloatingParserState extends ParserState { for (Derivation deriv : derivations) applyFloatingRule(rule, depth, deriv, null, deriv.canonicalUtterance + " " + rhs2); } else { // $Cat $Cat - for (int subDepth = 0; subDepth < depth; subDepth++) { // depth-1 <=depth-1 - List derivations1 = getDerivations(floatingCell(rhs1, depth - 1)); - List derivations2 = getDerivations(floatingCell(rhs2, subDepth)); - for (Derivation deriv1 : derivations1) - for (Derivation deriv2 : derivations2) - applyFloatingRule(rule, depth, deriv1, deriv2, deriv1.canonicalUtterance + " " + deriv2.canonicalUtterance); - } - for (int subDepth = 0; subDepth < depth - 1; subDepth++) { // derivations1 = getDerivations(floatingCell(rhs1, subDepth)); - List derivations2 = getDerivations(floatingCell(rhs2, depth - 1)); - for (Derivation deriv1 : derivations1) - for (Derivation deriv2 : derivations2) - applyFloatingRule(rule, depth, deriv1, deriv2, deriv1.canonicalUtterance + " " + deriv2.canonicalUtterance); + if (FloatingParser.opts.useSizeInsteadOfDepth) { + for (int depth1 = 0; depth1 < depth; depth1++) { + int depth2 = depth - 1 - depth1; + List derivations1 = getDerivations(floatingCell(rhs1, depth1)); + List derivations2 = getDerivations(floatingCell(rhs2, depth2)); + for (Derivation deriv1 : derivations1) + for (Derivation deriv2 : derivations2) + applyFloatingRule(rule, depth, deriv1, deriv2, deriv1.canonicalUtterance + " " + deriv2.canonicalUtterance); + } + } else { + for (int subDepth = 0; subDepth < depth; subDepth++) { // depth-1 <=depth-1 + List derivations1 = getDerivations(floatingCell(rhs1, depth - 1)); + List derivations2 = getDerivations(floatingCell(rhs2, subDepth)); + for (Derivation deriv1 : derivations1) + for (Derivation deriv2 : derivations2) + applyFloatingRule(rule, depth, deriv1, deriv2, deriv1.canonicalUtterance + " " + deriv2.canonicalUtterance); + } + for (int subDepth = 0; subDepth < depth - 1; subDepth++) { // derivations1 = getDerivations(floatingCell(rhs1, subDepth)); + List derivations2 = getDerivations(floatingCell(rhs2, depth - 1)); + for (Derivation deriv1 : derivations1) + for (Derivation deriv2 : derivations2) + applyFloatingRule(rule, depth, deriv1, deriv2, deriv1.canonicalUtterance + " " + deriv2.canonicalUtterance); + } } } } @@ -289,14 +330,6 @@ class FloatingParserState extends ParserState { buildAnchored(i, i + len); for (String cat : categories) { String cell = anchoredCell(cat, i, i + len).toString(); - /*if (cat.equals("$Word") || cat.equals("$Fragment")) { - LogInfo.logs("Looking at cell: %s", cell); - if (chart.get(cell) == null) LogInfo.logs("???"); - else { - for (Derivation deriv : chart.get(cell)) - LogInfo.logs("Found cell of interest: %s", deriv.toString()); - } - }*/ pruneCell(cell, chart.get(cell)); } } @@ -307,14 +340,6 @@ class FloatingParserState extends ParserState { buildFloating(depth); for (String cat : categories) { String cell = floatingCell(cat, depth).toString(); - /*if (cat.equals("$Word") || cat.equals("$Fragment")) { - LogInfo.logs("Looking at floating cell: %s", cell); - if (chart.get(cell) == null) LogInfo.logs("???"); - else { - for (Derivation deriv : chart.get(cell)) - LogInfo.logs("Found cell of interest: %s", deriv.toString()); - } - }*/ pruneCell(cell, chart.get(cell)); } } @@ -339,9 +364,29 @@ class FloatingParserState extends ParserState { 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()); + } + private void visualizeAnchoredChart(Set categories) { for (String cat : categories) { for (int len = 1; len <= numTokens; ++len) { diff --git a/src/edu/stanford/nlp/sempre/FloatingRuleUtils.java b/src/edu/stanford/nlp/sempre/FloatingRuleUtils.java index eed165d..37185fd 100644 --- a/src/edu/stanford/nlp/sempre/FloatingRuleUtils.java +++ b/src/edu/stanford/nlp/sempre/FloatingRuleUtils.java @@ -31,6 +31,7 @@ public final class FloatingRuleUtils { * that spans) [2, 4] then we have an overlap. */ public static boolean derivationAnchorsOverlap(Derivation a, Derivation b) { + /* List aRoots = getDerivationAnchors(a); List bRoots = getDerivationAnchors(b); for (Derivation aRoot : aRoots) { @@ -40,5 +41,10 @@ public final class FloatingRuleUtils { } } return false; + */ + boolean[] aAnchors = a.getAnchoredTokens(), bAnchors = b.getAnchoredTokens(); + for (int i = 0; i < aAnchors.length && i < bAnchors.length; i++) + if (aAnchors[i] && bAnchors[i]) return true; + return false; } } diff --git a/src/edu/stanford/nlp/sempre/Formula.java b/src/edu/stanford/nlp/sempre/Formula.java index 862150c..4e0f812 100644 --- a/src/edu/stanford/nlp/sempre/Formula.java +++ b/src/edu/stanford/nlp/sempre/Formula.java @@ -50,6 +50,7 @@ public abstract class Formula { public static Formula nullFormula = new PrimitiveFormula() { public LispTree toLispTree() { return LispTree.proto.newLeaf("null"); } + @SuppressWarnings({"equalshashcode"}) @Override public boolean equals(Object o) { return this == o; } public int computeHashCode() { return 0; } }; diff --git a/src/edu/stanford/nlp/sempre/FuzzyMatchFn.java b/src/edu/stanford/nlp/sempre/FuzzyMatchFn.java index 9d4efb1..963b028 100644 --- a/src/edu/stanford/nlp/sempre/FuzzyMatchFn.java +++ b/src/edu/stanford/nlp/sempre/FuzzyMatchFn.java @@ -5,7 +5,7 @@ import java.util.*; import fig.basic.*; /** - * Similar to LexiconFn, but list all approximate matches from a TableKnowledgeGraph. + * Similar to LexiconFn, but list all approximate matches from a TableKnowledgeGraph or PuzzleKnowledgeGraph. * * @author ppasupat */ @@ -15,7 +15,8 @@ public class FuzzyMatchFn extends SemanticFn { } public static Options opts = new Options(); - public enum FuzzyMatchFnMode { UNARY, BINARY, ENTITY }; + public enum FuzzyMatchFnMode { UNARY, BINARY, ENTITY, + ORDER_BEFORE, ORDER_AFTER, ORDER_NEXT, ORDER_PREV, ORDER_ADJACENT }; private FuzzyMatchFnMode mode; // Generate all possible denotations regardless of the phrase @@ -29,6 +30,11 @@ public class FuzzyMatchFn extends SemanticFn { else if ("binary".equals(value)) this.mode = FuzzyMatchFnMode.BINARY; else if ("entity".equals(value)) this.mode = FuzzyMatchFnMode.ENTITY; else if ("any".equals(value)) this.matchAny = true; + else if ("before".equals(value)) this.mode = FuzzyMatchFnMode.ORDER_BEFORE; + else if ("after".equals(value)) this.mode = FuzzyMatchFnMode.ORDER_AFTER; + else if ("next".equals(value)) this.mode = FuzzyMatchFnMode.ORDER_NEXT; + else if ("prev".equals(value)) this.mode = FuzzyMatchFnMode.ORDER_PREV; + else if ("adjacent".equals(value)) this.mode = FuzzyMatchFnMode.ORDER_ADJACENT; else throw new RuntimeException("Invalid argument: " + value); } } @@ -57,7 +63,7 @@ public class FuzzyMatchFn extends SemanticFn { this.ex = ex; this.graph = (ex.context == null) ? null : ex.context.graph; this.c = c; - this.query = matchAny ? null : c.childStringValue(0); + this.query = (matchAny || c.getChildren().isEmpty()) ? null : c.childStringValue(0); this.mode = mode; this.matchAny = matchAny; if (opts.verbose >= 2) @@ -68,6 +74,7 @@ public class FuzzyMatchFn extends SemanticFn { @Override public Derivation createDerivation() { if (graph == null) return null; + if (query == null && !matchAny) return null; // Compute the formulas if not computed yet if (formulas == null) { diff --git a/src/edu/stanford/nlp/sempre/Grammar.java b/src/edu/stanford/nlp/sempre/Grammar.java index 3581023..740d60f 100644 --- a/src/edu/stanford/nlp/sempre/Grammar.java +++ b/src/edu/stanford/nlp/sempre/Grammar.java @@ -184,6 +184,8 @@ public class Grammar { return !interpretBoolean(tree.child(1), tags); if ("and".equals(tree.child(0).value)) return interpretBoolean(tree.child(1), tags) && interpretBoolean(tree.child(2), tags); + if ("or".equals(tree.child(0).value)) + return interpretBoolean(tree.child(1), tags) || interpretBoolean(tree.child(2), tags); throw new RuntimeException("Expected a single tag, but got: " + tree); } diff --git a/src/edu/stanford/nlp/sempre/JavaExecutor.java b/src/edu/stanford/nlp/sempre/JavaExecutor.java index 4a84e56..53259e1 100644 --- a/src/edu/stanford/nlp/sempre/JavaExecutor.java +++ b/src/edu/stanford/nlp/sempre/JavaExecutor.java @@ -3,12 +3,13 @@ package edu.stanford.nlp.sempre; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import fig.basic.MapUtils; +import fig.basic.Option; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.Arrays; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; @@ -19,6 +20,12 @@ import java.util.Map; * @author Percy Liang */ public class JavaExecutor extends Executor { + public static class Options { + @Option(gloss = "Whether to convert NumberValue to int/double") public boolean convertNumberValues = true; + @Option(gloss = "Print stack trace on exception") public boolean printStackTrace = false; + } + public static Options opts = new Options(); + private static JavaExecutor defaultExecutor = new JavaExecutor(); // To simplify logical forms, define some shortcuts. @@ -78,6 +85,12 @@ public class JavaExecutor extends Executor { public static String plus(String a, String b, String c, String d, String e) { return a + b + c + d + e; } + public static String plus(String a, String b, String c, String d, String e, String f) { + return a + b + c + d + e + f; + } + public static String plus(String a, String b, String c, String d, String e, String f, String g) { + return a + b + c + d + e + f + g; + } private static String toString(Object x) { if (x instanceof String) return (String) x; @@ -146,7 +159,8 @@ public class JavaExecutor extends Executor { try { return new Response(toValue(processFormula(formula))); } catch (Exception e) { - e.printStackTrace(); + // Comment this out if we expect lots of innocuous type checking failures + if (opts.printStackTrace) e.printStackTrace(); return new Response(ErrorValue.badJava(e.toString())); } } @@ -156,6 +170,7 @@ public class JavaExecutor extends Executor { return toObject(((ValueFormula) formula).value); if (formula instanceof CallFormula) { // Invoke the function. + //LogInfo.logs("formula=%s", formula); // Recurse CallFormula call = (CallFormula) formula; Object func = processFormula(call.func); @@ -170,8 +185,9 @@ public class JavaExecutor extends Executor { String id = ((NameValue) func).id; id = MapUtils.get(shortcuts, id, id); - if (id.startsWith(".")) // Instance method + if (id.startsWith(".")) // Instance method return invoke(id.substring(1), args.get(0), args.subList(1, args.size()).toArray(new Object[0])); + else // Static method return invoke(id, null, args.toArray(new Object[0])); } @@ -199,7 +215,7 @@ public class JavaExecutor extends Executor { // Convert a Value (which are specified in the formulas) to an Object (which // many Java functions take). private static Object toObject(Value value) { - if (value instanceof NumberValue) { + if (value instanceof NumberValue && opts.convertNumberValues) { // Unfortunately, NumberValues don't make a distinction between ints and // doubles, so this is a hack. double x = ((NumberValue) value).value; @@ -252,6 +268,7 @@ public class JavaExecutor extends Executor { int bestCost = INVALID_TYPE_COST; for (Method m : methods) { if (!m.getName().equals(methodName)) continue; + m.setAccessible(true); nameMatches.add(m); if (isStatic != Modifier.isStatic(m.getModifiers())) continue; int cost = typeCastCost(m.getParameterTypes(), args); diff --git a/src/edu/stanford/nlp/sempre/JoinFn.java b/src/edu/stanford/nlp/sempre/JoinFn.java index b5122fd..95220be 100644 --- a/src/edu/stanford/nlp/sempre/JoinFn.java +++ b/src/edu/stanford/nlp/sempre/JoinFn.java @@ -24,6 +24,8 @@ public class JoinFn extends SemanticFn { @Option(gloss = "Verbose") public int verbose = 0; @Option public boolean showTypeCheckFailures = false; @Option public boolean typeInference = false; + // TODO(joberant): this flag is for backward compatibility. If we don't + // need it for the new results, get rid of it. @Option public boolean specializedTypeCheck = true; } diff --git a/src/edu/stanford/nlp/sempre/JoinFormula.java b/src/edu/stanford/nlp/sempre/JoinFormula.java index b4be44e..eeee10c 100644 --- a/src/edu/stanford/nlp/sempre/JoinFormula.java +++ b/src/edu/stanford/nlp/sempre/JoinFormula.java @@ -51,6 +51,7 @@ public class JoinFormula extends Formula { return res; } + @SuppressWarnings({"equalshashcode"}) @Override public boolean equals(Object thatObj) { if (!(thatObj instanceof JoinFormula)) return false; diff --git a/src/edu/stanford/nlp/sempre/KnowledgeGraph.java b/src/edu/stanford/nlp/sempre/KnowledgeGraph.java index 3934d2f..ce06b2c 100644 --- a/src/edu/stanford/nlp/sempre/KnowledgeGraph.java +++ b/src/edu/stanford/nlp/sempre/KnowledgeGraph.java @@ -25,8 +25,13 @@ public abstract class KnowledgeGraph { String className = tree.child(1).value; Class classObject = Class.forName(SempreUtils.resolveClassName(className)); return (KnowledgeGraph) classObject.getDeclaredMethod("fromLispTree", LispTree.class).invoke(null, tree); - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException - | NoSuchMethodException | SecurityException | ClassNotFoundException e) { + } catch (InvocationTargetException e) { + e.getCause().printStackTrace(); + LogInfo.fail(e.getCause()); + throw new RuntimeException(e); + } catch (IllegalAccessException | IllegalArgumentException | + NoSuchMethodException | SecurityException | ClassNotFoundException e) { + e.printStackTrace(); throw new RuntimeException(e); } } else { diff --git a/src/edu/stanford/nlp/sempre/LambdaFormula.java b/src/edu/stanford/nlp/sempre/LambdaFormula.java index 57239a1..7f83b46 100644 --- a/src/edu/stanford/nlp/sempre/LambdaFormula.java +++ b/src/edu/stanford/nlp/sempre/LambdaFormula.java @@ -39,6 +39,7 @@ public class LambdaFormula extends Formula { return res; } + @SuppressWarnings({"equalshashcode"}) @Override public boolean equals(Object thatObj) { if (!(thatObj instanceof LambdaFormula)) return false; diff --git a/src/edu/stanford/nlp/sempre/LanguageInfo.java b/src/edu/stanford/nlp/sempre/LanguageInfo.java index c469c43..1bece1d 100644 --- a/src/edu/stanford/nlp/sempre/LanguageInfo.java +++ b/src/edu/stanford/nlp/sempre/LanguageInfo.java @@ -35,6 +35,9 @@ public class LanguageInfo implements MemUsage.Instrumented { public final List nerValues; // NER values (contains times, dates, etc.) private Map lemmaSpans; + private Set lowercasedSpans; + + public static class DependencyEdge { @JsonProperty @@ -291,7 +294,7 @@ public class LanguageInfo implements MemUsage.Instrumented { public Map getLemmaSpans() { if (lemmaSpans == null) { - lemmaSpans = new HashMap(); + lemmaSpans = new HashMap<>(); for (int i = 0; i < numTokens() - 1; ++i) { for (int j = i + 1; j < numTokens(); ++j) lemmaSpans.put(lemmaPhrase(i, j), new IntPair(i, j)); @@ -300,6 +303,17 @@ public class LanguageInfo implements MemUsage.Instrumented { return lemmaSpans; } + public Set getLowerCasedSpans() { + if (lowercasedSpans == null) { + lowercasedSpans = new HashSet<>(); + for (int i = 0; i < numTokens() - 1; ++i) { + for (int j = i + 1; j < numTokens(); ++j) + lowercasedSpans.add(phrase(i, j).toLowerCase()); + } + } + return lowercasedSpans; + } + public boolean matchLemmas(List wordInfos) { for (int i = 0; i < numTokens(); ++i) { if (matchLemmasFromIndex(wordInfos, i)) @@ -324,6 +338,7 @@ public class LanguageInfo implements MemUsage.Instrumented { * */ public static class LanguageUtils { + public static boolean sameProperNounClass(String noun1, String noun2) { if ((noun1.equals("NNP") || noun1.equals("NNPS")) && (noun2.equals("NNP") || noun2.equals("NNPS"))) @@ -335,6 +350,10 @@ public class LanguageInfo implements MemUsage.Instrumented { return pos.startsWith("NNP"); } + public static boolean isSuperlative(String pos) { return pos.equals("RBS") || pos.equals("JJS"); } + public static boolean isComparative(String pos) { return pos.equals("RBR") || pos.equals("JJR"); } + + public static boolean isEntity(LanguageInfo info, int i) { return isProperNoun(info.posTags.get(i)) || !(info.nerTags.get(i).equals("O")); } @@ -361,6 +380,30 @@ public class LanguageInfo implements MemUsage.Instrumented { if (pos.startsWith("W")) return "W"; return pos; } + + // Uses a few rules to stem tokens + public static String stem(String a) { + int i = a.indexOf(' '); + if (i != -1) + return stem(a.substring(0, i)) + ' ' + stem(a.substring(i + 1)); + //Maybe we should just use the Stanford stemmer + String res = a; + //hard coded words + if (a.equals("having") || a.equals("has")) res = "have"; + else if (a.equals("using")) res = "use"; + else if (a.equals("including")) res = "include"; + else if (a.equals("beginning")) res = "begin"; + else if (a.equals("utilizing")) res = "utilize"; + else if (a.equals("featuring")) res = "feature"; + else if (a.equals("preceding")) res = "precede"; + //rules + else if (a.endsWith("ing")) res = a.substring(0, a.length() - 3); + else if (a.endsWith("s") && !a.equals("'s")) res = a.substring(0, a.length() - 1); + //don't return an empty string + if (res.length() > 0) return res; + return a; + } + } @Override diff --git a/src/edu/stanford/nlp/sempre/Learner.java b/src/edu/stanford/nlp/sempre/Learner.java index 9a89938..302ce1f 100644 --- a/src/edu/stanford/nlp/sempre/Learner.java +++ b/src/edu/stanford/nlp/sempre/Learner.java @@ -59,11 +59,11 @@ public class Learner { this.params = params; this.dataset = dataset; this.eventsOut = IOUtils.openOutAppendEasy(Execution.getFile("learner.events")); - if (opts.initialization != null) + if (opts.initialization != null && this.params.isEmpty()) this.params.init(opts.initialization); - // collect all semantic functions to update - semFuncsToUpdate = new ArrayList<>(); + // Collect all semantic functions to update. + semFuncsToUpdate = new ArrayList<>(); for (Rule rule : parser.grammar.getRules()) { SemanticFn currSemFn = rule.getSem(); boolean toAdd = true; @@ -125,11 +125,6 @@ public class Learner { Utils.systemHard("ln -sf params." + iter + " " + Execution.getFile("params")); } - // Write out examples and predictions - if (opts.outputPredDerivations) - for (String group : dataset.groups()) - ExampleUtils.writeSDF(iter, group, meanEvaluations.get(group), dataset.examples(group), opts.outputPredDerivations); - LogInfo.end_track(); } LogInfo.end_track(); @@ -199,6 +194,11 @@ public class Learner { if (opts.addFeedback && computeExpectedCounts) addFeedback(ex); + // Write out examples and predictions + if (opts.outputPredDerivations && Builder.opts.parser.equals("FloatingParser")) { + ExampleUtils.writeParaphraseSDF(iter, group, ex, opts.outputPredDerivations); + } + // To save memory ex.predDerivations.clear(); } @@ -212,6 +212,7 @@ public class Learner { LogInfo.end_track(); logEvaluationStats(evaluation, prefix); printLearnerEventsSummary(evaluation, iter, group); + ExampleUtils.writeEvaluationSDF(iter, group, evaluation, examples.size()); LogInfo.end_track(); return evaluation; } diff --git a/src/edu/stanford/nlp/sempre/MarkFormula.java b/src/edu/stanford/nlp/sempre/MarkFormula.java index e5ad4b3..3b4704e 100644 --- a/src/edu/stanford/nlp/sempre/MarkFormula.java +++ b/src/edu/stanford/nlp/sempre/MarkFormula.java @@ -46,6 +46,7 @@ public class MarkFormula extends Formula { return res; } + @SuppressWarnings({"equalshashcode"}) @Override public boolean equals(Object thatObj) { if (!(thatObj instanceof MarkFormula)) return false; diff --git a/src/edu/stanford/nlp/sempre/Master.java b/src/edu/stanford/nlp/sempre/Master.java index f26738c..3847dd4 100644 --- a/src/edu/stanford/nlp/sempre/Master.java +++ b/src/edu/stanford/nlp/sempre/Master.java @@ -19,11 +19,16 @@ public class Master { public static class Options { @Option(gloss = "Execute these commands before starting") public List scriptPaths = Lists.newArrayList(); - @Option(gloss = "Write out input lines to this path") + @Option(gloss = "Execute these commands before starting (after scriptPaths)") + public List commands = Lists.newArrayList(); + @Option(gloss = "Write a log of this session to this path") public String logPath; + @Option(gloss = "Print help on startup") + public boolean printHelp = true; + @Option(gloss = "Number of exchanges to keep in the context") - public int contextMaxExchanges = 1; + public int contextMaxExchanges = 0; @Option(gloss = "Online update weights on new examples.") public boolean onlineLearnExamples = true; @@ -93,6 +98,8 @@ public class Master { session = new Session(id); for (String path : opts.scriptPaths) processScript(session, path); + for (String command : opts.commands) + processQuery(session, command); if (id != null) sessions.put(id, session); } @@ -116,13 +123,14 @@ public class Master { LogInfo.log(" (execute |logical form|): execute the logical form (e.g., (execute (call + (number 3) (number 4))))"); LogInfo.log(" (def |key| |value|): define a macro to replace |key| with |value| in all commands (e.g., (def type fb:type.object type)))"); LogInfo.log(" (context [(user |user|) (date |date|) (exchange |exchange|) (graph |graph|)]): prints out or set the context"); + LogInfo.log("Press Ctrl-D to exit."); } public void runInteractivePrompt() { Session session = getSession("stdin"); - printHelp(); - LogInfo.log("Press Ctrl-D to exit."); + if (opts.printHelp) + printHelp(); while (true) { LogInfo.stdout.print("> "); @@ -215,12 +223,13 @@ public class Master { b.setContext(session.context); Example ex = b.createExample(); - ex.preprocess(LanguageAnalyzer.getSingleton()); + ex.preprocess(); // Parse! builder.parser.parse(builder.params, ex, false); response.ex = ex; + ex.log(); if (ex.predDerivations.size() > 0) { response.candidateIndex = 0; printDerivation(response.getDerivation()); diff --git a/src/edu/stanford/nlp/sempre/MergeFormula.java b/src/edu/stanford/nlp/sempre/MergeFormula.java index 7153ef9..c0909a0 100644 --- a/src/edu/stanford/nlp/sempre/MergeFormula.java +++ b/src/edu/stanford/nlp/sempre/MergeFormula.java @@ -51,6 +51,7 @@ public class MergeFormula extends Formula { return null; } + @SuppressWarnings({"equalshashcode"}) @Override public boolean equals(Object thatObj) { if (!(thatObj instanceof MergeFormula)) return false; diff --git a/src/edu/stanford/nlp/sempre/NaiveKnowledgeGraph.java b/src/edu/stanford/nlp/sempre/NaiveKnowledgeGraph.java index 3bef98a..2482a77 100644 --- a/src/edu/stanford/nlp/sempre/NaiveKnowledgeGraph.java +++ b/src/edu/stanford/nlp/sempre/NaiveKnowledgeGraph.java @@ -50,6 +50,11 @@ public class NaiveKnowledgeGraph extends KnowledgeGraph { tree.addChild(e2.toLispTree()); return tree; } + + @Override + public String toString() { + return "<" + e1 + ", " + r + ", " + e2 + ">"; + } } // Simplest graph representation: triples of values diff --git a/src/edu/stanford/nlp/sempre/NotFormula.java b/src/edu/stanford/nlp/sempre/NotFormula.java index 80a715f..7f37c8c 100644 --- a/src/edu/stanford/nlp/sempre/NotFormula.java +++ b/src/edu/stanford/nlp/sempre/NotFormula.java @@ -35,6 +35,7 @@ public class NotFormula extends Formula { return res; } + @SuppressWarnings({"equalshashcode"}) @Override public boolean equals(Object thatObj) { if (!(thatObj instanceof NotFormula)) return false; diff --git a/src/edu/stanford/nlp/sempre/NumberFn.java b/src/edu/stanford/nlp/sempre/NumberFn.java index 032656a..2c31620 100644 --- a/src/edu/stanford/nlp/sempre/NumberFn.java +++ b/src/edu/stanford/nlp/sempre/NumberFn.java @@ -10,6 +10,13 @@ import fig.basic.*; * @author Percy Liang */ public class NumberFn extends SemanticFn { + public static class Options { + @Option(gloss = "Omit units") public boolean unitless = false; + @Option(gloss = "Also test numbers by try converting to float (instead of using NER tags)") + public boolean alsoTestByConversion = false; + } + public static Options opts = new Options(); + private List requests; // List of types of fields to get (e.g., NUMBER) private boolean request(String req) { @@ -37,10 +44,10 @@ public class NumberFn extends SemanticFn { NumberValue numberValue = new NumberValue(Double.parseDouble(value)); SemType type = numberValue.value == (int) numberValue.value ? SemType.intType : SemType.floatType; return new Derivation.Builder() - .withCallable(c) - .formula(new ValueFormula<>(numberValue)) - .type(type) - .createDerivation(); + .withCallable(c) + .formula(new ValueFormula<>(numberValue)) + .type(type) + .createDerivation(); } catch (NumberFormatException e) { LogInfo.warnings("NumberFn: Cannot convert NerSpan \"%s\" to a number", value); } @@ -52,13 +59,15 @@ public class NumberFn extends SemanticFn { String value = ex.languageInfo.getNormalizedNerSpan("ORDINAL", c.getStart(), c.getEnd()); if (value != null) { try { - NumberValue numberValue = new NumberValue(Double.parseDouble(value), "fb:en.ordinal_number"); + NumberValue numberValue = (opts.unitless ? + new NumberValue(Double.parseDouble(value)) : + new NumberValue(Double.parseDouble(value), "fb:en.ordinal_number")); SemType type = SemType.intType; return new Derivation.Builder() - .withCallable(c) - .formula(new ValueFormula<>(numberValue)) - .type(type) - .createDerivation(); + .withCallable(c) + .formula(new ValueFormula<>(numberValue)) + .type(type) + .createDerivation(); } catch (NumberFormatException e) { LogInfo.warnings("NumberFn: Cannot convert NerSpan \"%s\" to a number", value); } @@ -70,13 +79,15 @@ public class NumberFn extends SemanticFn { String value = ex.languageInfo.getNormalizedNerSpan("PERCENT", c.getStart(), c.getEnd()); if (value != null) { try { - NumberValue numberValue = new NumberValue(0.01 * Double.parseDouble(value.substring(1))); + NumberValue numberValue = (opts.unitless ? + new NumberValue(Double.parseDouble(value.substring(1))) : + new NumberValue(0.01 * Double.parseDouble(value.substring(1)))); SemType type = SemType.floatType; return new Derivation.Builder() - .withCallable(c) - .formula(new ValueFormula<>(numberValue)) - .type(type) - .createDerivation(); + .withCallable(c) + .formula(new ValueFormula<>(numberValue)) + .type(type) + .createDerivation(); } catch (NumberFormatException e) { LogInfo.warnings("NumberFn: Cannot convert NerSpan \"%s\" to a number", value); } @@ -88,19 +99,39 @@ public class NumberFn extends SemanticFn { String value = ex.languageInfo.getNormalizedNerSpan("MONEY", c.getStart(), c.getEnd()); if (value != null) { try { - NumberValue numberValue = new NumberValue(Double.parseDouble(value.substring(1)), "fb:en.dollar"); + NumberValue numberValue = (opts.unitless ? + new NumberValue(Double.parseDouble(value.substring(1))) : + new NumberValue(Double.parseDouble(value.substring(1)), "fb:en.dollar")); SemType type = SemType.floatType; return new Derivation.Builder() - .withCallable(c) - .formula(new ValueFormula<>(numberValue)) - .type(type) - .createDerivation(); + .withCallable(c) + .formula(new ValueFormula<>(numberValue)) + .type(type) + .createDerivation(); } catch (NumberFormatException e) { LogInfo.warnings("NumberFn: Cannot convert NerSpan \"%s\" to a number", value); } } } + // Test by converting string to number directly (don't look at NER) + if (opts.alsoTestByConversion && request("NUMBER") & c.getEnd() - c.getStart() == 1) { + String value = ex.languageInfo.tokens.get(c.getStart()); + if (value != null) { + try { + NumberValue numberValue = new NumberValue(Double.parseDouble(value)); + SemType type = numberValue.value == (int) numberValue.value ? SemType.intType : SemType.floatType; + return new Derivation.Builder() + .withCallable(c) + .formula(new ValueFormula<>(numberValue)) + .type(type) + .createDerivation(); + } catch (NumberFormatException e) { + // Don't issue warnings; most spans are not numbers + } + } + } + return null; } }; diff --git a/src/edu/stanford/nlp/sempre/Params.java b/src/edu/stanford/nlp/sempre/Params.java index e756051..46441cf 100644 --- a/src/edu/stanford/nlp/sempre/Params.java +++ b/src/edu/stanford/nlp/sempre/Params.java @@ -33,6 +33,7 @@ public class Params { @Option(gloss = "Use dual averaging") public boolean dualAveraging = false; @Option(gloss = "Whether to do lazy l1 reg updates") public String l1Reg = "none"; @Option(gloss = "L1 reg coefficient") public double l1RegCoeff = 0d; + @Option(gloss = "Lazy L1 full update frequency") public int lazyL1FullUpdateFreq = 5000; } public static Options opts = new Options(); public enum L1Reg { @@ -49,7 +50,7 @@ public class Params { private L1Reg l1Reg = parseReg(opts.l1Reg); // Discriminative weights - private HashMap weights = new HashMap<>(); + private Map weights = new HashMap<>(); // For AdaGrad Map sumSquaredGradients = new HashMap<>(); @@ -110,13 +111,12 @@ public class Params { // Update weights by adding |gradient| (modified appropriately with step size). public synchronized void update(Map gradient) { - numUpdates++; - for (Map.Entry entry : gradient.entrySet()) { String f = entry.getKey(); double g = entry.getValue(); if (g * g == 0) continue; // In order to not divide by zero + if (l1Reg == L1Reg.LAZY) lazyL1Update(f); double stepSize = computeStepSize(f, g); if (opts.dualAveraging) { @@ -132,17 +132,25 @@ public class Params { throw new RuntimeException("Gradient absolute value is too large or too small"); } MapUtils.incr(weights, f, stepSize * g); + if (l1Reg == L1Reg.LAZY) l1UpdateTimeMap.put(f, numUpdates); } } // non lazy implementation goes over all weights if (l1Reg == L1Reg.NONLAZY) { Set features = new HashSet(weights.keySet()); - for (String f :features) { + for (String f : features) { double stepSize = computeStepSize(f, 0d); // no update for gradient here double update = opts.l1RegCoeff * -Math.signum(MapUtils.getDouble(weights, f, opts.defaultWeight)); clipUpdate(f, stepSize * update); } } + numUpdates++; + if (l1Reg == L1Reg.LAZY && opts.lazyL1FullUpdateFreq > 0 && numUpdates % opts.lazyL1FullUpdateFreq == 0) { + LogInfo.begin_track("Fully apply L1 regularization."); + finalizeWeights(); + System.gc(); + LogInfo.end_track(); + } } private double computeStepSize(String feature, double gradient) { @@ -174,20 +182,24 @@ public class Params { } private void lazyL1Update(String f) { + if (MapUtils.getDouble(weights, f, 0.0) == 0) return; + // For pre-initialized weights, which have no updates yet if (sumSquaredGradients.get(f) == null || l1UpdateTimeMap.get(f) == null) { l1UpdateTimeMap.put(f, numUpdates); + sumSquaredGradients.put(f, 0.0); return; } - int numOfIter = numUpdates - l1UpdateTimeMap.get(f); - if (numOfIter <= 0) { - l1UpdateTimeMap.put(f, numUpdates); - return; - } + int numOfIter = numUpdates - MapUtils.get(l1UpdateTimeMap, f, 0); + if (numOfIter == 0) return; + if (numOfIter < 0) throw new RuntimeException("l1UpdateTimeMap is out of sync."); double stepSize = (numOfIter * opts.initStepSize) / (Math.sqrt(sumSquaredGradients.get(f) + 1)); double update = -opts.l1RegCoeff * Math.signum(MapUtils.getDouble(weights, f, 0.0)); clipUpdate(f, stepSize * update); - l1UpdateTimeMap.put(f, numUpdates); + if (weights.containsKey(f)) + l1UpdateTimeMap.put(f, numUpdates); + else + l1UpdateTimeMap.remove(f); } public synchronized double getWeight(String f) { @@ -200,7 +212,7 @@ public class Params { } } - public synchronized Map getWeights() { return weights; } + public synchronized Map getWeights() { finalizeWeights(); return weights; } public void write(PrintWriter out) { write(null, out); } diff --git a/src/edu/stanford/nlp/sempre/ParaphraseModel.java b/src/edu/stanford/nlp/sempre/ParaphraseModel.java deleted file mode 100644 index be7ac13..0000000 --- a/src/edu/stanford/nlp/sempre/ParaphraseModel.java +++ /dev/null @@ -1,75 +0,0 @@ -package edu.stanford.nlp.sempre; - -import java.util.HashMap; -import java.util.Map; - -import fig.basic.IOUtils; -import fig.basic.Option; -import fig.basic.MapUtils; -import fig.basic.LogInfo; - -/** - * ParaphraseModel extracts and scores paraphrasing featues from derivations. - * This model is intended to be used with FloatingParser - * - * @author Yushi Wang - */ - -public final class ParaphraseModel { - public static class Options { - @Option(gloss = "Path to file with alignment table") - public String paraphraseModelPath = "regex/regex-alignments.txt"; - } - - public static Options opts = new Options(); - - public static ParaphraseModel model; - - Map> table; - - // We should only have one paraphrase model - public static ParaphraseModel getSingleton() { - if (model == null) { - model = new ParaphraseModel(); - } - return model; - } - - private ParaphraseModel() { - table = loadParaphraseModel(opts.paraphraseModelPath); - } - - /** - * Loading paraphrase model from file - */ - private Map> loadParaphraseModel(String path) { - LogInfo.begin_track("Loading paraphrase model"); - Map> res = new HashMap>(); - for (String line: IOUtils.readLinesHard(path)) { - String[] tokens = line.split("\t"); - // double count = Double.parseDouble(tokens[2]); - MapUtils.putIfAbsent(res, tokens[0], new HashMap()); - for (String token : tokens) - LogInfo.logs("%s", token); - res.get(tokens[0]).put(tokens[1], 1.0); - /* Alignment stats don't matter right now - if(count>=threshold) { - AlignmentStats aStats = new AlignmentStats(count, Double.parseDouble(tokens[3]), Double.parseDouble(tokens[4])); - res.get(tokens[0]).put(tokens[1], aStats); - } - */ - } - LogInfo.logs("ParaphraseUtils.loadPhraseTable: number of entries=%s", res.size()); - LogInfo.end_track(); - return res; - } - - public boolean containsKey(String key) { - return table.containsKey(key); - } - - public Double get(String key, String token) { - if (!table.containsKey(key) || !table.get(key).containsKey(token)) return 0.0; - return table.get(key).get(token); - } -} diff --git a/src/edu/stanford/nlp/sempre/Parser.java b/src/edu/stanford/nlp/sempre/Parser.java index 064539d..7b28ae0 100644 --- a/src/edu/stanford/nlp/sempre/Parser.java +++ b/src/edu/stanford/nlp/sempre/Parser.java @@ -21,6 +21,8 @@ public abstract class Parser { @Option(gloss = "Maximal number of predictions to print") public int maxPrintedPredictions = Integer.MAX_VALUE; + @Option(gloss = "Maximal number of correct predictions to print") + public int maxPrintedTrue = Integer.MAX_VALUE; @Option(gloss = "Use a coarse pass to prune the chart before full parsing") public boolean coarsePrune = false; @@ -42,6 +44,18 @@ public abstract class Parser { @Option(gloss = "Whether to unroll derivation streams (applies to lazy parsers)") public boolean unrollStream = false; + + @Option(gloss = "Inject random noise into the score (to mix things up a bit)") + public double derivationScoreNoise = 0; + + @Option(gloss = "Source of random noise") + public Random derivationScoreRandom = new Random(1); + + @Option (gloss = "Prune away error denotations") + public boolean pruneErrorValues = false; + + @Option(gloss = "Dump all features (for debugging)") + public boolean dumpAllFeatures = false; } public static final Options opts = new Options(); @@ -155,6 +169,11 @@ public abstract class Parser { ex.evaluation = new Evaluation(); addToEvaluation(state, ex.evaluation); + // Clean up temporary state used during parsing + ex.clearTempState(); + for (Derivation deriv : ex.predDerivations) + deriv.clearTempState(); + return state; } @@ -214,7 +233,6 @@ public abstract class Parser { double topMass = 0; if (ex.targetValue != null) { while (numTop < numCandidates && - compatibilities[numTop] > 0.0d && Math.abs(predDerivations.get(numTop).score - predDerivations.get(0).score) < 1e-10) { topMass += probs[numTop]; numTop++; @@ -248,21 +266,33 @@ public abstract class Parser { } // Fully correct + int numPrintedSoFar = 0; for (int i = 0; i < predDerivations.size(); i++) { Derivation deriv = predDerivations.get(i); if (compatibilities != null && compatibilities[i] == 1) { - LogInfo.logs( - "True@%04d: %s [score=%s, prob=%s%s]", i, deriv.toString(), - Fmt.D(deriv.score), Fmt.D(probs[i]), compatibilities != null ? ", comp=" + Fmt.D(compatibilities[i]) : ""); + boolean print = printAllPredictions || (numPrintedSoFar < opts.maxPrintedTrue); + if (print) { + LogInfo.logs( + "True@%04d: %s [score=%s, prob=%s%s]", i, deriv.toString(), + Fmt.D(deriv.score), Fmt.D(probs[i]), compatibilities != null ? ", comp=" + Fmt.D(compatibilities[i]) : ""); + numPrintedSoFar++; + if (opts.dumpAllFeatures) FeatureVector.logFeatureWeights("Features", deriv.getAllFeatureVector(), state.params); + } } } // Partially correct + numPrintedSoFar = 0; for (int i = 0; i < predDerivations.size(); i++) { Derivation deriv = predDerivations.get(i); if (compatibilities != null && compatibilities[i] > 0 && compatibilities[i] < 1) { - LogInfo.logs( - "Part@%04d: %s [score=%s, prob=%s%s]", i, deriv.toString(), - Fmt.D(deriv.score), Fmt.D(probs[i]), compatibilities != null ? ", comp=" + Fmt.D(compatibilities[i]) : ""); + boolean print = printAllPredictions || (numPrintedSoFar < opts.maxPrintedTrue); + if (print) { + LogInfo.logs( + "Part@%04d: %s [score=%s, prob=%s%s]", i, deriv.toString(), + Fmt.D(deriv.score), Fmt.D(probs[i]), compatibilities != null ? ", comp=" + Fmt.D(compatibilities[i]) : ""); + numPrintedSoFar++; + if (opts.dumpAllFeatures) FeatureVector.logFeatureWeights("Features", deriv.getAllFeatureVector(), state.params); + } } } // Anything that's predicted. @@ -276,6 +306,7 @@ public abstract class Parser { "Pred@%04d: %s [score=%s, prob=%s%s]", i, deriv.toString(), Fmt.D(deriv.score), Fmt.D(probs[i]), compatibilities != null ? ", comp=" + Fmt.D(compatibilities[i]) : ""); // LogInfo.logs("Derivation tree: %s", deriv.toRecursiveString()); + if (opts.dumpAllFeatures) FeatureVector.logFeatureWeights("Features", deriv.getAllFeatureVector(), state.params); } } diff --git a/src/edu/stanford/nlp/sempre/ParserState.java b/src/edu/stanford/nlp/sempre/ParserState.java index 75debf0..def6570 100644 --- a/src/edu/stanford/nlp/sempre/ParserState.java +++ b/src/edu/stanford/nlp/sempre/ParserState.java @@ -4,6 +4,7 @@ import fig.basic.Fmt; import fig.basic.LogInfo; import fig.basic.NumUtils; import fig.basic.Evaluation; +import fig.basic.Option; import java.util.ArrayList; import java.util.List; @@ -16,6 +17,14 @@ import java.util.Map; * @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; + } + public static Options opts = new Options(); + + public enum CustomExpectedCount { NONE, UNIFORM, TOP, RANDOM, } + //// Input: specification of how to parse public final Parser parser; @@ -41,8 +50,6 @@ public abstract class ParserState { public int totalGeneratedDerivs; // Total number of derivations produced public int numOfFeaturizedDerivs = 0; // Number of derivations featured - - @SuppressWarnings({ "unchecked" }) public ParserState(Parser parser, Params params, Example ex, boolean computeExpectedCounts) { this.parser = parser; this.params = params; @@ -70,7 +77,7 @@ public abstract class ParserState { 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); + Fmt.D(deriv.score), deriv.cat, ex.spanString(deriv.start, deriv.end), deriv, deriv.rule); } numOfFeaturizedDerivs++; } @@ -113,8 +120,26 @@ public abstract class ParserState { 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]", deriv.cat, deriv.start, deriv.end, deriv.formula, + deriv.canonicalUtterance, deriv.score); + } + + + LogInfo.end_track(); + } + // Max beam position (after sorting) i = 0; for (Derivation deriv : derivations) { @@ -142,46 +167,46 @@ public abstract class ParserState { // 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()); + 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()); + 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()); + 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()); + 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; @@ -231,12 +256,36 @@ public abstract class ParserState { 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)); - trueScores[i] = deriv.score + logReward; - predScores[i] = deriv.score; + 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. @@ -247,7 +296,45 @@ public abstract class ParserState { 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 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; + } + } + } + return (chosenGood == -1 || chosenBad == -1) ? null : new int[] {chosenGood, chosenBad}; + } + + private static int[] getRandomDerivations(List derivations) { + int chosenGood = -1, chosenBad = -1, numGoodSoFar = 0, numBadSoFar = 0; + 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}; + } } diff --git a/src/edu/stanford/nlp/sempre/ReinforcementParser.java b/src/edu/stanford/nlp/sempre/ReinforcementParser.java index 66d2e06..4ca9408 100644 --- a/src/edu/stanford/nlp/sempre/ReinforcementParser.java +++ b/src/edu/stanford/nlp/sempre/ReinforcementParser.java @@ -412,7 +412,7 @@ final class ReinforcementParserState extends AbstractReinforcementParserState { updateBackpointers(pds.derivStream, nextDeriv); DerivationStream derivStream = SingleDerivationStream.constant(nextDeriv); - if (parser.verbose(3)) { + if (parser.verbose(3) && derivStream.hasNext()) { Derivation deriv = derivStream.peek(); LogInfo.logs("unrollHighProbStreams(): add deriv=%s(%s,%s) [%s] score=%s, |stream|=%s", deriv.cat, deriv.start, deriv.end, deriv.formula, deriv.score, pds.derivStream.estimatedSize()); @@ -820,7 +820,7 @@ final class ReinforcementParserState extends AbstractReinforcementParserState { modified = true; Derivation nextDeriv = pds.derivStream.next(); DerivationStream newDerivStream = SingleDerivationStream.constant(nextDeriv); - if (parser.verbose(3)) { + if (parser.verbose(3) && newDerivStream.hasNext()) { Derivation deriv = newDerivStream.peek(); LogInfo.logs("MultiplicativeSampler.unroll(): add necessary deriv=%s(%s,%s) [%s] score=%s, |stream|=%s, creationIndex=%s", deriv.cat, deriv.start, deriv.end, deriv.formula, deriv.score, pds.derivStream.estimatedSize(), deriv.creationIndex); diff --git a/src/edu/stanford/nlp/sempre/ReverseFormula.java b/src/edu/stanford/nlp/sempre/ReverseFormula.java index 3dbc698..395f58c 100644 --- a/src/edu/stanford/nlp/sempre/ReverseFormula.java +++ b/src/edu/stanford/nlp/sempre/ReverseFormula.java @@ -39,6 +39,7 @@ public class ReverseFormula extends Formula { return res; } + @SuppressWarnings({"equalshashcode"}) @Override public boolean equals(Object thatObj) { if (!(thatObj instanceof ReverseFormula)) return false; diff --git a/src/edu/stanford/nlp/sempre/Rule.java b/src/edu/stanford/nlp/sempre/Rule.java index 12f7ba7..2094546 100644 --- a/src/edu/stanford/nlp/sempre/Rule.java +++ b/src/edu/stanford/nlp/sempre/Rule.java @@ -75,6 +75,16 @@ public class Rule { return true; } + // Return the number of categories on the RHS + public int numRhsCats() { + int ret = 0; + for (int i = 0; i < rhs.size(); ++i) { + if (isCat(rhs.get(i))) + ret++; + } + return ret; + } + public LispTree toLispTree() { LispTree tree = LispTree.proto.newList(); tree.addChild("rule"); diff --git a/src/edu/stanford/nlp/sempre/SemType.java b/src/edu/stanford/nlp/sempre/SemType.java index fc5be94..a89a44b 100644 --- a/src/edu/stanford/nlp/sempre/SemType.java +++ b/src/edu/stanford/nlp/sempre/SemType.java @@ -102,6 +102,7 @@ public abstract class SemType { public static final SemType intType = new AtomicSemType(CanonicalNames.INT); public static final SemType floatType = new AtomicSemType(CanonicalNames.FLOAT); public static final SemType dateType = new AtomicSemType(CanonicalNames.DATE); + public static final SemType timeType = new AtomicSemType(CanonicalNames.TIME); public static final SemType numberType = new AtomicSemType(CanonicalNames.NUMBER); public static final SemType numberOrDateType = new UnionSemType(numberType, dateType); public static final SemType entityType = new AtomicSemType(CanonicalNames.ENTITY); diff --git a/src/edu/stanford/nlp/sempre/SimpleAnalyzer.java b/src/edu/stanford/nlp/sempre/SimpleAnalyzer.java index 86a7a17..ed995ff 100644 --- a/src/edu/stanford/nlp/sempre/SimpleAnalyzer.java +++ b/src/edu/stanford/nlp/sempre/SimpleAnalyzer.java @@ -38,18 +38,36 @@ public class SimpleAnalyzer extends LanguageAnalyzer { utterance = breakHyphens(utterance); // Default analysis - create tokens crudely - utterance = utterance.replaceAll(",", " ,"); - utterance = utterance.replaceAll("\\.", " ."); - utterance = utterance.replaceAll("\\?", " ?"); - utterance = utterance.replaceAll("'", " '"); - utterance = utterance.replaceAll("\\[", " [ "); - utterance = utterance.replaceAll("\\]", " ] "); - utterance = utterance.trim(); + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < utterance.length(); i++) { + char c = utterance.charAt(i); + // Put whitespace around certain characters. + // TODO(pliang): handle contractions such as "can't" properly. + boolean boundaryBefore = !(i - 1 >= 0) || utterance.charAt(i - 1) == ' '; + boolean boundaryAfter = !(i + 1 < utterance.length()) || utterance.charAt(i + 1) == ' '; + boolean separate; + if (c == '.') // Break off period if already space around it (to preserve numbers like 3.5) + separate = boundaryBefore || boundaryAfter; + else + separate = (",?'\"[]".indexOf(c) != -1); + + if (separate) buf.append(' '); + // Convert quotes + if (c == '"') + buf.append(boundaryBefore ? "``" : "''"); + else if (c == '\'') + buf.append(boundaryBefore ? "`" : "'"); + else + buf.append(c); + if (separate) buf.append(' '); + } + utterance = buf.toString().trim(); if (!utterance.equals("")) { - for (String token : utterance.split("\\s+")) { + String[] tokens = utterance.split("\\s+"); + for (String token : tokens) { languageInfo.tokens.add(LanguageAnalyzer.opts.lowerCaseTokens ? token.toLowerCase() : token); String lemma = token; - if (token.endsWith("s")) + if (token.endsWith("s") && token.length() > 1) lemma = token.substring(0, token.length() - 1); languageInfo.lemmaTokens.add(LanguageAnalyzer.opts.lowerCaseTokens ? lemma.toLowerCase() : lemma); @@ -65,15 +83,14 @@ public class SimpleAnalyzer extends LanguageAnalyzer { try { Double.parseDouble(token); languageInfo.posTags.add("CD"); - if (token.length() == 4) - languageInfo.nerTags.add("DATE"); - else - languageInfo.nerTags.add("NUMBER"); + languageInfo.nerTags.add("NUMBER"); languageInfo.nerValues.add(token); } catch (NumberFormatException e) { // Guess that capitalized nouns are proper if (Character.isUpperCase(token.charAt(0))) languageInfo.posTags.add("NNP"); + else if (token.equals("'") || token.equals("\"") || token.equals("''") || token.equals("``")) + languageInfo.posTags.add("''"); else languageInfo.posTags.add("UNK"); languageInfo.nerTags.add("UNK"); diff --git a/src/edu/stanford/nlp/sempre/SuperlativeFormula.java b/src/edu/stanford/nlp/sempre/SuperlativeFormula.java index bc23f9d..3529df6 100644 --- a/src/edu/stanford/nlp/sempre/SuperlativeFormula.java +++ b/src/edu/stanford/nlp/sempre/SuperlativeFormula.java @@ -61,6 +61,7 @@ public class SuperlativeFormula extends Formula { return res; } + @SuppressWarnings({"equalshashcode"}) @Override public boolean equals(Object thatObj) { if (!(thatObj instanceof SuperlativeFormula)) return false; diff --git a/src/edu/stanford/nlp/sempre/TargetValuePreprocessor.java b/src/edu/stanford/nlp/sempre/TargetValuePreprocessor.java new file mode 100644 index 0000000..ac1c817 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/TargetValuePreprocessor.java @@ -0,0 +1,36 @@ +package edu.stanford.nlp.sempre; + +import fig.basic.*; + +/** + * Preprocess the targetValue of an example. + * + * @author ppasupat + */ +public abstract class TargetValuePreprocessor { + public static class Options { + @Option public String targetValuePreprocessor = null; + } + public static Options opts = new Options(); + + private static TargetValuePreprocessor singleton; + + public static TargetValuePreprocessor getSingleton() { + if (singleton == null) { + if (opts.targetValuePreprocessor == null || opts.targetValuePreprocessor.isEmpty()) + singleton = new IdentityTargetValuePreprocessor(); + else + singleton = (TargetValuePreprocessor) Utils.newInstanceHard( + SempreUtils.resolveClassName(opts.targetValuePreprocessor)); + } + return singleton; + } + public static void setSingleton(TargetValuePreprocessor processor) { singleton = processor; } + + public abstract Value preprocess(Value value); + +} + +class IdentityTargetValuePreprocessor extends TargetValuePreprocessor { + public Value preprocess(Value value) { return value; } +} diff --git a/src/edu/stanford/nlp/sempre/TimeValue.java b/src/edu/stanford/nlp/sempre/TimeValue.java new file mode 100644 index 0000000..d4f278a --- /dev/null +++ b/src/edu/stanford/nlp/sempre/TimeValue.java @@ -0,0 +1,51 @@ +package edu.stanford.nlp.sempre; + +import fig.basic.LispTree; + +/** + * Created by joberant on 1/23/15. + * Value for representing time + */ +public class TimeValue extends Value { + + public final int hour; + public final int minute; + + public TimeValue(int hour, int minute) { + if (hour > 23 || hour < 0) throw new RuntimeException("Illegal hour: " + hour); + if (minute > 59 || minute < 0) throw new RuntimeException("Illegal minute: " + minute); + this.hour = hour; + this.minute = minute; + } + + public TimeValue(LispTree tree) { + this.hour = Integer.valueOf(tree.child(1).value); + this.minute = Integer.valueOf(tree.child(2).value); + } + + @Override + public LispTree toLispTree() { + LispTree tree = LispTree.proto.newList(); + tree.addChild("time"); + tree.addChild(String.valueOf(hour)); + tree.addChild(String.valueOf(minute)); + return tree; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TimeValue timeValue = (TimeValue) o; + if (hour != timeValue.hour) return false; + if (minute != timeValue.minute) return false; + return true; + } + + @Override + public int hashCode() { + int result = hour; + result = 31 * result + minute; + return result; + } +} diff --git a/src/edu/stanford/nlp/sempre/TypeInference.java b/src/edu/stanford/nlp/sempre/TypeInference.java index 32897ec..2a54cd0 100644 --- a/src/edu/stanford/nlp/sempre/TypeInference.java +++ b/src/edu/stanford/nlp/sempre/TypeInference.java @@ -1,6 +1,7 @@ package edu.stanford.nlp.sempre; import fig.basic.*; + import java.util.*; /** @@ -56,6 +57,13 @@ public final class TypeInference { } private static final ValueFormula typeFormula = new ValueFormula(new NameValue(CanonicalNames.TYPE)); + + private static final Set comparisonFormulas = new HashSet<>(Arrays.asList( + new ValueFormula(new NameValue("<")), + new ValueFormula(new NameValue(">")), + new ValueFormula(new NameValue("<=")), + new ValueFormula(new NameValue(">=")))); + private static class TypeException extends Exception { } private static class Env { @@ -109,16 +117,6 @@ public final class TypeInference { return type; } - // Unary: fb:domain.type [contains exactly one period] - // Binary: fb:domain.type.property, <, >, etc. - private static boolean isUnary(String s) { - int i = s.indexOf('.'); - if (i == -1) return false; - i = s.indexOf('.', i + 1); - if (i == -1) return true; - return false; - } - private static SemType check(SemType type) throws TypeException { if (!type.isValid()) throw new TypeException(); return type; @@ -137,10 +135,11 @@ public final class TypeInference { if (value instanceof NumberValue) return check(type.meet(SemType.numberType)); else if (value instanceof StringValue) return check(type.meet(SemType.stringType)); else if (value instanceof DateValue) return check(type.meet(SemType.dateType)); + else if (value instanceof TimeValue) return check(type.meet(SemType.timeType)); else if (value instanceof NameValue) { String id = ((NameValue) value).id; - if (isUnary(id)) { // Unary + if (CanonicalNames.isUnary(id)) { // Unary SemType unaryType = env.typeLookup.getEntityType(id); if (unaryType == null) unaryType = SemType.entityType; @@ -170,6 +169,10 @@ public final class TypeInference { if (typeFormula.equals(join.relation) && join.child instanceof ValueFormula) return check(type.meet(SemType.newAtomicSemType(Formulas.getString(join.child)))); + // Special case: (<= (number 5)) => same type as (number 5) + if (comparisonFormulas.contains(join.relation)) + return check(type.meet(inferType(join.child, env, SemType.numberOrDateType))); + SemType relationType = inferType(join.relation, env, new FuncSemType(SemType.topType, type)); // Relation SemType childType = inferType(join.child, env, relationType.getArgType()); // Child relationType = inferType(join.relation, env, new FuncSemType(childType, type)); // Relation again diff --git a/src/edu/stanford/nlp/sempre/Value.java b/src/edu/stanford/nlp/sempre/Value.java index 1662bcb..019c9c7 100644 --- a/src/edu/stanford/nlp/sempre/Value.java +++ b/src/edu/stanford/nlp/sempre/Value.java @@ -5,6 +5,8 @@ import com.fasterxml.jackson.annotation.JsonValue; import fig.basic.LispTree; import fig.basic.LogInfo; +import java.util.Comparator; + /** * Values represent denotations (or partial denotations). * @@ -26,4 +28,11 @@ public abstract class Value { @Override public abstract boolean equals(Object o); @Override public abstract int hashCode(); + + public static class ValueComparator implements Comparator { + @Override + public int compare(Value o1, Value o2) { + return o1.toString().compareTo(o2.toString()); + } + } } diff --git a/src/edu/stanford/nlp/sempre/ValueFormula.java b/src/edu/stanford/nlp/sempre/ValueFormula.java index 0ab2847..eb9c017 100644 --- a/src/edu/stanford/nlp/sempre/ValueFormula.java +++ b/src/edu/stanford/nlp/sempre/ValueFormula.java @@ -17,6 +17,7 @@ public class ValueFormula extends PrimitiveFormula { return value.toLispTree(); } + @SuppressWarnings({"equalshashcode"}) @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/src/edu/stanford/nlp/sempre/Values.java b/src/edu/stanford/nlp/sempre/Values.java index 9a4692d..714bc3b 100644 --- a/src/edu/stanford/nlp/sempre/Values.java +++ b/src/edu/stanford/nlp/sempre/Values.java @@ -27,6 +27,7 @@ public final class Values { if ("context".equals(type)) return new ContextValue(tree); if ("date".equals(type)) return new DateValue(tree); if ("error".equals(type)) return new ErrorValue(tree); + if ("time".equals(type)) return new TimeValue(tree); return null; } diff --git a/src/edu/stanford/nlp/sempre/VariableFormula.java b/src/edu/stanford/nlp/sempre/VariableFormula.java index 7e6f679..bd7fcca 100644 --- a/src/edu/stanford/nlp/sempre/VariableFormula.java +++ b/src/edu/stanford/nlp/sempre/VariableFormula.java @@ -12,6 +12,7 @@ public class VariableFormula extends PrimitiveFormula { public VariableFormula(String name) { this.name = name; } public LispTree toLispTree() { return LispTree.proto.newList("var", name); } + @SuppressWarnings({"equalshashcode"}) @Override public boolean equals(Object thatObj) { if (!(thatObj instanceof VariableFormula)) return false; diff --git a/src/edu/stanford/nlp/sempre/build.xml b/src/edu/stanford/nlp/sempre/build.xml deleted file mode 100644 index 20bc99a..0000000 --- a/src/edu/stanford/nlp/sempre/build.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/src/edu/stanford/nlp/sempre/cache/build.xml b/src/edu/stanford/nlp/sempre/cache/build.xml deleted file mode 100644 index f1e217a..0000000 --- a/src/edu/stanford/nlp/sempre/cache/build.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/edu/stanford/nlp/sempre/corenlp/CoreNLPAnalyzer.java b/src/edu/stanford/nlp/sempre/corenlp/CoreNLPAnalyzer.java index 55ffffe..1a98895 100644 --- a/src/edu/stanford/nlp/sempre/corenlp/CoreNLPAnalyzer.java +++ b/src/edu/stanford/nlp/sempre/corenlp/CoreNLPAnalyzer.java @@ -2,7 +2,6 @@ package edu.stanford.nlp.sempre.corenlp; import edu.stanford.nlp.sempre.*; import edu.stanford.nlp.sempre.LanguageInfo.DependencyEdge; - import edu.stanford.nlp.ling.CoreAnnotations; import edu.stanford.nlp.ling.IndexedWord; import edu.stanford.nlp.ling.CoreAnnotations.*; @@ -13,9 +12,13 @@ import edu.stanford.nlp.semgraph.SemanticGraph; import edu.stanford.nlp.semgraph.SemanticGraphCoreAnnotations; import edu.stanford.nlp.semgraph.SemanticGraphEdge; import edu.stanford.nlp.util.CoreMap; + import com.google.common.collect.Lists; import com.google.common.base.Joiner; + import fig.basic.*; + +import java.io.*; import java.util.*; /** @@ -113,6 +116,7 @@ public class CoreNLPAnalyzer extends LanguageAnalyzer { // Fills in a stanford dependency graph for constructing a feature for (CoreMap sentence : annotation.get(CoreAnnotations.SentencesAnnotation.class)) { SemanticGraph ccDeps = sentence.get(SemanticGraphCoreAnnotations.CollapsedCCProcessedDependenciesAnnotation.class); + if (ccDeps == null) continue; int sentenceBegin = sentence.get(CoreAnnotations.TokenBeginAnnotation.class); // Iterate over all tokens and their dependencies @@ -133,4 +137,27 @@ public class CoreNLPAnalyzer extends LanguageAnalyzer { } return languageInfo; } + + // Test on example sentence. + public static void main(String[] args) { + CoreNLPAnalyzer analyzer = new CoreNLPAnalyzer(); + while (true) { + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + System.out.println("Enter some text:"); + String text = reader.readLine(); + LanguageInfo langInfo = analyzer.analyze(text); + LogInfo.begin_track("Analyzing \"%s\"", text); + LogInfo.logs("tokens: %s", langInfo.tokens); + LogInfo.logs("lemmaTokens: %s", langInfo.lemmaTokens); + LogInfo.logs("posTags: %s", langInfo.posTags); + LogInfo.logs("nerTags: %s", langInfo.nerTags); + LogInfo.logs("nerValues: %s", langInfo.nerValues); + LogInfo.logs("dependencyChildren: %s", langInfo.dependencyChildren); + LogInfo.end_track(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } } diff --git a/src/edu/stanford/nlp/sempre/corenlp/build.xml b/src/edu/stanford/nlp/sempre/corenlp/build.xml deleted file mode 100644 index b8cb705..0000000 --- a/src/edu/stanford/nlp/sempre/corenlp/build.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/edu/stanford/nlp/sempre/freebase/Free917Converter.java b/src/edu/stanford/nlp/sempre/freebase/Free917Converter.java index f47a914..f1944d2 100644 --- a/src/edu/stanford/nlp/sempre/freebase/Free917Converter.java +++ b/src/edu/stanford/nlp/sempre/freebase/Free917Converter.java @@ -87,7 +87,7 @@ public class Free917Converter implements Runnable { questionWriter.println("(lambda $0 /common/topic (exists $1 (exists $2 (/film/film/estimated_budget&/measurement_unit/dated_money_value@valid_date@amount@currency:t /en/transformers:/film/film $-1 $1 $0 $2))))"); else if (line.equals("(lambda $0 /location/location (exists $1 (exists $2 (exists $3 (exists $4 (exists $5 (/library/public_library/address&/location/mailing_address@street_address@street_address_2@citytown@postal_code@state_province_region@country:t /m/02ncllz:/library/public_library $1 $2 $0 $3 $4 $5)))))))")) questionWriter.println("(lambda $0 /location/location (exists $1 (exists $2 (exists $3 (exists $4 (exists $5 (/library/public_library/address&/location/mailing_address@street_address@street_address_2@citytown@postal_code@state_province_region@country:t /m/02ncllz:/library/public_library $-1 $1 $2 $0 $3 $4 $5)))))))"); - else if (line.equals("who won ali–frazier ii")) + else if (line.matches("who won ali.*frazier ii")) questionWriter.println("who won muhammad ali vs. joe frazier ii"); else if (line.equals("(lambda $0 /people/person (exists $1 (exists $2 (exists $3 (/base/boxing/match_boxer_relationship@match@boxer@winner_won@points:t $1 $0 $2 $3)))))")) { if (i++ == 0) @@ -341,7 +341,7 @@ public class Free917Converter implements Runnable { npWriter.println("film domain :- NP : /m/010s:/freebase/domain_profile"); else if (line.equals("newscaster :- NP : /en/newscaster:/tv/non_character_role")) npWriter.println("newscaster :- NP : /en/news_presenter:/tv/non_character_role"); - else if (line.equals("ali�frazier ii :- NP : /en/ali-frazier_ii:/base/boxing/boxing_match")) { + else if (line.matches("ali.*frazier ii :- NP : /en/ali-frazier_ii:/base/boxing/boxing_match")) { } else npWriter.println(line); } diff --git a/src/edu/stanford/nlp/sempre/freebase/LambdaCalculusConverter.java b/src/edu/stanford/nlp/sempre/freebase/LambdaCalculusConverter.java index e436d36..a891a29 100644 --- a/src/edu/stanford/nlp/sempre/freebase/LambdaCalculusConverter.java +++ b/src/edu/stanford/nlp/sempre/freebase/LambdaCalculusConverter.java @@ -1,10 +1,13 @@ package edu.stanford.nlp.sempre.freebase; -import java.io.*; -import java.util.*; +import edu.stanford.nlp.sempre.*; import fig.basic.*; import fig.exec.Execution; -import edu.stanford.nlp.sempre.*; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.*; /** * Converts Luke Zettlemoyer's lambda calculus data format into our example files. @@ -15,25 +18,25 @@ import edu.stanford.nlp.sempre.*; public class LambdaCalculusConverter implements Runnable { public static class Options { @Option(gloss = "Input path (lambda calculus)") - public String inPath = "freebase/data/geo/geosents600-typed.ccg.dev"; + public String inPath = "overnight/geo/geosents280-typed.ccg.test.new"; @Option(gloss = "Specification of translations") - public String specPath = "freebase/data/geo/geo.spec"; + public String specPath = "overnight/geo/geo.spec"; @Option(gloss = "Specification of variable names") - public String varPath = "freebase/data/geo/geo.vars"; + public String varPath = "overnight/geo/geo.vars"; @Option(gloss = "Specification of primitive types") - public String primPath = "freebase/data/geo/geo.primitives"; + public String primPath = "overnight/geo/geo.primitives"; @Option(gloss = "Specification of formula replacements") - public String replacePath = "freebase/data/geo/geo.replace"; + public String replacePath = "overnight/geo/geo.replace"; @Option(gloss = "Specification of manual conversions") - public String manualConversionsPath = "freebase/data/geo/geo.manual_conversions"; - @Option(gloss = "Input path (examples)") - public String outPath = "freebase/data/geo/geo.out.json"; + public String manualConversionsPath = "overnight/geo/geo.manual_conversions"; + @Option(gloss = "Output path (examples)") + public String outPath = "overnight/geo/geo.out.json"; @Option(gloss = "Specific example to parse and run") - public int runInd = -1; + public int runInd = -1; @Option(gloss = "Output path for lexicon grammar") - public String lexiconPath = "freebase/data/geo/geo.out.grammar"; + public String lexiconPath = "overnight/geo/geo.out.grammar"; @Option(gloss = "Verbose output (for debugging)") - public boolean verbose = false; // TODO Currently unused + public boolean verbose = false; // TODO Currently unused } public static Options opts = new Options(); @@ -72,11 +75,13 @@ public class LambdaCalculusConverter implements Runnable { } public void readPrereqs() { + LogInfo.begin_track("Reading prereq"); readSpec(); readPrimitives(); readVars(); readStringMap(opts.replacePath, replaceMap); readStringMap(opts.manualConversionsPath, manualConversionsMap); + LogInfo.end_track(); } void readSpec() { @@ -248,7 +253,7 @@ public class LambdaCalculusConverter implements Runnable { if (primitive == null) return null; // if (primitive.equals("string")) - // parts[0] = capitalizeWords(parts[0]); + // parts[0] = capitalizeWords(parts[0]); // return Formula.fromString(String.format("(%s %s)", primitive, parts[0])); if (primitive.equals("string")) return Formula.fromString(String.format("(!fb:type.object.name fb:en.%s)", parts[0])); @@ -257,41 +262,41 @@ public class LambdaCalculusConverter implements Runnable { } Formula handleExists(LispTree tree, String headVar, List existsVars) { - // (exists $1 (and (river:t $1) (loc:t $1 $0))) - String eVar = tree.child(1).value; - existsVars.add(eVar); - // FIXME Currently assuming format where exists contains and statement with - // n clauses where first n - 1 clauses describe exists var and last clause - // relates exists var to the head var - List andTreeList = tree.child(2).children; - // FIXME Don't assume and tree - LispTree newAndTree = LispTree.proto.newList(); + // (exists $1 (and (river:t $1) (loc:t $1 $0))) + String eVar = tree.child(1).value; + existsVars.add(eVar); + // FIXME Currently assuming format where exists contains and statement with + // n clauses where first n - 1 clauses describe exists var and last clause + // relates exists var to the head var + List andTreeList = tree.child(2).children; + // FIXME Don't assume and tree + LispTree newAndTree = LispTree.proto.newList(); - int predTreeInd = -1; - int numPredTrees = 0; - for (int k = 0; k < andTreeList.size(); k++) { - String childStr = andTreeList.get(k).toString(); - if (childStr.contains(headVar)) { - predTreeInd = k; - numPredTrees++; - } + int predTreeInd = -1; + int numPredTrees = 0; + for (int k = 0; k < andTreeList.size(); k++) { + String childStr = andTreeList.get(k).toString(); + if (childStr.contains(headVar)) { + predTreeInd = k; + numPredTrees++; } + } - String func = tree.child(2).child(0).value; - if (numPredTrees == 0 || func.equals("exists")) { - return toLambda(eVar, toFormula(tree.child(2), eVar, existsVars)); - } + String func = tree.child(2).child(0).value; + if (numPredTrees == 0 || func.equals("exists")) { + return toLambda(eVar, toFormula(tree.child(2), eVar, existsVars)); + } - newAndTree.children = new ArrayList(andTreeList.subList(0, andTreeList.size())); - LispTree predTree = newAndTree.children.remove(predTreeInd); - if (newAndTree.children.size() == 2) - newAndTree = newAndTree.child(1); + newAndTree.children = new ArrayList(andTreeList.subList(0, andTreeList.size())); + LispTree predTree = newAndTree.children.remove(predTreeInd); + if (newAndTree.children.size() == 2) + newAndTree = newAndTree.child(1); - LispTree newPredTree = LispTree.proto.parseFromString(predTree.toString().replace(eVar, newAndTree.toString())); + LispTree newPredTree = LispTree.proto.parseFromString(predTree.toString().replace(eVar, newAndTree.toString())); - Formula form = toFormula(newPredTree, eVar, existsVars); - existsVars.remove(eVar); - return form; + Formula form = toFormula(newPredTree, eVar, existsVars); + existsVars.remove(eVar); + return form; } // hit: ignore these clauses in (and ...) constructions @@ -323,7 +328,7 @@ public class LambdaCalculusConverter implements Runnable { // (sum $0 (and (state:t $0) (next_to:t $0 texas:s)) (population:i $0)) String numFunc = tree.child(3).child(0).value; return new AggregateFormula(AggregateFormula.Mode.sum, - toJoin(numFunc, toFormula(tree.child(2), tree.child(1).value, existsVars))); + toJoin(numFunc, toFormula(tree.child(2), tree.child(1).value, existsVars))); } else if (func.equals("argmax") || func.equals("argmin")) { // (argmax $0 (state:t $0) (density:i $0)) // (argmin $1 (river:t $1) (len:i $1)) @@ -379,7 +384,7 @@ public class LambdaCalculusConverter implements Runnable { else // secondIsHead return toJoin("!" + func, form1); // else - // return toJoin(func, form1); + // return toJoin(func, form1); } else { throw new RuntimeException("Bad arity: " + tree); } @@ -458,8 +463,10 @@ public class LambdaCalculusConverter implements Runnable { ArrayList existsVars = new ArrayList(); if (manualConversionsMap.containsKey(line)) { + LogInfo.logs("MANUAL CONVERSION=%s", line); ex.setTargetFormula(Formula.fromString(manualConversionsMap.get(line))); } else { + LogInfo.log("AUTOMATIC CONVERSION"); ex.setTargetFormula(toFormula(tree, null, existsVars)); } LogInfo.logs(color.colorize("OUT [%d]: %s", "yellow"), newId, ex.createExample().targetFormula.toLispTree()); @@ -476,8 +483,9 @@ public class LambdaCalculusConverter implements Runnable { void executeExamples() { int runInd = opts.runInd; // FIXME Should be passed in - SparqlExecutor.opts.endpointUrl = "http://localhost:3093/sparql"; + SparqlExecutor.opts.endpointUrl = "http://localhost:3094/sparql"; SparqlExecutor.opts.cachePath = "SparqlExecutor.cache"; + SparqlExecutor.opts.lambdaAllowDiagonals = false; //jonathan SparqlExecutor executor = new SparqlExecutor(); int exInd = 1; @@ -506,12 +514,12 @@ public class LambdaCalculusConverter implements Runnable { void writeExamples() { // Sort validExampleIds by the length of the utterance of the example - Collections.sort(validExampleIds, new Comparator() { - public int compare(Integer left, Integer right) { - return Integer.compare(validExampleLengths.get(validExampleIds.indexOf(left)), - validExampleLengths.get(validExampleIds.indexOf(right))); - } - }); +// Collections.sort(validExampleIds, new Comparator() { +// public int compare(Integer left, Integer right) { +// return Integer.compare(validExampleLengths.get(validExampleIds.indexOf(left)), +// validExampleLengths.get(validExampleIds.indexOf(right))); +// } +// }); PrintWriter out = IOUtils.openOutHard(opts.outPath); out.println("["); // Print out as a list diff --git a/src/edu/stanford/nlp/sempre/freebase/build.xml b/src/edu/stanford/nlp/sempre/freebase/build.xml deleted file mode 100644 index 0112ffe..0000000 --- a/src/edu/stanford/nlp/sempre/freebase/build.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/edu/stanford/nlp/sempre/freebase/test/SparqlExecutorTest.java b/src/edu/stanford/nlp/sempre/freebase/test/SparqlExecutorTest.java index 2d5eb68..e7eb0a4 100644 --- a/src/edu/stanford/nlp/sempre/freebase/test/SparqlExecutorTest.java +++ b/src/edu/stanford/nlp/sempre/freebase/test/SparqlExecutorTest.java @@ -69,8 +69,10 @@ public class SparqlExecutorTest { SparqlExecutor executor = new SparqlExecutor(); public SparqlExecutorTest() { + SparqlExecutor.opts.endpointUrl = System.getProperty("sparqlserver"); // Hard-coding not ideal. - SparqlExecutor.opts.endpointUrl = "http://localhost:3093/sparql"; + if (SparqlExecutor.opts.endpointUrl == null) + SparqlExecutor.opts.endpointUrl = "http://freebase.cloudapp.net:3093/sparql"; SparqlExecutor.opts.verbose = 3; } diff --git a/src/edu/stanford/nlp/sempre/freebase/utils/FileUtils.java b/src/edu/stanford/nlp/sempre/freebase/utils/FileUtils.java index 40bf435..b4ae651 100644 --- a/src/edu/stanford/nlp/sempre/freebase/utils/FileUtils.java +++ b/src/edu/stanford/nlp/sempre/freebase/utils/FileUtils.java @@ -1,12 +1,18 @@ package edu.stanford.nlp.sempre.freebase.utils; +import com.google.common.base.Joiner; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import edu.stanford.nlp.io.IOUtils; import edu.stanford.nlp.objectbank.ObjectBank; +import edu.stanford.nlp.sempre.DateValue; +import edu.stanford.nlp.sempre.DescriptionValue; +import edu.stanford.nlp.sempre.Json; +import edu.stanford.nlp.sempre.NameValue; import edu.stanford.nlp.stats.ClassicCounter; import edu.stanford.nlp.stats.Counter; import edu.stanford.nlp.util.StringUtils; +import fig.basic.LispTree; import fig.basic.LogInfo; import java.io.BufferedReader; @@ -202,4 +208,54 @@ public final class FileUtils { } return sb.toString(); } + + //input: tab separated file with |utterance| |gold| |predicted| + //output: prediction file for codalab + public static void generatePredictionFile(String inFile, String outFile) throws IOException { + + PrintWriter writer = IOUtils.getPrintWriter(outFile); + for (String line : IOUtils.readLines(inFile)) { + String[] tokens = line.split("\\t"); + if (tokens.length == 0) + continue; + if (tokens.length < 2) + throw new RuntimeException("Illegal line: " + line); + String utterance = tokens[0]; + // get gold + List goldDescriptions = new ArrayList<>(); + LispTree goldTree = LispTree.proto.parseFromString(tokens[1]); + for (int i = 1; i < goldTree.children.size(); ++i) { + DescriptionValue dValue = (DescriptionValue) DescriptionValue.fromString(goldTree.child(i).toString()); + goldDescriptions.add(dValue.value); + } + // get predicted + List predictedDescriptions = new ArrayList<>(); + if (tokens.length > 2) { + LispTree predictedTree = LispTree.proto.parseFromString(tokens[2]); + for (int i = 1; i < predictedTree.children.size(); ++i) { + if (predictedTree.child(i).child(0).value.equals("name")) { + NameValue nValue = (NameValue) NameValue.fromString(predictedTree.child(i).toString()); + predictedDescriptions.add(nValue.description); + } else if (predictedTree.child(i).child(0).value.equals("date")) { + DateValue dateValue = (DateValue) DateValue.fromString(predictedTree.child(i).toString()); + predictedDescriptions.add(dateValue.toString()); + } else + throw new RuntimeException("Can not support this value: " + line); + } + } + writer.println(Joiner.on('\t').join(utterance, + Json.writeValueAsStringHard(goldDescriptions), + Json.writeValueAsStringHard(predictedDescriptions))); + } + writer.close(); + } + + public static void main(String[] args) { + try { + generatePredictionFile(args[0], args[1]); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } } diff --git a/src/edu/stanford/nlp/sempre/overnight/Aligner.java b/src/edu/stanford/nlp/sempre/overnight/Aligner.java new file mode 100644 index 0000000..7265203 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/overnight/Aligner.java @@ -0,0 +1,146 @@ +package edu.stanford.nlp.sempre.overnight; + +import com.google.common.base.Joiner; +import edu.stanford.nlp.io.IOUtils; +import edu.stanford.nlp.stats.ClassicCounter; +import edu.stanford.nlp.stats.Counter; +import edu.stanford.nlp.stats.Counters; +import fig.basic.LispTree; +import fig.basic.MapUtils; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * Word-aligns original utterances with their paraphrases + * Created by joberant on 2/20/15. + */ +public class Aligner { + + private Map> model = new HashMap<>(); + + public double getCondProb(String target, String source) { + if (model.containsKey(source)) { + if (model.get(source).containsKey(target)) + return model.get(source).getCount(target); + } + return 0d; + } + + public int size() { return model.size(); } + + //takes an example file and creates a model + public void heuristicAlign(String exampleFile, int threshold) { + + model.clear(); + Iterator iter = LispTree.proto.parseFromFile(exampleFile); + + while (iter.hasNext()) { + LispTree tree = iter.next(); + LispTree utteranceTree = tree.child(1); + LispTree originalTree = tree.child(2); + String utterance = preprocessUtterance(utteranceTree.child(1).value); + String original = preprocessUtterance(originalTree.child(1).value); + String[] utteranceTokens = utterance.split("\\s+"); + String[] originalTokens = original.split("\\s+"); + + align(utteranceTokens, originalTokens); + } + normalize(threshold); + } + + public void saveModel(String out) throws IOException { + PrintWriter writer = IOUtils.getPrintWriter(out); + for (String source: model.keySet()) { + Counter counts = model.get(source); + for (String target: counts.keySet()) { + writer.println(Joiner.on('\t').join(source, target, counts.getCount(target))); + } + } + } + + //normalize all the counts to get conditional probabilities + private void normalize(int threshold) { + for (String source: model.keySet()) { + Counter counts = model.get(source); + Counters.removeKeys(counts, Counters.keysBelow(counts, threshold)); + Counters.normalize(counts); + } + } + + //count every co-occurrence + private void align(String[] utteranceTokens, String[] originalTokens) { + for (String utteranceToken: utteranceTokens) { + for (String originalToken: originalTokens) { + MapUtils.putIfAbsent(model, utteranceToken.toLowerCase(), new ClassicCounter<>()); + MapUtils.putIfAbsent(model, originalToken.toLowerCase(), new ClassicCounter<>()); + model.get(utteranceToken.toLowerCase()).incrementCount(originalToken.toLowerCase()); + model.get(originalToken.toLowerCase()).incrementCount(utteranceToken.toLowerCase()); + } + } + } + + //remove '?' and '.' + public String preprocessUtterance(String utterance) { + if (utterance.endsWith("?")) + return utterance.substring(0, utterance.length() - 1); + if (utterance.endsWith(".")) + return utterance.substring(0, utterance.length() - 1); + return utterance; + } + + //read from serialized file + public static Aligner read(String path) { + Aligner res = new Aligner(); + for (String line: edu.stanford.nlp.io.IOUtils.readLines(path)) { + String[] tokens = line.split("\t"); + MapUtils.putIfAbsent(res.model, tokens[0], new ClassicCounter<>()); + res.model.get(tokens[0]).incrementCount(tokens[1], Double.parseDouble(tokens[2])); + } + return res; + } + + private void berkeleyAlign(String file, int threshold) { + for (String line: IOUtils.readLines(file)) { + String[] tokens = line.split("\t"); + String[] sourceTokens = tokens[0].split("\\s+"); + String[] targetTokens = tokens[1].split("\\s+"); + String[] alignmentTokens = tokens[2].split("\\s+"); + for (String alignmentToken: alignmentTokens) { + String[] alignment = alignmentToken.split("-"); + Integer source = Integer.parseInt(alignment[0]); + Integer target = Integer.parseInt(alignment[1]); + MapUtils.putIfAbsent(model, sourceTokens[source], new ClassicCounter<>()); + MapUtils.putIfAbsent(model, targetTokens[target], new ClassicCounter<>()); + model.get(sourceTokens[source]).incrementCount(targetTokens[target]); + model.get(targetTokens[target]).incrementCount(sourceTokens[source]); + } + } + normalize(threshold); + } + + //args[0] - example file with utterance and original + //args[1] - output file + //args[2] - heuristic or berkeley + //args[3] - threshold + public static void main(String[] args) { + Aligner aligner = new Aligner(); + int threshold = Integer.parseInt(args[3]); + if (args[2].equals("heuristic")) + aligner.heuristicAlign(args[0], threshold); + else if (args[2].equals("berkeley")) + aligner.berkeleyAlign(args[0], threshold); + else throw new RuntimeException("bad alignment mode: " + args[2]); + try { + aligner.saveModel(args[1]); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + +} diff --git a/src/edu/stanford/nlp/sempre/overnight/ConvertTargetValueFromListToString.java b/src/edu/stanford/nlp/sempre/overnight/ConvertTargetValueFromListToString.java new file mode 100644 index 0000000..a9c3857 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/overnight/ConvertTargetValueFromListToString.java @@ -0,0 +1,59 @@ +package edu.stanford.nlp.sempre.overnight; + +import edu.stanford.nlp.io.IOUtils; +import edu.stanford.nlp.sempre.StringValue; +import fig.basic.LispTree; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Created by joberant on 2/24/15. + * This converts + * (targetValue (list (name fb:en.place.walton_county))) + * to + * (targetValue (string [(name fb:en.place.walton_county)])) + * This has nothing to do with 'paraphrase', it's just here for no reason + */ +public final class ConvertTargetValueFromListToString { + private ConvertTargetValueFromListToString() { } + + public static void main(String[] args) { + + try { + PrintWriter writer = IOUtils.getPrintWriter(args[1]); + Iterator trees = LispTree.proto.parseFromFile(args[0]); + while (trees.hasNext()) { + LispTree tree = trees.next(); + + LispTree outTree = LispTree.proto.newList(); + outTree.addChild("example"); + outTree.addChild(tree.child(1)); + + List output = new ArrayList<>(); + LispTree targetValue = tree.child(3); + if (!targetValue.child(0).value.equals("targetValue")) + throw new RuntimeException("Expected a target value as second child: " + targetValue); + LispTree list = targetValue.child(1); + if (!list.child(0).value.equals("list")) + throw new RuntimeException("Expected a list as first child: " + list); + for (int i = 1; i < list.children.size(); ++i) + output.add(list.child(i).toString()); + StringValue newTargetValue = new StringValue(output.toString()); + LispTree newTargetValueTree = LispTree.proto.newList(); + newTargetValueTree.addChild("targetValue"); + newTargetValueTree.addChild(newTargetValue.toLispTree()); + outTree.addChild(newTargetValueTree); + outTree.print(120, 120, writer); + writer.println(); + } + writer.close(); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } +} diff --git a/src/edu/stanford/nlp/sempre/overnight/CreateBerkeleyAlignerInputFromLispTree.java b/src/edu/stanford/nlp/sempre/overnight/CreateBerkeleyAlignerInputFromLispTree.java new file mode 100644 index 0000000..5c1630a --- /dev/null +++ b/src/edu/stanford/nlp/sempre/overnight/CreateBerkeleyAlignerInputFromLispTree.java @@ -0,0 +1,53 @@ +package edu.stanford.nlp.sempre.overnight; + +import edu.stanford.nlp.io.IOUtils; +import fig.basic.LispTree; +import fig.basic.LogInfo; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Iterator; + +/** + * Created by joberant on 2/22/15. + * Takes a file with lisp trees of original and canonical utterances + * and creates the input for the berkley aligner + */ +public final class CreateBerkeleyAlignerInputFromLispTree { + private CreateBerkeleyAlignerInputFromLispTree() { } + + //args[0]: lisp tree file + //args[1] output directory + public static void main(String[] args) { + + Iterator trees = LispTree.proto.parseFromFile(args[0]); + try { + PrintWriter writerOriginal = IOUtils.getPrintWriter(args[1] + ".e"); + PrintWriter writerUtterance = IOUtils.getPrintWriter(args[1] + ".f"); + LogInfo.logs("output directory=%s", args[1]); + + int i = 0; + while (trees.hasNext()) { + i++; + LispTree tree = trees.next(); + LispTree utteranceTree = tree.child(1); + LispTree originalTree = tree.child(2); + if (!utteranceTree.child(0).value.equals("utterance")) + throw new RuntimeException("First child is not an utterance " + utteranceTree); + if (!originalTree.child(0).value.equals("original")) + throw new RuntimeException("second child is not the original " + originalTree); + String uttearnce = utteranceTree.child(1).value; + if (uttearnce.endsWith("?") || uttearnce.endsWith(".")) + uttearnce = uttearnce.substring(0, uttearnce.length() - 1); + String original = originalTree.child(1).value; + writerOriginal.println(original); + writerUtterance.println(uttearnce); + } + LogInfo.logs("Numebr of trees=%s", i); + writerOriginal.close(); + writerUtterance.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/edu/stanford/nlp/sempre/overnight/GenerationMain.java b/src/edu/stanford/nlp/sempre/overnight/GenerationMain.java new file mode 100644 index 0000000..7012cf4 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/overnight/GenerationMain.java @@ -0,0 +1,50 @@ +package edu.stanford.nlp.sempre.overnight; + +import fig.basic.Option; +import fig.exec.Execution; +import edu.stanford.nlp.sempre.*; + +/** + * Created by joberant on 1/27/15. + * Generating canonical utterances from grammar with various depths + */ +public class GenerationMain implements Runnable { + @Option + public boolean interactive = false; + @Option + public boolean varyMaxDepth = false; + + @Override + public void run() { + Builder builder = new Builder(); + builder.build(); + + Dataset dataset = new Dataset(); + dataset.read(); + + int currDepth = varyMaxDepth ? 1 : FloatingParser.opts.maxDepth; + int maxDepth = FloatingParser.opts.maxDepth; + + for (; currDepth < maxDepth + 1; currDepth++) { + FloatingParser.opts.maxDepth = currDepth; + //LogInfo.logs("Curr depth=%s", currDepth); + //LogInfo.logs("file = %s", FloatingParser.opts.predictedUtterancesFile); + //PrintWriter writer = IOUtils.openOutAppendEasy(Execution.getFile(FloatingParser.opts.predictedUtterancesFile)); + //writer.println(String.format("Depth=%s", currDepth)); + //writer.println(String.format("--------", currDepth)); + //writer.close(); + Learner learner = new Learner(builder.parser, builder.params, dataset); + learner.learn(); + } + + + if (interactive) { + Master master = new Master(builder); + master.runInteractivePrompt(); + } + } + + public static void main(String[] args) { + Execution.run(args, "GenerationMain", new GenerationMain(), Master.getOptionsParser()); + } +} diff --git a/src/edu/stanford/nlp/sempre/overnight/OvernightDerivationPruningComputer.java b/src/edu/stanford/nlp/sempre/overnight/OvernightDerivationPruningComputer.java new file mode 100644 index 0000000..14f84d4 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/overnight/OvernightDerivationPruningComputer.java @@ -0,0 +1,57 @@ +package edu.stanford.nlp.sempre.overnight; + +import java.util.*; +import java.util.regex.Pattern; + +import edu.stanford.nlp.sempre.*; +import fig.basic.Option; +import fig.basic.MapUtils; + +/** + * Hard-coded hacks for pruning derivations in floating parser for overnight domains. + * + */ + +public class OvernightDerivationPruningComputer extends DerivationPruningComputer { + public static class Options { + @Option (gloss = "Whether filter derivations using hard constraints") + public boolean applyHardConstraints = false; + } + public static Options opts = new Options(); + + + public OvernightDerivationPruningComputer(DerivationPruner pruner) { + super(pruner); + } + + @Override + public boolean isPruned(Derivation deriv) { + if (opts.applyHardConstraints && violateHardConstraints(deriv)) return true; + return false; + } + + // Check a few hard constraints on each derivation + private static boolean violateHardConstraints(Derivation deriv) { + if (deriv.value != null) { + if (deriv.value instanceof ErrorValue) return true; + if (deriv.value instanceof StringValue) { //empty denotation + if (((StringValue) deriv.value).value.equals("[]")) return true; + } + if (deriv.value instanceof ListValue) { + List values = ((ListValue) deriv.value).values; + // empty lists + if (values.size() == 0) return true; + // NaN + if (values.size() == 1 && values.get(0) instanceof NumberValue) { + if (Double.isNaN(((NumberValue) values.get(0)).value)) return true; + } + // If we are supposed to get a number but we get a string (some sparql weirdness) + if (deriv.type.equals(SemType.numberType) && + values.size() == 1 && + !(values.get(0) instanceof NumberValue)) return true; + } + } + return false; + } + +} diff --git a/src/edu/stanford/nlp/sempre/overnight/OvernightFeatureComputer.java b/src/edu/stanford/nlp/sempre/overnight/OvernightFeatureComputer.java new file mode 100644 index 0000000..b1c1596 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/overnight/OvernightFeatureComputer.java @@ -0,0 +1,609 @@ +package edu.stanford.nlp.sempre.overnight; + +import com.google.common.base.Joiner; +import com.google.common.collect.Sets; +import edu.stanford.nlp.io.IOUtils; +import fig.basic.BipartiteMatcher; +import fig.basic.LogInfo; +import fig.basic.MapUtils; +import fig.basic.Option; +import edu.stanford.nlp.sempre.*; + +import java.util.*; + +/** + * Define features on the input utterance and a partial canonical utterance. + * + * Feature computation recipe: + * - For both the input and (partial) canonical utterance, extract a list of tokens + * (perhaps with POS tags). + * - Given a list of tokens, extract a set of items, where an item is a (tag, + * data) pair, where the tag specifies the "type" of the data, and is used + * to determine features. Example: ("bigram", "not contains"), ("unigram", + * "not"), ("unigram-RB", "not") + * - Given the input and canonical items, define recall features (how much of + * the input items is the canononical covering). + * This recipe allows us to decouple the extraction of items on one utterance + * from the computation of actual precision/recall features. + * + * @author Percy Liang + * @author Yushi Wang + */ +public class OvernightFeatureComputer implements FeatureComputer { + public static class Options { + @Option(gloss = "Set of paraphrasing feature domains to include") + public Set featureDomains = new HashSet<>(); + + @Option(gloss = "Whether or not to count intermediate categories for size feature") + public boolean countIntermediate = true; + + @Option(gloss = "Whether or not to do match/ppdb analysis") + public boolean itemAnalysis = true; + + @Option(gloss = "Whether or not to learn paraphrases") + public boolean learnParaphrase = true; + + @Option(gloss = "Verbose flag") + public int verbose = 0; + + @Option(gloss = "Path to alignment file") + public String wordAlignmentPath; + @Option(gloss = "Path to phrase alignment file") + public String phraseAlignmentPath; + @Option(gloss = "Threshold for phrase table co-occurrence") + public int phraseTableThreshold = 3; + } + + public static Options opts = new Options(); + + private static Aligner aligner; + private static Map> phraseTable; + public final SimpleLexicon simpleLexicon = SimpleLexicon.getSingleton(); + + @Override public void extractLocal(Example ex, Derivation deriv) { + if (deriv.rule.rhs == null) return; + + // Optimization: feature vector same as child, so don't do anything. + if (deriv.rule.isCatUnary()) { + if (deriv.isRootCat()) { + extractValueInFormulaFeature(deriv); + extractRootFeatures(ex, deriv); + return; + } + } + + // Important! We want to define the global feature vector for this + // derivation, but we can only specify the local feature vector. So to + // make things cancel out, we subtract out the unwanted feature vectors of + // descendents. + subtractDescendentsFeatures(deriv, deriv); + + deriv.addFeature("paraphrase", "size", derivationSize(deriv)); + extractRootFeatures(ex, deriv); + extractLexicalFeatures(ex, deriv); + extractPhraseAlignmentFeatures(ex, deriv); + extractLogicalFormFeatures(ex, deriv); + + if (!opts.itemAnalysis) return; + + List inputItems = computeInputItems(ex); + List candidateItems = computeCandidateItems(ex, deriv); + + for (Item input : inputItems) { + double match = 0; + double ppdb = 0; + double skipBigram = 0; + double skipPpdb = 0; + for (Item candidate : candidateItems) { + if (!input.tag.equals(candidate.tag)) continue; + if (input.tag.equals("skip-bigram")) { + skipBigram = Math.max(skipBigram, computeMatch(input.data, candidate.data)); + skipPpdb = Math.max(skipPpdb, computeParaphrase(input.data, candidate.data)); + } else { + + match = Math.max(match, computeMatch(input.data, candidate.data)); + ppdb = Math.max(ppdb, computeParaphrase(input.data, candidate.data)); + } + } + if (match > 0 && opts.featureDomains.contains("match")) deriv.addFeature("paraphrase", "match"); + if (ppdb > 0 && opts.featureDomains.contains("ppdb")) deriv.addFeature("paraphrase", "ppdb"); + if (skipBigram > 0 && opts.featureDomains.contains("skip-bigram")) deriv.addFeature("paraphrase", "skip-bigram"); + if (skipPpdb > 0 && opts.featureDomains.contains("skip-ppdb")) deriv.addFeature("paraphrase", "skip-ppdb"); + } + + HashMap features = new LinkedHashMap<>(); + deriv.incrementAllFeatureVector(+1, features); + if (opts.verbose >= 1) { + LogInfo.logs("category %s, %s %s", deriv.cat, inputItems, candidateItems); + FeatureVector.logFeatures(features); + } + } + + private void extractValueInFormulaFeature(Derivation deriv) { + if (!opts.featureDomains.contains("denotation")) return; + + if (deriv.value instanceof StringValue) { + + //get strings from value + List valueList = new ArrayList<>(); + + String value = ((StringValue) deriv.value).value; + + if (value.charAt(0) == '[') + value = value.substring(1, value.length() - 1); //strip "[]" + String[] tokens = value.split(","); + for (String token : tokens) { + token = token.trim(); //strip spaces + if (token.length() > 0) + valueList.add(token); + } + + //get strings from formula + List formulaList = deriv.formula.mapToList(formula -> { + List res = new ArrayList<>(); + if (formula instanceof ValueFormula) { + res.add(formula); + } + return res; + }, true); + + for (Formula f : formulaList) { + Value formulaValue = ((ValueFormula) f).value; + String valueStr = (formulaValue instanceof StringValue) ? ((StringValue) formulaValue).value : formulaValue.toString(); + if (valueList.contains(valueStr)) + deriv.addFeature("denotation", "value_in_formula"); + } + } + } + + private void extractRootFeatures(Example ex, Derivation deriv) { + if (!deriv.isRootCat()) return; + if (!opts.featureDomains.contains("root") && !opts.featureDomains.contains("root_lexical")) return; + + List derivTokens = Arrays.asList(deriv.canonicalUtterance.split("\\s+")); + List inputTokens = ex.getTokens(); + //alignment features + BipartiteMatcher bMatcher = new BipartiteMatcher(); + List filteredInputTokens = filterStopWords(inputTokens); + List filteredDerivTokens = filterStopWords(derivTokens); + + int[] assignment = bMatcher.findMaxWeightAssignment(buildAlignmentMatrix(filteredInputTokens, filteredDerivTokens)); + + if (opts.featureDomains.contains("root")) { + //number of unmathced words based on exact match and ppdb + int matches = 0; + for (int i = 0; i < filteredInputTokens.size(); ++i) { + if (assignment[i] != i) { + matches++; + } + } + deriv.addFeature("root", "unmatched_input", filteredInputTokens.size() - matches); + deriv.addFeature("root", "unmatched_deriv", filteredDerivTokens.size() - matches); + if (deriv.value != null) { + if (deriv.value instanceof ListValue) { + ListValue list = (ListValue) deriv.value; + deriv.addFeature("root", String.format("pos0=%s&returnType=%s", ex.posTag(0), list.values.get(0).getClass())); + } + } + } + + if (opts.featureDomains.contains("root_lexical")) { + for (int i = 0; i < assignment.length; ++i) { + if (assignment[i] == i) { + if (i < filteredInputTokens.size()) { + String inputToken = filteredInputTokens.get(i).toLowerCase(); + deriv.addFeature("root_lexical", "deleted_token=" + inputToken); + if (!simpleLexicon.lookup(inputToken).isEmpty()) { + deriv.addFeature("root_lexical", "deleted_entity"); + } + } + else { + String derivToken = filteredDerivTokens.get(i - filteredInputTokens.size()); + deriv.addFeature("root_lexical", "deleted_token=" + derivToken); + if (!simpleLexicon.lookup(derivToken).isEmpty()) { + deriv.addFeature("root_lexical", "deleted_entity"); + } + } + } + } + } + } + + private List getCallFormulas(Derivation deriv) { + return deriv.formula.mapToList(formula -> { + List res = new ArrayList<>(); + if (formula instanceof CallFormula) { + res.add(((CallFormula) formula).func); + } + return res; + }, true); + } + private void extractLogicalFormFeatures(Example ex, Derivation deriv) { + if (!opts.featureDomains.contains("lf")) return; + for (int i = 0; i < ex.numTokens(); ++i) { + List callFormulas = getCallFormulas(deriv); + if (ex.posTag(i).equals("JJS")) { + if (ex.token(i).equals("least") || ex.token(i).equals("most")) //at least and at most are not what we want + continue; + for (Formula callFormula: callFormulas) { + String callFormulaDesc = callFormula.toString(); + //LogInfo.logs("SUPER: utterance=%s, formula=%s", ex.utterance, deriv.formula); + deriv.addFeature("lf", callFormulaDesc + "& superlative"); + } + } + } + if (!opts.featureDomains.contains("simpleworld")) return; + //specific handling of simple world methods + if (deriv.formula instanceof CallFormula) { + CallFormula callFormula = (CallFormula) deriv.formula; + String desc = callFormula.func.toString(); + switch (desc) { + case "edu.stanford.nlp.sempre.overnight.SimpleWorld.filter": + deriv.addFeature("simpleworld", "filter&" + callFormula.args.get(1)); + break; + case "edu.stanford.nlp.sempre.overnight.SimpleWorld.getProperty": + deriv.addFeature("simpleworld", "getProperty&" + callFormula.args.get(1)); + break; + case "edu.stanford.nlp.sempre.overnight.SimpleWorld.superlative": + deriv.addFeature("simpleworld", "superlative&" + callFormula.args.get(1) + "&" + callFormula.args.get(2)); + break; + case "edu.stanford.nlp.sempre.overnight.SimpleWorld.countSuperlative": + deriv.addFeature("simpleworld", "countSuperlative&" + callFormula.args.get(1) + "&" + callFormula.args.get(2)); + break; + case "edu.stanford.nlp.sempre.overnight.SimpleWorld.countComparative": + deriv.addFeature("simpleworld", "countComparative&" + callFormula.args.get(2) + "&" + callFormula.args.get(1)); + break; + case "edu.stanford.nlp.sempre.overnight.SimpleWorld.aggregate": + deriv.addFeature("simpleworld", "countComparative&" + callFormula.args.get(0)); + break; + default: break; + } + } + } + + private void extractPhraseAlignmentFeatures(Example ex, Derivation deriv) { + + if (!opts.featureDomains.contains("alignment")) return; + if (phraseTable == null) phraseTable = loadPhraseTable(); + + //get the tokens + List derivTokens = Arrays.asList(deriv.canonicalUtterance.split("\\s+")); + Set inputSubspans = ex.languageInfo.getLowerCasedSpans(); + + for (int i = 0; i < derivTokens.size(); ++i) { + for (int j = i + 1; j <= derivTokens.size() && j <= i + 4; ++j) { + + String lhs = Joiner.on(' ').join(derivTokens.subList(i, j)); + if (entities.contains(lhs)) continue; //optimization + + if (phraseTable.containsKey(lhs)) { + Map rhsCandidates = phraseTable.get(lhs); + Set intersection = Sets.intersection(rhsCandidates.keySet(), inputSubspans); + for (String rhs: intersection) { + addAndFilterLexicalFeature(deriv, "alignment", rhs, lhs); + } + } + } + } + } + + private Map> loadPhraseTable() { + Map> res = new HashMap<>(); + int num = 0; + for (String line : IOUtils.readLines(opts.phraseAlignmentPath)) { + String[] tokens = line.split("\t"); + if (tokens.length != 3) throw new RuntimeException("Bad alignment line: " + line); + MapUtils.putIfAbsent(res, tokens[0], new HashMap<>()); + + double value = Double.parseDouble(tokens[2]); + if (value >= opts.phraseTableThreshold) { + res.get(tokens[0]).put(tokens[1], value); + num++; + } + } + LogInfo.logs("Number of entries=%s", num); + return res; + } + + + private void addAndFilterLexicalFeature(Derivation deriv, String domain, String str1, String str2) { + + String[] str1Tokens = str1.split("\\s+"); + String[] str2Tokens = str2.split("\\s+"); + for (String str1Token: str1Tokens) + if (entities.contains(str1Token)) return; + for (String str2Token: str2Tokens) + if (entities.contains(str2Token)) return; + + if (stopWords.contains(str1) || stopWords.contains(str2)) return; + deriv.addFeature(domain, str1 + "--" + str2); + } + + private void extractLexicalFeatures(Example ex, Derivation deriv) { + + if (!opts.featureDomains.contains("lexical")) return; + + List derivTokens = Arrays.asList(deriv.canonicalUtterance.split("\\s+")); + List inputTokens = ex.getTokens(); + //alignment features + BipartiteMatcher bMatcher = new BipartiteMatcher(); + List filteredInputTokens = filterStopWords(inputTokens); + List filteredDerivTokens = filterStopWords(derivTokens); + + double[][] alignmentMatrix = buildLexicalAlignmentMatrix(filteredInputTokens, filteredDerivTokens); + int[] assignment = bMatcher.findMaxWeightAssignment(alignmentMatrix); + for (int i = 0; i < filteredInputTokens.size(); ++i) { + if (assignment[i] != i) { + int derivIndex = assignment[i] - filteredInputTokens.size(); + String inputToken = filteredInputTokens.get(i).toLowerCase(); + + if (entities.contains(inputToken)) continue; //optimization - stop here + + String derivToken = filteredDerivTokens.get(derivIndex).toLowerCase(); + if (!inputToken.equals(derivToken)) { + addAndFilterLexicalFeature(deriv, "lexical", inputToken, derivToken); + extractStringSimilarityFeatures(deriv, inputToken, derivToken); + + //2:2 features + if (i < filteredInputTokens.size() - 1) { + if (assignment[i + 1] == assignment[i] + 1) { + String inputBigram = Joiner.on(' ').join(inputToken, filteredInputTokens.get(i + 1)).toLowerCase(); + String derivBigram = Joiner.on(' ').join(derivToken, filteredDerivTokens.get(derivIndex + 1)).toLowerCase(); + if (!inputBigram.equals(derivBigram)) { + addAndFilterLexicalFeature(deriv, "lexical", inputBigram, derivBigram); + } + } + } + //1:2 features + if (derivIndex > 0) { + addAndFilterLexicalFeature(deriv, "lexical", inputToken, + Joiner.on(' ').join(filteredDerivTokens.get(derivIndex - 1), filteredDerivTokens.get(derivIndex))); + } + if (derivIndex < filteredDerivTokens.size() - 1) { + addAndFilterLexicalFeature(deriv, "lexical", inputToken, + Joiner.on(' ').join(filteredDerivTokens.get(derivIndex), filteredDerivTokens.get(derivIndex + 1))); + } + } + } + } + } + + private void extractStringSimilarityFeatures(Derivation deriv, String inputToken, String derivToken) { + if (inputToken.startsWith(derivToken) || derivToken.startsWith(inputToken)) + deriv.addFeature("lexical", "starts_with"); + else if (inputToken.length() > 4 && derivToken.length() > 4) { + if (inputToken.substring(0, 4).equals(derivToken.substring(0, 4))) + deriv.addFeature("lexical", "common_prefix"); + } + } + + //return a list without wtop words + private List filterStopWords(List tokens) { + List res = new ArrayList<>(); + for (String token : tokens) { + if (!stopWords.contains(token)) + res.add(token); + } + return res; + } + + private double[][] buildAlignmentMatrix(List inputTokens, List derivTokens) { + + double[][] res = new double[inputTokens.size() + derivTokens.size()][inputTokens.size() + derivTokens.size()]; + for (int i = 0; i < inputTokens.size(); ++i) { + for (int j = 0; j < derivTokens.size(); ++j) { + String inputToken = inputTokens.get(i); + String derivToken = derivTokens.get(j); + + if (computeMatch(inputToken, derivToken) > 0d) { + res[i][inputTokens.size() + j] = 1d; + res[inputTokens.size() + j][i] = 1d; + } + else if (computeParaphrase(inputToken, derivToken) > 0d) { + res[i][inputTokens.size() + j] = 0.5d; + res[inputTokens.size() + j][i] = 0.5d; + } + } + } + for (int i = 0; i < res.length - 1; i++) { + for (int j = i + 1; j < res.length; j++) { + if (i != j && res[i][j] < 1) { + res[i][j] = Double.NEGATIVE_INFINITY; + res[j][i] = Double.NEGATIVE_INFINITY; + } + } + } + return res; + } + + private double[][] buildLexicalAlignmentMatrix(List inputTokens, List derivTokens) { + if (aligner == null) { + aligner = Aligner.read(opts.wordAlignmentPath); + } + + double[][] res = new double[inputTokens.size() + derivTokens.size()][inputTokens.size() + derivTokens.size()]; + //init with -infnty and low score on the diagonal + for (int i = 0; i < res.length - 1; i++) { + for (int j = i; j < res.length; j++) { + if (i == j) { + res[i][j] = 0d; + res[j][i] = 0d; + } + else { + res[i][j] = -1000d; + res[j][i] = -1000d; + } + } + } + + for (int i = 0; i < inputTokens.size(); ++i) { + for (int j = 0; j < derivTokens.size(); ++j) { + String inputToken = inputTokens.get(i).toLowerCase(); + String derivToken = derivTokens.get(j).toLowerCase(); + + if (computeMatch(inputToken, derivToken) > 0) { + res[i][inputTokens.size() + j] = 1d; + res[inputTokens.size() + j][i] = 1d; + } else if (computeParaphrase(inputToken, derivToken) > 0) { + res[i][inputTokens.size() + j] = 0.5d; + res[inputTokens.size() + j][i] = 0.5d; + } + else if (aligner.getCondProb(inputToken, derivToken) > 0d && + aligner.getCondProb(derivToken, inputToken) > 0d) { + double product = aligner.getCondProb(inputToken, derivToken) * aligner.getCondProb(derivToken, inputToken); + res[i][inputTokens.size() + j] = product; + res[inputTokens.size() + j][i] = product; + } + } + } + return res; + } + + // Represents a local pattern on an utterance. + private static class Item { + public final String tag; + public final String data; + public Item(String tag, String data) { + this.tag = tag; + this.data = data; + } + @Override public String toString() { + return tag + ":" + data; + } + } + + // Fetch items from the temporary state. + // If it doesn't exist, create one. + private static List getItems(Map tempState) { + List items = (List) tempState.get("items"); + if (items == null) + tempState.put("items", items = new ArrayList<>()); + return items; + } + private static void setItems(Map tempState, List items) { + tempState.put("items", items); + } + + // TODO(yushi): make this less hacky + private static final List stopWords = Arrays.asList("\' \" `` ` \'\' a an the that which . what ? is are am be of".split(" ")); + private static final Set entities = + new HashSet<>(Arrays.asList("alice", "bob", "greenberg", "greenberg cafe", "central office", + "sacramento", "austin", "california", "texas", "colorado", "colorado river", "red river", "lake tahoe", "tahoe", "lake huron", "huron", "mount whitney", "whitney", "mount rainier", "rainier", "death valley", "pacific ocean", "pacific", + "sesame", "mission ave", "mission", "chelsea", + "multivariate data analysis", "multivariate data", "data analysis", "multivariate", "data", "efron", "lakoff", "annals of statistics", "annals", "annals of", "of statistics", "statistics", "computational linguistics", "computational", "linguistics", + "thai cafe", "pizzeria juno", + "new york", "york", "beijing", "brown university", "ucla", "mckinsey", "google")); + + + private static boolean isStopWord(String token) { + return stopWords.contains(token); + } + + private static void populateItems(List tokens, List items) { + List prunedTokens = new ArrayList<>(); + // Populate items with unpruned tokens + for (int i = 0; i < tokens.size(); i++) { + items.add(new Item("unigram", tokens.get(i))); + if (i - 1 >= 0) { + items.add(new Item("bigram", tokens.get(i - 1) + " " + tokens.get(i))); + } + if (!isStopWord(tokens.get(i)) || (i > 0 && (tokens.get(i - 1).equals('`') || tokens.get(i - 1).equals("``")))) + prunedTokens.add(tokens.get(i)); + } + + // Populate items with skip words removed + for (int i = 1; i < prunedTokens.size(); i++) { + items.add(new Item("skip-bigram", prunedTokens.get(i - i) + " " + prunedTokens.get(i))); + } + } + + // Compute the items for the input utterance. + private static List computeInputItems(Example ex) { + List items = getItems(ex.getTempState()); + if (items.size() != 0) return items; + List tokens = new ArrayList<>(ex.getTokens()); + populateItems(tokens, items); + LogInfo.logs("input %s, items %s", ex, items); + return items; + } + + // Return the set of tokens (partial canonical utterance) produced by the + // derivation. + public static List extractTokens(Example ex, Derivation deriv, List tokens) { + int childIndex = 0; + if (deriv.rule.rhs != null) { + for (String p : deriv.rule.rhs) + if (Rule.isCat(p)) + extractTokens(ex, deriv.children.get(childIndex++), tokens); + else + tokens.add(p); + + } else if (deriv.start != -1 && deriv.end != -1) { + for (int i = deriv.start; i < deriv.end; i++) + tokens.add(ex.token(i)); + } + return tokens; + } + + // Compute the items for a partial canonical utterance. + private static List computeCandidateItems(Example ex, Derivation deriv) { + // Get tokens + List tokens = new ArrayList<>(); + extractTokens(ex, deriv, tokens); + // Compute items + List items = new ArrayList<>(); + populateItems(tokens, items); + return items; + } + + private static void subtractDescendentsFeatures(Derivation deriv, Derivation subderiv) { + if (subderiv.children != null) { + for (Derivation child : subderiv.children) { + deriv.getLocalFeatureVector().add(-1, child.getLocalFeatureVector()); + subtractDescendentsFeatures(deriv, child); + } + } + } + + // Return the "complexity" of the given derivation. + private static int derivationSize(Derivation deriv) { + int sum = 0; + if (opts.countIntermediate || !(deriv.rule.lhs.contains("Intermediate"))) sum++; + if (deriv.children != null) { + for (Derivation child : deriv.children) + sum += derivationSize(child); + } + return sum; + } + + private static double computeMatch(String a, String b) { + if (a.equals(b)) return 1; + if (LanguageInfo.LanguageUtils.stem(a).equals(LanguageInfo.LanguageUtils.stem(b))) return 1; + return 0; + } + + private static double computeParaphrase(String a, String b) { + if (computeMatch(a, b) > 0) return 0; + + String[] aGrams = a.split(" "); + String[] bGrams = b.split(" "); + if (aGrams.length != bGrams.length) return 0; + + PPDBModel model = PPDBModel.getSingleton(); + int numPpdb = 0; + int numMisses = 0; + for (int i = 0; i < aGrams.length; i++) { + if (computeMatch(aGrams[i], bGrams[i]) == 0d) { + if (model.get(aGrams[i], bGrams[i]) > 0d || + model.get(LanguageInfo.LanguageUtils.stem(aGrams[i]), + LanguageInfo.LanguageUtils.stem(bGrams[i])) > 0d) { + numPpdb++; + } + else { + numMisses++; + } + } + } + return (numMisses == 0 && numPpdb <= 1 ? 1d : 0d); + } +} diff --git a/src/edu/stanford/nlp/sempre/overnight/PPDBModel.java b/src/edu/stanford/nlp/sempre/overnight/PPDBModel.java new file mode 100644 index 0000000..e51dd23 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/overnight/PPDBModel.java @@ -0,0 +1,90 @@ +package edu.stanford.nlp.sempre.overnight; + +import fig.basic.IOUtils; +import fig.basic.LogInfo; +import fig.basic.MapUtils; +import fig.basic.Option; +import edu.stanford.nlp.sempre.*; + +import java.util.HashMap; +import java.util.Map; + +/** + * PPDBModel extracts and scores paraphrasing featues from derivations. + * This model is intended to be used with FloatingParser + * + * @author Yushi Wang + */ + +public final class PPDBModel { + public static class Options { + @Option(gloss = "Path to file with alignment table") + public String ppdbModelPath = "regex/regex-ppdb.txt"; + + @Option(gloss = "Using ppdb format") + public boolean ppdb = true; + } + + public static Options opts = new Options(); + + public static PPDBModel model; + + Map> table; + + // We should only have one paraphrase model + public static PPDBModel getSingleton() { + if (model == null) { + model = new PPDBModel(); + } + return model; + } + + private PPDBModel() { + table = loadPPDBModel(opts.ppdbModelPath); + } + + /** + * Loading ppdb model from file + */ + private Map> loadPPDBModel(String path) { + LogInfo.begin_track("Loading ppdb model"); + Map> res = new HashMap<>(); + for (String line: IOUtils.readLinesHard(path)) { + if (opts.ppdb) { + String[] tokens = line.split("\\|\\|\\|"); + String first = tokens[1].trim(); + String second = tokens[2].trim(); + String stemmedFirst = LanguageInfo.LanguageUtils.stem(first); + String stemmedSecond = LanguageInfo.LanguageUtils.stem(second); + + putParaphraseEntry(res, first, second); + if ((!stemmedFirst.equals(first) || !stemmedSecond.equals(second)) && + !stemmedFirst.equals(stemmedSecond)) + putParaphraseEntry(res, stemmedFirst, stemmedSecond); + } else { + String[] tokens = line.split("\t"); + MapUtils.putIfAbsent(res, tokens[0], new HashMap<>()); + for (String token : tokens) + LogInfo.logs("%s", token); + res.get(tokens[0]).put(tokens[1], 1.0); + } + } + LogInfo.logs("ParaphraseUtils.loadPhraseTable: number of entries=%s", res.size()); + LogInfo.end_track(); + return res; + } + + private void putParaphraseEntry(Map> res, String first, String second) { + MapUtils.putIfAbsent(res, first, new HashMap<>()); + res.get(first).put(second, 1.0); + } + + public boolean containsKey(String key) { + return table.containsKey(key); + } + + public Double get(String key, String token) { + if (!table.containsKey(key) || !table.get(key).containsKey(token)) return 0.0; + return table.get(key).get(token); + } +} diff --git a/src/edu/stanford/nlp/sempre/overnight/SimpleWorld.java b/src/edu/stanford/nlp/sempre/overnight/SimpleWorld.java new file mode 100644 index 0000000..5aa8085 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/overnight/SimpleWorld.java @@ -0,0 +1,827 @@ +package edu.stanford.nlp.sempre.overnight; + +import com.google.common.collect.Lists; +import fig.basic.*; +import edu.stanford.nlp.sempre.*; + +import java.util.*; + +/** + * Functions for supporting a simple database. + * This is very inefficient and it works only for small worlds. + * Example applications: calendar, blocks world + * + * Types: DateValue, TimeValue, Value + * All arguments are lists of Values. + * + * @author Jonathan Berant + * @author Yushi Wang + */ +public final class SimpleWorld { + public static class Options { + @Option(gloss = "Number of entity samples") + public int numOfValueSamples = 60; + @Option(gloss = "Domain specifies which predicates/entities exist in the world") + public String domain; + @Option(gloss = "Verbosity") + public int verbose = 0; + @Option(gloss = "Path to load up the DB from (triples)") + public String dbPath = null; + @Option(gloss = "When performing a join with getProperty, do we want to deduplicate?") + public boolean joinDedup = true; + } + public static Options opts = new Options(); + + private SimpleWorld() { } + + // en.person.alice => en.person + private static String extractType(String id) { + int i = id.lastIndexOf('.'); + if (id.charAt(i + 1) == '_') { //to deal with /fb:en.lake._st_clair and such + id = id.substring(0, i); + i = id.lastIndexOf('.'); + return id.substring(0, i); + } + else return id.substring(0, i); + } + + private static String getType(Value v) { + if (v instanceof NumberValue) { + String unit = ((NumberValue) v).unit; + return unit + "_number"; // So we can quickly tell if something is a number or not + } else if (v instanceof DateValue) { + return "en.date"; + } else if (v instanceof TimeValue) { + return "en.time"; + } else if (v instanceof BooleanValue) { + return "en.boolean"; + } else if (v instanceof NameValue) { + return extractType(((NameValue) v).id); + } else if (v instanceof ListValue) { + return getType(((ListValue) v).values.get(0)); + } else { + throw new RuntimeException("Can't get type of value " + v); + } + } + + // Make sure that the types of the objects in the two lists are the same + private static void checkTypeMatch(List l1, List l2) { + for (Value o1 : l1) { + for (Value o2 : l2) { + if (!getType(o1).equals(getType(o2))) + throw new RuntimeException("Intersecting objects with non-matching types, object 1: " + + o1 + ", object2: " + o2); + } + } + } + + private static boolean intersects(List l1, List l2) { + //optimization + if (l1.size() < l2.size()) { + for (T o1 : l1) { + if (l2.contains(o1)) + return true; + } + } + else { + for (T o2 : l2) { + if (l1.contains(o2)) + return true; + } + } + return false; + } + + // Modes of superlatives + private static final String MIN = "min"; + private static final String MAX = "max"; + + //////////////////////////////////////////////////////////// + // Methods exposed to the public. + + public static List singleton(Value value) { return Collections.singletonList(value); } + + // Return the set of entities who can be the first argument of property + public static List domain(String property) { + createWorld(); + String type1 = propertyToType1.get(property); + if (type1 == null) + throw new RuntimeException("Property " + property + " has no type1"); + return getProperty(singleton(new NameValue(type1)), reverse("type")); + } + + public static String reverse(String property) { + if (property.startsWith("!")) return property.substring(1); + return "!" + property; + } + + // Return the concatenation of two lists. + public static List concat(Value v1, Value v2) { + return concat(singleton(v1), singleton(v2)); + } + public static List concat(List l1, List l2) { + checkTypeMatch(l1, l2); + if (l1.equals(l2)) // Disallow 'alice' or 'alice' + throw new RuntimeException("Cannot concatenate two copies of the same list: " + l1); + List newList = new ArrayList(); + newList.addAll(l1); + newList.addAll(l2); + return newList; + } + + private static void checkType1(String property, Value e1) { + String type1 = propertyToType1.get(property); + String type2 = "?"; + if (type1 == null) + throw new RuntimeException("Property " + property + " has no type1"); + if (!getType(e1).equals(type1)) + throw new RuntimeException("Type check failed: " + property + " : (-> " + type1 + " " + type2 + ") doesn't match arg1 " + e1 + " : " + getType(e1)); + } + + private static void checkType2(String property, Value e2) { + String type1 = "?"; + String type2 = propertyToType2.get(property); + if (type2 == null) + throw new RuntimeException("Property " + property + " has no type2"); + if (!getType(e2).equals(type2)) + throw new RuntimeException("Type check failed: " + property + " : (-> " + type1 + " " + type2 + ") doesn't match arg2 " + e2 + " : " + getType(e2)); + } + + private static void ensureNonnumericType2(String property) { + createWorld(); + String type2 = propertyToType2.get(property); + if (type2 == null) + throw new RuntimeException("Property " + property + " has no type2"); + if (type2.endsWith("_number") || type2.equals("en.date") || type2.equals("en.time")) + throw new RuntimeException("Property " + property + " has numeric type2, which is not allowed"); + } + + public static String ensureNumericProperty(String property) { + createWorld(); + String type2 = propertyToType2.get(property); + if (type2.endsWith("number") || type2.equals("en.date") || type2.equals("en.time")) { + return property; + } + throw new RuntimeException("Property " + property + " has non-numeric type2, which is not allowed"); + } + + public static List ensureNumericEntity(Value value) { + return ensureNumericEntity(singleton(value)); + } + + public static List ensureNumericEntity(List list) { + createWorld(); + String type = getType(list.get(0)); + if (type.endsWith("number") || type.equals("en.date") || type.equals("en.time")) { + return list; + } + throw new RuntimeException("List " + list + " is non-numeric, which is not allowed"); + } + + + // Return the subset of |objects| whose |property| |compare| refValues. + public static List filter(List entities, String property) { // Unary properties + return filter(entities, property, "=", singleton(new BooleanValue(true))); + } + public static List filter(List entities, String property, String compare, Value refValue) { + return filter(entities, property, compare, singleton(refValue)); + } + public static List filter(List entities, String property, String compare, List refValues) { + List newEntities = new ArrayList<>(); + + for (Value v : refValues) + checkType2(property, v); + + for (Value obj : entities) { + if (!(obj instanceof NameValue)) continue; + NameValue e = (NameValue) obj; + + List values = lookupDB(e, property); + boolean match = false; + + checkType1(property, e); + + if (compare.equals("=")) { + match = intersects(values, refValues); + } else if (compare.equals("!=")) { + match = !intersects(values, refValues); // Note this is not the existential interpretation! + } else if (compare.equals("<")) { + match = getDegree(values, MIN) < getDegree(refValues, MAX); + } else if (compare.equals(">")) { + match = getDegree(values, MAX) > getDegree(refValues, MIN); + } else if (compare.equals("<=")) { + match = getDegree(values, MIN) <= getDegree(refValues, MAX); + } else if (compare.equals(">=")) { + match = getDegree(values, MAX) >= getDegree(refValues, MIN); + } + if (match) newEntities.add(e); + } + return newEntities; + } + + private static double getDouble(Value v) { + if (!(v instanceof NumberValue)) + throw new RuntimeException("Not a number: " + v); + return ((NumberValue) v).value; + } + + // Degree is used to compare (either take the max or min). + private static double getDegree(List values, String mode) { + double deg = Double.NaN; + for (Value v : values) { + double x = getDegree(v); + if (Double.isNaN(deg) || (mode.equals(MAX) ? x > deg : x < deg)) + deg = x; + } + return deg; + } + private static double getDegree(Value value) { + if (value instanceof TimeValue) { + TimeValue timeValue = (TimeValue) value; + return timeValue.hour; + } else if (value instanceof DateValue) { + DateValue dateValue = (DateValue) value; + double dValue = 0; + if (dateValue.year != -1) dValue += dateValue.year * 10000; + if (dateValue.month != -1) dValue += dateValue.month * 100; + if (dateValue.day != -1) dValue += dateValue.day; + return dValue; + } else if (value instanceof NumberValue) { + return ((NumberValue) value).value; + } else { + throw new RuntimeException("Can't get degree from " + value); + } + } + + // Return the subset of entities that obtain the min/max value of property. + public static List superlative(List entities, String mode, String property) { + List bestEntities = null; + double bestDegree = Double.NaN; + + for (Value e : entities) { + double degree = getDegree(lookupDB(e, property), mode); + checkType1(property, e); + if (bestEntities == null || (mode.equals(MAX) ? degree > bestDegree : degree < bestDegree)) { + bestEntities = new ArrayList(); + bestEntities.add(e); + bestDegree = degree; + } else if (degree == bestDegree) { + bestEntities.add(e); + } + } + return bestEntities; + } + + // Return the subset of entities that obtain the most/least number of values of property that fall in restrictors. + public static List countSuperlative(List entities, String mode, String property) { + return countSuperlative(entities, mode, property, null); + } + + //make sure lists are returned in a unique order + public static String sortAndToString(Object obj) { + if (obj instanceof List) { + List strList = new ArrayList<>(); + List list = (List) obj; + for (Object listObj: list) + strList.add(listObj.toString()); + Collections.sort(strList); + return strList.toString(); + } + return obj.toString(); + } + + + public static List countSuperlative(List entities, String mode, String property, List restrictors) { + List bestEntities = null; + double bestDegree = Double.NaN; + + if (restrictors != null) { + for (Value v : restrictors) + checkType2(property, v); + } + ensureNonnumericType2(property); + + for (Value e : entities) { + List values = lookupDB(e, property); + double degree = 0; + for (Value v : values) + if (restrictors == null || restrictors.contains(v)) + degree++; + + checkType1(property, e); + + if (bestEntities == null || (mode.equals(MAX) ? degree > bestDegree : degree < bestDegree)) { + bestEntities = new ArrayList(); + bestEntities.add(e); + bestDegree = degree; + } else if (degree == bestDegree) { + bestEntities.add(e); + } + } + return bestEntities; + } + + // Return subset of entities that have the number of values of property that meet the mode/threshold criteria (e.g. >= 3). + public static List countComparative(List entities, String property, String mode, NumberValue thresholdValue) { + return countComparative(entities, property, mode, thresholdValue, null); + } + public static List countComparative(List entities, String property, String mode, NumberValue thresholdValue, List restrictors) { + List newEntities = new ArrayList<>(); + double threshold = getDouble(thresholdValue); + + if (restrictors != null) { + for (Value v : restrictors) + checkType2(property, v); + } + ensureNonnumericType2(property); + + for (Value e : entities) { + List values = lookupDB(e, property); + double degree = 0; + for (Value v : values) + if (restrictors == null || restrictors.contains(v)) + degree++; + + checkType1(property, e); + + switch (mode) { + case "=": if (degree == threshold) newEntities.add(e); break; + case "<": if (degree < threshold) newEntities.add(e); break; + case ">": if (degree > threshold) newEntities.add(e); break; + case "<=": if (degree <= threshold) newEntities.add(e); break; + case ">=": if (degree >= threshold) newEntities.add(e); break; + default: throw new RuntimeException("Illegal mode: " + mode); + } + } + return newEntities; + } + + // Return sum of values. + public static List sum(List values) { + double sum = 0; + for (Value v : values) + sum += getDouble(v); + return Collections.singletonList(new NumberValue(sum)); + } + + // Return sum/mean/min/max of values. + public static List aggregate(String mode, List values) { + // Note: this is probably too strong to reject empty lists. + if (values.size() == 0) + throw new RuntimeException("Can't aggregate " + mode + " over empty list"); + double sum = 0; + for (Value v : values) { + // Note: we're leaving out dates and times, but sum/avg doesn't quite work with them anyway. + if (!(v instanceof NumberValue)) + throw new RuntimeException("Can only aggregate over numbers, but got " + v); + double x = getDouble(v); + sum += x; + } + double result; + switch (mode) { + case "avg": result = sum / values.size(); break; + case "sum": result = sum; break; + default: throw new RuntimeException("Bad mode: " + mode); + } + return Collections.singletonList(new NumberValue(result, ((NumberValue) values.get(0)).unit)); + } + + // Return the properties of the entities (database join). + public static List getProperty(Value inObject, String property) { return getProperty(singleton(inObject), property); } + public static List getProperty(List inObjects, String property) { + List outObjects = new ArrayList<>(); + Set outObjectsCache = new HashSet<>(); //optimization - run "contains" on set and not list + for (Value obj : inObjects) { + List values = lookupDB(obj, property); + checkType1(property, obj); + for (Value v : values) { + if (!opts.joinDedup || !outObjectsCache.contains(v)) { + outObjects.add(v); + outObjectsCache.add(v); + } + } + } + if (outObjects.size() == 0) + throw new RuntimeException("The property " + property + " does not appear in any of the objects " + inObjects); + return outObjects; + } + + private static double arithOp(String op, double v1, double v2) { + switch (op) { + case "+": return v1 + v2; + case "-": return v1 - v2; + case "*": return v1 * v2; + case "/": return v1 / v2; + default: throw new RuntimeException("Invalid operation: " + op); + } + } + public static List arithOp(String op, Value v1, Value v2) { + return singleton(new NumberValue(arithOp(op, getDouble(v1), getDouble(v2)))); + } + public static List arithOp(String op, List args1, List args2) { + // FUTURE: should pay attention to units + List result = new ArrayList(); + for (Value v1 : args1) + for (Value v2 : args2) + result.addAll(arithOp(op, v1, v2)); + return result; + } + + //////////////////////////////////////////////////////////// + // Internal state of the world + + private static final Random random = new Random(1); + + private static Set entities; // Keep track of all the entities + private static Set properties; // Keep track of all the properties + private static Map propertyToType1, propertyToType2; // types + private static Map, List> database; // Database consists of (e1, property, e2) triples + + public static int sizeofDB() { + return database.size(); + } + + public static List lookupDB(Value e, String property) { + createWorld(); + if (!entities.contains(e)) throw new RuntimeException("DB doesn't contain entity " + e); + if (!properties.contains(property)) throw new RuntimeException("DB doesn't contain property " + property); + List values = database.get(new Pair(e, property)); + if (values == null) return Collections.EMPTY_LIST; + return values; + } + + private static void insertDB(Value e1, String property) { // For unary properties + insertDB(e1, property, new BooleanValue(true)); + } + private static void insertDB(Value e1, String property, List e2s) { + for (Value e2 : e2s) insertDB(e1, property, e2); + } + private static void insertDB(Value e1, String property, Value e2) { + //LogInfo.logs("insertDB (%s, %s, %s)", e1, property, e2); + entities.add(e1); + properties.add(property); + properties.add(reverse(property)); + entities.add(e2); + MapUtils.addToList(database, new Pair(e1, property), e2); + MapUtils.addToList(database, new Pair(e2, reverse(property)), e1); + propertyToType1.put(property, getType(e1)); + propertyToType2.put(property, getType(e2)); + propertyToType1.put(reverse(property), getType(e2)); + propertyToType2.put(reverse(property), getType(e1)); + } + + public static void dumpDatabase() { + for (Pair pair : database.keySet()) + LogInfo.logs("%s %s %s", pair.getFirst(), pair.getSecond(), database.get(pair)); + } + + // Used for testing + public static void recreateWorld() { + database = null; + createWorld(); + } + + public static void createWorld() { + if (database != null) return; + entities = new HashSet<>(); + properties = new HashSet<>(); + database = new HashMap<>(); + propertyToType1 = new HashMap<>(); + propertyToType2 = new HashMap<>(); + + Domain domain = null; + switch (opts.domain) { + case "blocks": domain = new BlocksDomain(); break; + case "calendar": domain = new CalendarDomain(); break; + case "housing": domain = new HousingDomain(); break; + case "restaurants": domain = new RestaurantsDomain(); break; + case "publications": domain = new PublicationDomain(); break; + case "socialnetwork": domain = new SocialNetworkDomain(); break; + case "basketball": domain = new BasketballDomain(); break; + case "recipes": domain = new RecipesDomain(); break; + case "geo880": opts.dbPath = "lib/data/overnight/geo880.db"; domain = new ExternalDomain(); break; + case "external": domain = new ExternalDomain(); break; + default: throw new RuntimeException("Unknown domain: " + opts.domain); + } + domain.createEntities(opts.numOfValueSamples); + + // Dump the entire database + LogInfo.begin_track("SimpleWorld.createWorld: domain = %s (%d entity/property pairs)", opts.domain, database.size()); + if (opts.verbose >= 1) { + dumpDatabase(); + } + LogInfo.end_track(); + } + + private static List L(T... list) { + return Arrays.asList(list); + } + + // Convert the Object back to a Value + private static Value toValue(Object obj) { + if (obj instanceof Value) return (Value) obj; + if (obj instanceof Boolean) return new BooleanValue((Boolean) obj); + if (obj instanceof Integer) return new NumberValue((Integer) obj, "count"); + if (obj instanceof Double) return new NumberValue((Double) obj); + if (obj instanceof String) return new StringValue((String) obj); + if (obj instanceof List) { + List list = Lists.newArrayList(); + for (Object elem : (List) obj) + list.add(toValue(elem)); + return new ListValue(list); + } + throw new RuntimeException("Unhandled object: " + obj + " with class " + obj.getClass()); + } + + // Convert the Object to list value + public static ListValue listValue(Object obj) { + Value value = toValue(obj); + if (value instanceof ListValue) { + ListValue lv = (ListValue) value; + Collections.sort(lv.values, new Value.ValueComparator()); + return (ListValue) value; + } + return new ListValue(singleton(value)); + } + + public static int sampleInt(int min, int max) { + return random.nextInt(max - min) + min; + } + + public static boolean sampleBernoulli(double prob) { + return random.nextDouble() < prob; + } + + // Choose a single random element from the list + public static T sampleMultinomial(List list) { + return list.get(sampleInt(0, list.size())); + } + + // Choose n random elements from list (duplicates are possible). + public static List sampleMultinomial(List list, int n) { + List sublist = new ArrayList(); + if (list.size() > 0) { + for (int i = 0; i < n; i++) + sublist.add(sampleMultinomial(list)); + } + return sublist; + } + + // Keep each element with probability |prob|. + public static List subsample(List list, double prob) { + List sublist = new ArrayList(); + for (T x : list) + if (sampleBernoulli(prob)) + sublist.add(x); + return sublist; + } + + // With high probability, return one of the first few to avoid empty denotations. + public static T focusSampleMultinomial(List list) { + if (sampleBernoulli(0.5)) + return list.get(0); + return list.get(sampleInt(0, list.size())); + } + + public abstract static class Domain { + public abstract void createEntities(int numEntities); + } + + // Create |numEntities| entities, the first few have ids. + private static List makeValues(List ids) { return makeValues(ids.size(), ids); } + private static List makeValues(int numEntities, List ids) { + List values = new ArrayList(); + String type = extractType(ids.get(0)); + for (int i = 0; i < numEntities; i++) + values.add(makeValue(i < ids.size() ? ids.get(i) : type + "." + i, type)); + return values; + } + private static Value makeValue(String id) { return makeValue(id, extractType(id)); } + private static Value makeValue(String id, String type) { + Value e = new NameValue(id); + if (!entities.contains(e)) { + insertDB(e, "type", new NameValue(type)); + } + return e; + } + + // All dates for all domains are assumed to be in this range. + private static DateValue sampleDate() { + return new DateValue(sampleInt(2000, 2010), -1, -1); + } + + //////////////////////////////////////////////////////////// + // Specific domains (important to synchronize the constants with overnight/grammar!) + + public static class BlocksDomain extends Domain { + public void createEntities(int numEntities) { + List blocks = makeValues(numEntities, L("en.block.block1", "en.block.block2")); + List shapes = makeValues(L("en.shape.pyramid", "en.shape.cube")); + List colors = makeValues(L("en.color.red", "en.color.green")); + for (Value e : blocks) { + insertDB(e, "shape", sampleMultinomial(shapes)); + insertDB(e, "color", sampleMultinomial(colors)); + insertDB(e, "length", new NumberValue(sampleInt(2, 8), "en.inch")); + insertDB(e, "width", new NumberValue(sampleInt(2, 8), "en.inch")); + insertDB(e, "height", new NumberValue(sampleInt(2, 8), "en.inch")); + insertDB(e, "left", sampleMultinomial(blocks)); + insertDB(e, "right", sampleMultinomial(blocks)); + insertDB(e, "above", sampleMultinomial(blocks)); + insertDB(e, "below", sampleMultinomial(blocks)); + if (sampleBernoulli(0.5)) + insertDB(e, "is_special"); + } + } + } + + public static class CalendarDomain extends Domain { + private static DateValue sampleDate() { + return new DateValue(2015, 1, sampleInt(1, 5)); + } + private static TimeValue sampleTime() { + return new TimeValue(sampleInt(9, 16), 0); + } + public void createEntities(int numEntities) { + List meetings = makeValues(numEntities, L("en.meeting.weekly_standup", "en.meeting.annual_review")); + List people = makeValues(L("en.person.alice", "en.person.bob")); + List locations = makeValues(L("en.location.greenberg_cafe", "en.location.central_office")); + for (Value e : meetings) { + insertDB(e, "date", sampleDate()); + insertDB(e, "start_time", sampleTime()); + insertDB(e, "end_time", sampleTime()); + insertDB(e, "length", new NumberValue(sampleInt(1, 4), "en.hour")); + insertDB(e, "attendee", sampleMultinomial(people, 2)); + insertDB(e, "location", sampleMultinomial(locations)); + if (sampleBernoulli(0.5)) + insertDB(e, "is_important"); + } + } + } + + public static class RestaurantsDomain extends Domain { + public static final List RESTAURANT_VPS = Arrays.asList("reserve,credit,outdoor,takeout,delivery,waiter,kids,groups".split(",")); + public static final List RESTAURANT_CUISINES = Arrays.asList("en.cuisine.thai,en.cuisine.french,en.cuisine.italian".split(",")); + public static final List RESTAURANT_MEALS = Arrays.asList("en.food.breakfast,en.food.lunch,en.food.dinner".split(",")); + public static final List NEIGHBORHOODS = Arrays.asList("en.neighborhood.tribeca,en.neighborhood.midtown_west,en.neighborhood.chelsea".split(",")); + + public void createEntities(int numEntities) { + List restaurants = makeValues(numEntities, L("en.restaurant.thai_cafe", "en.restaurant.pizzeria_juno")); + List neighborhoods = makeValues(NEIGHBORHOODS); + List cuisines = makeValues(RESTAURANT_CUISINES); + List meals = makeValues(RESTAURANT_MEALS); + for (Value e : restaurants) { + insertDB(e, "star_rating", new NumberValue(sampleInt(0, 6), "en.star")); + insertDB(e, "price_rating", new NumberValue(sampleInt(1, 5), "en.dollar_sign")); + insertDB(e, "num_reviews", new NumberValue(sampleInt(20, 60), "en.review")); + insertDB(e, "neighborhood", sampleMultinomial(neighborhoods)); + insertDB(e, "cuisine", sampleMultinomial(cuisines)); + insertDB(e, "meals", subsample(meals, 0.5)); + for (String vp : RESTAURANT_VPS) { + if (sampleBernoulli(0.5)) + insertDB(e, vp); + } + } + } + } + + public static class HousingDomain extends Domain { + public static final List HOUSING_VPS = Arrays.asList("allows_cats,allows_dogs,has_private_bath,has_private_room".split(",")); + public static final List HOUSING_TYPES = Arrays.asList("en.housing.apartment,en.housing.condo,en.housing.house,en.housing.flat".split(",")); + public static final List NEIGHBORHOODS = Arrays.asList("en.neighborhood.tribeca,en.neighborhood.midtown_west,en.neighborhood.chelsea".split(",")); + + public void createEntities(int numEntities) { + List units = makeValues(numEntities, L("en.housing_unit.123_sesame_street", "en.housing_unit.900_mission_ave")); + List housingTypes = makeValues(HOUSING_TYPES); + List neighborhoods = makeValues(NEIGHBORHOODS); + for (Value e : units) { + insertDB(e, "rent", new NumberValue(sampleMultinomial(L(1500, sampleInt(1000, 3000))), "en.dollar")); + insertDB(e, "size", new NumberValue(sampleMultinomial(L(800, sampleInt(500, 1500))), "en.square_feet")); + insertDB(e, "posting_date", sampleDate()); + insertDB(e, "neighborhood", sampleMultinomial(neighborhoods)); + insertDB(e, "housing_type", sampleMultinomial(housingTypes)); + for (String vp : HOUSING_VPS) { + if (sampleBernoulli(0.5)) + insertDB(e, vp); + } + } + } + } + + public static class PublicationDomain extends Domain { + public void createEntities(int numEntities) { + List articles = makeValues(numEntities, L("en.article.multivariate_data_analysis")); + List people = makeValues(L("en.person.efron", "en.person.lakoff")); + List venues = makeValues(L("en.venue.computational_linguistics", "en.venue.annals_of_statistics")); + for (Value e : articles) { + insertDB(e, "author", sampleMultinomial(people, 2)); + insertDB(e, "venue", sampleMultinomial(venues)); + insertDB(e, "publication_date", sampleDate()); + insertDB(e, "cites", sampleMultinomial(articles, sampleInt(1, 10))); + insertDB(e, "won_award"); + } + } + } + + public static class SocialNetworkDomain extends Domain { + public void createEntities(int numEntities) { + List people = makeValues(numEntities, L("en.person.alice", "en.person.bob")); + List genders = makeValues(L("en.gender.male", "en.gender.female")); + List relationshipStatuses = makeValues(L("en.relationship_status.single", "en.relationship_status.married")); + List cities = makeValues(L("en.city.new_york", "en.city.beijing")); + List universities = makeValues(L("en.university.brown", "en.university.berkeley", "en.university.ucla")); + List fields = makeValues(L("en.field.computer_science", "en.field.economics", "en.field.history")); + List companies = makeValues(L("en.company.google", "en.company.mckinsey", "en.company.toyota")); + List jobTitles = makeValues(L("en.job_title.ceo", "en.job_title.software_engineer", "en.job_title.program_manager")); + for (Value e : people) { + insertDB(e, "gender", sampleMultinomial(genders)); + insertDB(e, "relationship_status", sampleMultinomial(relationshipStatuses)); + insertDB(e, "height", new NumberValue(sampleInt(150, 210), "en.cm")); + insertDB(e, "birthdate", sampleDate()); + insertDB(e, "birthplace", sampleMultinomial(cities)); + insertDB(e, "friend", sampleMultinomial(people, sampleInt(0, 3))); + if (sampleBernoulli(0.5)) + insertDB(e, "logged_in"); + } + for (Value e : makeValues(numEntities, L("en.education.0"))) { + insertDB(e, "student", focusSampleMultinomial(people)); + insertDB(e, "university", sampleMultinomial(universities)); + insertDB(e, "field_of_study", sampleMultinomial(fields)); + insertDB(e, "education_start_date", sampleDate()); + insertDB(e, "education_end_date", sampleDate()); + } + for (Value e : makeValues(numEntities, L("en.employment.0"))) { + insertDB(e, "employee", focusSampleMultinomial(people)); + insertDB(e, "employer", sampleMultinomial(companies)); + insertDB(e, "job_title", sampleMultinomial(jobTitles)); + insertDB(e, "employment_start_date", sampleDate()); + insertDB(e, "employment_end_date", sampleDate()); + } + } + } + + public static class BasketballDomain extends Domain { + public void createEntities(int numEntities) { + List players = makeValues(numEntities, L("en.player.kobe_bryant", "en.player.lebron_james")); + List teams = makeValues(L("en.team.lakers", "en.team.cavaliers")); + List positions = makeValues(L("en.position.point_guard", "en.position.forward")); + for (Value e : makeValues(numEntities, L("en.stats.0"))) { + insertDB(e, "player", focusSampleMultinomial(players)); + insertDB(e, "position", sampleMultinomial(positions)); + insertDB(e, "team", sampleMultinomial(teams)); + insertDB(e, "season", sampleDate()); + + insertDB(e, "num_points", new NumberValue(sampleInt(0, 10), "point")); + insertDB(e, "num_assists", new NumberValue(sampleInt(0, 10), "assist")); + insertDB(e, "num_steals", new NumberValue(sampleInt(0, 10), "steal")); + insertDB(e, "num_turnovers", new NumberValue(sampleInt(0, 10), "turnover")); + insertDB(e, "num_rebounds", new NumberValue(sampleInt(0, 10), "rebound")); + insertDB(e, "num_blocks", new NumberValue(sampleInt(0, 10), "block")); + insertDB(e, "num_fouls", new NumberValue(sampleInt(0, 10), "foul")); + insertDB(e, "num_games_played", new NumberValue(sampleInt(0, 10), "game")); + } + } + } + + public static class RecipesDomain extends Domain { + public void createEntities(int numEntities) { + List recipes = makeValues(numEntities, L("en.recipe.rice_pudding", "en.recipe.quiche")); + List cuisines = makeValues(L("en.cuisine.chinese", "en.cuisine.french")); + List ingredients = makeValues(L("en.ingredient.milk", "en.ingredient.spinach")); + List meals = makeValues(L("en.meal.lunch", "en.meal.dinner")); + for (Value e : recipes) { + insertDB(e, "preparation_time", new NumberValue(sampleInt(5, 30), "en.minute")); + insertDB(e, "cooking_time", new NumberValue(sampleInt(5, 30), "en.minute")); + insertDB(e, "cuisine", sampleMultinomial(cuisines)); + insertDB(e, "requires", sampleMultinomial(ingredients)); + insertDB(e, "meal", sampleMultinomial(meals)); + insertDB(e, "posting_date", sampleDate()); + } + } + } + + // Domain that corresponds to reading from a file + public static class ExternalDomain extends Domain { + public void createEntities(int numEntities) { + LogInfo.begin_track("ExternalDomain.createEntities: %s", opts.dbPath); + // Load up from database + for (String line : IOUtils.readLinesHard(opts.dbPath)) { + String[] tokens = line.split("\t"); + String pred = tokens[0]; + Value e = makeValue(tokens[1]); + if (tokens.length == 2) { // Unary + insertDB(e, pred); + } else if (tokens.length == 3) { // Binary + Value f; + if (tokens[2].startsWith("fb:en.")) // Named entity + f = makeValue(tokens[2]); + else + f = Value.fromString(tokens[2]); // Number + insertDB(e, pred, f); + } else { + throw new RuntimeException("Unhandled: " + line); + } + } + LogInfo.end_track(); + } + } +} diff --git a/src/edu/stanford/nlp/sempre/overnight/test/SimpleWorldTest.java b/src/edu/stanford/nlp/sempre/overnight/test/SimpleWorldTest.java new file mode 100644 index 0000000..0ddb4cb --- /dev/null +++ b/src/edu/stanford/nlp/sempre/overnight/test/SimpleWorldTest.java @@ -0,0 +1,30 @@ +package edu.stanford.nlp.sempre.overnight.test; + +import static org.testng.AssertJUnit.assertEquals; + +import org.testng.annotations.Test; + +import edu.stanford.nlp.sempre.*; +import edu.stanford.nlp.sempre.overnight.*; +import fig.basic.*; + +/** + * Test simple world from overnight framework. + * Creates a small database using SimpleWorld, + * and does sanity checks on the induced knowledge graph + * @author Yushi Wang + */ +public class SimpleWorldTest { + @Test public void externalWorldTest() { + edu.stanford.nlp.sempre.overnight.SimpleWorld.opts.domain = "external"; + edu.stanford.nlp.sempre.overnight.SimpleWorld.opts.dbPath = "lib/data/overnight/test/unittest.db"; + edu.stanford.nlp.sempre.overnight.SimpleWorld.opts.verbose = 1; + edu.stanford.nlp.sempre.overnight.SimpleWorld.recreateWorld(); + + assertEquals(edu.stanford.nlp.sempre.overnight.SimpleWorld.sizeofDB(), 12); + } + + public static void main(String[] args) { + new SimpleWorldTest().externalWorldTest(); + } +} diff --git a/src/edu/stanford/nlp/sempre/tables/ConvertCsvToTtl.java b/src/edu/stanford/nlp/sempre/tables/ConvertCsvToTtl.java new file mode 100644 index 0000000..f69c39b --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/ConvertCsvToTtl.java @@ -0,0 +1,285 @@ +package edu.stanford.nlp.sempre.tables; + +import java.io.*; +import java.util.*; +import java.text.*; + +import au.com.bytecode.opencsv.CSVReader; +import edu.stanford.nlp.sempre.*; +import fig.basic.*; +import fig.exec.*; +import static fig.basic.LogInfo.*; + +/** +Converts a CSV file into a TTL file to be loaded into Virtuoso. +Also dumps a lexicon file that maps strings to Freebase constants. +This allows us to quickly deploy semantic parsers on random CSV data that's not +already in Freebase. + +Domain (e.g., paleo): everything will live under fb:.* +The input is a set of tables. Each table has: +- A set of column names (each column corresponds to a property) +- A set of row names (each row corresponds to an event). +- A name (this determines the type of the event). + +Columns that contain strings are considered entities, and we define a new type +for that based on the column name. Important: note that different tables +interact by virtue of having the same column name (think of joining two tables +by string matching their column names). Of course, this is restrictive, but +it's good enough for now. + +Example (domain: foo) + + table name: info + columns: person, age, marital_status, social_security, place_of_birth + ... + + table name: education + columns: person, elementary_school, high_school, college + ... + + Event types: fb:foo.info, fb:foo.education + Properties: fb:foo.info.person, fb:foo.info.age, ... + Entity types: fb:foo.person, ... + Events: fb:foo.info0, fb:foo.info1, ... + Entities: fb:foo.barack_obama, ... + +Each column is a property that could take on several values. A column could +have many different types (e.g., int, text, entity) depending on how the +values are parsed, but we can't do this with one pass over the data, so we're +punting on this for now. + +@author Percy Liang +*/ +public class ConvertCsvToTtl implements Runnable { + @Option(required = true, gloss = "Domain (used to specify all the entities)") public String domain; + @Option(required = true, gloss = "Input CSV :
(assume each file has a header)") public List tables; + @Option(required = true, gloss = "Output schema ttl to this path") public String outSchemaPath; + @Option(required = true, gloss = "Output ttl to this path") public String outTtlPath; + @Option(required = true, gloss = "Output lexicon to this path") public String outLexiconPath; + @Option(gloss = "Only use these columns") public List keepProperties; + @Option(gloss = "Maximum number of rows to read per file") public int maxRowsPerFile = Integer.MAX_VALUE; + + public static final String ttlPrefix = "@prefix fb: ."; + + // Names and types of entities + Map id2name = new HashMap(); + Map> id2types = new HashMap>(); + + // Used to parse values into dates + List dateFormats = Arrays.asList( + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH), // One used by Freebase + new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy", Locale.ENGLISH), + new SimpleDateFormat("MM/dd/yyyy", Locale.ENGLISH)); + + String prependDomain(String s) { return "fb:" + domain + "." + s; } + String makeString(String s) { + if (s.length() >= 2 && s.startsWith("\"") && s.endsWith("\"")) + s = s.substring(1, s.length() - 1); + s = s.replaceAll("\"", "\\\\\""); // Quote + return "\"" + s + "\"@en"; + } + String lexEntry(String s, String id, Set types) { + SemType type = types == null ? SemType.anyType : SemType.newUnionSemType(types); + Map result = new HashMap(); + result.put("lexeme", s); // Note: this is not actually a lexeme, but the full rawPhrase. + result.put("formula", id); + result.put("source", "STRING_MATCH"); + result.put("type", type); + return Json.writeValueAsStringHard(result); + } + + String canonicalize(String value) { + if (Character.isDigit(value.charAt(value.length() - 1))) { + // Try to convert to integer + try { + Integer.parseInt(value); + return "\"" + value + "\"^^xsd:int"; + } catch (NumberFormatException e) { + } + + // Try to convert to double + try { + Double.parseDouble(value); + return "\"" + value + "\"^^xsd:double"; + } catch (NumberFormatException e) { + } + + // Try to convert to date + for (DateFormat format : dateFormats) { + try { + Date date = format.parse(value); + return "\"" + dateFormats.get(0).format(date) + "\"^^xsd:datetime"; + } catch (ParseException e) { + } + } + } + + // Try to interpret as entity if it is short enough + if (value.split(" ").length <= 5) { + String id = value; + id = id.replaceAll("[^\\w]", "_"); // Replace abnormal characters with _ + id = id.replaceAll("_+", "_"); // Merge consecutive _'s + id = id.replaceAll("_$", ""); + id = id.toLowerCase(); + if (id.length() == 0) id = "null"; + id = prependDomain(id); + id2name.put(id, value); + return id; + } + + // Just interpret as string + return makeString(value); + } + + public static void writeTriple(PrintWriter out, String arg1, String property, String arg2) { + out.println(arg1 + "\t" + property + "\t" + arg2 + "."); + } + + static class Column { + String description; + String header; + String property; + int numInt, numDouble, numDate, numText, numEntity; + + // Return whether we have an entity. + boolean add(String value) { + if (value.endsWith("xsd:int")) + numInt++; + else if (value.endsWith("xsd:double")) + numDouble++; + else if (value.endsWith("xsd:datetime")) + numDate++; + else if (value.endsWith("@en")) + numText++; + else { + numEntity++; + return true; + } + return false; + } + + String getEntityType() { return header; } + + String getType() { + int[] counts = new int[] {numInt, numDouble, numDate, numText, numEntity}; + int max = ListUtils.max(counts); + // if (numInt == max) return FreebaseInfo.INT; + // if (numDouble == max) return FreebaseInfo.FLOAT; + if (numInt == max) return CanonicalNames.NUMBER; + if (numDouble == max) return CanonicalNames.NUMBER; + if (numDate == max) return CanonicalNames.DATE; + if (numText == max) return CanonicalNames.TEXT; + return getEntityType(); + } + + @Override public String toString() { + StringBuilder b = new StringBuilder(); + b.append(header); + if (numInt > 0) b.append(", int=" + numInt); + if (numDouble > 0) b.append(", double=" + numDouble); + if (numDate > 0) b.append(", date=" + numDate); + if (numText > 0) b.append(", text=" + numText); + if (numEntity > 0) b.append(", entity=" + numEntity); + return b.toString(); + } + } + + public void run() { + PrintWriter schemaOut = IOUtils.openOutHard(outSchemaPath); + PrintWriter ttlOut = IOUtils.openOutHard(outTtlPath); + PrintWriter lexiconOut = IOUtils.openOutHard(outLexiconPath); + + ttlOut.println(ttlPrefix); + int total = 0; + for (String pairStr : tables) { + int num = 0; // The row number (standards for an event / CSV) + String[] pair = pairStr.split(":", 2); + if (pair.length != 2) throw new RuntimeException("Expected
: pair, but got: " + pair); + String tableName = pair[0]; + String inPath = pair[1]; + String eventType = prependDomain(tableName); // e.g., fb:paleo.taxon + + LogInfo.begin_track("Reading %s for events of type %s", inPath, eventType); + Column[] columns = null; + try (CSVReader csv = new CSVReader(new FileReader(inPath))) { + for (String[] row : csv) { + if (num >= maxRowsPerFile) break; + + // Initialize the columns + if (columns == null) { + columns = new Column[row.length]; + for (int i = 0; i < columns.length; i++) { + Column c = columns[i] = new Column(); + row[i] = row[i].trim(); + c.description = row[i]; + c.header = canonicalize(row[i]); + if (keepProperties != null && !keepProperties.contains(c.header)) { + c.header = null; + continue; + } + if (!c.header.startsWith("fb:" + domain)) + throw new RuntimeException("Invalid (internal problem): " + c.header); + c.property = c.header.replace("fb:" + domain, "fb:" + domain + "." + tableName); // Property + } + continue; + } + + // Read a row (corresponds to an event/CVT) + String event = prependDomain(tableName + (num++)); + writeTriple(ttlOut, event, "fb:type.object.type", tableName); + for (int i = 0; i < Math.min(row.length, columns.length); i++) { // For each column... + Column c = columns[i]; + if (c.header == null || row[i].equals("")) continue; + row[i] = canonicalize(row[i]); + writeTriple(ttlOut, event, c.property, row[i]); // Write out the assertion + + if (c.add(row[i])) { // Write out type for entities + MapUtils.addToSet(id2types, row[i], c.getEntityType()); + writeTriple(ttlOut, row[i], "fb:type.object.type", c.getEntityType()); + writeTriple(ttlOut, row[i], "fb:type.object.type", CanonicalNames.ENTITY); + } + } + if (num % 10000 == 0) + logs("Read %d rows (events)", num); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + + // Write out schema + writeTriple(schemaOut, eventType, "fb:freebase.type_hints.mediator", "\"true\"^^xsd:boolean"); // event type is a CVT + for (Column c : columns) { + if (c.header == null) continue; + writeTriple(schemaOut, c.property, "fb:type.object.type", "fb:type.property"); + writeTriple(schemaOut, c.property, "fb:type.property.schema", eventType); + writeTriple(schemaOut, c.property, "fb:type.property.expected_type", c.getType()); + writeTriple(schemaOut, c.property, "fb:type.object.name", makeString(c.description)); + if (c.getType().equals(c.getEntityType())) { + writeTriple(schemaOut, c.getEntityType(), "fb:type.object.name", makeString(c.description)); + writeTriple(schemaOut, c.getEntityType(), "fb:freebase.type_hints.included_types", CanonicalNames.ENTITY); + } + LogInfo.logs("%s", c); + } + LogInfo.end_track(); + total += num; + } + + logs("%d events, %d entities (ones with names)", total, id2name.size()); + for (Map.Entry e : id2name.entrySet()) { + writeTriple(ttlOut, e.getKey(), "fb:type.object.name", makeString(e.getValue())); + String k = e.getKey(); + String s = e.getValue(); + Set types = id2types.get(k); + lexiconOut.println(lexEntry(s, k, types)); + } + + schemaOut.close(); + ttlOut.close(); + lexiconOut.close(); + } + + public static void main(String[] args) { + Execution.run(args, new ConvertCsvToTtl()); + } +} diff --git a/src/edu/stanford/nlp/sempre/tables/DPParser.java b/src/edu/stanford/nlp/sempre/tables/DPParser.java new file mode 100644 index 0000000..a840ab4 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/DPParser.java @@ -0,0 +1,720 @@ +package edu.stanford.nlp.sempre.tables; + +import java.util.*; + +import edu.stanford.nlp.sempre.*; +import fig.basic.*; + +/** + * A DPParser parses utterances like a FloatingParser, + * but the dynamic programming states are derivations instead of formulas. + * + * DPParser makes 2 passes: + * - Pass 1: Find the set of denotations that lead to the correct value. + * - Pass 2: Use regular beam search (from FloatingParser), but only restrict + * the formulas to the ones found in Pass 1. + * + * @author ppasupat + */ +public class DPParser extends FloatingParser { + public static class Options { + @Option(gloss = "Use the targetValue at test time") public boolean cheat = false; + @Option(gloss = "During training, combine the derivation list from FloatingParser") + public boolean combineFromFloatingParser = false; + @Option(gloss = "Random object for shuffling the derivation list") + public Random shuffleRandom = new Random(1); + @Option(gloss = "Custom maximum depth for DPParser (default = FloatingParser's maxDepth)") + public int dpParserMaxDepth = -1; + @Option(gloss = "Custom beam size for DPParser (default = FloatingParser's beamSize)") + public int dpParserBeamSize = -1; + @Option(gloss = "During the first pass, try add floating derivation to the cell with the lowest depth first") + public boolean collapseFirstPass = false; + @Option(gloss = "Use all pruners regardless of correctness") + public boolean useAllPruners = false; + } + public static Options opts = new Options(); + + public DPParser(Spec spec) { + super(spec); + } + + @Override + public ParserState newParserState(Params params, Example ex, boolean computeExpectedCounts) { + // Test time (not cheated) --> use FloatingParser + if (!computeExpectedCounts && !opts.cheat) + return super.newParserState(params, ex, computeExpectedCounts); + // Training time (regardless of cheating) --> use mixture + if (computeExpectedCounts && opts.combineFromFloatingParser) + return new DPParserState(this, params, ex, computeExpectedCounts, + super.newParserState(params, ex, computeExpectedCounts)); + // Cheated test OR training without mixture --> use DPParser + return new DPParserState(this, params, ex, computeExpectedCounts); + } + +} + +/** + * Actual parsing logic. + */ +class DPParserState extends ParserState { + + private final DerivationPruner pruner; + private final int maxDepth, beamSize; + private final ParserState backoffParserState; + + public DPParserState(DPParser parser, Params params, Example ex, boolean computeExpectedCounts) { + this(parser, params, ex, computeExpectedCounts, null); + } + + public DPParserState(DPParser parser, Params params, Example ex, boolean computeExpectedCounts, ParserState backoff) { + super(parser, params, ex, computeExpectedCounts); + pruner = new DerivationPruner(this); + maxDepth = DPParser.opts.dpParserMaxDepth > 0 ? DPParser.opts.dpParserMaxDepth : FloatingParser.opts.maxDepth; + beamSize = DPParser.opts.dpParserBeamSize > 0 ? DPParser.opts.dpParserBeamSize : Parser.opts.beamSize; + backoffParserState = backoff; + } + + @Override + protected int getBeamSize() { return beamSize; } + + protected void ensureExecuted(Derivation deriv) { + deriv.ensureExecuted(parser.executor, ex.context); + if (!deriv.isFeaturizedAndScored() && currentPass != ParsingPass.FIRST) + featurizeAndScoreDerivation(deriv); + } + + // ============================================================ + // Dynamic programming cells + // ============================================================ + + // Pass 1: Just try to reach the correct denotation + // state name => denotation => FirstPassData + private final Map> firstPassCells = new HashMap<>(); + // Pass 2: Using results from Pass 1 to prune the possible formulas + // state name => denotation => SecondPassData + private final Map> secondPassCells = new HashMap<>(); + + enum ParsingPass { FIRST, SECOND, DONE }; + ParsingPass currentPass = ParsingPass.FIRST; + + private Map> getCellsForCurrentPass() { + return currentPass == ParsingPass.FIRST ? firstPassCells : secondPassCells; + } + + // ============================================================ + // DenotationIngredient + // ============================================================ + + // Represents a possible method for creating a particular denotation. + class DenotationIngredient { + public final Rule rule; + public final Value denotation1, denotation2; + private int hashCode; + + public DenotationIngredient() { + this.rule = null; + this.denotation1 = null; + this.denotation2 = null; + computeHashCode(); + } + + public DenotationIngredient(Rule rule, Derivation deriv1, Derivation deriv2) { + this.rule = rule; + if (deriv1 == null) { + this.denotation1 = null; + } else { + ensureExecuted(deriv1); + this.denotation1 = deriv1.value; + } + if (deriv2 == null) { + this.denotation2 = null; + } else { + ensureExecuted(deriv1); + this.denotation2 = deriv1.value; + } + computeHashCode(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof DenotationIngredient)) return false; + DenotationIngredient that = (DenotationIngredient) o; + if (rule != that.rule) return false; // Rules must be the same object + if (denotation1 == null) { + if (that.denotation1 != null) return false; + } else { + if (!denotation1.equals(that.denotation1)) return false; + } + if (denotation2 == null) { + if (that.denotation2 != null) return false; + } else { + if (!denotation2.equals(that.denotation2)) return false; + } + return true; + } + + private void computeHashCode() { + hashCode = ((rule == null) ? 0 : rule.hashCode() * 1729) + + ((denotation1 == null) ? 0 : denotation1.hashCode() * 42) + + ((denotation2 == null) ? 0 : denotation2.hashCode()); + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public String toString() { + return "[" + rule + " | " + denotation1 + " | " + denotation2 + "]"; + } + } + + private final Set allowedDenotationIngredients = new HashSet<>(); + + // ============================================================ + // BackPointer + // ============================================================ + + // Back pointers for dynamic programming. Points to previous cells. + class BackPointer { + public final String cell; + public final Value denotation; + + public BackPointer(String cell, Value denotation) { + this.cell = cell; + this.denotation = denotation; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof BackPointer)) return false; + BackPointer that = (BackPointer) o; + return cell.equals(that.cell) && denotation.equals(that.denotation); + } + + @Override + public int hashCode() { + return cell.hashCode() * 100 + denotation.hashCode(); + } + + @Override + public String toString() { + return cell + " " + denotation; + } + } + + public BackPointer getBackPointer(String cell, Derivation child) { + if (cell == null || child == null) return null; + ensureExecuted(child); + return new BackPointer(cell, child.value); + } + + // ============================================================ + // Metadata + // ============================================================ + + // Stores derivations and other data + class Metadata { + public final Value denotation; + // List of possible methods to create this denotation + public Set possibleIngredients = new HashSet<>(); + // Backpointers for backtracking after the first pass + public Set backpointers = new HashSet<>(); + // All derivations containing formulas that execute to the denotation. + // For the FIRST pass, this has only 1 formula. + // For the SECOND pass, this will eventually be pruned to the beam size. + public List derivations = new ArrayList<>(); + + public Metadata(Value denotation) { + this.denotation = denotation; + } + + public void add(Derivation deriv, DenotationIngredient ingredient, + BackPointer bp1, BackPointer bp2) { + if (currentPass == ParsingPass.FIRST) { + if (derivations.isEmpty()) { + if (Parser.opts.verbose >= 3) + LogInfo.logs("Metadata.add: %s %s", denotation, deriv); + derivations.add(deriv); + } + if (Parser.opts.verbose >= 3) { + LogInfo.logs("possibleIngredients.add: %s", ingredient); + } + possibleIngredients.add(ingredient); + if (bp1 != null) backpointers.add(bp1); + if (bp2 != null) backpointers.add(bp2); + } else if (currentPass == ParsingPass.SECOND) { + derivations.add(deriv); + } + } + } + + // ============================================================ + // Add to Chart + // ============================================================ + + private void addToChart(String name, Derivation deriv, DenotationIngredient ingredient, + BackPointer bp1, BackPointer bp2) { + if (Parser.opts.verbose >= 3) + LogInfo.logs("addToChart %s %s: %s", name, deriv.value, deriv); + ensureExecuted(deriv); + Map> cells = getCellsForCurrentPass(); + Map denotationToData = cells.get(name); + if (denotationToData == null) + cells.put(name, denotationToData = new HashMap<>()); + Metadata metadata = denotationToData.get(deriv.value); + if (metadata == null) + denotationToData.put(deriv.value, metadata = new Metadata(deriv.value)); + metadata.add(deriv, ingredient, bp1, bp2); + } + + private String anchoredCell(String cat, int start, int end) { + return cat + "[" + start + "," + end + "]"; + } + + private String floatingCell(String cat, int depth) { + return cat + ":" + depth; + } + + private int getDepth(String cell) { + String[] tokens = cell.split(":"); + return tokens.length == 2 ? Integer.parseInt(tokens[1]) : 0; + } + + // ============================================================ + // Apply Rule + // ============================================================ + + private void applyRule(Rule rule, int start, int end, int depth, + String cell1, Derivation child1, String cell2, Derivation child2) { + if (Parser.opts.verbose >= 5) + LogInfo.logs("applyRule %s [%s:%s] depth=%s, %s %s", rule, start, end, depth, child1, child2); + + DenotationIngredient ingredient = new DenotationIngredient(rule, child1, child2); + if (currentPass == ParsingPass.SECOND) { + // Prune invalid ingredient + if (!allowedDenotationIngredients.contains(ingredient)) return; + } + BackPointer bp1 = getBackPointer(cell1, child1), bp2 = getBackPointer(cell2, child2); + + List children; + if (child1 == null) // 0-ary + children = Collections.emptyList(); + else if (child2 == null) // 1-ary + children = Collections.singletonList(child1); + else { + children = ListUtils.newList(child1, child2); + // optionally: ensure that specific anchors are only used once per final derivation + // TODO(ice): can we impose useAnchorsOnce on the first pass without dropping correct derivations? + if (FloatingParser.opts.useAnchorsOnce && currentPass != ParsingPass.FIRST + && FloatingRuleUtils.derivationAnchorsOverlap(child1, child2)) + return; + } + + // Call the semantic function on the children and read the results + DerivationStream results = rule.sem.call(ex, + new SemanticFn.CallInfo(rule.lhs, start, end, rule, children)); + while (results.hasNext()) { + Derivation newDeriv = results.next(); + if (pruner.isPruned(newDeriv)) continue; + if (depth == -1) { + // Anchored rule + addToChart(anchoredCell(rule.lhs, start, end), newDeriv, ingredient, bp1, bp2); + addToChart(floatingCell(rule.lhs, 0), newDeriv, ingredient, bp1, bp2); + } else { + // Floating rule + if (DPParser.opts.collapseFirstPass && currentPass == ParsingPass.FIRST) { + for (int lowerDepth = 0; lowerDepth <= depth; lowerDepth++) { + Map denotationToData = getCellsForCurrentPass().get(floatingCell(rule.lhs, lowerDepth)); + if (lowerDepth == depth || (denotationToData != null && denotationToData.containsKey(newDeriv.value))) { + addToChart(floatingCell(rule.lhs, lowerDepth), newDeriv, ingredient, bp1, bp2); + break; + } + } + } else { + addToChart(floatingCell(rule.lhs, depth), newDeriv, ingredient, bp1, bp2); + } + } + } + } + + private void applyAnchoredRule(Rule rule, int start, int end) { + applyRule(rule, start, end, -1, null, null, null, null); + } + private void applyAnchoredRule(Rule rule, int start, int end, + String cell1, Derivation child1) { + applyRule(rule, start, end, -1, cell1, child1, null, null); + } + private void applyAnchoredRule(Rule rule, int start, int end, + String cell1, Derivation child1, String cell2, Derivation child2) { + applyRule(rule, start, end, -1, cell1, child1, cell2, child2); + } + + private void applyFloatingRule(Rule rule, int depth) { + applyRule(rule, -1, -1, depth, null, null, null, null); + } + private void applyFloatingRule(Rule rule, int depth, + String cell1, Derivation child1) { + applyRule(rule, -1, -1, depth, cell1, child1, null, null); + } + private void applyFloatingRule(Rule rule, int depth, + String cell1, Derivation child1, String cell2, Derivation child2) { + applyRule(rule, -1, -1, depth, cell1, child1, cell2, child2); + } + + // ============================================================ + // Get rules and derivations + // ============================================================ + + private List getDerivations(Object cell) { + Map> cells = getCellsForCurrentPass(); + Map denotationToData = cells.get(cell); + if (denotationToData == null) return Collections.emptyList(); + List derivations = new ArrayList<>(); + for (Metadata metadata : denotationToData.values()) { + derivations.addAll(metadata.derivations); + } + return derivations; + } + + // ============================================================ + // Build Anchored + // ============================================================ + + // 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.getRules()) { + 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) + applyAnchoredRule(rule, start, end); + } + + // Apply binaries on spans (rule $A ($B $C)), ... + for (int mid = start + 1; mid < end; mid++) { + for (Rule rule : parser.grammar.getRules()) { + if (!rule.isAnchored()) continue; + if (rule.rhs.size() != 2) continue; + + 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) { + String cell = anchoredCell(rhs2, mid, end); + List derivations = getDerivations(cell); + for (Derivation deriv : derivations) + applyAnchoredRule(rule, start, end, cell, deriv); + } + } else if (Rule.isCat(rhs1) && !Rule.isCat(rhs2)) { // $Cat token + if (match2) { + String cell = anchoredCell(rhs1, start, mid); + List derivations = getDerivations(cell); + for (Derivation deriv : derivations) + applyAnchoredRule(rule, start, end, cell, deriv); + } + } else if (!Rule.isCat(rhs1) && !Rule.isCat(rhs2)) { // token token + if (match1 && match2) + applyAnchoredRule(rule, start, end); + } else { // $Cat $Cat + String cell1 = anchoredCell(rhs1, start, mid); + String cell2 = anchoredCell(rhs2, mid, end); + List derivations1 = getDerivations(cell1); + List derivations2 = getDerivations(cell2); + for (Derivation deriv1 : derivations1) + for (Derivation deriv2 : derivations2) + applyAnchoredRule(rule, start, end, cell1, deriv1, cell2, deriv2); + } + } + } + + // 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.getCatUnaryRules()) { + if (!rule.isAnchored()) continue; + String cell = anchoredCell(rule.rhs.get(0), start, end); + List derivations = getDerivations(cell); + for (Derivation deriv : derivations) { + applyAnchoredRule(rule, start, end, cell, deriv); + } + } + } + + // ============================================================ + // Build Floating + // ============================================================ + + // Build floating derivations of exactly depth |depth|. + private void buildFloating(int depth) { + // Apply unary tokens on spans (rule $A (a)) + if (depth == 1) { + for (Rule rule : parser.grammar.getRules()) { + if (!rule.isFloating()) continue; + if (rule.rhs.size() != 1 || rule.isCatUnary()) continue; + applyFloatingRule(rule, depth); + } + } + + // Apply binaries on spans (rule $A ($B $C)), ... + for (Rule rule : parser.grammar.getRules()) { + if (!rule.isFloating()) continue; + if (rule.rhs.size() != 2) continue; + + String rhs1 = rule.rhs.get(0); + String rhs2 = rule.rhs.get(1); + + if (!Rule.isCat(rhs1) && !Rule.isCat(rhs2)) { // token token + if (depth == 1) + applyFloatingRule(rule, depth); + } else if (!Rule.isCat(rhs1) && Rule.isCat(rhs2)) { // token $Cat + String cell = floatingCell(rhs2, depth - 1); + List derivations = getDerivations(cell); + for (Derivation deriv : derivations) + applyFloatingRule(rule, depth, cell, deriv); + } else if (Rule.isCat(rhs1) && !Rule.isCat(rhs2)) { // $Cat token + String cell = floatingCell(rhs1, depth - 1); + List derivations = getDerivations(cell); + for (Derivation deriv : derivations) + applyFloatingRule(rule, depth, cell, deriv); + } else { // $Cat $Cat + if (FloatingParser.opts.useSizeInsteadOfDepth) { + for (int depth1 = 0; depth1 < depth; depth1++) { + int depth2 = depth - 1 - depth1; + String cell1 = floatingCell(rhs1, depth1); + String cell2 = floatingCell(rhs2, depth2); + List derivations1 = getDerivations(cell1); + List derivations2 = getDerivations(cell2); + for (Derivation deriv1 : derivations1) + for (Derivation deriv2 : derivations2) + applyFloatingRule(rule, depth, cell1, deriv1, cell2, deriv2); + } + } else { + for (int subDepth = 0; subDepth < depth; subDepth++) { // depth-1 <=depth-1 + String cell1 = floatingCell(rhs1, depth - 1); + String cell2 = floatingCell(rhs2, subDepth); + List derivations1 = getDerivations(cell1); + List derivations2 = getDerivations(cell2); + for (Derivation deriv1 : derivations1) + for (Derivation deriv2 : derivations2) + applyFloatingRule(rule, depth, cell1, deriv1, cell2, deriv2); + } + for (int subDepth = 0; subDepth < depth - 1; subDepth++) { // derivations1 = getDerivations(cell1); + List derivations2 = getDerivations(cell2); + for (Derivation deriv1 : derivations1) + for (Derivation deriv2 : derivations2) + applyFloatingRule(rule, depth, cell1, deriv1, cell2, deriv2); + } + } + } + } + + // 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.getCatUnaryRules()) { + if (!rule.isFloating()) continue; + String cell = floatingCell(rule.rhs.get(0), depth - 1); + List derivations = getDerivations(cell); + for (Derivation deriv : derivations) + applyFloatingRule(rule, depth, cell, deriv); + } + } + + // ============================================================ + // Infer (main entry) + // ============================================================ + + @Override public void infer() { + LogInfo.begin_track("DPParser.infer()"); + // First pass + LogInfo.begin_track("First pass"); + StopWatchSet.begin("DPParser.firstPass"); + currentPass = ParsingPass.FIRST; + runParsingPass(); + collectPossibleIngredients(); + StopWatchSet.end(); + LogInfo.end_track(); + // Second pass + LogInfo.begin_track("Second pass"); + StopWatchSet.begin("DPParser.secondPass"); + currentPass = ParsingPass.SECOND; + runParsingPass(); + StopWatchSet.end(); + LogInfo.end_track(); + // Compile + StopWatchSet.begin("DPParser.final"); + currentPass = ParsingPass.DONE; + collectFinalDerivations(); + ensureExecuted(); + if (computeExpectedCounts) { + expectedCounts = new HashMap<>(); + ParserState.computeExpectedCounts(predDerivations, expectedCounts); + } + StopWatchSet.end(); + LogInfo.end_track(); + } + + private void runParsingPass() { + Set categories = new HashSet(); + for (Rule rule : parser.grammar.getRules()) + categories.add(rule.lhs); + + // Set the pruner + if (currentPass == ParsingPass.FIRST && !DPParser.opts.useAllPruners) + pruner.setCustomAllowedDomains(Arrays.asList( + "emptyDenotation", "nonLambdaError", "badSuperlativeHead", "sameMerge", "mistypedMerge")); + else + pruner.setCustomAllowedDomains(null); + + // Base case ($TOKEN, $PHRASE, $LEMMA_PHRASE) + // Denotations are StringValue + for (Derivation deriv : gatherTokenAndPhraseDerivations()) { + ensureExecuted(deriv); + addToChart(anchoredCell(deriv.cat, deriv.start, deriv.end), deriv, new DenotationIngredient(), null, null); + } + + // Build up anchored derivations + 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) { + pruneBeam(anchoredCell(cat, i, i + len)); + } + } + } + + // Build up floating derivations + for (int depth = 1; depth <= maxDepth; depth++) { + buildFloating(depth); + for (String cat : categories) { + pruneBeam(floatingCell(cat, depth)); + } + } + } + + // Prune to the beam size during Pass 2 + private void pruneBeam(String cell) { + if (currentPass == ParsingPass.SECOND) { + Map> cells = getCellsForCurrentPass(); + Map denotationToData = cells.get(cell); + if (denotationToData == null) return; + for (Metadata metadata : denotationToData.values()) { + pruneCell(cell, metadata.derivations); + } + } + } + + private void collectPossibleIngredients() { + if (Parser.opts.verbose >= 4) + LogInfo.logs("DPParserState.collectPossibleIngredients()"); + Set usedBps = new HashSet<>(); + collectPossibleIngredients(anchoredCell(Rule.rootCat, 0, numTokens), usedBps); + for (int depth = 1; depth <= maxDepth; depth++) + collectPossibleIngredients(floatingCell(Rule.rootCat, depth), usedBps); + if (Parser.opts.verbose >= 5) { + for (DenotationIngredient ingredient : allowedDenotationIngredients) + LogInfo.logs("%s", ingredient); + } + } + + private void collectPossibleIngredients(String cell, Set usedBps) { + if (Parser.opts.verbose >= 4) + LogInfo.logs("DPParserState.collectPossibleIngredients(%s)", cell); + Map denotationToMetadata = firstPassCells.get(cell); + if (denotationToMetadata == null) return; + for (Value denotation : denotationToMetadata.keySet()) { + double compatibility = parser.valueEvaluator.getCompatibility(ex.targetValue, denotation); + if (Parser.opts.verbose >= 2) + LogInfo.logs("[%f] %s", compatibility, denotationToMetadata.get(denotation).derivations.get(0)); + if (compatibility != 1) continue; + BackPointer bp = new BackPointer(cell, denotation); + if (!usedBps.contains(bp)) + collectPossibleIngredients(bp, usedBps, 0); + } + } + + private void collectPossibleIngredients(BackPointer bp, Set usedBps, int depth) { + if (Parser.opts.verbose >= 4) + LogInfo.logs("DPParserState.collectPossibleIngredients(%s)", bp); + if (DPParser.opts.collapseFirstPass && depth + getDepth(bp.cell) > maxDepth) { + if (Parser.opts.verbose >= 2) + LogInfo.logs("Too deep: [%d] [%d] [%d] %s", depth, getDepth(bp.cell), maxDepth, bp.cell); + return; + } + usedBps.add(bp); + Map denotationToMetadata = firstPassCells.get(bp.cell); + if (denotationToMetadata == null) return; + Metadata metadata = denotationToMetadata.get(bp.denotation); + if (metadata == null) return; + allowedDenotationIngredients.addAll(metadata.possibleIngredients); + // Recurse + for (BackPointer childBp : metadata.backpointers) { + if (!usedBps.contains(childBp)) + collectPossibleIngredients(childBp, usedBps, depth + 1); + } + } + + // Collect final predicted derivations + private void collectFinalDerivations() { + predDerivations.addAll(getDerivations(anchoredCell(Rule.rootCat, 0, numTokens))); + for (int depth = 1; depth <= maxDepth; depth++) + predDerivations.addAll(getDerivations(floatingCell(Rule.rootCat, depth))); + if (backoffParserState != null) { + // Also combine derivations from the backoff parser state + LogInfo.begin_track("Backoff ParserState"); + backoffParserState.infer(); + predDerivations.addAll(backoffParserState.predDerivations); + // Prevent oracles from always being at the front. + Collections.shuffle(predDerivations, DPParser.opts.shuffleRandom); + LogInfo.end_track(); + } + } + + // Collect the statistics and put them into the Evaluation object + @Override + protected void setEvaluation() { + super.setEvaluation(); + // Number of cells + countNumCells(firstPassCells, "firstPass"); + countNumCells(secondPassCells, "secondPass"); + // Number of possible ingredients + evaluation.add("allowedIngredients", allowedDenotationIngredients.size()); + } + + private void countNumCells(Map> cells, String prefix) { + int numAnchored = 0, numFloating = 0, numDenotations = 0, + numErrorDenotations = 0, numUniqueErrorDenotations = 0, numDerivations = 0; + Set uniqueDenotations = new HashSet<>(); + for (Map.Entry> entry : cells.entrySet()) { + if (entry.getKey().contains(",")) numAnchored++; else numFloating++; + for (Map.Entry subentry : entry.getValue().entrySet()) { + Value denotation = subentry.getKey(); + numDenotations++; + uniqueDenotations.add(denotation); + if (denotation instanceof ErrorValue) + numErrorDenotations++; + numDerivations += subentry.getValue().derivations.size(); + } + } + for (Value denotation : uniqueDenotations) { + if (denotation instanceof ErrorValue) + numUniqueErrorDenotations++; + } + evaluation.add(prefix + "Cells", cells.size()); + evaluation.add(prefix + "Anchored", numAnchored); + evaluation.add(prefix + "Floating", numFloating); + evaluation.add(prefix + "CellDenotations", numDenotations); + evaluation.add(prefix + "ErrorDenotations", numErrorDenotations); + evaluation.add(prefix + "UniqueDenotations", uniqueDenotations.size()); + evaluation.add(prefix + "UniqueErrorDenotations", numUniqueErrorDenotations); + evaluation.add(prefix + "Derivations", numDerivations); + } +} diff --git a/src/edu/stanford/nlp/sempre/tables/FuzzyMatcher.java b/src/edu/stanford/nlp/sempre/tables/FuzzyMatcher.java new file mode 100644 index 0000000..0bbf036 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/FuzzyMatcher.java @@ -0,0 +1,127 @@ +package edu.stanford.nlp.sempre.tables; + +import java.util.*; + +import edu.stanford.nlp.sempre.*; +import edu.stanford.nlp.sempre.tables.TableKnowledgeGraph.TableCell; +import edu.stanford.nlp.sempre.tables.TableKnowledgeGraph.TableColumn; +import fig.basic.MapUtils; +import fig.basic.Option; + +/** + * Perform fuzzy matching on the table knowledge graph. + * + * @author ppasupat + */ +public class FuzzyMatcher { + public static class Options { + // This would prevent "canada ?" from fuzzy matching: we already fuzzy match "canada" + @Option(gloss = "Ignore query strings where a boundary word is a punctuation (prevent overgeneration)") + public boolean ignorePunctuationBoundedQueries = true; + @Option(gloss = "Allow token subsequence match (can overgenerate a lot)") + public boolean allowTokenSubsequenceMatch = false; + @Option(gloss = "Do not fuzzy match if the query matches more than this number of formulas (prevent overgeneration)") + public int maxFuzzyMatchCandidates = Integer.MAX_VALUE; + } + public static Options opts = new Options(); + + public final TableKnowledgeGraph graph; + + public FuzzyMatcher(TableKnowledgeGraph graph) { + this.graph = graph; + precomputeForFuzzyMatching(); + } + + private static Collection getAllCollapsedForms(String original) { + Set collapsedForms = new HashSet<>(); + collapsedForms.add(StringNormalizationUtils.collapseNormalize(original)); + String normalized = StringNormalizationUtils.aggressiveNormalize(original); + collapsedForms.add(StringNormalizationUtils.collapseNormalize(normalized)); + if (opts.allowTokenSubsequenceMatch) { + String[] tokens = normalized.trim().split("\\s+"); + for (int i = 0; i < tokens.length; i++) { + StringBuilder sb = new StringBuilder(); + for (int j = i; j < tokens.length; j++) { + sb.append(tokens[j]); + collapsedForms.add(StringNormalizationUtils.collapseNormalize(sb.toString())); + } + } + } + collapsedForms.remove(""); + return collapsedForms; + } + + private static String getCanonicalCollapsedForm(String original) { + return StringNormalizationUtils.collapseNormalize(original); + } + + // Map normalized strings to Values + // ENTITIY --> ValueFormula fb:cell.___ or other primitive format + // UNARY --> JoinFormula (type fb:column.___) + // BINARY --> ValueFormula fb:row.row.___ + Set allEntityFormulas, allUnaryFormulas, allBinaryFormulas; + Map> phraseToEntityFormulas, phraseToUnaryFormulas, phraseToBinaryFormulas; + + protected void precomputeForFuzzyMatching() { + allEntityFormulas = new HashSet<>(); + allUnaryFormulas = new HashSet<>(); + allBinaryFormulas = new HashSet<>(); + phraseToEntityFormulas = new HashMap<>(); + phraseToUnaryFormulas = new HashMap<>(); + phraseToBinaryFormulas = new HashMap<>(); + for (TableColumn column : graph.columns) { + // unary and binary + Formula unary = new JoinFormula( + new ValueFormula<>(KnowledgeGraph.getReversedPredicate(column.propertyNameValue)), + new JoinFormula(new ValueFormula<>(new NameValue(CanonicalNames.TYPE)), + new ValueFormula<>(new NameValue(TableTypeSystem.ROW_TYPE))) + ); + Formula binary = new ValueFormula<>(column.propertyNameValue); + allUnaryFormulas.add(unary); + allBinaryFormulas.add(binary); + for (String s : getAllCollapsedForms(column.originalString)) { + MapUtils.addToSet(phraseToUnaryFormulas, s, unary); + MapUtils.addToSet(phraseToBinaryFormulas, s, binary); + } + // entity + for (TableCell cell : column.children) { + Formula entity = new ValueFormula<>(cell.properties.entityNameValue); + allEntityFormulas.add(entity); + for (String s : getAllCollapsedForms(cell.properties.originalString)) + MapUtils.addToSet(phraseToEntityFormulas, s, entity); + } + } + } + + boolean checkPunctuationBoundaries(String term) { + String[] tokens = term.trim().split("\\s+"); + if (tokens.length == 0) return false; + if (StringNormalizationUtils.collapseNormalize(tokens[0]).isEmpty()) return false; + if (tokens.length == 1) return true; + if (StringNormalizationUtils.collapseNormalize(tokens[tokens.length - 1]).isEmpty()) return false; + return true; + } + + public Collection getFuzzyMatchedFormulas(String term, FuzzyMatchFn.FuzzyMatchFnMode mode) { + if (opts.ignorePunctuationBoundedQueries && !checkPunctuationBoundaries(term)) return Collections.emptySet(); + String normalized = getCanonicalCollapsedForm(term); + Set answer; + switch (mode) { + case ENTITY: answer = phraseToEntityFormulas.get(normalized); break; + case UNARY: answer = phraseToUnaryFormulas.get(normalized); break; + case BINARY: answer = phraseToBinaryFormulas.get(normalized); break; + default: throw new RuntimeException("Unknown FuzzyMatchMode " + mode); + } + return (answer == null || answer.size() > opts.maxFuzzyMatchCandidates) ? Collections.emptySet() : answer; + } + + public Collection getAllFormulas(FuzzyMatchFn.FuzzyMatchFnMode mode) { + switch (mode) { + case ENTITY: return allEntityFormulas; + case UNARY: return allUnaryFormulas; + case BINARY: return allBinaryFormulas; + default: throw new RuntimeException("Unknown FuzzyMatchMode " + mode); + } + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/StringNormalizationUtils.java b/src/edu/stanford/nlp/sempre/tables/StringNormalizationUtils.java new file mode 100644 index 0000000..29c538b --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/StringNormalizationUtils.java @@ -0,0 +1,339 @@ +package edu.stanford.nlp.sempre.tables; + +import java.text.*; +import java.util.*; +import java.util.regex.*; + +import org.joda.time.DateTime; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; + +import edu.stanford.nlp.sempre.DateValue; +import edu.stanford.nlp.sempre.LanguageAnalyzer; +import edu.stanford.nlp.sempre.LanguageInfo; +import edu.stanford.nlp.sempre.NameValue; +import edu.stanford.nlp.sempre.NumberValue; +import edu.stanford.nlp.sempre.StringValue; +import edu.stanford.nlp.sempre.Value; +import edu.stanford.nlp.sempre.tables.TableKnowledgeGraph.TableCell; +import edu.stanford.nlp.sempre.tables.TableKnowledgeGraph.TableColumn; +import fig.basic.*; + +/** + * Utilities for string normalization. + * + * @author ppasupat + */ +public final class StringNormalizationUtils { + public static class Options { + @Option(gloss = "Use language analyzer") public boolean useLanguageAnalyzer = true; + @Option(gloss = "Verbosity") public int verbose = 0; + } + public static Options opts = new Options(); + + private StringNormalizationUtils() { } // Should not be instantiated. + + /** + * Analyze the content of the cells in the same column, and then generate possible normalizations. + * Modify the property map in each cell. + * + * TODO(ice): Take the homogeneity of the cells into account. + */ + public static void analyzeColumn(TableColumn column) { + for (TableCell cell : column.children) { + if (!cell.properties.metadata.isEmpty()) continue; // Already analyzed. + analyzeString(cell.properties.originalString, cell.properties.metadata); + } + } + + // ============================================================ + // Cell normalization + // ============================================================ + + public static final Pattern DASH = Pattern.compile("\\s*[-‐‑⁃‒–—―/,:;]\\s*"); + public static final Pattern SPACE = Pattern.compile("\\s+"); + + public static void analyzeString(String o, Map metadata) { + metadata.clear(); + Value value; + LanguageAnalyzer analyzer = LanguageAnalyzer.getSingleton(); + LanguageInfo languageInfo = analyzer.analyze(o); + // ===== Number: Also handle "2,000 ft." --> (number 2000) + value = parseNumberLenient(o); + if (value == null && opts.useLanguageAnalyzer) + value = parseNumberWithLanguageAnalyzer(languageInfo); + if (value != null) metadata.put(TableTypeSystem.CELL_NUMBER_VALUE, value); + // ===== Date and Time + value = parseDate(o); + if (value == null && opts.useLanguageAnalyzer) + value = parseDateWithLanguageAnalyzer(languageInfo); + if (value != null) metadata.put(TableTypeSystem.CELL_DATE_VALUE, value); + // ===== First and Second: "2-1" --> first = (number 2), second = (number 1) + // TODO(ice): Do we want non-numeric stuff? + String[] dashSplitted = DASH.split(o); + if (dashSplitted.length == 2) { + NumberValue first = parseNumberStrict(dashSplitted[0]), second = parseNumberStrict(dashSplitted[1]); + if (first != null && second != null) { + //metadata.put(TableTypeSystem.CELL_FIRST_VALUE, first); + metadata.put(TableTypeSystem.CELL_SECOND_VALUE, second); + } + } + // ===== Unit: "2,000 ft." --> "ft." + // TODO(ice): This is very crude + String[] spaceSplitted = SPACE.split(o); + if (spaceSplitted.length == 2) { + if (parseNumberStrict(spaceSplitted[0]) != null) + metadata.put(TableTypeSystem.CELL_UNIT_VALUE, new StringValue(spaceSplitted[1])); + } + // ===== Normalize + metadata.put(TableTypeSystem.CELL_NORMALIZED_VALUE, new StringValue(aggressiveNormalize(o))); + } + + // ============================================================ + // Type Conversion + // ============================================================ + + public static final NumberFormat numberFormat = NumberFormat.getInstance(Locale.US); + + /** + * Convert string to number. + * Partial match is allowed: "9,000 cakes" --> 9000 + */ + public static NumberValue parseNumberLenient(String s) { + try { + Number parsed = numberFormat.parse(s.replace(" ", "")); + return new NumberValue(parsed.doubleValue()); + } catch (ParseException e) { + return null; + } + } + + /** + * Convert string to number. + * Partial match is not allowed: "9,000 cakes" --> null + */ + public static NumberValue parseNumberStrict(String s) { + ParsePosition pos = new ParsePosition(0); + Number parsed = numberFormat.parse(s, pos); + if (parsed == null || s.length() != pos.getIndex()) return null; + return new NumberValue(parsed.doubleValue()); + } + + /** + * Convert string to number + unit. + * Must exactly match the pattern "number unit" (e.g., "9,000 cakes") + */ + public static NumberValue parseNumberWithUnitStrict(String s) { + String[] tokens = s.split(" "); + if (tokens.length != 2) return null; + ParsePosition pos = new ParsePosition(0); + Number parsed = numberFormat.parse(tokens[0], pos); + if (parsed == null || tokens[0].length() != pos.getIndex()) return null; + return new NumberValue(parsed.doubleValue(), tokens[1]); + } + + public static NumberValue parseNumberWithLanguageAnalyzer(LanguageInfo languageInfo) { + if (languageInfo.numTokens() == 0) return null; + String nerSpan; + nerSpan = languageInfo.getNormalizedNerSpan("NUMBER", 0, languageInfo.numTokens()); + if (nerSpan != null) { + try { + return new NumberValue(Double.parseDouble(nerSpan)); + } catch (NumberFormatException e) { } + } + nerSpan = languageInfo.getNormalizedNerSpan("ORDINAL", 0, languageInfo.numTokens()); + if (nerSpan != null) { + try { + return new NumberValue(Double.parseDouble(nerSpan)); + } catch (NumberFormatException e) { } + } + nerSpan = languageInfo.getNormalizedNerSpan("PERCENT", 0, languageInfo.numTokens()); + if (nerSpan != null) { + try { + return new NumberValue(Double.parseDouble(nerSpan.substring(1))); + } catch (NumberFormatException e) { } + } + nerSpan = languageInfo.getNormalizedNerSpan("MONEY", 0, languageInfo.numTokens()); + if (nerSpan != null) { + try { + return new NumberValue(Double.parseDouble(nerSpan.substring(1))); + } catch (NumberFormatException e) { } + } + return null; + } + + public static final DateTimeFormatter dateFormat = DateTimeFormat.forPattern("MMM d, yyyy"); + + /** + * Convert string to DateValue. + */ + public static DateValue parseDate(String s) { + try { + DateTime date = dateFormat.parseDateTime(s); + return new DateValue(date.getYear(), date.getMonthOfYear(), date.getDayOfMonth()); + } catch (IllegalArgumentException e) { + return null; + } + } + + public static final Pattern suTimeDateFormat = Pattern.compile("([0-9X]{4})(?:-([0-9X]{2}))?(?:-([0-9X]{2}))?"); + + public static DateValue parseDateWithLanguageAnalyzer(LanguageInfo languageInfo) { + if (languageInfo.numTokens() == 0) return null; + String nerSpan = languageInfo.getNormalizedNerSpan("DATE", 0, languageInfo.numTokens()); + if (opts.verbose >= 2) + LogInfo.logs("%s %s %s %s", languageInfo.tokens, languageInfo.nerTags, languageInfo.nerValues, nerSpan); + if (nerSpan == null) return null; + Matcher matcher = suTimeDateFormat.matcher(nerSpan); + if (!matcher.matches()) return null; + String yS = matcher.group(1), mS = matcher.group(2), dS = matcher.group(3); + int y = -1, m = -1, d = -1; + if (!(yS == null || yS.isEmpty() || yS.contains("X"))) y = Integer.parseInt(yS); + if (!(mS == null || mS.isEmpty() || mS.contains("X"))) m = Integer.parseInt(mS); + if (!(dS == null || dS.isEmpty() || dS.contains("X"))) d = Integer.parseInt(dS); + if (y == -1 && m == -1 && d == -1) return null; + return new DateValue(y, m, d); + } + + // ============================================================ + // Generic String normalization + // ============================================================ + + /** + * Convert escaped characters to actual values + */ + public static String unescape(String x) { + return x.replaceAll("\\\\n", "\n"); + } + + /** + * Collapse multiple spaces into one. + */ + public static String whitespaceNormalize(String x) { + return x.replaceAll("\\s", " ").trim(); + } + + /** + * Remove ALL spaces and non-alphanumeric characters, then convert to lower case. + * Used for fuzzy matching. + */ + public static String collapseNormalize(String x) { + return Normalizer.normalize(x, Normalizer.Form.NFD).replaceAll("[^A-Za-z0-9]", "").toLowerCase(); + } + + /** + * String to number + */ + public static NumberValue nameValueToNumberValue(NameValue v) { + if (v.description == null) return null; + try { + Number result = numberFormat.parse(v.description); + return new NumberValue(result.doubleValue()); + } catch (ParseException e) { + return null; + } + } + + /** + * Character normalization. + */ + public static String characterNormalize(String string) { + // Remove diacritics // (Sorry European people) + string = Normalizer.normalize(string, Normalizer.Form.NFD).replaceAll("[\u0300-\u036F]", ""); + // Special symbols + string = string + .replaceAll("‚", ",") + .replaceAll("„", ",,") + .replaceAll("[·・]", ".") + .replaceAll("…", "...") + .replaceAll("ˆ", "^") + .replaceAll("˜", "~") + .replaceAll("‹", "<") + .replaceAll("›", ">") + .replaceAll("[‘’´`]", "'") + .replaceAll("[“”«»]", "\"") + .replaceAll("[•†‡]", "") + .replaceAll("[‐‑–—]", "-") + .replaceAll("[\\u2E00-\\uFFFF]", ""); // (Sorry Chinese people) + return string.replaceAll("\\s+", " ").trim(); + } + + /** + * Simple normalization. (Include whitespace normalization) + */ + public static String simpleNormalize(String string) { + string = characterNormalize(string); + // Citation + string = string.replaceAll("\\[(nb ?)?\\d+\\]", ""); + string = string.replaceAll("\\*+$", ""); + // Year in parentheses + string = string.replaceAll("\\(\\d* ?-? ?\\d*\\)", ""); + // Outside Quote + string = string.replaceAll("^\"(.*)\"$", "$1"); + // Numbering + if (!string.matches("^[0-9.]+$")) + string = string.replaceAll("^\\d+\\.", ""); + return string.replaceAll("\\s+", " ").trim(); + } + + /** + * More aggressive normalization. (Include simple and whitespace normalization) + */ + public static String aggressiveNormalize(String string) { + // Dashed / Parenthesized information + string = simpleNormalize(string); + string = string.replaceAll("\\[[^\\]]*\\]", ""); + string = string.replaceAll("[\\u007F-\\uFFFF]", ""); + string = string.trim().replaceAll(" - .*$", ""); + string = string.trim().replaceAll("\\([^)]*\\)$", ""); + return string.replaceAll("\\s+", " ").trim(); + } + + // ============================================================ + // Test + // ============================================================ + + private static void unitTest(String o) { + Map metadata = new HashMap<>(); + analyzeString(o, metadata); + LogInfo.logs("%s %s", o, metadata); + } + + public static void main(String[] args) { + LanguageAnalyzer.opts.languageAnalyzer = "corenlp.CoreNLPAnalyzer"; + opts.verbose = 2; + unitTest("2"); + unitTest("twenty three"); + unitTest("21st"); + unitTest("2001st"); + unitTest("2,000,000 ft."); + unitTest("2,000,000.3579"); + unitTest("1/2"); + unitTest("1-2"); + unitTest("50%"); + unitTest("$30"); + unitTest("1 104"); + unitTest("United States of America (USA)"); + unitTest("January 3, 1993"); + unitTest("July 2008"); + // normalized-annotated-200.examples + unitTest("19 September 1984"); // Ex 90 + unitTest("July 9"); // Ex 139, 155 + unitTest("March 1983"); // Ex 167 + // Other things handled by SUTime + unitTest("Friday"); + unitTest("Every Friday"); + unitTest("7:00"); + unitTest("7pm"); + unitTest("January 2, 7am"); + unitTest("morning"); + unitTest("1993-95"); + unitTest("from dawn to dusk"); + unitTest("January 4 - February 10"); + unitTest("July 500"); + unitTest("July 500 B.C."); + unitTest("1800s"); + unitTest("19th century"); + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/TableDerivationPruningComputer.java b/src/edu/stanford/nlp/sempre/tables/TableDerivationPruningComputer.java new file mode 100644 index 0000000..fc750b2 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/TableDerivationPruningComputer.java @@ -0,0 +1,72 @@ +package edu.stanford.nlp.sempre.tables; + +import edu.stanford.nlp.sempre.*; +import fig.basic.LogInfo; + +public class TableDerivationPruningComputer extends DerivationPruningComputer { + + public TableDerivationPruningComputer(DerivationPruner pruner) { + super(pruner); + } + + @Override + public boolean isPruned(Derivation deriv) { + return pruneJoins(deriv); + } + + /** + * Consider strings of joins such as (relation1 (relation2 (...))) + * + * - forwardBackward: prune (relation (!relation (...))), etc. + * - doubleNext: prune (next (next (...))), (!next (next (...))), etc. + */ + private boolean pruneJoins(Derivation deriv) { + if (!containsStrategy("forwardBackward") && !containsStrategy("doubleNext")) return false; + Formula formula = deriv.formula, current = formula; + String rid1 = null, rid2 = null; + while (current instanceof JoinFormula) { + rid2 = rid1; + rid1 = getRelationIdIfPossible(((JoinFormula) current).relation); + if (rid1 != null && rid2 != null) { + // Prune (!relation (relation (...))) + if (containsStrategy("forwardBackward")) { + if (rid1.equals("!" + rid2) || rid2.equals("!" + rid1)) { + if (DerivationPruner.opts.pruningVerbosity >= 2) + LogInfo.logs("PRUNED [forwardBackward] %s", formula); + return true; + } + } + // Prune (next (next (...))) + if (containsStrategy("doubleNext")) { + if ((rid1.equals("fb:row.row.next") && rid2.equals("fb:row.row.next")) || + (rid1.equals("!fb:row.row.next") && rid2.equals("!fb:row.row.next"))) { + if (DerivationPruner.opts.pruningVerbosity >= 2) + LogInfo.logs("PRUNED [doubleNext] %s", formula); + return true; + } + } + } + current = ((JoinFormula) current).child; + } + return false; + } + + /** + * Helper method: Convert formula to relation id if possible. + */ + protected String getRelationIdIfPossible(Formula formula) { + if (formula instanceof ReverseFormula) { + String childId = getRelationIdIfPossible(((ReverseFormula) formula).child); + if (childId != null && !childId.isEmpty()) { + return childId.charAt(0) == '!' ? childId.substring(1) : "!" + childId; + } + } else if (formula instanceof ValueFormula) { + Value v = ((ValueFormula) formula).value; + if (v instanceof NameValue) { + return ((NameValue) v).id; + } + } + return null; + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/TableKnowledgeGraph.java b/src/edu/stanford/nlp/sempre/tables/TableKnowledgeGraph.java new file mode 100644 index 0000000..812e993 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/TableKnowledgeGraph.java @@ -0,0 +1,600 @@ +package edu.stanford.nlp.sempre.tables; + +import java.io.*; +import java.util.*; + +import au.com.bytecode.opencsv.CSVReader; +import edu.stanford.nlp.sempre.*; +import edu.stanford.nlp.sempre.tables.lambdadcs.LambdaDCSException; +import edu.stanford.nlp.sempre.tables.lambdadcs.LambdaDCSException.Type; +import fig.basic.*; + +/** + * A knowledge graph constructed from a table. + * + * - Each row becomes an entity + * - Each cell becomes an entity + * - Each column becomes a property between a row and a cell + * e.g., (row5 nationality canada) + * - Rows have several special properties (next, index) + * + * === Special Row Properties === + * - name = fb:row.row.next | type = (-> fb:type.row fb:type.row) + * - name = fb:row.row.index | type = (-> fb:type.int fb:type.row) + * + * === Special Cell Properties === + * - name = fb:cell.cell.number | type = (-> fb:type.number fb:type.cell) + * + * @author ppasupat + */ +public class TableKnowledgeGraph extends KnowledgeGraph { + public static class Options { + @Option(gloss = "Verbosity") public int verbose = 0; + @Option(gloss = "Base directory for CSV files") public String baseCSVDir = null; + @Option(gloss = "Normalize cell content before assigning id") + public boolean normalizeBeforeCreatingId = true; + @Option(gloss = "Forbid row.row.next on multiple rows") + public boolean forbidNextOnManyRows = true; + } + public static Options opts = new Options(); + + // ============================================================ + // Data Structure + // ============================================================ + + static class TableColumn { + public final List children; + public final String originalString; + public final String fieldName; + public final int index; + // Property Name + public final NameValue propertyNameValue; + // Property Type (FuncSemType) + public final SemType propertySemType; + // Children Cell's Type (EntitySemType) + public final String cellTypeString; + public final NameValue cellTypeValue; + public final SemType cellSemType; + + public TableColumn(String originalString, String fieldName, int index) { + this.children = new ArrayList<>(); + this.originalString = originalString; + this.fieldName = fieldName; + this.index = index; + this.propertyNameValue = new NameValue(TableTypeSystem.getPropertyName(fieldName), originalString); + this.propertySemType = TableTypeSystem.getPropertySemType(fieldName); + this.cellTypeString = TableTypeSystem.getCellType(fieldName); + this.cellTypeValue = new NameValue(this.cellTypeString, originalString); + this.cellSemType = SemType.newAtomicSemType(this.cellTypeString); + } + + public static Set getReservedFieldNames() { + Set usedNames = new HashSet<>(); + usedNames.add("next"); + usedNames.add("index"); + return usedNames; + } + + @Override + public String toString() { + return propertyNameValue.toString(); + } + } + + static class TableRow { + public final List children; + public final int index; + public final NumberValue indexValue; + public final NameValue entityNameValue; + + public TableRow(int index) { + this.children = new ArrayList<>(); + this.index = index; + this.indexValue = new NumberValue(index); + this.entityNameValue = new NameValue(TableTypeSystem.getRowName(index), "" + index); + } + + @Override + public String toString() { + return entityNameValue.toString(); + } + } + + static final class TableCell { + public final TableColumn parentColumn; + public final TableRow parentRow; + public final TableCellProperties properties; + + private TableCell(TableCellProperties properties, TableColumn column, TableRow row) { + this.parentColumn = column; + this.parentRow = row; + this.properties = properties; + } + + public static TableCell createAndAddTo(TableCellProperties properties, TableColumn column, TableRow row) { + TableCell answer = new TableCell(properties, column, row); + column.children.add(answer); + row.children.add(answer); + return answer; + } + + @Override + public String toString() { + return properties.entityNameValue.toString(); + } + } + + // Contract: There is only one TableCellProperties for each unique id. + static class TableCellProperties { + public final String id; + public final String originalString; + public final NameValue entityNameValue; + public final Map metadata; + + public TableCellProperties(String id, String originalString) { + this.id = id; + this.originalString = originalString; + this.entityNameValue = new NameValue(id, originalString); + this.metadata = new HashMap<>(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof TableCellProperties)) return false; + return id.equals(((TableCellProperties) o).id); + } + + @Override + public int hashCode() { + return id.hashCode(); + } + } + + // ============================================================ + // Fields + // ============================================================ + + List rows; + List columns; + String filename; + + Map rowNameToTableRow; + Map columnNameToTableColumn; + Map propertyIdToTableColumn; + Map cellIdToTableCellProperties; + FuzzyMatcher fuzzyMatcher; + + // ============================================================ + // Constructor + // ============================================================ + + /** + * Constructor (not visible to public) + */ + TableKnowledgeGraph(String filename) { + // Cells in the same column with the same string content gets the same id. + Map, String> columnAndOriginalStringToCellId = new HashMap<>(); + + // Read the CSV file + this.filename = filename; + filename = new File(opts.baseCSVDir, filename).getPath(); + try (CSVReader csv = new CSVReader(new FileReader(filename))) { + for (String[] record : csv) { + if (columns == null) { + // Initialize + rows = new ArrayList<>(); + columns = new ArrayList<>(); + rowNameToTableRow = new HashMap<>(); + columnNameToTableColumn = new HashMap<>(); + propertyIdToTableColumn = new HashMap<>(); + cellIdToTableCellProperties = new HashMap<>(); + // Read the header row + for (String entry : record) { + entry = StringNormalizationUtils.unescape(entry); + String normalizedEntry = (opts.normalizeBeforeCreatingId ? + StringNormalizationUtils.characterNormalize(entry).toLowerCase() : entry); + String columnName = TableTypeSystem.getUnusedName( + TableTypeSystem.canonicalizeName(normalizedEntry), columnNameToTableColumn.keySet()); + TableColumn column = new TableColumn(entry, columnName, columns.size()); + columns.add(column); + columnNameToTableColumn.put(columnName, column); + propertyIdToTableColumn.put(column.propertyNameValue.id, column); + } + } else { + // Read the content row + if (record.length != columns.size()) { + LogInfo.warnings("Table has %d columns but row has %d cells: %s | %s", columns.size(), + record.length, columns, Fmt.D(record)); + } + TableRow currentRow = new TableRow(rows.size()); + rowNameToTableRow.put(currentRow.entityNameValue.id, currentRow); + rows.add(currentRow); + for (int i = 0; i < columns.size(); i++) { + TableColumn column = columns.get(i); + String entry = (i < record.length) ? record[i] : ""; + entry = StringNormalizationUtils.unescape(entry); + String normalizedEntry = (opts.normalizeBeforeCreatingId ? + StringNormalizationUtils.characterNormalize(entry).toLowerCase() : entry); + Pair columnAndOriginalString = new Pair<>(column, normalizedEntry); + String id = columnAndOriginalStringToCellId.get(columnAndOriginalString); + if (id == null) { + String canonicalName = TableTypeSystem.canonicalizeName(normalizedEntry); + id = TableTypeSystem.getUnusedName( + TableTypeSystem.getCellName(canonicalName, column.fieldName), + cellIdToTableCellProperties.keySet()); + columnAndOriginalStringToCellId.put(columnAndOriginalString, id); + cellIdToTableCellProperties.put(id, new TableCellProperties(id, entry)); + } + TableCellProperties properties = cellIdToTableCellProperties.get(id); + TableCell.createAndAddTo(properties, column, currentRow); + } + } + } + // Generate cell properties by analyzing cell content in each column + for (TableColumn column : columns) + StringNormalizationUtils.analyzeColumn(column); + // Precompute normalized strings for fuzzy matching + fuzzyMatcher = new FuzzyMatcher(this); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + // Cache (don't create multiple graphs for the same CSV file) + static final Map filenameToGraph = new HashMap<>(); + + public static synchronized TableKnowledgeGraph fromFilename(String filename) { + // Get from cache if possible + TableKnowledgeGraph graph = filenameToGraph.get(filename); + if (graph == null) { + if (opts.verbose >= 1) + LogInfo.logs("create new TableKnowledgeGraph from filename = %s", filename); + StopWatchSet.begin("TableKnowledgeGraph.new"); + graph = new TableKnowledgeGraph(filename); + StopWatchSet.end(); + filenameToGraph.put(filename, graph); + } + return graph; + } + + public static TableKnowledgeGraph fromLispTree(LispTree tree) { + return fromFilename(tree.child(2).value); + } + + // ============================================================ + // Convert to other formats + // ============================================================ + + @Override + public LispTree toLispTree() { + if (filename != null) { + // short version: just print the filename + LispTree tree = LispTree.proto.newList(); + tree.addChild("graph"); + tree.addChild("tables.TableKnowledgeGraph"); + tree.addChild(filename); + return tree; + } + return toTableValue().toLispTree(); + } + + public TableValue toTableValue() { + List tableValueHeader = new ArrayList<>(); + List> tableValueRows = new ArrayList<>(); + for (TableColumn column : columns) { + tableValueHeader.add(column.originalString); + } + for (TableRow row : rows) { + List tableValueRow = new ArrayList<>(); + for (TableCell cell : row.children) { + tableValueRow.add(cell.properties.entityNameValue); + } + tableValueRows.add(tableValueRow); + } + return new TableValue(tableValueHeader, tableValueRows); + } + + // ============================================================ + // Fuzzy matching + // ============================================================ + + @Override + public Collection getFuzzyMatchedFormulas(String term, FuzzyMatchFn.FuzzyMatchFnMode mode) { + return fuzzyMatcher.getFuzzyMatchedFormulas(term, mode); + } + + @Override + public Collection getAllFormulas(FuzzyMatchFn.FuzzyMatchFnMode mode) { + return fuzzyMatcher.getAllFormulas(mode); + } + + // ============================================================ + // Query + // ============================================================ + + public static final NameValue TYPE = new NameValue(CanonicalNames.TYPE); + public static final NameValue ROW_TYPE = new NameValue(TableTypeSystem.ROW_TYPE); + public static final NameValue CELL_TYPE = new NameValue(TableTypeSystem.CELL_GENERIC_TYPE); + + /** Return all y such that x in firsts and (x,r,y) in graph */ + @Override + public List joinFirst(Value r, Collection firsts) { + return joinSecond(getReversedPredicate(r), firsts); + } + + /** Return all x such that y in seconds and (x,r,y) in graph */ + @Override + public List joinSecond(Value r, Collection seconds) { + List answer = new ArrayList<>(); + for (Pair pair : filterSecond(r, seconds)) + answer.add(pair.getFirst()); + return answer; + } + + /** Return all (x,y) such that x in firsts and (x,r,y) in graph */ + @Override + public List> filterFirst(Value r, Collection firsts) { + return getReversedPairs(filterSecond(getReversedPredicate(r), firsts)); + } + + /* + * - one-one and many-one: Each X maps to 1 Y: + * X-Y = row-row, row-primitive, primitive-row, row-cell, cell-primitive + * - one-many: Deduplicate first, then each X maps to possibly many Y's + * X-Y = cell-row, primitive-cell + */ + /** Return all (x,y) such that y in seconds and (x,r,y) in graph */ + // TODO(ice): Check correctness + @Override + public List> filterSecond(Value r, Collection seconds) { + List> answer = new ArrayList<>(); + Value reversed = isReversedRelation(r); + if (reversed != null) { + r = reversed; + if (r.equals(TYPE)) { + // (!fb:type.object.type fb:row.r5) --> fb:type.row + // Not handled right now. + throw new BadFormulaException("Unhandled! " + r); + } else if (r.equals(TableTypeSystem.ROW_NEXT_VALUE)) { + // (!fb:row.row.next fb:row.r5) --> fb:row.r6 + if (opts.forbidNextOnManyRows && seconds.size() != 1) { + throw new LambdaDCSException(Type.nonSingletonList, "cannot call next on " + seconds.size() + " rows."); + } + if (seconds.size() == Integer.MAX_VALUE) { + for (int i = 0; i < rows.size() - 1; i++) { + if (!seconds.contains(rows.get(i).entityNameValue)) continue; + answer.add(new Pair<>(rows.get(i + 1).entityNameValue, rows.get(i).entityNameValue)); + } + } else { + for (Value value : seconds) { + if (!(value instanceof NameValue)) continue; + TableRow row = rowNameToTableRow.get(((NameValue) value).id); + if (row == null) continue; + int i = row.index; + if (i + 1 >= rows.size()) continue; + answer.add(new Pair<>(rows.get(i + 1).entityNameValue, row.entityNameValue)); + } + } + } else if (r.equals(TableTypeSystem.ROW_INDEX_VALUE)) { + // (!fb:row.row.index fb:row.r5) --> (number 5) + if (seconds.size() == Integer.MAX_VALUE) { + for (TableRow row : rows) { + if (!seconds.contains(row.entityNameValue)) continue; + answer.add(new Pair<>(row.indexValue, row.entityNameValue)); + } + } else { + for (Value value : seconds) { + if (!(value instanceof NameValue)) continue; + TableRow row = rowNameToTableRow.get(((NameValue) value).id); + if (row == null) continue; + answer.add(new Pair<>(row.indexValue, row.entityNameValue)); + } + } + } else if (TableTypeSystem.isCellProperty(r)) { + // (!fb:cell.cell.number fb:cell_id.5) --> 5 + if (seconds.size() == Integer.MAX_VALUE) { + for (TableColumn column : columns) { + for (TableCell cell : column.children) { + Value property = cell.properties.metadata.get(r); + if (property == null || !seconds.contains(cell.properties.entityNameValue)) continue; + answer.add(new Pair<>(property, cell.properties.entityNameValue)); + } + } + } else { + for (Value value : seconds) { + if (!(value instanceof NameValue)) continue; + TableCellProperties properties = cellIdToTableCellProperties.get(((NameValue) value).id); + if (properties == null) continue; + Value property = properties.metadata.get(r); + if (property == null) continue; + answer.add(new Pair<>(property, properties.entityNameValue)); + } + } + } else { + // (!fb:column.nationality fb:row.r5) --> fb:cell.canada + if (seconds.size() == Integer.MAX_VALUE) { + for (int i = 0; i < columns.size(); i++) { + if (!r.equals(columns.get(i).propertyNameValue)) continue; + for (TableRow row : rows) { + if (!seconds.contains(row.entityNameValue)) continue; + answer.add(new Pair<>(row.children.get(i).properties.entityNameValue, row.entityNameValue)); + } + } + } else { + for (int i = 0; i < columns.size(); i++) { + if (!r.equals(columns.get(i).propertyNameValue)) continue; + for (Value value : seconds) { + if (!(value instanceof NameValue)) continue; + TableRow row = rowNameToTableRow.get(((NameValue) value).id); + if (row == null) continue; + answer.add(new Pair<>(row.children.get(i).properties.entityNameValue, row.entityNameValue)); + } + } + } + } + } else { + if (r.equals(TYPE)) { + // (fb:type.object.type fb:type.row) --> {fb:row.r1, fb:row.r2, ...} + // Right now handles fb:type.row, fb:type.cell, fb:column.___ + for (Value second : seconds) { + if (second.equals(ROW_TYPE)) { + for (TableRow row : rows) + answer.add(new Pair<>(row.entityNameValue, second)); + } else if (second.equals(CELL_TYPE)) { + for (TableRow row : rows) + for (TableCell cell : row.children) + answer.add(new Pair<>(cell.properties.entityNameValue, second)); + } else { + for (TableColumn column : columns) { + if (!second.equals(column.cellTypeValue)) continue; + for (TableCell cell : column.children) + answer.add(new Pair<>(cell.properties.entityNameValue, second)); + } + } + } + } else if (r.equals(TableTypeSystem.ROW_NEXT_VALUE)) { + // (fb:row.row.next fb:row.r5) --> fb:row.r4 + if (opts.forbidNextOnManyRows && seconds.size() != 1) { + throw new LambdaDCSException(Type.nonSingletonList, "cannot call next on " + seconds.size() + " rows."); + } + if (seconds.size() == Integer.MAX_VALUE) { + for (int i = 1; i < rows.size(); i++) { + if (!seconds.contains(rows.get(i).entityNameValue)) continue; + answer.add(new Pair<>(rows.get(i - 1).entityNameValue, rows.get(i).entityNameValue)); + } + } else { + for (Value value : seconds) { + if (!(value instanceof NameValue)) continue; + TableRow row = rowNameToTableRow.get(((NameValue) value).id); + if (row == null) continue; + int i = row.index; + if (i - 1 < 0) continue; + answer.add(new Pair<>(rows.get(i - 1).entityNameValue, row.entityNameValue)); + } + } + } else if (r.equals(TableTypeSystem.ROW_INDEX_VALUE)) { + // (fb:row.row.index (number 5)) --> fb:row.r5 + if (seconds.size() == Integer.MAX_VALUE) { + for (TableRow row : rows) { + if (!seconds.contains(row.indexValue)) continue; + answer.add(new Pair<>(row.entityNameValue, row.indexValue)); + } + } else { + for (Value value : seconds) { + if (!(value instanceof NumberValue)) continue; + int i = (int) ((NumberValue) value).value; + if (i < 0 || i >= rows.size()) continue; + TableRow row = rows.get(i); + answer.add(new Pair<>(row.entityNameValue, row.indexValue)); + } + } + } else if (TableTypeSystem.isCellProperty(r)) { + // (fb:cell.cell.number (number 5)) --> {fb:cell_id.5 fb:cell_population.5, ...} + // Possibly with repeated id (if there are multiple cells with that id) + for (TableColumn column : columns) { + for (TableCell cell : column.children) { + Value property = cell.properties.metadata.get(r); + if (property == null || !seconds.contains(property)) continue; + answer.add(new Pair<>(cell.properties.entityNameValue, property)); + } + } + } else { + // (fb:column.nationality fb:cell.canada) --> fb:row.r5 + for (int i = 0; i < columns.size(); i++) { + if (!r.equals(columns.get(i).propertyNameValue)) continue; + for (TableRow row : rows) { + if (!seconds.contains(row.children.get(i).properties.entityNameValue)) continue; + answer.add(new Pair<>(row.entityNameValue, row.children.get(i).properties.entityNameValue)); + } + } + } + } + return answer; + } + + // ============================================================ + // Methods specific to TableKnowledgeGraph + // ============================================================ + + public void populateStats(Evaluation evaluation) { + evaluation.add("rows", rows.size()); + evaluation.add("columns", columns.size()); + evaluation.add("cells", rows.size() * columns.size()); + } + + public int numRows() { return rows.size(); } + public int numColumns() { return columns.size(); } + + public List getAllColumnStrings() { + List columnStrings = new ArrayList<>(); + for (TableColumn column : columns) { + columnStrings.add(column.originalString); + } + return columnStrings; + } + + public List getAllCellStrings() { + List cellStrings = new ArrayList<>(); + for (TableColumn column : columns) { + for (TableCell cell : column.children) { + cellStrings.add(cell.properties.originalString); + } + } + return cellStrings; + } + + public String getOriginalString(Value value) { + return (value instanceof NameValue) ? getOriginalString(((NameValue) value).id) : null; + } + + public String getOriginalString(String nameValueId) { + if (nameValueId.startsWith("!")) nameValueId = nameValueId.substring(1); + if (cellIdToTableCellProperties.containsKey(nameValueId)) + return cellIdToTableCellProperties.get(nameValueId).originalString; + if (propertyIdToTableColumn.containsKey(nameValueId)) + return propertyIdToTableColumn.get(nameValueId).originalString; + if (nameValueId.startsWith(TableTypeSystem.CELL_SPECIFIC_TYPE_PREFIX)) { + String property = nameValueId.replace(TableTypeSystem.CELL_SPECIFIC_TYPE_PREFIX, TableTypeSystem.ROW_PROPERTY_NAME_PREFIX); + if (propertyIdToTableColumn.containsKey(property)) + return propertyIdToTableColumn.get(property).originalString; + } + return null; + } + + public List getRowIndices(String nameValueId) { + String property = TableTypeSystem.getPropertyOfEntity(nameValueId); + if (property == null) return null; + TableColumn column = propertyIdToTableColumn.get(property); + if (column == null) return null; + List answer = new ArrayList<>(); + for (int i = 0; i < column.children.size(); i++) { + if (column.children.get(i).properties.id.equals(nameValueId)) + answer.add(i); + } + return answer; + } + + // ============================================================ + // Test + // ============================================================ + + public static void main(String[] args) { + //opts.baseCSVDir = "tables/toy-examples/random/"; + //String filename = "nikos_machlas.csv"; + opts.normalizeBeforeCreatingId = true; + opts.baseCSVDir = "lib/data/tables/"; + String filename = "csv/204-csv/255.csv"; + TableKnowledgeGraph graph = (TableKnowledgeGraph) KnowledgeGraph.fromLispTree( + LispTree.proto.parseFromString("(graph tables.TableKnowledgeGraph " + filename + ")")); + //LogInfo.logs("%s", graph.toLispTree().toStringWrap()); + //LogInfo.logs("%s", graph.toTableValue().toLispTree().toStringWrap(100)); + for (TableColumn column : graph.columns) { + LogInfo.begin_track("%s", column.fieldName); + for (TableCell cell : column.children) { + LogInfo.logs("%s %s", cell.properties.entityNameValue, cell.properties.metadata); + } + LogInfo.end_track(); + } + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/TableTypeLookup.java b/src/edu/stanford/nlp/sempre/tables/TableTypeLookup.java new file mode 100644 index 0000000..a90b6c2 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/TableTypeLookup.java @@ -0,0 +1,40 @@ +package edu.stanford.nlp.sempre.tables; + +import edu.stanford.nlp.sempre.SemType; +import edu.stanford.nlp.sempre.TypeLookup; +import fig.basic.LogInfo; +import fig.basic.Option; + +/** + * Look up types for entities and properties in TableKnowledgeGraph. + * (Delegate all decisions to TableTypeSystem.) + * + * @author ppasupat + */ +public class TableTypeLookup implements TypeLookup { + public static class Options { + @Option(gloss = "Verbosity") public int verbose = 0; + } + public static Options opts = new Options(); + + @Override + public SemType getEntityType(String entity) { + if (opts.verbose >= 1) + LogInfo.logs("TableTypeLookup.getEntityType %s", entity); + SemType type = TableTypeSystem.getEntityTypeFromId(entity); + if (type == null && opts.verbose >= 1) + LogInfo.logs("TableTypeLookup.getEntityType FAIL %s", entity); + return type; + } + + @Override + public SemType getPropertyType(String property) { + if (opts.verbose >= 1) + LogInfo.logs("TableTypeLookup.getPropertyType %s", property); + SemType type = TableTypeSystem.getPropertyTypeFromId(property); + if (type == null && opts.verbose >= 1) + LogInfo.logs("TableTypeLookup.getPropertyType FAIL %s", property); + return type; + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/TableTypeSystem.java b/src/edu/stanford/nlp/sempre/tables/TableTypeSystem.java new file mode 100644 index 0000000..3508d50 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/TableTypeSystem.java @@ -0,0 +1,175 @@ +package edu.stanford.nlp.sempre.tables; + +import java.util.*; + +import edu.stanford.nlp.sempre.CanonicalNames; +import edu.stanford.nlp.sempre.NameValue; +import edu.stanford.nlp.sempre.SemType; +import edu.stanford.nlp.sempre.Value; +import fig.basic.LispTree; + +/** + * Typing System for table. Affects naming convention and how the types of formulas are inferred. + * + * ROW: name = fb:row.r[index] | type = fb:type.row + * CELL: name = fb:cell_[fieldName].[string] | type = (union fb:type.cell fb:column.[fieldName]) + * PROPERTY: name = fb:row.row.[fieldName] | type = (-> (union fb:type.cell fb:column.[fieldName]) fb:type.row) + * + * Note that the same string in different columns are mapped to different names. + * + * @author ppasupat + */ +public abstract class TableTypeSystem { + + // Value names + public static final String ROW_NAME_PREFIX = "fb:row"; + public static final String CELL_NAME_PREFIX = "fb:cell"; + + // Type names + public static final String ROW_TYPE = "fb:type.row"; + public static final SemType ROW_SEMTYPE = SemType.newAtomicSemType(ROW_TYPE); + public static final String CELL_GENERIC_TYPE = "fb:type.cell"; + public static final SemType CELL_GENERIC_SEMTYPE = SemType.newAtomicSemType(CELL_GENERIC_TYPE); + public static final String CELL_SPECIFIC_TYPE_PREFIX = "fb:column"; + + // Row properties + public static final String ROW_PROPERTY_NAME_PREFIX = "fb:row.row"; + public static final NameValue ROW_NEXT_VALUE = new NameValue("fb:row.row.next"); + public static final NameValue ROW_INDEX_VALUE = new NameValue("fb:row.row.index"); + public static final Map ROW_PROPERTIES = new HashMap<>(); + static { + ROW_PROPERTIES.put(ROW_NEXT_VALUE, SemType.newFuncSemType(ROW_TYPE, ROW_TYPE)); + ROW_PROPERTIES.put(ROW_INDEX_VALUE, SemType.newFuncSemType(CanonicalNames.INT, ROW_TYPE)); + } + + // Cell properties + public static final String CELL_PROPERTY_NAME_PREFIX = "fb:cell.cell"; + public static final NameValue CELL_NUMBER_VALUE = new NameValue("fb:cell.cell.number"); + public static final NameValue CELL_DATE_VALUE = new NameValue("fb:cell.cell.date"); + public static final NameValue CELL_SECOND_VALUE = new NameValue("fb:cell.cell.second"); + public static final NameValue CELL_UNIT_VALUE = new NameValue("fb:cell.cell.unit"); + public static final NameValue CELL_NORMALIZED_VALUE = new NameValue("fb:cell.cell.normalized"); + public static final Map CELL_PROPERTIES = new HashMap<>(); + static { + CELL_PROPERTIES.put(CELL_NUMBER_VALUE, SemType.newFuncSemType(CanonicalNames.NUMBER, CELL_GENERIC_TYPE)); + CELL_PROPERTIES.put(CELL_DATE_VALUE, SemType.newFuncSemType(CanonicalNames.DATE, CELL_GENERIC_TYPE)); + CELL_PROPERTIES.put(CELL_SECOND_VALUE, SemType.newFuncSemType(CanonicalNames.NUMBER, CELL_GENERIC_TYPE)); + CELL_PROPERTIES.put(CELL_UNIT_VALUE, SemType.newFuncSemType(CanonicalNames.TEXT, CELL_GENERIC_TYPE)); + CELL_PROPERTIES.put(CELL_NORMALIZED_VALUE, SemType.newFuncSemType(CanonicalNames.TEXT, CELL_GENERIC_TYPE)); + } + + // ============================================================ + // Helper Functions + // ============================================================ + + /** + * Convert string entry to an alpha-numeric name + */ + public static String canonicalizeName(String originalString) { + String id = originalString; + id = id.replaceAll("[^\\w]", "_"); // Replace abnormal characters with _ + id = id.replaceAll("_+", "_"); // Merge consecutive _'s + id = id.replaceAll("_$", ""); + id = id.toLowerCase(); + if (id.length() == 0) id = "null"; + return id; + } + + /** + * Add suffix to make the name unique. (Does not modify usedNames) + */ + public static String getUnusedName(String baseName, Collection usedNames, String sep) { + int suffix = 2; + String appendedId = baseName; + while (usedNames.contains(appendedId)) { + appendedId = baseName + sep + (suffix++); + } + return appendedId; + } + public static String getUnusedName(String baseName, Collection usedNames) { + return getUnusedName(baseName, usedNames, "_"); + } + + /** + * When id = [prefix]_[1].[2], get [1]. For example: + * - fb:row_[tableId].r[index] --> [tableId] + * - fb:cell_[fieldName].[string] --> [fieldName] + */ + public static String getIdAfterUnderscore(String id, String prefix) { + return id.substring(prefix.length() + 1).split("\\.", 2)[0]; + } + + /** + * When id = [prefix]_[1].[2], get [2]. For example: + * - fb:row_[tableId].r[index] --> r[index] + * - fb:cell.[string] or fb:cell_[fieldName].[string] --> [string] + */ + public static String getIdAfterPeriod(String id, String prefix) { + return id.substring(prefix.length()).split("\\.", 2)[1]; + } + + public static boolean isRowProperty(Value r) { + return r instanceof NameValue && ((NameValue) r).id.startsWith(ROW_PROPERTY_NAME_PREFIX); + } + + public static boolean isCellProperty(Value r) { + return r instanceof NameValue && ((NameValue) r).id.startsWith(CELL_PROPERTY_NAME_PREFIX); + } + + // ============================================================ + // Main Functions + // ============================================================ + + public static String getRowName(int index) { + return ROW_NAME_PREFIX + ".r" + index; + } + + public static String getCellName(String id, String fieldName) { + return CELL_NAME_PREFIX + "_" + fieldName + "." + id; + } + + public static String getCellType(String fieldName) { + return CELL_SPECIFIC_TYPE_PREFIX + "." + fieldName; + } + + public static String getPropertyName(String fieldName) { + return ROW_PROPERTY_NAME_PREFIX + "." + fieldName; + } + + public static SemType getPropertySemType(String fieldName) { + return SemType.fromLispTree(LispTree.proto.L("->", + LispTree.proto.L("union", getCellType(fieldName), CELL_GENERIC_SEMTYPE), + ROW_TYPE)); + } + + public static SemType getEntityTypeFromId(String entity) { + if (entity.startsWith(CELL_NAME_PREFIX)) { + String fieldName = getIdAfterUnderscore(entity, CELL_NAME_PREFIX); + return SemType.newUnionSemType(CELL_GENERIC_TYPE, getCellType(fieldName)); + } + return null; + } + + public static SemType getPropertyTypeFromId(String property) { + if (property.startsWith(ROW_PROPERTY_NAME_PREFIX)) { + SemType rowPropertyType = ROW_PROPERTIES.get(new NameValue(property)); + if (rowPropertyType != null) return rowPropertyType; + String fieldName = getIdAfterPeriod(property, ROW_PROPERTY_NAME_PREFIX); + return getPropertySemType(fieldName); + } + if (property.startsWith(CELL_PROPERTY_NAME_PREFIX)) { + SemType cellPropertyType = CELL_PROPERTIES.get(new NameValue(property)); + return cellPropertyType; + } + return null; + } + + public static String getPropertyOfEntity(String entity) { + if (entity.startsWith(CELL_NAME_PREFIX)) { + String fieldName = getIdAfterUnderscore(entity, CELL_NAME_PREFIX); + return getPropertyName(fieldName); + } + return null; + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/TableValueEvaluator.java b/src/edu/stanford/nlp/sempre/tables/TableValueEvaluator.java new file mode 100644 index 0000000..d1b8fe9 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/TableValueEvaluator.java @@ -0,0 +1,121 @@ +package edu.stanford.nlp.sempre.tables; + +import java.util.*; + +import edu.stanford.nlp.sempre.*; +import fig.basic.LogInfo; +import fig.basic.Option; + +/** + * Return 1 if |pred| and |target| represent the same list and 0 otherwise. + * + * This is similar to FreebaseValueEvaluator, but + * - does not give partial credits + * - also check the type of the values (number, date, ...) + * + * @author ppasupat + */ +public class TableValueEvaluator implements ValueEvaluator { + public static class Options { + @Option(gloss = "Allow type conversion on predicted values before comparison") + public boolean allowMismatchedTypes = false; + @Option(gloss = "Allow matching on normalized strings (e.g. remove parentheses)") + public boolean allowNormalizedStringMatch = true; + @Option(gloss = "When comparing number values, only consider the value and not the unit") + public boolean ignoreNumberValueUnits = true; + } + public static Options opts = new Options(); + + public double getCompatibility(Value target, Value pred) { + List targetList = ((ListValue) target).values; + if (!(pred instanceof ListValue)) return 0; + List predList = ((ListValue) pred).values; + + if (targetList.size() != predList.size()) return 0; + + for (Value targetValue : targetList) { + boolean found = false; + for (Value predValue : predList) { + if (getItemCompatibility(targetValue, predValue)) { + found = true; + break; + } + } + if (!found) return 0; + } + return 1; + } + + // ============================================================ + // Item Compatibility + // ============================================================ + + // Compare one element of the list. + protected boolean getItemCompatibility(Value target, Value pred) { + if (pred instanceof ErrorValue) return false; // Never award points for error + if (pred == null) { + LogInfo.warning("Predicted value is null!"); + return false; + } + + if (target instanceof DescriptionValue) { + String targetText = ((DescriptionValue) target).value; + if (pred instanceof NameValue) { + // Just has to match the description + String predText = ((NameValue) pred).description; + if (predText == null) predText = ""; + if (opts.allowNormalizedStringMatch) { + targetText = StringNormalizationUtils.aggressiveNormalize(targetText); + predText = StringNormalizationUtils.aggressiveNormalize(predText); + } + return targetText.equals(predText); + } else if (pred instanceof NumberValue) { + if (opts.allowMismatchedTypes) { + NumberValue targetNumber = StringNormalizationUtils.parseNumberLenient(targetText); + return targetNumber != null && targetNumber.equals(pred); + } + } + } else if (target instanceof NumberValue) { + NumberValue targetNumber = (NumberValue) target; + if (pred instanceof NumberValue) { + // Compare number + return compareNumberValues(targetNumber, (NumberValue) pred); + } else if (pred instanceof DateValue) { + // Assume year + DateValue date = (DateValue) pred; + return date.year == targetNumber.value && date.month == -1 && date.day == -1; + } else if (pred instanceof NameValue) { + // Try converting NameValue String into NumberValue + if (opts.allowMismatchedTypes) { + NumberValue predNumber = StringNormalizationUtils.nameValueToNumberValue((NameValue) pred); + return compareNumberValues(targetNumber, predNumber); + } + } + } else if (target instanceof DateValue) { + DateValue targetDate = (DateValue) target; + if (pred instanceof DateValue) { + // Compare date and date + return compareDateValues(targetDate, (DateValue) pred); + } + } + + return target.equals(pred); + } + + protected boolean compareNumberValues(NumberValue target, NumberValue pred) { + if (opts.ignoreNumberValueUnits) { + return Math.abs(target.value - pred.value) < 1e-6; + } else { + return target.equals(pred); + } + } + + protected boolean compareDateValues(DateValue target, DateValue pred) { + // If a field in target is not blank (-1), pred must match target on that field + if (target.year != -1 && target.year != pred.year) return false; + if (target.month != -1 && target.month != pred.month) return false; + if (target.day != -1 && target.day != pred.day) return false; + return true; + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/TableValuePreprocessor.java b/src/edu/stanford/nlp/sempre/tables/TableValuePreprocessor.java new file mode 100644 index 0000000..9cad480 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/TableValuePreprocessor.java @@ -0,0 +1,69 @@ +package edu.stanford.nlp.sempre.tables; + +import java.util.*; + +import edu.stanford.nlp.sempre.*; +import fig.basic.*; + +public class TableValuePreprocessor extends TargetValuePreprocessor { + public static class Options { + @Option(gloss = "Verbosity") public int verbose = 0; + } + public static Options opts = new Options(); + + public Value preprocess(Value value) { + if (value instanceof ListValue) { + List values = new ArrayList<>(); + for (Value entry : ((ListValue) value).values) { + values.add(preprocess(entry)); + } + return new ListValue(values); + } else { + return preprocessSingle(value); + } + } + + public Value preprocessSingle(Value origTarget) { + if (origTarget instanceof DescriptionValue) { + String origString = ((DescriptionValue) origTarget).value; + Value canonical = canonicalize(origString); + if (opts.verbose >= 1) + LogInfo.logs("Canonicalize %s --> %s", origString, canonical); + return canonical; + } else { + return origTarget; + } + } + + /* + * Most common origString patterns: + * - number (4, 20, "4,000", 1996, ".15") + * - number range ("1997/98", "2000-2005") + * - number + unit ("4 years", "82.6 m") + * - ordinal ("1st") + * - date ("January 4, 1994", "7 August 2004", "9-1-1990") + * - time -- point or amount ("4:47", " + * - short strings (yes, no, more, less, before, after) + * - string ("Poland", "World Championship") + */ + protected Value canonicalize(String origString) { + Value answer; + LanguageInfo languageInfo = LanguageAnalyzer.getSingleton().analyze(origString); + // Try converting to a number. + answer = StringNormalizationUtils.parseNumberStrict(origString); + if (answer != null) return answer; + //answer = StringNormalizationUtils.parseNumberWithLanguageAnalyzer(languageInfo); + //if (answer != null) return answer; + // Try converting to a date. + answer = StringNormalizationUtils.parseDate(origString); + if (answer != null) return answer; + answer = StringNormalizationUtils.parseDateWithLanguageAnalyzer(languageInfo); + if (answer != null) return answer; + // Maybe it's number + unit + answer = StringNormalizationUtils.parseNumberWithUnitStrict(origString); + if (answer != null) return answer; + // Just treat as a description string + return new DescriptionValue(origString); + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/alignment/AgreementAlignmentComputer.java b/src/edu/stanford/nlp/sempre/tables/alignment/AgreementAlignmentComputer.java new file mode 100644 index 0000000..bd8e835 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/alignment/AgreementAlignmentComputer.java @@ -0,0 +1,122 @@ +package edu.stanford.nlp.sempre.tables.alignment; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import edu.stanford.nlp.sempre.tables.alignment.IBMAligner.NullWordHandling; +import fig.basic.LogInfo; +import fig.basic.MapUtils; +import fig.basic.Pair; + +class AgreementAlignmentComputer implements AlignmentComputer { + + private final BitextData data; + + public AgreementAlignmentComputer(BitextData data) { + this.data = data; + } + + @Override + public DoubleMap align() { + // The order is (word, pred) for both DoubleMaps + Set allWords, allPreds; + allWords = data.allWords(); + allPreds = data.allPreds(); + if (IBMAligner.opts.nullWordHandling == NullWordHandling.trained) { + allWords.add(null); + allPreds.add(null); + } + + // Initialize uniformly + DoubleMap predGivenWord, wordGivenPred; + LogInfo.begin_track("Initialize"); + predGivenWord = new DoubleMap.ConstantDoubleMap(1.0 / allPreds.size()); + wordGivenPred = new DoubleMap.ConstantDoubleMap(1.0 / allWords.size()); + LogInfo.end_track(); + + // EM + for (int iter = 0; iter < IBMAligner.opts.maxIters; iter++) { + LogInfo.begin_track("EM Iteration %d", iter); + // (word, pred) => soft count + DoubleMap softCounts = new DoubleMap(); + Map wordToMarginalized = new HashMap<>(), predToMarginalized = new HashMap<>(); + for (BitextDataGroup group : data.dataGroups()) { + for (BitextDatum datum : group.groupData) { + double weight = 1.0 / group.count; + List words = datum.words, preds = datum.preds; + if (IBMAligner.opts.nullWordHandling == NullWordHandling.trained) { + // Add a null word in front + words = new ArrayList<>(words); + words.add(0, null); + preds = new ArrayList<>(preds); + preds.add(0, null); + } + double[][] probsWordToPred = new double[words.size()][preds.size()], + probsPredToWord = new double[words.size()][preds.size()]; + // word to pred + for (int j = 0; j < preds.size(); j++) { + String pred = preds.get(j); + double normalizer = 1e-10; + for (int i = 0; i < words.size(); i++) { + String word = words.get(i); + normalizer += (probsWordToPred[i][j] = predGivenWord.get(word, pred)); + } + if (IBMAligner.opts.nullWordHandling == NullWordHandling.fixed) { + normalizer += IBMAligner.opts.nullWordProb; + } else if (IBMAligner.opts.nullWordHandling == NullWordHandling.varied) { + normalizer += 1.0 / (words.size() + 1); + } + for (int i = 0; i < words.size(); i++) { + probsWordToPred[i][j] *= (weight / normalizer); + } + } + // pred to word + for (int i = 0; i < words.size(); i++) { + String word = words.get(i); + double normalizer = 1e-10; + for (int j = 0; j < preds.size(); j++) { + String pred = preds.get(j); + normalizer += (probsPredToWord[i][j] = wordGivenPred.get(word, pred)); + } + if (IBMAligner.opts.nullWordHandling == NullWordHandling.fixed) { + normalizer += IBMAligner.opts.nullWordProb; + } else if (IBMAligner.opts.nullWordHandling == NullWordHandling.varied) { + normalizer += 1.0 / (preds.size() + 1); + } + for (int j = 0; j < preds.size(); j++) { + probsPredToWord[i][j] *= (weight / normalizer); + } + } + // soft count + for (int i = 0; i < words.size(); i++) { + String word = words.get(i); + for (int j = 0; j < preds.size(); j++) { + String pred = preds.get(j); + double softCount = probsWordToPred[i][j] * probsPredToWord[i][j]; + softCounts.incr(word, pred, softCount); + MapUtils.incr(wordToMarginalized, word, softCount); + MapUtils.incr(predToMarginalized, pred, softCount); + } + } + } + } + predGivenWord = new DoubleMap(); + wordGivenPred = new DoubleMap(); + for (Map.Entry, Double> entry : softCounts.entrySet()) { + if (entry.getValue() <= 0) continue; + double prob = entry.getValue() / wordToMarginalized.get(entry.getKey().getFirst()); + if (prob > IBMAligner.epsilon) + predGivenWord.put(entry.getKey(), prob); + prob = entry.getValue() / predToMarginalized.get(entry.getKey().getSecond()); + if (prob > IBMAligner.epsilon) + wordGivenPred.put(entry.getKey(), prob); + } + LogInfo.end_track(); + } + return DoubleMap.product(predGivenWord, wordGivenPred); + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/alignment/AlignmentComputer.java b/src/edu/stanford/nlp/sempre/tables/alignment/AlignmentComputer.java new file mode 100644 index 0000000..c4283fc --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/alignment/AlignmentComputer.java @@ -0,0 +1,6 @@ +package edu.stanford.nlp.sempre.tables.alignment; + +interface AlignmentComputer { + // Return (word, predicate) => alignment score + DoubleMap align(); +} diff --git a/src/edu/stanford/nlp/sempre/tables/alignment/BitextData.java b/src/edu/stanford/nlp/sempre/tables/alignment/BitextData.java new file mode 100644 index 0000000..125abf6 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/alignment/BitextData.java @@ -0,0 +1,45 @@ +package edu.stanford.nlp.sempre.tables.alignment; + +import java.util.*; + +public class BitextData { + + private final Map idToDataGroup; + private final Set allReadWords, allReadPreds; + + public BitextData() { + idToDataGroup = new HashMap<>(); + allReadWords = new HashSet<>(); + allReadPreds = new HashSet<>(); + } + + public Collection dataGroups() { + return idToDataGroup.values(); + } + + public Set allWords() { + return new HashSet<>(allReadWords); + } + + public Set allPreds() { + return new HashSet<>(allReadPreds); + } + + public Set allSources(boolean swap) { + return new HashSet<>(swap ? allReadPreds : allReadWords); + } + + public Set allTargets(boolean swap) { + return new HashSet<>(swap ? allReadWords : allReadPreds); + } + + public void add(String id, int count, List words, List preds) { + BitextDataGroup group = idToDataGroup.get(id); + if (group == null) { + idToDataGroup.put(id, group = new BitextDataGroup(count, words)); + allReadWords.addAll(words); + } + allReadPreds.addAll(preds); + group.add(new BitextDatum(words, preds)); + } +} diff --git a/src/edu/stanford/nlp/sempre/tables/alignment/BitextDataGroup.java b/src/edu/stanford/nlp/sempre/tables/alignment/BitextDataGroup.java new file mode 100644 index 0000000..2b80a09 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/alignment/BitextDataGroup.java @@ -0,0 +1,18 @@ +package edu.stanford.nlp.sempre.tables.alignment; + +import java.util.*; + +class BitextDataGroup { + public final int count; + public final List words; + public final List groupData = new ArrayList<>(); + + public BitextDataGroup(int count, List words) { + this.count = count; + this.words = words; + } + + public void add(BitextDatum datum) { + groupData.add(datum); + } +} diff --git a/src/edu/stanford/nlp/sempre/tables/alignment/BitextDatum.java b/src/edu/stanford/nlp/sempre/tables/alignment/BitextDatum.java new file mode 100644 index 0000000..059a356 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/alignment/BitextDatum.java @@ -0,0 +1,21 @@ +package edu.stanford.nlp.sempre.tables.alignment; + +import java.util.List; + +class BitextDatum { + public final List words, preds; + + public BitextDatum(List words, List preds) { + this.words = words; + this.preds = preds; + } + + public List getSource(boolean swap) { + return swap ? preds : words; + } + + public List getTarget(boolean swap) { + return swap ? words : preds; + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/alignment/DoubleMap.java b/src/edu/stanford/nlp/sempre/tables/alignment/DoubleMap.java new file mode 100644 index 0000000..0f5c2f2 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/alignment/DoubleMap.java @@ -0,0 +1,79 @@ +package edu.stanford.nlp.sempre.tables.alignment; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import fig.basic.Pair; + +class DoubleMap { + private Map, Double> m; + + public DoubleMap() { + m = new HashMap<>(); + } + + public double get(Pair k) { + Double v = m.get(k); + return v == null ? 0.0 : v; + } + public double get(String k1, String k2) { return get(new Pair<>(k1, k2)); } + + public void put(Pair k, double v) { + m.put(k, v); + } + public void put(String k1, String k2, double v) { put(new Pair<>(k1, k2), v); } + + public void incr(Pair k, double v) { + Double oldV = m.get(k); + m.put(k, (oldV == null ? 0.0 : oldV) + v); + } + public void incr(String k1, String k2, double v) { incr(new Pair<>(k1, k2), v); } + + public void reverseKeys() { + Map, Double> newM = new HashMap<>(); + for (Map.Entry, Double> entry : m.entrySet()) { + newM.put(entry.getKey().reverse(), entry.getValue()); + } + m = newM; + } + + public DoubleMap getReverseKeys() { + DoubleMap newM = new DoubleMap(); + for (Map.Entry, Double> entry : m.entrySet()) { + newM.put(entry.getKey().reverse(), entry.getValue()); + } + return newM; + } + + public Set, Double>> entrySet() { + return m.entrySet(); + } + + public static DoubleMap product(DoubleMap dm1, DoubleMap dm2) { + DoubleMap dmp = new DoubleMap(); + for (Map.Entry, Double> entry : dm1.entrySet()) { + double v1 = entry.getValue(), v2 = dm2.get(entry.getKey()); + if (v1 * v2 > 0) dmp.put(entry.getKey(), v1 * v2); + } + return dmp; + } + + // A DoubleMap that returns the same value always. + static class ConstantDoubleMap extends DoubleMap { + private final double value; + + public ConstantDoubleMap(double value) { + this.value = value; + } + + public double get(Pair k) { return value; } + public double get(String k1, String k2) { return value; } + public void put(Pair k, double v) { throw new RuntimeException("cannot put"); } + public void put(String k1, String k2, double v) { throw new RuntimeException("cannot put"); } + public void incr(Pair k, double v) { throw new RuntimeException("cannot incr"); } + public void incr(String k1, String k2, double v) { throw new RuntimeException("cannot incr"); } + public void reverseKeys() { } + public DoubleMap getReverseKeys() { return this; } + public Set, Double>> entrySet() { throw new RuntimeException("cannot entrySet"); } + } +} diff --git a/src/edu/stanford/nlp/sempre/tables/alignment/GroupAlignmentComputer.java b/src/edu/stanford/nlp/sempre/tables/alignment/GroupAlignmentComputer.java new file mode 100644 index 0000000..4e56bf4 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/alignment/GroupAlignmentComputer.java @@ -0,0 +1,102 @@ +package edu.stanford.nlp.sempre.tables.alignment; + +import java.util.*; + +import fig.basic.*; + +public class GroupAlignmentComputer implements AlignmentComputer { + + private final BitextData data; + + public GroupAlignmentComputer(BitextData data) { + this.data = data; + } + + @Override + public DoubleMap align() { + // (word, pred) => p(word|pred) + DoubleMap alignment; + + // Initialize uniformly + LogInfo.begin_track("Initialize"); + alignment = new DoubleMap.ConstantDoubleMap(1.0 / data.allWords().size()); + LogInfo.end_track(); + + // EM + for (int iter = 0; iter <= IBMAligner.opts.maxIters; iter++) { + LogInfo.begin_track("EM Iteration %d", iter); + // count(word|pred) + DoubleMap softCounts = new DoubleMap(); + // count(*|pred) + Map predToMarginalized = new HashMap<>(); + // Go through each group: + for (BitextDataGroup group : data.dataGroups()) { + // Compute the posterior + List words = group.words; + List posteriors = new ArrayList<>(); + List posteriorsSumOverAi = new ArrayList<>(); + double[] totalPosteriors; + { + double[] logTotalPosteriors = new double[group.groupData.size()]; + for (int r = 0; r < group.groupData.size(); r++) { + List preds = group.groupData.get(r).preds; + // TODO(ice): Make the one below vary with formula size + double rGivenF = 1.0 / group.groupData.size(); + logTotalPosteriors[r] += Math.log(rGivenF); + // posteriorsForFr[i][j] = p(a_i = j) p(e_i | f_j) + double[][] posteriorsForFr = new double[words.size()][preds.size()]; + double[] posteriorsSumOverAiForFr = new double[words.size()]; + posteriors.add(posteriorsForFr); + posteriorsSumOverAi.add(posteriorsSumOverAiForFr); + for (int i = 0; i < words.size(); i++) { + posteriorsSumOverAiForFr[i] = IBMAligner.opts.nullWordProb; + for (int j = 0; j < preds.size(); j++) { + posteriorsForFr[i][j] = alignment.get(words.get(i), preds.get(j)) / preds.size(); + posteriorsSumOverAiForFr[i] += posteriorsForFr[i][j]; + } + logTotalPosteriors[r] += Math.log(posteriorsSumOverAiForFr[i]); + } + } + NumUtils.expNormalize(logTotalPosteriors); + totalPosteriors = logTotalPosteriors; + } + // Aggregate the soft counts + for (int r = 0; r < group.groupData.size(); r++) { + List preds = group.groupData.get(r).preds; + for (int i = 0; i < words.size(); i++) { + for (int j = 0; j < preds.size(); j++) { + double softCount = totalPosteriors[r] / posteriorsSumOverAi.get(r)[i] * posteriors.get(r)[i][j]; + softCounts.incr(words.get(i), preds.get(j), softCount); + MapUtils.incr(predToMarginalized, preds.get(j), softCount); + } + } + } + if (iter == IBMAligner.opts.maxIters) { + int rBest = 0; + double rBestScore = 0; + for (int r = 0; r < group.groupData.size(); r++) { + if (totalPosteriors[r] > rBestScore) { + rBest = r; + rBestScore = totalPosteriors[r]; + } + } + LogInfo.logs("%s", group.words); + LogInfo.logs(">> %s", group.groupData.get(rBest).preds); + } + } + if (iter < IBMAligner.opts.maxIters) { + // Update parameters + alignment = new DoubleMap(); + for (Map.Entry, Double> entry : softCounts.entrySet()) { + if (entry.getValue() <= 0) continue; + double prob = entry.getValue() / predToMarginalized.get(entry.getKey().getSecond()); + if (prob > IBMAligner.epsilon) + alignment.put(entry.getKey(), prob); + } + } + LogInfo.end_track(); + } + + return alignment; + } +} diff --git a/src/edu/stanford/nlp/sempre/tables/alignment/IBM1AlignmentComputer.java b/src/edu/stanford/nlp/sempre/tables/alignment/IBM1AlignmentComputer.java new file mode 100644 index 0000000..c94ef39 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/alignment/IBM1AlignmentComputer.java @@ -0,0 +1,87 @@ +package edu.stanford.nlp.sempre.tables.alignment; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import edu.stanford.nlp.sempre.tables.alignment.IBMAligner.NullWordHandling; +import fig.basic.LogInfo; +import fig.basic.MapUtils; +import fig.basic.Pair; + +class IBM1AlignmentComputer implements AlignmentComputer { + + private final BitextData data; + private final boolean swap; + + public IBM1AlignmentComputer(BitextData data, boolean swap) { + this.data = data; + this.swap = swap; + } + + @Override + public DoubleMap align() { + // (source, target) => P(target|source) + DoubleMap alignment; + Set allSources = data.allSources(swap); + Set allTargets = data.allTargets(swap); + if (IBMAligner.opts.nullWordHandling == NullWordHandling.trained) + allSources.add(null); + + // Initialize uniformly + LogInfo.begin_track("Initialize"); + alignment = new DoubleMap.ConstantDoubleMap(1.0 / allTargets.size()); + LogInfo.end_track(); + + // EM + for (int iter = 0; iter < IBMAligner.opts.maxIters; iter++) { + LogInfo.begin_track("EM Iteration %d", iter); + // (source, target) => count(source, target) + DoubleMap softCounts = new DoubleMap(); + Map sourceToMarginalized = new HashMap<>(); + for (BitextDataGroup group : data.dataGroups()) { + for (BitextDatum datum : group.groupData) { + double weight = 1.0 / group.count; + List sources = datum.getSource(swap); + if (IBMAligner.opts.nullWordHandling == NullWordHandling.trained) { + // Add a null word in front + sources = new ArrayList(sources); + sources.add(0, null); + } + double[] probs = new double[sources.size()]; + for (String target : datum.getTarget(swap)) { + double normalizer = 1e-10; + for (int i = 0; i < sources.size(); i++) { + probs[i] = alignment.get(sources.get(i), target); + normalizer += probs[i]; + } + if (IBMAligner.opts.nullWordHandling == NullWordHandling.fixed) { + normalizer += IBMAligner.opts.nullWordProb; + } else if (IBMAligner.opts.nullWordHandling == NullWordHandling.varied) { + normalizer += 1.0 / (sources.size() + 1); + } + for (int i = 0; i < sources.size(); i++) { + double softCount = probs[i] * weight / normalizer; + softCounts.incr(sources.get(i), target, softCount); + MapUtils.incr(sourceToMarginalized, sources.get(i), softCount); + } + } + } + } + alignment = new DoubleMap(); + for (Map.Entry, Double> entry : softCounts.entrySet()) { + if (entry.getValue() <= 0) continue; + double prob = entry.getValue() / sourceToMarginalized.get(entry.getKey().getFirst()); + if (prob > IBMAligner.epsilon) + alignment.put(entry.getKey(), prob); + } + LogInfo.end_track(); + } + + if (swap) alignment.reverseKeys(); + return alignment; + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/alignment/IBMAligner.java b/src/edu/stanford/nlp/sempre/tables/alignment/IBMAligner.java new file mode 100644 index 0000000..1a79f15 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/alignment/IBMAligner.java @@ -0,0 +1,115 @@ +package edu.stanford.nlp.sempre.tables.alignment; + +import java.io.*; +import java.util.*; + +import edu.stanford.nlp.sempre.*; +import fig.basic.*; +import fig.exec.*; + +public class IBMAligner implements Runnable { + public static class Options { + @Option public String inputFile; + @Option public int maxInputLines = Integer.MAX_VALUE; + @Option public int maxIters = 10; + @Option public Direction direction = Direction.wordToPred; + @Option public NullWordHandling nullWordHandling = NullWordHandling.trained; + @Option public double nullWordProb = 0.1; + } + public static Options opts = new Options(); + + public static enum Direction { + wordToPred, predToWord, product, agreement, group, productGroup + } + + public static enum NullWordHandling { + fixed, varied, trained, none + } + + public static final double epsilon = 1e-6; + + public static void main(String[] args) { + Execution.run(args, "IBMAlignerMain", new IBMAligner(), Master.getOptionsParser()); + } + + @Override + public void run() { + BitextData data = readInput(); + AlignmentComputer computer = null; + switch (opts.direction) { + case wordToPred: computer = new IBM1AlignmentComputer(data, false); break; + case predToWord: computer = new IBM1AlignmentComputer(data, true); break; + case product: computer = new ProductAlignmentComputer( + new IBM1AlignmentComputer(data, false), new IBM1AlignmentComputer(data, true)); break; + case agreement: computer = new AgreementAlignmentComputer(data); break; + case group: computer = new GroupAlignmentComputer(data); break; + case productGroup: computer = new ProductAlignmentComputer( + new IBM1AlignmentComputer(data, false), new GroupAlignmentComputer(data)); break; + default: throw new RuntimeException("Unknown direction " + opts.direction); + } + writeOutput(computer.align()); + } + + // ============================================================ + // Read input + // ============================================================ + + // Each line is: id [tab] count [tab] word word ... [tab] predicate predicate ... + + private BitextData readInput() { + LogInfo.begin_track("Reading data from %s", opts.inputFile); + BitextData data = new BitextData(); + int count = 0; + try { + BufferedReader reader = IOUtils.openInHard(opts.inputFile); + String line = null; + while ((line = reader.readLine()) != null) { + String[] tokens = line.split("\t"); + data.add(tokens[0], Integer.parseInt(tokens[1]), split(tokens[2]), split(tokens[3])); + if (++count >= opts.maxInputLines) break; + } + reader.close(); + } catch (Exception e) { + e.printStackTrace(); + LogInfo.fail(e); + } + LogInfo.logs("Read %d data lines", count); + LogInfo.logs("# words = %d | # preds = %d", data.allWords().size(), data.allPreds().size()); + LogInfo.end_track(); + return data; + } + + private List split(String x) { + String[] tokens = x.split(" "); + for (int i = 0; i < tokens.length; i++) tokens[i] = tokens[i].intern(); + return Arrays.asList(tokens); + } + + // ============================================================ + // Write output + // ============================================================ + + private void writeOutput(DoubleMap alignment) { + String filename = Execution.getFile("alignment"); + LogInfo.begin_track("Writing to %s", filename); + try (PrintWriter out = new PrintWriter(filename)) { + printHeaderComment(out); + for (Map.Entry, Double> entry : alignment.entrySet()) { + double value = entry.getValue(); + if (value < epsilon) continue; + out.printf("%s\t%s\t%.6f\n", entry.getKey().getFirst(), entry.getKey().getSecond(), value); + } + } catch (Exception e) { + e.printStackTrace(); + LogInfo.fail(e); + } + LogInfo.end_track(); + } + + private void printHeaderComment(PrintWriter out) { + if (opts.nullWordHandling != NullWordHandling.fixed) + out.printf("# input=%s direction=%s nullProb=%s\n", opts.inputFile, opts.direction, opts.nullWordHandling); + else + out.printf("# input=%s direction=%s nullProb=%s\n", opts.inputFile, opts.direction, opts.nullWordProb); + } +} diff --git a/src/edu/stanford/nlp/sempre/tables/alignment/ProductAlignmentComputer.java b/src/edu/stanford/nlp/sempre/tables/alignment/ProductAlignmentComputer.java new file mode 100644 index 0000000..8e2fc4b --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/alignment/ProductAlignmentComputer.java @@ -0,0 +1,45 @@ +package edu.stanford.nlp.sempre.tables.alignment; + +import fig.basic.LogInfo; + +class ProductAlignmentComputer implements AlignmentComputer { + + private final AlignmentComputer aligner1, aligner2; + + public ProductAlignmentComputer(AlignmentComputer aligner1, AlignmentComputer aligner2) { + this.aligner1 = aligner1; + this.aligner2 = aligner2; + } + + DoubleMap wordToPred, predToWord; + + @Override + public DoubleMap align() { + Thread t1, t2; + t1 = new Thread(new Runnable() { + @Override public void run() { + LogInfo.logs("wordToPred STARTED!"); + wordToPred = aligner1.align(); + } + }); + t2 = new Thread(new Runnable() { + @Override public void run() { + LogInfo.logs("predToWord STARTED!"); + predToWord = aligner2.align(); + } + }); + LogInfo.begin_threads(); + t1.start(); + t2.start(); + try { + t1.join(); + t2.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + LogInfo.fail(e); + } + LogInfo.end_threads(); + return DoubleMap.product(wordToPred, predToWord); + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/baseline/TableBaselineFeatureComputer.java b/src/edu/stanford/nlp/sempre/tables/baseline/TableBaselineFeatureComputer.java new file mode 100644 index 0000000..981107b --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/baseline/TableBaselineFeatureComputer.java @@ -0,0 +1,111 @@ +package edu.stanford.nlp.sempre.tables.baseline; + +import java.util.*; + +import edu.stanford.nlp.sempre.*; +import edu.stanford.nlp.sempre.tables.*; +import edu.stanford.nlp.sempre.tables.features.*; +import fig.basic.*; + +/** + * Compute features for BaselineParser + * + * @author ppasupat + */ +public class TableBaselineFeatureComputer implements FeatureComputer { + public static class Options { + @Option(gloss = "Verbosity") public int verbosity = 0; + } + public static Options opts = new Options(); + + @Override + public void extractLocal(Example ex, Derivation deriv) { + if (!deriv.isRoot(ex.numTokens())) return; + if (!FeatureExtractor.containsDomain("table-baseline")) return; + List phraseInfos = PhraseInfo.getPhraseInfos(ex); + // Find the list of all entities mentioned in the question + Set mentionedEntities = new HashSet<>(), mentionedProperties = new HashSet<>(); + for (PhraseInfo phraseInfo : phraseInfos) { + for (String s : phraseInfo.fuzzyMatchedPredicates) { + // s is either an ENTITY or a BINARY + SemType entityType = TableTypeSystem.getEntityTypeFromId(s); + SemType propertyType = TableTypeSystem.getPropertyTypeFromId(s); + if (entityType != null) mentionedEntities.add(s); + if (propertyType != null) mentionedProperties.add(s); + } + } + // Find the base cell(s) + TableKnowledgeGraph graph = (TableKnowledgeGraph) ex.context.graph; + List values = ((ListValue) deriv.value).values; + if (opts.verbosity >= 2) LogInfo.logs("%s", values); + if (values.get(0) instanceof NumberValue) { + values = graph.joinSecond(TableTypeSystem.CELL_NUMBER_VALUE, values); + } else if (values.get(0) instanceof DateValue) { + values = graph.joinSecond(TableTypeSystem.CELL_DATE_VALUE, values); + } else { + values = new ArrayList<>(values); + } + if (opts.verbosity >= 2) LogInfo.logs("%s", values); + List predictedEntities = new ArrayList<>(); + for (Value value : values) { + predictedEntities.add(((NameValue) value).id); + } + // Define features + for (String predicted : predictedEntities) { + String pProp = TableTypeSystem.getPropertyOfEntity(predicted); + List pRows = graph.getRowIndices(predicted); + if (opts.verbosity >= 2) LogInfo.logs("[p] %s %s %s", predicted, pProp, pRows); + for (String mentioned : mentionedEntities) { + String mProp = TableTypeSystem.getPropertyOfEntity(mentioned); + List mRows = graph.getRowIndices(mentioned); + if (opts.verbosity >= 2) LogInfo.logs("[m] %s %s %s", mentioned, mProp, mRows); + // Same column as ENTITY + offset + if (pProp != null && mProp != null && pProp.equals(mProp)) { + defineAllFeatures(deriv, "same-column", phraseInfos); + if (pRows != null && pRows.size() == 1 && mRows != null && mRows.size() == 1) { + defineAllFeatures(deriv, "same-column;offset=" + (pRows.get(0) - mRows.get(0)), phraseInfos); + } + } + // Same row as ENTITY + if (mRows != null && pRows != null) { + for (int pRow : pRows) { + if (mRows.contains(pRow)) { + defineAllFeatures(deriv, "same-row", phraseInfos); + break; + } + } + } + } + for (String mentioned : mentionedProperties) { + // match column name BINARY + if (opts.verbosity >= 2) LogInfo.logs("%s %s", pProp, mentioned); + if (mentioned.equals(pProp)) { + defineAllFeatures(deriv, "match-column-binary", phraseInfos); + } + } + // Row index (first or last) + if (pRows != null && pRows.contains(0)) + defineAllFeatures(deriv, "first-row", phraseInfos); + if (pRows != null && pRows.contains(graph.numRows() - 1)) + defineAllFeatures(deriv, "last-row", phraseInfos); + } + + } + + private void defineAllFeatures(Derivation deriv, String name, List phraseInfos) { + defineUnlexicalizedFeatures(deriv, name); + defineLexicalizedFeatures(deriv, name, phraseInfos); + } + + private void defineUnlexicalizedFeatures(Derivation deriv, String name) { + deriv.addFeature("table-baseline", name); + } + + private void defineLexicalizedFeatures(Derivation deriv, String name, List phraseInfos) { + for (PhraseInfo phraseInfo : phraseInfos) { + deriv.addFeature("table-baseline", "phrase=" + phraseInfo.lemmaText + ";" + name); + } + } + +} + diff --git a/src/edu/stanford/nlp/sempre/tables/baseline/TableBaselineParser.java b/src/edu/stanford/nlp/sempre/tables/baseline/TableBaselineParser.java new file mode 100644 index 0000000..5e7325f --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/baseline/TableBaselineParser.java @@ -0,0 +1,75 @@ +package edu.stanford.nlp.sempre.tables.baseline; + +import java.util.*; + +import edu.stanford.nlp.sempre.*; +import edu.stanford.nlp.sempre.tables.*; +import fig.basic.*; + +/** + * Baseline parser for table. + * + * Choose the answer from a table cell. + * + * @author ppasupat + */ +public class TableBaselineParser extends Parser { + + public TableBaselineParser(Spec spec) { + super(spec); + } + + @Override + public ParserState newParserState(Params params, Example ex, boolean computeExpectedCounts) { + return new TableBaselineParserState(this, params, ex, computeExpectedCounts); + } + +} + +/** + * Actual logic for generating candidates. + */ +class TableBaselineParserState extends ParserState { + + public TableBaselineParserState(Parser parser, Params params, Example ex, boolean computeExpectedCounts) { + super(parser, params, ex, computeExpectedCounts); + } + + @Override + public void infer() { + LogInfo.begin_track("TableBaselineParser.infer()"); + // Add all entities and possible normalizations to the list of candidates + TableKnowledgeGraph graph = (TableKnowledgeGraph) ex.context.graph; + for (Formula f : graph.getAllFormulas(FuzzyMatchFn.FuzzyMatchFnMode.ENTITY)) { + buildAllDerivations(f); + } + // Execute + Compute expected counts + ensureExecuted(); + if (computeExpectedCounts) { + expectedCounts = new HashMap<>(); + ParserState.computeExpectedCounts(predDerivations, expectedCounts); + } + LogInfo.end_track(); + } + + private void buildAllDerivations(Formula f) { + generateDerivation(f); + // Try number and date normalizations as well + generateDerivation(new JoinFormula(Formula.fromString("!" + TableTypeSystem.CELL_NUMBER_VALUE.id), f)); + generateDerivation(new JoinFormula(Formula.fromString("!" + TableTypeSystem.CELL_DATE_VALUE.id), f)); + } + + private void generateDerivation(Formula f) { + Derivation deriv = new Derivation.Builder() + .cat(Rule.rootCat).start(-1).end(-1) + .formula(f).children(Collections.emptyList()) + .type(TypeInference.inferType(f)) + .createDerivation(); + deriv.ensureExecuted(parser.executor, ex.context); + if (deriv.value instanceof ErrorValue) return; + if (deriv.value instanceof ListValue && ((ListValue) deriv.value).values.isEmpty()) return; + if (!deriv.isFeaturizedAndScored()) featurizeAndScoreDerivation(deriv); + predDerivations.add(deriv); + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/features/HeadwordInfo.java b/src/edu/stanford/nlp/sempre/tables/features/HeadwordInfo.java new file mode 100644 index 0000000..250fafe --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/features/HeadwordInfo.java @@ -0,0 +1,60 @@ +package edu.stanford.nlp.sempre.tables.features; + +import edu.stanford.nlp.sempre.*; + +/** + * Information about the headword of the utterance. + * + * Examples: + * - Which person is the fastest? ==> (which, person) + * - Who is the fastest person? ==> (who, person) + * - How many cars are red? ==> (how many, red) + * - Who is the fastest? ==> [skipped] + * + * Currently a simple heuristic is used to find the headword. + * @author ppasupat + * + */ +public class HeadwordInfo { + + public final String questionWord; + public final String headword; + + public HeadwordInfo(String questionWord, String headword) { + this.questionWord = questionWord; + this.headword = headword; + } + + public String toString() { + return "(" + questionWord + "," + headword + ")"; + } + + public String questionWordTuple() { + return "(" + questionWord + ",*)"; + } + + public String headwordTuple() { + return "(*," + headword + ")"; + } + + // Heuristics: find the first N* after the first W* + // Example: tell me [who] is the first [person] ... --> person + public static HeadwordInfo analyze(LanguageInfo langInfo) { + String questionWord = null; + for (int i = 0; i < langInfo.numTokens(); i++) { + String posTag = langInfo.posTags.get(i); + if (posTag.startsWith("W")) { + questionWord = langInfo.lemmaTokens.get(i); + if (questionWord.equals("how")) { + // Possibly "how many", "how much", ... + if (i + 1 < langInfo.numTokens() && langInfo.posTags.get(i + 1).startsWith("J")) + questionWord += " " + langInfo.lemmaTokens.get(i + 1); + } + } else if (posTag.startsWith("N") && questionWord != null) { + return new HeadwordInfo(questionWord, langInfo.lemmaTokens.get(i)); + } + } + return null; + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/features/PhraseDenotationFeatureComputer.java b/src/edu/stanford/nlp/sempre/tables/features/PhraseDenotationFeatureComputer.java new file mode 100644 index 0000000..93de056 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/features/PhraseDenotationFeatureComputer.java @@ -0,0 +1,182 @@ +package edu.stanford.nlp.sempre.tables.features; + +import java.util.*; + +import edu.stanford.nlp.sempre.*; +import edu.stanford.nlp.sempre.tables.TableTypeSystem; +import fig.basic.*; + +/** + * Extract features based on (phrase, denotation) pairs. + * Intuition: "when" usually matches a date, which "how many" usually matches a number. + * + * @author ppasupat + */ +public class PhraseDenotationFeatureComputer implements FeatureComputer { + public static class Options { + @Option(gloss = "Verbosity") + public int verbose = 0; + @Option(gloss = "Look for the type under the first cell property") + public boolean lookUnderCellProperty = false; + @Option(gloss = "Define features for generic cell types too") + public boolean useGenericCellType = false; + } + public static Options opts = new Options(); + + @Override + public void extractLocal(Example ex, Derivation deriv) { + if (!(FeatureExtractor.containsDomain("custom-denotation") + || FeatureExtractor.containsDomain("phrase-denotation") + || FeatureExtractor.containsDomain("headword-denotation"))) return; + // Only compute features at the root. + if (!deriv.isRoot(ex.numTokens())) return; + Collection denotationTypes = tableTypes(deriv); + extractCustomDenotationFeatures(ex, deriv, denotationTypes); + extractPhraseDenotationFeatures(ex, deriv, denotationTypes); + extractHeadwordDenotationFeatures(ex, deriv, denotationTypes); + } + + public static Collection tableTypes(Derivation deriv) { + Set denotationTypes = new HashSet<>(); + // Type based on SemType + populateSemType("", deriv.type, denotationTypes); + // Look for the type under the first cell property + if (opts.lookUnderCellProperty) { + Formula formula = deriv.formula; + if (formula instanceof JoinFormula) { + JoinFormula join = (JoinFormula) formula; + String property = getCellProperty(join.relation); + if (property != null) { + populateSemType(property + "/", TypeInference.inferType(join.child), denotationTypes); + } + } + } + if (denotationTypes.isEmpty()) denotationTypes.add("OTHER"); + return denotationTypes; + } + + private static void populateSemType(String prefix, SemType type, Collection denotationTypes) { + LispTree tree = type.toLispTree(); + if (tree.isLeaf()) { + denotationTypes.add(prefix + tree.value); + } else { + for (LispTree subtree : tree.children) { + if (!subtree.isLeaf()) continue; + if (subtree.value.startsWith(TableTypeSystem.CELL_SPECIFIC_TYPE_PREFIX)) { + denotationTypes.add(prefix + subtree.value); + if (opts.useGenericCellType) + denotationTypes.add(prefix + TableTypeSystem.CELL_GENERIC_TYPE); + } + } + } + } + + private static String getCellProperty(Formula formula) { + LispTree tree = formula.toLispTree(); + if (tree.isLeaf()) { + String value = tree.value; + if (value.charAt(0) == '!' && value.substring(1).startsWith(TableTypeSystem.CELL_PROPERTY_NAME_PREFIX)) + return value; + } else { + if ("reverse".equals(tree.child(0).value) && tree.child(1).value.startsWith(TableTypeSystem.CELL_PROPERTY_NAME_PREFIX)) + return "!" + tree.child(1).value; + } + return null; + } + + // ============================================================ + // Custom Denotation Features + // ============================================================ + + private void extractCustomDenotationFeatures(Example ex, Derivation deriv, Collection denotationTypes) { + if (!FeatureExtractor.containsDomain("custom-denotation")) return; + + if (deriv.value instanceof ErrorValue) { + deriv.addFeature("custom-denotation", "error"); + return; + } else if (deriv.value instanceof ListValue) { + ListValue list = (ListValue) deriv.value; + int size = list.values.size(); + deriv.addFeature("custom-denotation", "size" + (size < 3 ? "=" + size : ">=" + 3)); + if (size == 1) { + Value value = list.values.get(0); + if (value instanceof NumberValue) { + double number = ((NumberValue) value).value; + deriv.addFeature("custom-denotation", "number" + (number > 0 ? ">0" : number == 0 ? "=0" : "<0")); + deriv.addFeature("custom-denotation", "number" + ((int) number == number ? "-int" : "-frac")); + } + } + } + } + + // ============================================================ + // Phrase - Denotation + // ============================================================ + + private void extractPhraseDenotationFeatures(Example ex, Derivation deriv, Collection denotationTypes) { + if (!FeatureExtractor.containsDomain("phrase-denotation")) return; + List phraseInfos = PhraseInfo.getPhraseInfos(ex); + if (opts.verbose >= 2) + LogInfo.logs("%s %s %s", deriv.value, deriv.type, denotationTypes); + for (String denotationType : denotationTypes) { + for (PhraseInfo phraseInfo : phraseInfos) { + if (!PhraseInfo.opts.usePhraseLemmaOnly) { + deriv.addFeature("p-d", "(o)" + phraseInfo.text + ";" + denotationType); + } + deriv.addFeature("p-d", phraseInfo.lemmaText + ";" + denotationType); + } + // Check original column text + String[] tokens = denotationType.split("/"); + String actualType = tokens[tokens.length - 1], suffix = (tokens.length == 1) ? "" : "(" + tokens[0] + ")"; + String originalColumn; + if ((originalColumn = PredicateInfo.getOriginalString(actualType, ex)) != null) { + if (PredicateInfo.opts.usePredicateLemma) { + originalColumn = PredicateInfo.getLemma(originalColumn); + } + for (PhraseInfo phraseInfo : phraseInfos) { + if (!PhraseInfo.opts.usePhraseLemmaOnly && phraseInfo.text.equals(originalColumn)) { + deriv.addFeature("p-d", "(o)=" + suffix); + } + if (phraseInfo.lemmaText.equals(originalColumn)) { + if (opts.verbose >= 2) + LogInfo.logs("%s %s %s %s", phraseInfo, actualType, originalColumn, Arrays.asList(tokens)); + deriv.addFeature("p-d", "=" + suffix); + } + } + } + } + } + + // ============================================================ + // Headword - Denotation + // ============================================================ + + private void extractHeadwordDenotationFeatures(Example ex, Derivation deriv, Collection denotationTypes) { + if (!FeatureExtractor.containsDomain("headword-denotation")) return; + HeadwordInfo headwordInfo = HeadwordInfo.analyze(ex.languageInfo); + if (headwordInfo == null) return; + if (opts.verbose >= 2) + LogInfo.logs("%s [%s] | %s %s %s", ex.utterance, headwordInfo, deriv.value, deriv.type, denotationTypes); + for (String denotationType : denotationTypes) { + deriv.addFeature("h-d", headwordInfo + ";" + denotationType); + deriv.addFeature("h-d", headwordInfo.questionWordTuple() + ";" + denotationType); + deriv.addFeature("h-d", headwordInfo.headwordTuple() + ";" + denotationType); + // Check original column text + String[] tokens = denotationType.split("/"); + String actualType = tokens[tokens.length - 1], suffix = (tokens.length == 1) ? "" : "(" + tokens[0] + ")"; + String originalColumn; + if ((originalColumn = PredicateInfo.getOriginalString(actualType, ex)) != null) { + if (PredicateInfo.opts.usePredicateLemma) { + originalColumn = PredicateInfo.getLemma(originalColumn); + } + if (headwordInfo.headword.equals(originalColumn)) { + if (opts.verbose >= 2) + LogInfo.logs("%s %s %s %s", headwordInfo, actualType, originalColumn, Arrays.asList(tokens)); + deriv.addFeature("h-d", "=" + suffix); + deriv.addFeature("h-d", headwordInfo.questionWordTuple() + "=" + suffix); + } + } + } + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/features/PhraseInfo.java b/src/edu/stanford/nlp/sempre/tables/features/PhraseInfo.java new file mode 100644 index 0000000..dac9dd1 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/features/PhraseInfo.java @@ -0,0 +1,100 @@ +package edu.stanford.nlp.sempre.tables.features; + +import java.util.*; + +import edu.stanford.nlp.sempre.*; +import edu.stanford.nlp.sempre.FuzzyMatchFn.FuzzyMatchFnMode; +import edu.stanford.nlp.sempre.tables.TableKnowledgeGraph; +import fig.basic.*; + +/** + * Represents a phrase in the utterance. + * + * Also contains additional information such as POS and NER tags. + * + * @author ppasupat + */ +public class PhraseInfo { + public static class Options { + @Option(gloss = "Maximum number of tokens in a phrase") + public int maxPhraseLength = 3; + @Option(gloss = "Use lemma form only") + public boolean usePhraseLemmaOnly = false; + @Option(gloss = "Fuzzy match predicates") + public boolean computeFuzzyMatchPredicates = false; + } + public static Options opts = new Options(); + + public final int start, end, endOffset; + public final String text; + public final String lemmaText; + public final List tokens; + public final List lemmaTokens; + public final List posTags; + public final List nerTags; + public final String canonicalPosSeq; + public final List fuzzyMatchedPredicates; + + public PhraseInfo(Example ex, int start, int end) { + this.start = start; + this.end = end; + LanguageInfo languageInfo = ex.languageInfo; + this.endOffset = end - languageInfo.numTokens(); + tokens = languageInfo.tokens.subList(start, end); + lemmaTokens = languageInfo.tokens.subList(start, end); + posTags = languageInfo.posTags.subList(start, end); + nerTags = languageInfo.nerTags.subList(start, end); + text = languageInfo.phrase(start, end).toLowerCase(); + lemmaText = languageInfo.lemmaPhrase(start, end).toLowerCase(); + canonicalPosSeq = languageInfo.canonicalPosSeq(start, end); + fuzzyMatchedPredicates = opts.computeFuzzyMatchPredicates ? getFuzzyMatchedPredicates(ex.context) : null; + } + + private List getFuzzyMatchedPredicates(ContextValue context) { + if (context == null || context.graph == null || !(context.graph instanceof TableKnowledgeGraph)) + return null; + TableKnowledgeGraph graph = (TableKnowledgeGraph) context.graph; + List matchedPredicates = new ArrayList<>(); + // Assume everything is ValueFormula + List formulas = new ArrayList<>(); + formulas.addAll(graph.getFuzzyMatchedFormulas(text, FuzzyMatchFnMode.ENTITY)); + formulas.addAll(graph.getFuzzyMatchedFormulas(text, FuzzyMatchFnMode.BINARY)); + for (Formula formula : formulas) { + if (formula instanceof ValueFormula) { + Value value = ((ValueFormula) formula).value; + if (value instanceof NameValue) { + matchedPredicates.add(((NameValue) value).id); + } else { + throw new RuntimeException("Not a NameValue: " + value); + } + } else { + throw new RuntimeException("Not a ValueFormula: " + formula); + } + } + return matchedPredicates; + } + + @Override + public String toString() { + return "\"" + text + "\""; + } + + // Caching + public static final Map> phraseInfosCache = new HashMap<>(); + + public static synchronized List getPhraseInfos(Example ex) { + List phraseInfos = phraseInfosCache.get(ex); + if (phraseInfos == null) { + phraseInfos = new ArrayList<>(); + List tokens = ex.languageInfo.tokens; + for (int s = 1; s <= opts.maxPhraseLength; s++) { + for (int i = 0; i <= tokens.size() - s; i++) { + phraseInfos.add(new PhraseInfo(ex, i, i + s)); + } + } + phraseInfosCache.put(ex, phraseInfos); + } + return phraseInfos; + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/features/PhrasePredicateFeatureComputer.java b/src/edu/stanford/nlp/sempre/tables/features/PhrasePredicateFeatureComputer.java new file mode 100644 index 0000000..59e0f8e --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/features/PhrasePredicateFeatureComputer.java @@ -0,0 +1,225 @@ +package edu.stanford.nlp.sempre.tables.features; + +import java.util.*; + +import edu.stanford.nlp.sempre.*; +import edu.stanford.nlp.sempre.tables.features.PredicateInfo.PredicateType; +import fig.basic.*; + +/** + * Extract features based on (phrase, predicate) pairs. + * + * - |phrase| is an n-gram from the utterance (usually n = 1) + * - |predicate| is a predicate (LispTree leaf) from the formula + * Example: fb:cell_name.barack_obama, fb:row.row.name, argmax + * + * Properties of phrases: POS tags, length, word shapes, ... + * Properties of predicates: category (entity / binary / keyword), ... + * Properties of alignment: exact match, prefix match, suffix match, string contains, ... + * + * @author ppasupat + */ +public class PhrasePredicateFeatureComputer implements FeatureComputer { + public static class Options { + @Option(gloss = "Verbosity") public int verbose = 0; + @Option(gloss = "Define features on partial derivations as well") + public boolean defineOnPartialDerivs = true; + @Option(gloss = "Also define features on prefix and suffix matches") + public boolean usePrefixSuffixMatch = true; + @Option(gloss = "Also define features with POS tags") + public boolean usePosFeatures = true; + @Option(gloss = "Define unlexicalized phrase-predicate features") + public boolean unlexicalizedPhrasePredicate = true; + @Option(gloss = "Define lexicalized phrase-predicate features") + public boolean lexicalizedPhrasePredicate = true; + @Option(gloss = "Maximum ngram length for lexicalize all pair features") + public int maxNforLexicalizeAllPairs = Integer.MAX_VALUE; + } + public static Options opts = new Options(); + + public final int maxNforLexicalizeAllPairs; + + public PhrasePredicateFeatureComputer() { + maxNforLexicalizeAllPairs = Math.min(opts.maxNforLexicalizeAllPairs, PhraseInfo.opts.maxPhraseLength); + } + + @Override + public void extractLocal(Example ex, Derivation deriv) { + if (!(FeatureExtractor.containsDomain("phrase-predicate") + || FeatureExtractor.containsDomain("missing-predicate") + || FeatureExtractor.containsDomain("phrase-formula"))) 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); + List predicateInfos = PredicateInfo.getPredicateInfos(ex, deriv); + if (opts.verbose >= 2) { + LogInfo.logs("Example: %s", ex.utterance); + LogInfo.logs("Phrases: %s", phraseInfos); + LogInfo.logs("Derivation: %s", deriv); + LogInfo.logs("Predicates: %s", predicateInfos); + } + if (FeatureExtractor.containsDomain("phrase-predicate")) { + if (opts.defineOnPartialDerivs) { + deriv.getTempState().put("p-p", new ArrayList<>(predicateInfos)); + // Subtract predicates from children + Map predicateInfoCounts = new HashMap<>(); + for (PredicateInfo predicateInfo : predicateInfos) + MapUtils.incr(predicateInfoCounts, predicateInfo); + if (deriv.children != null) { + for (Derivation child : deriv.children) { + @SuppressWarnings("unchecked") + List childPredicateInfos = (List) child.getTempState().get("p-p"); + for (PredicateInfo predicateInfo : childPredicateInfos) + MapUtils.incr(predicateInfoCounts, predicateInfo, -1); + } + } + for (PhraseInfo phraseInfo : phraseInfos) { + for (Map.Entry entry : predicateInfoCounts.entrySet()) { + if (entry.getValue() != 0) + extractMatch(ex, deriv, phraseInfo, entry.getKey(), entry.getValue()); + } + } + } else { + for (PhraseInfo phraseInfo : phraseInfos) { + for (PredicateInfo predicateInfo : predicateInfos) { + extractMatch(ex, deriv, phraseInfo, predicateInfo, 1); + } + } + } + } + if (FeatureExtractor.containsDomain("missing-predicate")) { + extractMissing(ex, deriv, phraseInfos, predicateInfos); + } + if (FeatureExtractor.containsDomain("phrase-formula")) { + extractPhraseFormula(ex, deriv, phraseInfos); + } + } + + // ============================================================ + // Matching + // ============================================================ + + private void extractMatch(Example ex, Derivation deriv, PhraseInfo phraseInfo, PredicateInfo predicateInfo, double factor) { + if (predicateInfo.originalString != null) { + if (!PhraseInfo.opts.usePhraseLemmaOnly) { + extractMatch(ex, deriv, phraseInfo, phraseInfo.text, "(o)", + predicateInfo, predicateInfo.originalString, "(o)", factor); + } + extractMatch(ex, deriv, phraseInfo, phraseInfo.lemmaText, "", + predicateInfo, predicateInfo.originalString, "(o)", factor); + } else { + if (!PhraseInfo.opts.usePhraseLemmaOnly) { + extractMatch(ex, deriv, phraseInfo, phraseInfo.text, "(o)", + predicateInfo, predicateInfo.predicate, "(i)", factor); + } + extractMatch(ex, deriv, phraseInfo, phraseInfo.lemmaText, "", + predicateInfo, predicateInfo.predicate, "(i)", factor); + } + } + + 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 (phraseString.equals(predicateString)) { + defineFeatures(ex, deriv, phraseInfo, predicateInfo, phraseType + "=" + predicateType, + phraseString, predicateString, factor); + } else if (opts.usePrefixSuffixMatch) { + if (predicateString.startsWith(phraseString)) { + defineFeatures(ex, deriv, phraseInfo, predicateInfo, "*_" + phraseType + "=" + predicateType, + phraseString, predicateString, factor); + } + if (predicateString.endsWith(phraseString)) { + defineFeatures(ex, deriv, phraseInfo, predicateInfo, "_*" + phraseType + "=" + predicateType, + phraseString, predicateString, factor); + } + if (phraseString.startsWith(predicateString)) { + defineFeatures(ex, deriv, phraseInfo, predicateInfo, phraseType + "=_*" + predicateType, + phraseString, predicateString, factor); + } + if (phraseString.endsWith(predicateString)) { + defineFeatures(ex, deriv, phraseInfo, predicateInfo, phraseType + "=*_" + predicateType, + phraseString, predicateString, factor); + } + } + } + if (opts.lexicalizedPhrasePredicate && phraseInfo.end - phraseInfo.start <= maxNforLexicalizeAllPairs) { + deriv.addFeature("p-p", + phraseType + phraseString + ";" + predicateType + predicateString, factor); + } + } + + private void defineFeatures(Example ex, Derivation deriv, PhraseInfo phraseInfo, PredicateInfo predicateInfo, + String featurePrefix, String phraseString, String predicateString, double factor) { + defineFeatures(ex, deriv, phraseInfo, predicateInfo, featurePrefix, factor); + if (opts.usePosFeatures) + defineFeatures(ex, deriv, phraseInfo, predicateInfo, + featurePrefix + "," + phraseInfo.canonicalPosSeq, factor); + } + + private void defineFeatures(Example ex, Derivation deriv, PhraseInfo phraseInfo, PredicateInfo predicateInfo, + String featurePrefix, double factor) { + if (opts.verbose >= 2) LogInfo.logs("defineFeatures: %s %s %s %s", + featurePrefix, phraseInfo, predicateInfo, predicateInfo.type); + deriv.addFeature("p-p", featurePrefix, factor); + deriv.addFeature("p-p", featurePrefix + "," + predicateInfo.type, factor); + } + + // ============================================================ + // Missing predicate features + // ============================================================ + + private void extractMissing(Example ex, Derivation deriv, List phraseInfos, List predicateInfos) { + // Only makes sense at the root + if (!deriv.isRoot(ex.numTokens())) return; + // Get the list of all relevant predicates + Set relevantPredicates = new HashSet<>(); + for (PredicateInfo predicateInfo : predicateInfos) { + if (predicateInfo.type == PredicateType.BINARY || predicateInfo.type == PredicateType.ENTITY) { + String predicate = predicateInfo.predicate; + if (predicate.charAt(0) == '!') predicate = predicate.substring(1); + relevantPredicates.add(predicate); + } + } + // See which predicates are missing! + Set missingPredicates = new HashSet<>(); + for (PhraseInfo phraseInfo : phraseInfos) { + if (phraseInfo.fuzzyMatchedPredicates == null) continue; + for (String predicate : phraseInfo.fuzzyMatchedPredicates) { + if (!relevantPredicates.contains(predicate)) { + missingPredicates.add(predicate); + missingPredicates.add("type=" + PredicateInfo.inferType(predicate)); + } + } + } + if (opts.verbose >= 2) { + LogInfo.logs("have %s", relevantPredicates); + LogInfo.logs("missing %s", missingPredicates); + } + for (String missing : missingPredicates) { + deriv.addFeature("m-p", missing); + } + } + + // ============================================================ + // Phrase - Formula features + // ============================================================ + + private void extractPhraseFormula(Example ex, Derivation deriv, List phraseInfos) { + if (deriv.formula instanceof LambdaFormula) return; + // Get rough formula + LispTree tree = PredicateInfo.normalizeFormula(ex, deriv); + if (opts.verbose >= 2) { + LogInfo.logs("original formula %s", deriv.formula); + LogInfo.logs("normalized formula %s", tree); + } + deriv.addFeature("p-f", "" + tree); + for (PhraseInfo phraseInfo : phraseInfos) { + if (!PhraseInfo.opts.usePhraseLemmaOnly) { + deriv.addFeature("p-f", phraseInfo.text + "(o);" + tree); + } + deriv.addFeature("p-f", phraseInfo.lemmaText + ";" + tree); + } + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/features/PredicateInfo.java b/src/edu/stanford/nlp/sempre/tables/features/PredicateInfo.java new file mode 100644 index 0000000..dbf4b3a --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/features/PredicateInfo.java @@ -0,0 +1,395 @@ +package edu.stanford.nlp.sempre.tables.features; + +import java.util.*; + +import edu.stanford.nlp.sempre.*; +import edu.stanford.nlp.sempre.tables.*; +import fig.basic.*; + +/** + * Represents a predicate in the formula. + * + * Also contains additional information such as type and original string. + * + * @author ppasupat + */ +public class PredicateInfo { + public static class Options { + @Option(gloss = "Use lemma form of original strings (need LanguageAnalyzer -- slow)") + public boolean usePredicateLemma = false; + @Option(gloss = "Consider the formula types when generating the predicate list") + public boolean traverseWithFormulaTypes = false; + @Option(gloss = "Conversion between (reverse [NameValue]) and ![NameValue]") + public ReverseNameValueConversion reverseNameValueConversion = ReverseNameValueConversion.none; + @Option(gloss = "Allow repreated predicates") + public boolean allowRepeats = false; + @Option(gloss = "Maximum length of predicate string") + public int maxPredicateLength = 40; + @Option(gloss = "Perform beta reduction before finding predicates") + public boolean betaReduce = false; + } + public static Options opts = new Options(); + + static enum ReverseNameValueConversion { allBang, allReverse, none }; + + static enum PredicateType { KEYWORD, ENTITY, BINARY }; + + public final String predicate; + public final String originalString; + public final PredicateType type; + + public PredicateInfo(String predicate, ContextValue context) { + this.predicate = predicate; + this.type = inferType(predicate); + String s = getOriginalString(predicate, context); + this.originalString = (s == null) ? null : s.toLowerCase(); + } + + public static PredicateType inferType(String predicate) { + if (predicate.charAt(0) == '!') predicate = predicate.substring(1); + if (predicate.startsWith(CanonicalNames.PREFIX)) { + if (CanonicalNames.isUnary(predicate)) { + return PredicateType.ENTITY; + } else if (CanonicalNames.isBinary(predicate)) { + return PredicateType.BINARY; + } else { + throw new RuntimeException("Unrecognized predicate: " + predicate); + } + } else { + return PredicateType.KEYWORD; + } + } + + @Override + public String toString() { + return predicate + "(" + originalString + ")"; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || !(o instanceof PredicateInfo)) return false; + return predicate.equals(((PredicateInfo) o).predicate); + } + + @Override + public int hashCode() { + return predicate.hashCode(); + } + + // ============================================================ + // Get original strings and lemmas + // ============================================================ + + // Lemma cache + private static final Map lemmaCache = new HashMap<>(); + + // Helper function: get lemma form + public static synchronized String getLemma(String s) { + if (s == null || s.trim().isEmpty()) return null; + String lemma = lemmaCache.get(s); + if (lemma == null) { + LanguageInfo langInfo = LanguageAnalyzer.getSingleton().analyze(s); + lemma = (langInfo.numTokens() == 0) ? "" : langInfo.lemmaPhrase(0, langInfo.numTokens()); + lemmaCache.put(s, lemma); + } + return lemma; + } + + // Helper function: get original string from the table + public static String getOriginalString(String predicate, Example ex) { + return getOriginalString(predicate, ex.context); + } + + //Helper function: get original string from the table + public static String getOriginalString(String predicate, ContextValue context) { + if (context == null || context.graph == null || !(context.graph instanceof TableKnowledgeGraph)) + return null; + return getOriginalString(predicate, (TableKnowledgeGraph) context.graph); + } + + // Helper function: get original string from the table + public static String getOriginalString(String predicate, TableKnowledgeGraph graph) { + String s = graph.getOriginalString(predicate); + if (opts.usePredicateLemma) s = getLemma(s); + if (s != null && s.trim().isEmpty()) s = null; + return s; + } + + // ============================================================ + // Get the list of all PredicateInfos + // ============================================================ + + public static List getPredicateInfos(Example ex, Derivation deriv) { + Collection predicates; + Formula formula = deriv.formula; + if (opts.betaReduce) formula = Formulas.betaReduction(formula); + if (opts.traverseWithFormulaTypes) { + // Traverse on formula. Be more careful when generating predicates + FormulaTraverser traverser = new FormulaTraverser(ex); + traverser.traverse(formula); + predicates = traverser.predicates; + } else { + // Traverse on lisp tree (ignore formula types) + LispTreeTraverser traverser = new LispTreeTraverser(ex); + traverser.traverse(formula.toLispTree()); + predicates = traverser.predicates; + } + List answer = new ArrayList<>(); + for (PredicateInfo p : predicates) { + if (p.originalString == null || p.originalString.length() <= opts.maxPredicateLength) + answer.add(p); + } + return answer; + } + + private static class LispTreeTraverser { + public final Collection predicates; + private final ContextValue context; + + public LispTreeTraverser(Example ex) { + this.predicates = opts.allowRepeats ? new ArrayList<>() : new HashSet<>(); + this.context = ex.context; + } + + public void traverse(LispTree tree) { + if (tree.isLeaf()) { + predicates.add(new PredicateInfo(tree.value, context)); + } else { + for (LispTree child : tree.children) + traverse(child); + } + } + } + + private static class FormulaTraverser { + public final Collection predicates; + private final ContextValue context; + + public FormulaTraverser(Example ex) { + this.predicates = opts.allowRepeats ? new ArrayList<>() : new HashSet<>(); + this.context = ex.context; + } + + public void traverse(Formula formula) { + if (formula instanceof ValueFormula) { + Value value = ((ValueFormula) formula).value; + if (value instanceof NumberValue) { + NumberValue number = (NumberValue) value; + predicates.add(new PredicateInfo("number", context)); + predicates.add(new PredicateInfo(Fmt.D(number.value), context)); + + } else if (value instanceof DateValue) { + DateValue date = (DateValue) value; + predicates.add(new PredicateInfo("date", context)); + // Use prefixes to distinguish from numbers + predicates.add(new PredicateInfo("y:" + Fmt.D(date.year), context)); + predicates.add(new PredicateInfo("m:" + Fmt.D(date.month), context)); + predicates.add(new PredicateInfo("d:" + Fmt.D(date.day), context)); + + } else if (value instanceof StringValue) { + StringValue string = (StringValue) value; + predicates.add(new PredicateInfo("string", context)); + predicates.add(new PredicateInfo(string.value, context)); + + } else if (value instanceof NameValue) { + NameValue name = (NameValue) value; + String id = name.id; + if (opts.reverseNameValueConversion == ReverseNameValueConversion.allReverse + && id.startsWith("!") && !id.equals("!=")) { + predicates.add(new PredicateInfo("reverse", context)); + id = id.substring(1); + } + predicates.add(new PredicateInfo(id, context)); + + } + + } else if (formula instanceof JoinFormula) { + JoinFormula join = (JoinFormula) formula; + traverse(join.relation); traverse(join.child); + + } else if (formula instanceof ReverseFormula) { + ReverseFormula reverse = (ReverseFormula) formula; + if (opts.reverseNameValueConversion == ReverseNameValueConversion.allBang + && reverse.child instanceof ValueFormula + && ((ValueFormula) reverse.child).value instanceof NameValue) { + String id = ((NameValue) ((ValueFormula) reverse.child).value).id; + id = id.startsWith("!") ? id.substring(1) : ("!" + id); + traverse(new ValueFormula(new NameValue(id))); + } else { + predicates.add(new PredicateInfo("reverse", context)); + traverse(reverse.child); + } + + } else if (formula instanceof MergeFormula) { + MergeFormula merge = (MergeFormula) formula; + predicates.add(new PredicateInfo(merge.mode.toString(), context)); + traverse(merge.child1); traverse(merge.child2); + + } else if (formula instanceof AggregateFormula) { + AggregateFormula aggregate = (AggregateFormula) formula; + predicates.add(new PredicateInfo(aggregate.mode.toString(), context)); + traverse(aggregate.child); + + } else if (formula instanceof SuperlativeFormula) { + SuperlativeFormula superlative = (SuperlativeFormula) formula; + predicates.add(new PredicateInfo(superlative.mode.toString(), context)); + // Skip the "(number 1) (number 1)" part + traverse(superlative.head); traverse(superlative.relation); + + } else if (formula instanceof ArithmeticFormula) { + ArithmeticFormula arithmetic = (ArithmeticFormula) formula; + predicates.add(new PredicateInfo(arithmetic.mode.toString(), context)); + traverse(arithmetic.child1); traverse(arithmetic.child2); + + } else if (formula instanceof VariableFormula) { + // Skip variables + + } else if (formula instanceof MarkFormula) { + MarkFormula mark = (MarkFormula) formula; + predicates.add(new PredicateInfo("mark", context)); + // Skip variable + traverse(mark.body); + + } else if (formula instanceof LambdaFormula) { + LambdaFormula lambda = (LambdaFormula) formula; + predicates.add(new PredicateInfo("lambda", context)); + // Skip variable + traverse(lambda.body); + + } else { + throw new RuntimeException("[PredicateInfo] Cannot handle formula " + formula); + } + } + } + + // ============================================================ + // Formula normalization + // ============================================================ + + public static LispTree normalizeFormula(Example ex, Derivation deriv) { + Formula formula = deriv.formula; + if (opts.betaReduce) formula = Formulas.betaReduction(formula); + return new FormulaNormalizer(ex, formula).getNormalizedLispTree(); + } + + private static class FormulaNormalizer { + private final ContextValue context; + private final Formula formula; + private LispTree normalized; + private Map foundFields; + + public FormulaNormalizer(Example ex, Formula formula) { + this.context = ex.context; + this.formula = formula; + foundFields = new HashMap<>(); + } + + private String getNormalizedPredicate(String predicate) { + if (predicate.charAt(0) == '!') return "!" + getNormalizedPredicate(predicate.substring(1)); + if (predicate.equals(CanonicalNames.TYPE)) return "@type"; + if (predicate.equals(TableTypeSystem.ROW_TYPE)) return "@row"; + if (predicate.startsWith(TableTypeSystem.ROW_PROPERTY_NAME_PREFIX)) { + String fieldname = TableTypeSystem.getIdAfterPeriod(predicate, TableTypeSystem.ROW_PROPERTY_NAME_PREFIX); + if (predicate.equals(TableTypeSystem.ROW_NEXT_VALUE.id) || predicate.equals(TableTypeSystem.ROW_INDEX_VALUE.id)) + return "@" + fieldname; + if (!foundFields.containsKey(fieldname)) foundFields.put(fieldname, "" + foundFields.size()); + return "r" + foundFields.get(fieldname); + } else if (predicate.startsWith(TableTypeSystem.CELL_NAME_PREFIX)) { + if (predicate.startsWith(TableTypeSystem.CELL_PROPERTY_NAME_PREFIX)) { + return "@" + TableTypeSystem.getIdAfterPeriod(predicate, TableTypeSystem.CELL_PROPERTY_NAME_PREFIX); + } + String fieldname = TableTypeSystem.getIdAfterUnderscore(predicate, TableTypeSystem.CELL_NAME_PREFIX); + if (!foundFields.containsKey(fieldname)) foundFields.put(fieldname, "" + foundFields.size()); + return "c" + foundFields.get(fieldname); + } + return predicate; + } + + public LispTree getNormalizedLispTree() { + if (normalized == null) normalized = traverse(formula); + return normalized; + } + + public LispTree traverse(Formula formula) { + LispTree tree = LispTree.proto.newList(); + + if (formula instanceof ValueFormula) { + Value value = ((ValueFormula) formula).value; + if (value instanceof NumberValue) { + NumberValue number = (NumberValue) value; + return LispTree.proto.newLeaf("$number"); + + } else if (value instanceof DateValue) { + DateValue date = (DateValue) value; + return LispTree.proto.newLeaf("$date"); + + } else if (value instanceof StringValue) { + StringValue string = (StringValue) value; + return LispTree.proto.newLeaf("$string"); + + } else if (value instanceof NameValue) { + NameValue name = (NameValue) value; + String id = name.id; + if (opts.reverseNameValueConversion == ReverseNameValueConversion.allReverse + && id.startsWith("!") && !id.equals("!=")) { + tree.addChild(LispTree.proto.newLeaf("reverse")); + id = id.substring(1); + tree.addChild(getNormalizedPredicate(id)); + } else { + return LispTree.proto.newLeaf(getNormalizedPredicate(id)); + } + + } + + } else if (formula instanceof JoinFormula) { + JoinFormula join = (JoinFormula) formula; + tree.addChild(traverse(join.relation)).addChild(traverse(join.child)); + + } else if (formula instanceof ReverseFormula) { + ReverseFormula reverse = (ReverseFormula) formula; + if (opts.reverseNameValueConversion == ReverseNameValueConversion.allBang + && reverse.child instanceof ValueFormula + && ((ValueFormula) reverse.child).value instanceof NameValue) { + String id = ((NameValue) ((ValueFormula) reverse.child).value).id; + id = id.startsWith("!") ? id.substring(1) : ("!" + id); + return LispTree.proto.newLeaf(getNormalizedPredicate(id)); + } else { + tree.addChild(LispTree.proto.newLeaf("reverse")).addChild(traverse(reverse.child)); + } + + } else if (formula instanceof MergeFormula) { + MergeFormula merge = (MergeFormula) formula; + tree.addChild(merge.mode.toString()).addChild(traverse(merge.child1)).addChild(traverse(merge.child2)); + + } else if (formula instanceof AggregateFormula) { + AggregateFormula aggregate = (AggregateFormula) formula; + tree.addChild(aggregate.mode.toString()).addChild(traverse(aggregate.child)); + + } else if (formula instanceof SuperlativeFormula) { + SuperlativeFormula superlative = (SuperlativeFormula) formula; + tree.addChild(superlative.mode.toString()).addChild(traverse(superlative.head)).addChild(traverse(superlative.relation)); + + } else if (formula instanceof ArithmeticFormula) { + ArithmeticFormula arithmetic = (ArithmeticFormula) formula; + tree.addChild(arithmetic.mode.toString()).addChild(traverse(arithmetic.child1)).addChild(traverse(arithmetic.child2)); + + } else if (formula instanceof VariableFormula) { + return LispTree.proto.newLeaf("$var"); + + } else if (formula instanceof MarkFormula) { + MarkFormula mark = (MarkFormula) formula; + tree.addChild("mark").addChild(traverse(mark.body)); + + } else if (formula instanceof LambdaFormula) { + LambdaFormula lambda = (LambdaFormula) formula; + tree.addChild("lambda").addChild(traverse(lambda.body)); + + } else { + throw new RuntimeException("[PredicateInfo] Cannot handle formula " + formula); + } + + return tree; + } + } +} diff --git a/src/edu/stanford/nlp/sempre/tables/lambdadcs/BinaryDenotation.java b/src/edu/stanford/nlp/sempre/tables/lambdadcs/BinaryDenotation.java new file mode 100644 index 0000000..ee2ccf4 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/lambdadcs/BinaryDenotation.java @@ -0,0 +1,36 @@ +package edu.stanford.nlp.sempre.tables.lambdadcs; + +import edu.stanford.nlp.sempre.*; +import fig.basic.*; + +/** + * Binary denotation: a mapping from value to value. + * @author ppasupat + */ +public abstract class BinaryDenotation { + + @Override + public String toString() { + return toLispTree().toString(); + } + + public abstract LispTree toLispTree(); + + public abstract TableValue toTableValue(KnowledgeGraph graph); + + /** Return all y such that for some x in firsts, (x,y) in binary */ + public abstract UnaryDenotation joinFirst(UnaryDenotation firsts, KnowledgeGraph graph); + + /** Return all x such that for some y in seconds, (x,y) in binary */ + public abstract UnaryDenotation joinSecond(UnaryDenotation seconds, KnowledgeGraph graph); + + /** Return all (y,x) such that (x,y) in binary */ + public abstract BinaryDenotation reverse(); + + /** Return all (x,y) such that x in firsts and (x,y) in binary */ + public abstract ExplicitBinaryDenotation explicitlyFilterFirst(UnaryDenotation firsts, KnowledgeGraph graph); + + /** Return all (x,y) such that y in seconds and (x,y) in binary */ + public abstract ExplicitBinaryDenotation explicitlyFilterSecond(UnaryDenotation seconds, KnowledgeGraph graph); + +} diff --git a/src/edu/stanford/nlp/sempre/tables/lambdadcs/BinaryTypeHint.java b/src/edu/stanford/nlp/sempre/tables/lambdadcs/BinaryTypeHint.java new file mode 100644 index 0000000..418f94a --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/lambdadcs/BinaryTypeHint.java @@ -0,0 +1,47 @@ +package edu.stanford.nlp.sempre.tables.lambdadcs; + +import edu.stanford.nlp.sempre.*; + +/** + * Impose that the result is a binary and: + * - the set of first pair entries should be a subset of |upperBoundFirst| + * - the set of second pair entries should be a subset of |upperBoundSecond| + * + * @author ppasupat + */ +public class BinaryTypeHint extends TypeHint { + + public final UnaryDenotation firstUpperBound, secondUpperBound; + + // Should only be called within this package + protected BinaryTypeHint(UnaryDenotation first, UnaryDenotation second, VariableMap map) { + firstUpperBound = (first == null) ? InfiniteUnaryDenotation.STAR_UNARY : first; + secondUpperBound = (second == null) ? InfiniteUnaryDenotation.STAR_UNARY : second; + variableMap = map; + } + + @Override + public String toString() { + return "BinaryTypeHint [" + firstUpperBound + "|" + secondUpperBound + "] " + variableMap; + } + + // ============================================================ + // Derive a new type hint + // ============================================================ + + public BinaryTypeHint withVar(String name, Value value) { + return new BinaryTypeHint(firstUpperBound, secondUpperBound, variableMap.plus(name, value)); + } + + public BinaryTypeHint reverse() { + return newRestrictedBinary(secondUpperBound, firstUpperBound); + } + + public UnaryTypeHint first() { + return newRestrictedUnary(firstUpperBound); + } + + public UnaryTypeHint second() { + return newRestrictedUnary(secondUpperBound); + } +} diff --git a/src/edu/stanford/nlp/sempre/tables/lambdadcs/DenotationUtils.java b/src/edu/stanford/nlp/sempre/tables/lambdadcs/DenotationUtils.java new file mode 100644 index 0000000..30047a6 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/lambdadcs/DenotationUtils.java @@ -0,0 +1,378 @@ +package edu.stanford.nlp.sempre.tables.lambdadcs; + +import java.util.*; + +import edu.stanford.nlp.sempre.*; +import edu.stanford.nlp.sempre.tables.lambdadcs.LambdaDCSException.Type; +import fig.basic.*; + +/** + * Utilities for denotations. + * + * Handle aggregation, superlative, and arithmetic operations on different types of values. + * + * @author ppasupat + */ +public final class DenotationUtils { + public static class Options { + @Option(gloss = "Allow string comparison (lexicographic)") + public boolean allowStringComparison = false; + } + public static Options opts = new Options(); + + private DenotationUtils() { } + + // ============================================================ + // Type Enforcer + // ============================================================ + + /** + * Try to convert to a number + */ + public static int convertToInteger(Value value) { + if (value instanceof NumberValue) { + return (int) ((NumberValue) value).value; + } else { + throw new LambdaDCSException(Type.typeMismatch, "Cannot convert %s to number", value); + } + } + + /** + * Try to convert to a number + */ + public static double convertToNumber(Value value) { + if (value instanceof NumberValue) { + return ((NumberValue) value).value; + } else { + throw new LambdaDCSException(Type.typeMismatch, "Cannot convert %s to number", value); + } + } + + /** + * Ensure that the unary only has a single positive integer and return that integer + */ + public static int getSinglePositiveInteger(UnaryDenotation unary) { + if (unary.size() != 1) + throw new LambdaDCSException(Type.nonSingletonList, "getSinglePositiveInteger(): denotation %s has != 1 elements", unary); + int amount = convertToInteger(unary.iterator().next()); + if (amount > 0) return amount; + throw new LambdaDCSException(Type.typeMismatch, "getSinglePositiveInteger(): denotation %s is not a positive integer", unary); + } + + /** + * Ensure that the unary only has a single number and return that number + */ + public static double getSingleNumber(UnaryDenotation unary) { + if (unary.size() != 1) + throw new LambdaDCSException(Type.nonSingletonList, "getSingleNumber(): denotation %s has != 1 elements", unary); + return convertToNumber(unary.iterator().next()); + } + + /** + * Ensure that the unary has only a single value and return that value + */ + public static Value getSingleValue(UnaryDenotation unary) { + if (unary.size() != 1) + throw new LambdaDCSException(Type.nonSingletonList, "getSingleValue(): denotation %s has != 1 elements", unary); + return unary.iterator().next(); + } + + // ============================================================ + // Operations on Denotations + // ============================================================ + + /** + * Aggregate values of the same type. + */ + public static Value aggregate(Collection values, AggregateFormula.Mode mode) { + // Handle basic cases + if (mode == AggregateFormula.Mode.count) { + if (values.size() == Integer.MAX_VALUE) + throw new LambdaDCSException(Type.infiniteList, "Cannot call %s on an infinite list.", mode); + return new NumberValue(values.size()); + } + if (values.isEmpty()) { + if (LambdaDCSExecutor.opts.aggregatesFailOnEmptyLists) + throw new LambdaDCSException(Type.emptyList, "Cannot call %s on an empty list.", mode); + return new ListValue(Collections.emptyList()); + } + // General cases + TypeProcessor processor = getTypeProcessor(values); + switch (mode) { + case max: return processor.max(values); + case min: return processor.min(values); + case sum: return processor.sum(values); + case avg: return processor.avg(values); + default: throw new LambdaDCSException(Type.invalidFormula, "Unknown aggregate mode: %s", mode); + } + } + + /** + * Compute the superlative. + * + * If opt.aggregateReturnAllTopTies is true, (argmin 1 1 ...) and (argmax 1 1 ...) will return + * the list of all values that produce the min or max. Otherwise, only 1 arbitrary value will be returned. + */ + public static UnaryDenotation superlative(int rank, int count, ExplicitBinaryDenotation table, SuperlativeFormula.Mode mode) { + // Handle basic cases + if (table.pairs.isEmpty()) { + if (LambdaDCSExecutor.opts.superlativesFailOnEmptyLists) + throw new LambdaDCSException(Type.emptyList, "Cannot call %s on an empty list.", mode); + return new ExplicitUnaryDenotation(); + } + // General cases + List seconds = new ArrayList<>(); + for (Pair pair : table.pairs) + seconds.add(pair.getSecond()); + TypeProcessor processor = getTypeProcessor(seconds); + List answer = new ArrayList<>(); + if (LambdaDCSExecutor.opts.superlativesReturnAllTopTies && rank == 1 && count == 1) { + // Special case: Return all ties at the top + Value topValue; + switch (mode) { + case argmax: topValue = processor.max(seconds); break; + case argmin: topValue = processor.min(seconds); break; + default: throw new LambdaDCSException(Type.invalidFormula, "Unknown superlative mode: %s", mode); + } + for (Pair pair : table.pairs) + if (topValue.equals(pair.getSecond()) && !answer.contains(pair.getFirst())) + answer.add(pair.getFirst()); + } else { + // Other cases + List indices; + switch (mode) { + case argmax: indices = processor.argsort(seconds); Collections.reverse(indices); break; + case argmin: indices = processor.argsort(seconds); break; + default: throw new LambdaDCSException(Type.invalidFormula, "Unknown superlative mode: %s", mode); + } + int from = Math.min(rank - 1, indices.size()), + to = Math.min(from + count, indices.size()); + for (int index : indices.subList(from, to)) + answer.add(table.pairs.get(index).getFirst()); + } + return new ExplicitUnaryDenotation(answer); + } + + /** + * Perform arithmetic operation. + * + * Currently each Value must be a NumberValue. + */ + public static UnaryDenotation arithmetic(Collection child1D, Collection child2D, ArithmeticFormula.Mode mode) { + TypeProcessor processor = getTypeProcessor(child1D, child2D); + if (LambdaDCSExecutor.opts.arithmeticsFailOnMultipleElements) { + if (child1D.size() > 1 && child2D.size() > 1) + throw new LambdaDCSException(Type.nonSingletonList, "Cannot call %s when both denotations have > 1 values.", mode); + } + List answer = new ArrayList<>(); + switch (mode) { + case add: + for (Value v1 : child1D) for (Value v2 : child2D) answer.add(processor.add(v1, v2)); + break; + case sub: + for (Value v1 : child1D) for (Value v2 : child2D) answer.add(processor.sub(v1, v2)); + break; + case mul: + for (Value v1 : child1D) for (Value v2 : child2D) answer.add(processor.mul(v1, v2)); + break; + case div: + for (Value v1 : child1D) for (Value v2 : child2D) answer.add(processor.div(v1, v2)); + break; + default: + throw new LambdaDCSException(Type.invalidFormula, "Unknown arithmetic mode: %s", mode); + } + return new ExplicitUnaryDenotation(answer); + } + + // ============================================================ + // Processor for each data type + // ============================================================ + + /** + * Processor for each data type. + */ + public abstract static class TypeProcessor { + public abstract boolean isCompatible(Value v); + // positive if v1 > v2 | negative if v1 < v2 | 0 if v1 == v2 + public int compareValues(Value v1, Value v2) { throw new LambdaDCSException(Type.typeMismatch, "Cannot compare values with " + getClass().getSimpleName()); } + public Value sum(Collection values) { throw new LambdaDCSException(Type.typeMismatch, "Cannot compute sum with " + getClass().getSimpleName()); } + public Value avg(Collection values) { throw new LambdaDCSException(Type.typeMismatch, "Cannot compute avg with " + getClass().getSimpleName()); } + public Value add(Value v1, Value v2) { throw new LambdaDCSException(Type.typeMismatch, "Cannot compute add with " + getClass().getSimpleName()); } + public Value sub(Value v1, Value v2) { throw new LambdaDCSException(Type.typeMismatch, "Cannot compute sub with " + getClass().getSimpleName()); } + public Value mul(Value v1, Value v2) { throw new LambdaDCSException(Type.typeMismatch, "Cannot compute mul with " + getClass().getSimpleName()); } + public Value div(Value v1, Value v2) { throw new LambdaDCSException(Type.typeMismatch, "Cannot compute div with " + getClass().getSimpleName()); } + + public Value max(Collection values) { + Value max = null; + for (Value value : values) { + if (max == null || compareValues(max, value) < 0) + max = value; + } + return max; + } + + public Value min(Collection values) { + Value min = null; + for (Value value : values) { + if (min == null || compareValues(min, value) > 0) + min = value; + } + return min; + } + + public List argsort(List values) { + List indices = new ArrayList<>(); + for (int i = 0; i < values.size(); i++) + indices.add(i); + Collections.sort(indices, new Comparator() { + @Override + public int compare(Integer o1, Integer o2) { + return compareValues(values.get(o1), values.get(o2)); + } + }); + return indices; + } + } + + /** + * Handle NumberValue. All operations are possible. + */ + public static class NumberProcessor extends TypeProcessor { + public static TypeProcessor singleton = new NumberProcessor(); + + @Override + public boolean isCompatible(Value v) { + return v instanceof NumberValue; + } + + @Override + public int compareValues(Value v1, Value v2) { + double x1 = ((NumberValue) v1).value, x2 = ((NumberValue) v2).value; + return (x1 > x2) ? 1 : (x1 < x2) ? -1 : 0; + } + + @Override + public Value sum(Collection values) { + double sum = 0; + for (Value value : values) + sum += ((NumberValue) value).value; + return new NumberValue(sum); + } + + @Override + public Value avg(Collection values) { + double sum = 0; + for (Value value : values) + sum += ((NumberValue) value).value; + return new NumberValue(sum / values.size()); + } + + @Override public Value add(Value v1, Value v2) { return new NumberValue(((NumberValue) v1).value + ((NumberValue) v2).value); } + @Override public Value sub(Value v1, Value v2) { return new NumberValue(((NumberValue) v1).value - ((NumberValue) v2).value); } + @Override public Value mul(Value v1, Value v2) { return new NumberValue(((NumberValue) v1).value * ((NumberValue) v2).value); } + @Override public Value div(Value v1, Value v2) { return new NumberValue(((NumberValue) v1).value / ((NumberValue) v2).value); } + } + + /** + * Handle DateValue. Only comparison is possible. + */ + public static class DateProcessor extends TypeProcessor { + public static TypeProcessor singleton = new DateProcessor(); + + @Override + public boolean isCompatible(Value v) { + return v instanceof DateValue; + } + + @Override + public int compareValues(Value v1, Value v2) { + DateValue d1 = ((DateValue) v1), d2 = ((DateValue) v2); + if (d1.year == -1 || d2.year == -1 || d1.year == d2.year) { + if (d1.month == -1 || d2.month == -1 || d1.month == d2.month) { + if (d1.day == -1 || d2.day == -1 || d1.day == d2.day) { + return 0; + } else { + return d1.day - d2.day; + } + } else { + return d1.month - d2.month; + } + } else { + return d1.year - d2.year; + } + } + } + + /** + * Handle StringValue. Only comparison is possible. + */ + public static class StringProcessor extends TypeProcessor { + public static TypeProcessor singleton = new StringProcessor(); + + @Override + public boolean isCompatible(Value v) { + return v instanceof StringValue; + } + + String getString(Value v) { + if (v instanceof StringValue) return ((StringValue) v).value; + if (v instanceof NameValue) { + NameValue nameValue = (NameValue) v; + return (nameValue.description == null || nameValue.description.isEmpty()) ? nameValue.id : nameValue.description; + } + if (v instanceof NumberValue) return "" + ((NumberValue) v).value; + return v.toString(); + } + + @Override + public int compareValues(Value v1, Value v2) { + return getString(v1).compareTo(getString(v2)); + } + } + + /** + * Get the TypeProcessor corresponding to the strictest type. + */ + public static TypeProcessor getTypeProcessor(Value value) { + return getTypeProcessor(Collections.singleton(value), Collections.emptySet()); + } + + /** + * Get the TypeProcessor corresponding to the strictest type. + */ + public static TypeProcessor getTypeProcessor(Value value1, Value value2) { + return getTypeProcessor(Collections.singleton(value1), Collections.singleton(value2)); + } + + /** + * Get the TypeProcessor corresponding to the strictest type. + */ + public static TypeProcessor getTypeProcessor(Collection values) { + return getTypeProcessor(values, Collections.emptySet()); + } + + /** + * Get the TypeProcessor corresponding to the strictest type. + */ + public static TypeProcessor getTypeProcessor(Collection values1, Collection values2) { + boolean canBeNumber = true, canBeDate = true; + for (Value value : values1) { + if (!(value instanceof NumberValue)) canBeNumber = false; + if (!(value instanceof DateValue)) canBeDate = false; + } + for (Value value : values2) { + if (!(value instanceof NumberValue)) canBeNumber = false; + if (!(value instanceof DateValue)) canBeDate = false; + } + if (canBeNumber) { + return NumberProcessor.singleton; + } else if (canBeDate) { + return DateProcessor.singleton; + } else { + if (opts.allowStringComparison) + return StringProcessor.singleton; + else + throw new LambdaDCSException(Type.typeMismatch, "Cannot compare values"); + } + } +} diff --git a/src/edu/stanford/nlp/sempre/tables/lambdadcs/ExecutorCache.java b/src/edu/stanford/nlp/sempre/tables/lambdadcs/ExecutorCache.java new file mode 100644 index 0000000..d128fb3 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/lambdadcs/ExecutorCache.java @@ -0,0 +1,60 @@ +package edu.stanford.nlp.sempre.tables.lambdadcs; + +import java.util.*; + +import fig.basic.*; + +/** + * Cache the executed values of LambdaDCSExecutor. + * + * @author ppasupat + */ +public final class ExecutorCache { + public static class Options { + @Option(gloss = "maximum number of entries to retain") + public int maxCacheSize = 1; + } + public static Options opts = new Options(); + + public static final ExecutorCache singleton = new ExecutorCache(); + + private final Map> cache; + private final Map lastUsed; + private long timestamp = 0; + + private ExecutorCache() { + cache = new HashMap<>(); + lastUsed = new HashMap<>(); + } + + public synchronized Object get(Object metakey, Object key) { + Map cacheForMetakey = cache.get(metakey); + if (cacheForMetakey == null) return null; + lastUsed.put(metakey, timestamp++); + return cacheForMetakey.get(key); + } + + public synchronized void put(Object metakey, Object key, Object value) { + Map cacheForMetakey = cache.get(metakey); + if (cacheForMetakey == null) { + while (cache.size() >= opts.maxCacheSize) evict(); + cache.put(metakey, cacheForMetakey = new HashMap<>()); + } + cacheForMetakey.put(key, value); + } + + private void evict() { + long minLastUsed = Long.MAX_VALUE; + Object minLastUsedMetakey = null; + for (Object existingMetakey : cache.keySet()) { + Long existingLastUsed = lastUsed.get(existingMetakey); + if (existingLastUsed == null) existingLastUsed = 0L; + if (existingLastUsed < minLastUsed) { + minLastUsed = existingLastUsed; + minLastUsedMetakey = existingMetakey; + } + } + cache.remove(minLastUsedMetakey); + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/lambdadcs/ExplicitBinaryDenotation.java b/src/edu/stanford/nlp/sempre/tables/lambdadcs/ExplicitBinaryDenotation.java new file mode 100644 index 0000000..6e41687 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/lambdadcs/ExplicitBinaryDenotation.java @@ -0,0 +1,101 @@ +package edu.stanford.nlp.sempre.tables.lambdadcs; + +import java.util.*; + +import edu.stanford.nlp.sempre.*; +import fig.basic.*; + +/** + * A binary with finite number of possible mapping. Represented as a set of pairs of values. + * + * @author ppasupat + */ +public class ExplicitBinaryDenotation extends BinaryDenotation { + + protected List> pairs; + protected Set> pairsSet; + + public ExplicitBinaryDenotation() { + pairs = Collections.emptyList(); + pairsSet = Collections.emptySet(); + } + + public ExplicitBinaryDenotation(Value v1, Value v2) { + this(new Pair<>(v1, v2)); + } + + public ExplicitBinaryDenotation(Pair pair) { + pairs = Collections.singletonList(pair); + pairsSet = Collections.singleton(pair); + } + + public ExplicitBinaryDenotation(Collection> pairs) { + this.pairs = new ArrayList<>(pairs); + this.pairsSet = new HashSet<>(pairs); + } + + @Override + public LispTree toLispTree() { + LispTree tree = LispTree.proto.newList(); + tree.addChild("binary"); + for (Pair pair : pairs) + tree.addChild(LispTree.proto.newList(pair.getFirst().toLispTree(), pair.getSecond().toLispTree())); + return tree; + } + + @Override + public TableValue toTableValue(KnowledgeGraph graph) { + List header = Arrays.asList("key", "value"); + List> rows = new ArrayList<>(); + for (Pair pair : pairsSet) { + rows.add(Arrays.asList(pair.getFirst(), pair.getSecond())); + } + return new TableValue(header, rows); + } + + @Override + public UnaryDenotation joinFirst(UnaryDenotation firsts, KnowledgeGraph graph) { + List seconds = new ArrayList<>(); + for (Pair pair : pairs) { + if (firsts.contains(pair.getFirst())) seconds.add(pair.getSecond()); + } + return new ExplicitUnaryDenotation(seconds); + } + + @Override + public UnaryDenotation joinSecond(UnaryDenotation seconds, KnowledgeGraph graph) { + List firsts = new ArrayList<>(); + for (Pair pair : pairs) { + if (seconds.contains(pair.getSecond())) firsts.add(pair.getFirst()); + } + return new ExplicitUnaryDenotation(firsts); + } + + @Override + public BinaryDenotation reverse() { + List> reversedPairs = new ArrayList<>(); + for (Pair pair : pairs) { + reversedPairs.add(new Pair<>(pair.getSecond(), pair.getFirst())); + } + return new ExplicitBinaryDenotation(reversedPairs); + } + + @Override + public ExplicitBinaryDenotation explicitlyFilterFirst(UnaryDenotation firsts, KnowledgeGraph graph) { + List> filtered = new ArrayList<>(); + for (Pair pair : pairs) { + if (firsts.contains(pair.getFirst())) filtered.add(pair); + } + return new ExplicitBinaryDenotation(filtered); + } + + @Override + public ExplicitBinaryDenotation explicitlyFilterSecond(UnaryDenotation seconds, KnowledgeGraph graph) { + List> filtered = new ArrayList<>(); + for (Pair pair : pairs) { + if (seconds.contains(pair.getSecond())) filtered.add(pair); + } + return new ExplicitBinaryDenotation(filtered); + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/lambdadcs/ExplicitUnaryDenotation.java b/src/edu/stanford/nlp/sempre/tables/lambdadcs/ExplicitUnaryDenotation.java new file mode 100644 index 0000000..0cde1ac --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/lambdadcs/ExplicitUnaryDenotation.java @@ -0,0 +1,109 @@ +package edu.stanford.nlp.sempre.tables.lambdadcs; + +import java.util.*; + +import edu.stanford.nlp.sempre.*; +import edu.stanford.nlp.sempre.tables.lambdadcs.LambdaDCSException.Type; +import fig.basic.*; + +/** + * A unary with finite number of elements. Represented as a set of values. + * + * @author ppasupat + */ +public class ExplicitUnaryDenotation extends UnaryDenotation { + + protected List values; + protected Set valuesSet; + + public ExplicitUnaryDenotation() { + values = Collections.emptyList(); + valuesSet = Collections.emptySet(); + } + + public ExplicitUnaryDenotation(Value value) { + values = Collections.singletonList(value); + valuesSet = Collections.singleton(value); + } + + public ExplicitUnaryDenotation(Collection values) { + this.values = new ArrayList<>(values); + this.valuesSet = new HashSet<>(values); + } + + @Override + public LispTree toLispTree() { + LispTree tree = LispTree.proto.newList(); + tree.addChild("unary"); + for (Value value : values) + tree.addChild(value.toLispTree()); + return tree; + } + + @Override + public ListValue toListValue(KnowledgeGraph graph) { + return new ListValue(new ArrayList<>(valuesSet)); + } + + @Override + public String toString() { + return toLispTree().toString(); + } + + @Override + public boolean contains(Object o) { + return valuesSet.contains(o); + } + + @Override + public boolean containsAll(Collection c) { + return valuesSet.containsAll(c); + } + + @Override + public Iterator iterator() { + return values.iterator(); + } + + @Override + public int size() { + return values.size(); + } + + @Override + public UnaryDenotation uniqued() { + return new ExplicitUnaryDenotation(valuesSet); + } + + @Override + public UnaryDenotation merge(UnaryDenotation that, MergeFormula.Mode mode) { + if (that.size() == Integer.MAX_VALUE) return that.merge(this, mode); + Set merged = new HashSet<>(values); + switch (mode) { + case and: merged.retainAll(that); break; + case or: merged.addAll(that); break; + default: throw new LambdaDCSException(Type.invalidFormula, "Unknown merge mode: %s", mode); + } + return new ExplicitUnaryDenotation(merged); + } + + @Override + public UnaryDenotation aggregate(AggregateFormula.Mode mode) { + if (mode == AggregateFormula.Mode.count) { + // Count the set size, not the list size + return new ExplicitUnaryDenotation(new NumberValue(valuesSet.size())); + } + return new ExplicitUnaryDenotation(DenotationUtils.aggregate(this, mode)); + } + + @Override + public UnaryDenotation filter(UnaryDenotation upperBound) { + List filtered = new ArrayList<>(); + for (Value value : values) { + if (upperBound.contains(value)) + filtered.add(value); + } + return new ExplicitUnaryDenotation(filtered); + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/lambdadcs/InfiniteUnaryDenotation.java b/src/edu/stanford/nlp/sempre/tables/lambdadcs/InfiniteUnaryDenotation.java new file mode 100644 index 0000000..3d525a2 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/lambdadcs/InfiniteUnaryDenotation.java @@ -0,0 +1,175 @@ +package edu.stanford.nlp.sempre.tables.lambdadcs; + +import java.util.*; + +import edu.stanford.nlp.sempre.*; +import edu.stanford.nlp.sempre.tables.lambdadcs.LambdaDCSException.Type; +import fig.basic.*; + +/** + * A unary with infinite number of elements such as (>= 4) and * [= anything] + * + * @author ppasupat + */ +public abstract class InfiniteUnaryDenotation extends UnaryDenotation { + + @Override + public ListValue toListValue(KnowledgeGraph graph) { + throw new LambdaDCSException(Type.infiniteList, "Cannot convert to ListValue: %s", toLispTree()); + } + + // Default implementation: calls |contains| on all elements of |c| + @Override + public boolean containsAll(Collection c) { + for (Object o : c) { + if (!contains(o)) return false; + } + return true; + } + + @Override + public Iterator iterator() { + throw new LambdaDCSException(Type.infiniteList, "Cannot iterate over an infinite unary"); + } + + @Override + public int size() { + return Integer.MAX_VALUE; + } + + @Override + public UnaryDenotation uniqued() { + return this; // Already uniqued + } + + @Override + public UnaryDenotation filter(UnaryDenotation that) { + return merge(that, MergeFormula.Mode.and); + } + + // Create an InfiniteUnaryDenotation based on the specification + public static InfiniteUnaryDenotation create(String binary, UnaryDenotation second) { + try { + if (ComparisonUnaryDenotation.COMPARATORS.contains(binary)) { + return new ComparisonUnaryDenotation(binary, DenotationUtils.getSingleValue(second)); + } + } catch (Exception e) { } + throw new LambdaDCSException(Type.invalidFormula, + "Cannot create an InfiniteUnaryDenotation: binary = %s, second = %s", binary, second); + } + + // ============================================================ + // Everything (*) + // ============================================================ + + static class EverythingUnaryDenotation extends InfiniteUnaryDenotation { + + @Override + public LispTree toLispTree() { + LispTree tree = LispTree.proto.newList(); + tree.addChild("unary"); + tree.addChild("*"); + return tree; + } + + @Override + public boolean contains(Object o) { + return true; + } + + @Override + public UnaryDenotation merge(UnaryDenotation that, MergeFormula.Mode mode) { + switch (mode) { + case and: return that.uniqued(); + case or: return this; + default: throw new LambdaDCSException(Type.invalidFormula, "Unknown merge mode: %s", mode); + } + } + + @Override + public UnaryDenotation aggregate(AggregateFormula.Mode mode) { + throw new LambdaDCSException(Type.infiniteList, "Cannot use aggregate mode %s on *", mode); + } + + } + public static final InfiniteUnaryDenotation STAR_UNARY = new EverythingUnaryDenotation(); + + // ============================================================ + // Comparison + // ============================================================ + + public static class ComparisonUnaryDenotation extends InfiniteUnaryDenotation { + + public static final List COMPARATORS = Arrays.asList("!=", "<", ">", "<=", ">="); + public String comparator; + public Value value; + private final DenotationUtils.TypeProcessor valueProcessor; + + public ComparisonUnaryDenotation(String comparator, Value value) { + this.comparator = comparator; + this.value = value; + this.valueProcessor = comparator.equals("!=") ? null : DenotationUtils.getTypeProcessor(value); + } + + @Override + public LispTree toLispTree() { + LispTree tree = LispTree.proto.newList(); + tree.addChild(comparator); + tree.addChild(value.toLispTree()); + return tree; + } + + @Override + public UnaryDenotation merge(UnaryDenotation that, MergeFormula.Mode mode) { + if (that.size() != Integer.MAX_VALUE) { + if (mode == MergeFormula.Mode.and) { + Set filtered = new HashSet<>(); + for (Value value : that) + if (contains(value)) + filtered.add(value); + return new ExplicitUnaryDenotation(filtered); + } + } else if (that instanceof EverythingUnaryDenotation) { + return that.merge(this, mode); + } else if ("!=".equals(comparator)) { + if (mode == MergeFormula.Mode.and && !that.contains(value)) + return this; + if (mode == MergeFormula.Mode.or) + return that.contains(value) ? STAR_UNARY : this; + } else { + // TODO(ice): Handle some more cases + } + throw new LambdaDCSException(Type.infiniteList, "Cannot use merge mode %s on %s and %s", mode, this, that); + } + + @Override + public UnaryDenotation aggregate(AggregateFormula.Mode mode) { + // Handle some cases + if (">=".equals(comparator) && mode == AggregateFormula.Mode.min) + return new ExplicitUnaryDenotation(value); + if ("<=".equals(comparator) && mode == AggregateFormula.Mode.max) + return new ExplicitUnaryDenotation(value); + throw new LambdaDCSException(Type.infiniteList, "Cannot use aggregate mode %s on %s", mode, this); + } + + @Override + public boolean contains(Object o) { + if (!(o instanceof Value)) return false; + Value that = ((Value) o); + if (comparator.equals("!=")) return !that.equals(value); + if (!valueProcessor.isCompatible(that)) + throw new LambdaDCSException(Type.typeMismatch, "Cannot compare %s with %s", value, that); + int comparison = valueProcessor.compareValues(that, value); + switch (comparator) { + case "<": return comparison < 0; + case ">": return comparison > 0; + case "<=": return comparison <= 0; + case ">=": return comparison >= 0; + default: throw new LambdaDCSException(Type.invalidFormula, "Unknown comparator: %s", comparator); + } + } + + } + + +} diff --git a/src/edu/stanford/nlp/sempre/tables/lambdadcs/LambdaDCSException.java b/src/edu/stanford/nlp/sempre/tables/lambdadcs/LambdaDCSException.java new file mode 100644 index 0000000..934e899 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/lambdadcs/LambdaDCSException.java @@ -0,0 +1,40 @@ +package edu.stanford.nlp.sempre.tables.lambdadcs; + +public class LambdaDCSException extends RuntimeException { + private static final long serialVersionUID = -9174017483530966223L; + + public enum Type { + + // Unknown formula parameters (e.g., superlative that is not argmax or argmin) + // Should not occur. Otherwise there is a serious bug in the code. + invalidFormula, + + // Trying to perform an operation on unsupported denotations + emptyList, + nonSingletonList, + infiniteList, + + // Type mismatch + typeMismatch, + notUnary, + notBinary, + + // Other errors + unknown, + + }; + + public final Type type; + public final String message; + + public LambdaDCSException(Type type, String message, Object... args) { + this.type = type; + this.message = String.format(message, args); + } + + @Override + public String toString() { + return "" + type + ": " + message; + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/lambdadcs/LambdaDCSExecutor.java b/src/edu/stanford/nlp/sempre/tables/lambdadcs/LambdaDCSExecutor.java new file mode 100644 index 0000000..2b11319 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/lambdadcs/LambdaDCSExecutor.java @@ -0,0 +1,361 @@ +package edu.stanford.nlp.sempre.tables.lambdadcs; + +import java.util.*; + +import edu.stanford.nlp.sempre.*; +import edu.stanford.nlp.sempre.tables.lambdadcs.LambdaDCSException.Type; +import fig.basic.*; + +/** + * Execute a Formula on the given KnowledgeGraph instance. + * + * @author ppasupat + */ +public class LambdaDCSExecutor extends Executor { + public static class Options { + @Option(gloss = "Verbosity") public int verbose = 0; + @Option(gloss = "If the result is empty, return an ErrorValue instead of an empty ListValue") + public boolean failOnEmptyLists = false; + @Option(gloss = "Return all ties on (argmax 1 1 ...) and (argmin 1 1 ...)") + public boolean superlativesReturnAllTopTies = true; + @Option(gloss = "Aggregates (sum, avg, max, min) throw an error on empty lists") + public boolean aggregatesFailOnEmptyLists = false; + @Option(gloss = "Superlatives (argmax, argmin) throw an error on empty lists") + public boolean superlativesFailOnEmptyLists = false; + @Option(gloss = "Arithmetics (+, -, *, /) throw an error when both operants have > 1 values") + public boolean arithmeticsFailOnMultipleElements = false; + @Option(gloss = "Use caching") + public boolean useCache = true; + } + public static Options opts = new Options(); + + public final Evaluation stats = new Evaluation(); + + @Override + public Response execute(Formula formula, ContextValue context) { + LambdaDCSCoreLogic logic; + if (opts.verbose < 3) { + logic = new LambdaDCSCoreLogic(context, stats); + } else { + logic = new LambdaDCSCoreLogicWithVerbosity(context, stats); + } + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + Value answer = logic.execute(formula); + stopWatch.stop(); + stats.addCumulative("execTime", stopWatch.ms); + if (stopWatch.ms >= 10 && opts.verbose >= 1) + LogInfo.logs("long time: %s %s", Formulas.betaReduction(formula), answer); + /*///////// DEBUG! ////////// + if (answer instanceof ErrorValue) { + if (!((ErrorValue) answer).type.startsWith("notUnary")) + LogInfo.logs("%s %s", Formulas.betaReduction(formula), answer); + } + ////////// END DEBUG /////////*/ + return new Response(answer); + } + + public void summarize() { + LogInfo.begin_track("LambdaDCSExecutor: summarize"); + stats.logStats("LambdaDCSExecutor"); + LogInfo.end_track(); + } +} + +// ============================================================ +// Execution +// ============================================================ + +/** + * Main logic of Lambda DCS Executor. + * + * Find the denotation of a formula (logical form) with respect to the given knowledge graph. + * + * Assume that the denotation is either a unary or a binary, + * and the final denotation is a unary. + * + * Both unaries and binaries are lists (not sets). + * However, the following formula types will treat them as sets: + * - and, or + * - count (= count the number of distinct values) + * + * Note that (and (!weight (@type @row)) (@p.num (> (number 90)))) may give a wrong answer, + * but it can be rewritten as (!weight (and (@type @row) (weight (@p.num (> (number 90)))))) + * + * @author ppasupat + */ +class LambdaDCSCoreLogic { + + // Note: STAR does not work well with type checking + static final NameValue STAR = new NameValue("*"); + + final KnowledgeGraph graph; + final Evaluation stats; + + public LambdaDCSCoreLogic(ContextValue context, Evaluation stats) { + graph = context.graph; + this.stats = stats; + if (graph == null) + throw new RuntimeException("Cannot call LambdaDCSExecutor when context graph is null"); + } + + public Value execute(Formula formula) { + Formula f = Formulas.betaReduction(formula); + if (LambdaDCSExecutor.opts.verbose >= 2) + LogInfo.logs("%s", f); + try { + UnaryDenotation denotation = computeUnary(f, TypeHint.UNRESTRICTED_UNARY); + if (LambdaDCSExecutor.opts.useCache) { + ExecutorCache.singleton.put(graph, f, denotation); + } + ListValue answer = denotation.toListValue(graph); + if (answer.values.isEmpty()) { + if (LambdaDCSExecutor.opts.failOnEmptyLists) + return ErrorValue.empty; + } + return answer; + } catch (final LambdaDCSException e) { + return new ErrorValue(e.toString()); + } + } + + public UnaryDenotation computeUnary(Formula formula, UnaryTypeHint typeHint) { + assert typeHint != null; + if (formula instanceof LambdaFormula) { + throw new LambdaDCSException(Type.notUnary, "[Unary] Not a unary " + formula); + } + + if (LambdaDCSExecutor.opts.useCache) { + Object object = ExecutorCache.singleton.get(graph, formula); + if (object != null && object instanceof UnaryDenotation) { + stats.addCumulative("cacheHit", true); + return (UnaryDenotation) object; + } else { + stats.addCumulative("cacheHit", false); + } + } + + if (formula instanceof ValueFormula) { + // ============================================================ + // ValueFormula + // ============================================================ + Value value = ((ValueFormula) formula).value; + if (value instanceof BooleanValue || value instanceof NumberValue || + value instanceof StringValue || value instanceof DateValue || value instanceof NameValue) { + // Special case: * + if (STAR.equals(value)) return InfiniteUnaryDenotation.STAR_UNARY; + return typeHint.applyBound(new ExplicitUnaryDenotation(value)); + } + + } else if (formula instanceof JoinFormula) { + // ============================================================ + // JoinFormula + // ============================================================ + JoinFormula join = (JoinFormula) formula; + try { + // Compute unary, then join binary + UnaryDenotation childD = computeUnary(join.child, typeHint.newUnrestrictedUnary()); + BinaryDenotation relationD = computeBinary(join.relation, typeHint.asFirstOfBinaryWithSecond(childD)); + return typeHint.applyBound(relationD.joinSecond(childD, graph)); + } catch (LambdaDCSException e1) { + try { + // Compute binary, then join unary + BinaryDenotation relationD = computeBinary(join.relation, typeHint.asFirstOfBinary()); + UnaryDenotation childUpperBound = relationD.joinFirst(typeHint.upperBound, graph); + UnaryDenotation childD = computeUnary(join.child, typeHint.newRestrictedUnary(childUpperBound)); + return typeHint.applyBound(relationD.joinSecond(childD, graph)); + } catch (LambdaDCSException e2) { + throw new LambdaDCSException(Type.unknown, "Cannot join | %s | %s", e1, e2); + } + } + + } else if (formula instanceof MergeFormula) { + // ============================================================ + // Merge + // ============================================================ + MergeFormula merge = (MergeFormula) formula; + try { + UnaryDenotation child1D = computeUnary(merge.child1, typeHint); + UnaryDenotation child2D = computeUnary(merge.child2, + merge.mode == MergeFormula.Mode.and ? typeHint.restrict(child1D) : typeHint); + return typeHint.applyBound(child1D.merge(child2D, merge.mode)); + } catch (LambdaDCSException e1) { + try { + UnaryDenotation child2D = computeUnary(merge.child2, typeHint); + UnaryDenotation child1D = computeUnary(merge.child1, + merge.mode == MergeFormula.Mode.and ? typeHint.restrict(child2D) : typeHint); + return typeHint.applyBound(child2D.merge(child1D, merge.mode)); + } catch (LambdaDCSException e2) { + throw new LambdaDCSException(Type.unknown, "Cannot merge | %s | %s", e1, e2); + } + } + + } else if (formula instanceof NotFormula) { + // ============================================================ + // Not + // ============================================================ + // TODO(ice): (Low priority) + + } else if (formula instanceof AggregateFormula) { + // ============================================================ + // Aggregate + // ============================================================ + AggregateFormula aggregate = (AggregateFormula) formula; + UnaryDenotation childD = computeUnary(aggregate.child, typeHint.newUnrestrictedUnary()); + return typeHint.applyBound(childD.aggregate(aggregate.mode)); + + } else if (formula instanceof SuperlativeFormula) { + // ============================================================ + // Superlative + // ============================================================ + SuperlativeFormula superlative = (SuperlativeFormula) formula; + int rank = DenotationUtils.getSinglePositiveInteger(computeUnary(superlative.rank, typeHint.newUnrestrictedUnary())); + int count = DenotationUtils.getSinglePositiveInteger(computeUnary(superlative.count, typeHint.newUnrestrictedUnary())); + UnaryDenotation headD = computeUnary(superlative.head, typeHint); + BinaryDenotation relationD = computeBinary(superlative.relation, typeHint.newRestrictedBinary(headD, null)); + ExplicitBinaryDenotation table = relationD.explicitlyFilterFirst(headD, graph); + return typeHint.applyBound(DenotationUtils.superlative(rank, count, table, superlative.mode)); + + } else if (formula instanceof ArithmeticFormula) { + // ============================================================ + // Arithmetic + // ============================================================ + ArithmeticFormula arithmetic = (ArithmeticFormula) formula; + UnaryDenotation child1D = computeUnary(arithmetic.child1, typeHint.newUnrestrictedUnary()); + UnaryDenotation child2D = computeUnary(arithmetic.child2, typeHint.newUnrestrictedUnary()); + return typeHint.applyBound(DenotationUtils.arithmetic(child1D, child2D, arithmetic.mode)); + + } else if (formula instanceof CallFormula) { + // ============================================================ + // Call + // ============================================================ + // TODO(ice): (Low priority) + + } else if (formula instanceof VariableFormula) { + // ============================================================ + // Variable + // ============================================================ + VariableFormula variable = (VariableFormula) formula; + Value value = typeHint.get(variable.name); + return typeHint.applyBound(new ExplicitUnaryDenotation(value)); + + } else if (formula instanceof MarkFormula) { + // ============================================================ + // Mark + // ============================================================ + MarkFormula mark = (MarkFormula) formula; + String var = mark.var; + // Assuming that the type hint has enough information ... + List values = new ArrayList<>(); + for (Value varValue : typeHint.upperBound) { + UnaryDenotation results = computeUnary(mark.body, typeHint.withVar(var, varValue)); + if (results.contains(varValue)) { + values.add(varValue); + } + } + return typeHint.applyBound(new ExplicitUnaryDenotation(values)); + + } else { + throw new LambdaDCSException(Type.notUnary, "[Unary] Not a unary " + formula); + } + + // Catch-all error + throw new LambdaDCSException(Type.unknown, "[Unary] Cannot handle formula " + formula); + } + + public BinaryDenotation computeBinary(Formula formula, BinaryTypeHint typeHint) { + assert typeHint != null; + if (formula instanceof ValueFormula) { + // ============================================================ + // ValueFormula + // ============================================================ + Value value = ((ValueFormula) formula).value; + if (SpecialBinaryDenotation.isSpecial(value)) + return SpecialBinaryDenotation.create(value); + if (value instanceof BooleanValue || value instanceof NumberValue || + value instanceof StringValue || value instanceof DateValue || + value instanceof UriValue || value instanceof NameValue || + value instanceof ListValue) { + if (!STAR.equals(value)) + return new PredicateBinaryDenotation(value); + } + + } else if (formula instanceof ReverseFormula) { + // ============================================================ + // Reverse + // ============================================================ + ReverseFormula reverse = (ReverseFormula) formula; + BinaryDenotation childD = computeBinary(reverse.child, typeHint.reverse()); + return childD.reverse(); + + } else if (formula instanceof LambdaFormula) { + // ============================================================ + // Lambda + // ============================================================ + // Note: The variable's values become the SECOND argument of the binary pairs + LambdaFormula lambda = (LambdaFormula) formula; + String var = lambda.var; + // Assuming that the type hint has enough information ... + try { + List> pairs = new ArrayList<>(); + for (Value varValue : typeHint.secondUpperBound) { + UnaryDenotation results = computeUnary(lambda.body, typeHint.first().withVar(var, varValue)); + for (Value result : results) { + pairs.add(new Pair<>(result, varValue)); + } + } + return new ExplicitBinaryDenotation(pairs); + } catch (UnsupportedOperationException e) { + // The type hint does not have enough information. Probably a floating lambda. + // Just throw an Exception. + } + + } else { + throw new LambdaDCSException(Type.notBinary, "[Unary] Not a binary " + formula); + } + + // Catch-all error + throw new LambdaDCSException(Type.unknown, "[Binary] Cannot handle formula " + formula); + } + +} + +// ============================================================ +// Debug Print +// ============================================================ + +class LambdaDCSCoreLogicWithVerbosity extends LambdaDCSCoreLogic { + + public LambdaDCSCoreLogicWithVerbosity(ContextValue context, Evaluation stats) { + super(context, stats); + } + + @Override + public UnaryDenotation computeUnary(Formula formula, UnaryTypeHint typeHint) { + LogInfo.begin_track("UNARY %s [%s]", formula, typeHint); + try { + UnaryDenotation denotation = super.computeUnary(formula, typeHint); + LogInfo.logs("%s", denotation); + LogInfo.end_track(); + return denotation; + } catch (Exception e) { + LogInfo.end_track(); + throw e; + } + } + + @Override + public BinaryDenotation computeBinary(Formula formula, BinaryTypeHint typeHint) { + LogInfo.begin_track("BINARY %s [%s]", formula, typeHint); + try { + BinaryDenotation denotation = super.computeBinary(formula, typeHint); + LogInfo.logs("%s", denotation); + LogInfo.end_track(); + return denotation; + } catch (Exception e) { + LogInfo.end_track(); + throw e; + } + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/lambdadcs/PredicateBinaryDenotation.java b/src/edu/stanford/nlp/sempre/tables/lambdadcs/PredicateBinaryDenotation.java new file mode 100644 index 0000000..9a23fb0 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/lambdadcs/PredicateBinaryDenotation.java @@ -0,0 +1,93 @@ +package edu.stanford.nlp.sempre.tables.lambdadcs; + +import java.util.*; + +import edu.stanford.nlp.sempre.*; +import fig.basic.*; + +/** + * An implicit binary defined by binary predicates (e.g., fb:film.film.directed_by) + * Represented as a list of binary predicates. + * + * @author ppasupat + */ +public class PredicateBinaryDenotation extends BinaryDenotation { + + protected List values; + protected Set valuesSet; + + public PredicateBinaryDenotation() { + values = Collections.emptyList(); + valuesSet = Collections.emptySet(); + } + + public PredicateBinaryDenotation(Value value) { + values = Collections.singletonList(value); + valuesSet = Collections.singleton(value); + } + + public PredicateBinaryDenotation(Collection values) { + this.values = new ArrayList<>(values); + this.valuesSet = new HashSet<>(values); + } + + @Override + public LispTree toLispTree() { + LispTree tree = LispTree.proto.newList(); + tree.addChild("binary"); + for (Value value : values) + tree.addChild(value.toLispTree()); + return tree; + } + + @Override + public TableValue toTableValue(KnowledgeGraph graph) { + return explicitlyFilterSecond(InfiniteUnaryDenotation.STAR_UNARY, graph).toTableValue(graph); + } + + @Override + public UnaryDenotation joinFirst(UnaryDenotation firsts, KnowledgeGraph graph) { + List seconds = new ArrayList<>(); + for (Value predicate : values) { + seconds.addAll(graph.joinFirst(predicate, firsts)); + } + return new ExplicitUnaryDenotation(seconds); + } + + @Override + public UnaryDenotation joinSecond(UnaryDenotation seconds, KnowledgeGraph graph) { + List firsts = new ArrayList<>(); + for (Value predicate : values) { + firsts.addAll(graph.joinSecond(predicate, seconds)); + } + return new ExplicitUnaryDenotation(firsts); + } + + @Override + public BinaryDenotation reverse() { + List reversedValues = new ArrayList<>(); + for (Value value : values) { + reversedValues.add(KnowledgeGraph.getReversedPredicate(value)); + } + return new PredicateBinaryDenotation(reversedValues); + } + + @Override + public ExplicitBinaryDenotation explicitlyFilterFirst(UnaryDenotation firsts, KnowledgeGraph graph) { + List> filtered = new ArrayList<>(); + for (Value predicate : values) { + filtered.addAll(graph.filterFirst(predicate, firsts)); + } + return new ExplicitBinaryDenotation(filtered); + } + + @Override + public ExplicitBinaryDenotation explicitlyFilterSecond(UnaryDenotation seconds, KnowledgeGraph graph) { + List> filtered = new ArrayList<>(); + for (Value predicate : values) { + filtered.addAll(graph.filterSecond(predicate, seconds)); + } + return new ExplicitBinaryDenotation(filtered); + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/lambdadcs/SpecialBinaryDenotation.java b/src/edu/stanford/nlp/sempre/tables/lambdadcs/SpecialBinaryDenotation.java new file mode 100644 index 0000000..d28d6c1 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/lambdadcs/SpecialBinaryDenotation.java @@ -0,0 +1,83 @@ +package edu.stanford.nlp.sempre.tables.lambdadcs; + +import java.util.*; + +import edu.stanford.nlp.sempre.*; +import edu.stanford.nlp.sempre.tables.lambdadcs.LambdaDCSException.Type; +import fig.basic.LispTree; + +/** + * Special binary denotations. + * + * This includes: + * - comparison (!=, <, <=, >, >=) + * + * TODO(ice): In the future, we want to support + * - independent clause (:) + * - other functions (STRSTARTS, STRENDS) + * + * @author ppasupat + */ +public class SpecialBinaryDenotation extends BinaryDenotation { + + public static final Map COMPARATOR_REVERSE = new HashMap<>(); + static { + COMPARATOR_REVERSE.put("!=", "!="); + COMPARATOR_REVERSE.put("<", ">="); + COMPARATOR_REVERSE.put(">", "<="); + COMPARATOR_REVERSE.put(">=", "<"); + COMPARATOR_REVERSE.put("<=", ">"); + } + + String name; + + SpecialBinaryDenotation(String name) { + this.name = name; + } + + public static SpecialBinaryDenotation create(Value value) { + if (isSpecial(value)) + return new SpecialBinaryDenotation(((NameValue) value).id); + throw new LambdaDCSException(Type.invalidFormula, "Cannot create SpecialBinaryDenotation from %s", value); + } + + public static boolean isSpecial(Value value) { + return value instanceof NameValue && COMPARATOR_REVERSE.containsKey(((NameValue) value).id); + } + + @Override + public LispTree toLispTree() { + return LispTree.proto.newList("binary", name); + } + + @Override + public TableValue toTableValue(KnowledgeGraph graph) { + throw new LambdaDCSException(Type.infiniteList, "Cannot convert to TableValue: %s", toLispTree()); + } + + @Override + public UnaryDenotation joinFirst(UnaryDenotation firsts, KnowledgeGraph graph) { + return InfiniteUnaryDenotation.create(COMPARATOR_REVERSE.get(name), firsts); + } + + @Override + public UnaryDenotation joinSecond(UnaryDenotation seconds, KnowledgeGraph graph) { + return InfiniteUnaryDenotation.create(name, seconds); + } + + @Override + public BinaryDenotation reverse() { + return new SpecialBinaryDenotation(COMPARATOR_REVERSE.get(name)); + } + + @Override + public ExplicitBinaryDenotation explicitlyFilterFirst(UnaryDenotation firsts, KnowledgeGraph graph) { + throw new LambdaDCSException(Type.infiniteList, "Cannot call explicitlyFilter* on SpecialBinaryDenotation"); + } + + @Override + public ExplicitBinaryDenotation explicitlyFilterSecond(UnaryDenotation seconds, KnowledgeGraph graph) { + throw new LambdaDCSException(Type.infiniteList, "Cannot call explicitlyFilter* on SpecialBinaryDenotation"); + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/lambdadcs/TypeHint.java b/src/edu/stanford/nlp/sempre/tables/lambdadcs/TypeHint.java new file mode 100644 index 0000000..1a85d81 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/lambdadcs/TypeHint.java @@ -0,0 +1,85 @@ +package edu.stanford.nlp.sempre.tables.lambdadcs; + +import java.util.*; + +import edu.stanford.nlp.sempre.*; +import edu.stanford.nlp.sempre.tables.lambdadcs.LambdaDCSException.Type; + +/** + * Impose some constraints on the possible denotation of a Formula. + * + * TypeHint is immutable, but one can create a new TypeHint object using the information + * from the original TypeHint. + * + * @author ppasupat + */ +public abstract class TypeHint { + + /** + * Immutable map from variable name to its value. + */ + protected static class VariableMap { + + protected final Map mapping; + + public VariableMap() { + mapping = new HashMap<>(); + } + + public VariableMap plus(String name, Value value) { + VariableMap answer = new VariableMap(); + answer.mapping.putAll(mapping); + answer.mapping.put(name, value); + return answer; + } + + public Value get(String name) { + Value value = mapping.get(name); + if (value == null) throw new LambdaDCSException(Type.invalidFormula, "Unbound variable: " + name); + return value; + } + + @Override + public String toString() { + if (mapping.isEmpty()) return "{}"; + StringBuilder builder = new StringBuilder(); + for (Map.Entry entry : mapping.entrySet()) { + builder.append(", ").append(entry.getKey()).append(": ").append(entry.getValue()); + } + return "{" + builder.append("}").toString().substring(2); + } + } + + public VariableMap variableMap; + + public Value get(String name) { + return variableMap.get(name); + } + + // Unrestricted type hints + + public static final UnaryTypeHint UNRESTRICTED_UNARY = new UnaryTypeHint(null, new VariableMap()); + public static final BinaryTypeHint UNRESTRICTED_BINARY = new BinaryTypeHint(null, null, new VariableMap()); + + /** Create an unrestricted unary from the current variable map */ + public UnaryTypeHint newUnrestrictedUnary() { + return new UnaryTypeHint(null, variableMap); + } + + /** Create an unrestricted binary from the current variable map */ + public BinaryTypeHint newUnrestrictedBinary() { + return new BinaryTypeHint(null, null, variableMap); + } + + // Restricted type hints + + /** Create an restricted unary from the current variable map */ + public UnaryTypeHint newRestrictedUnary(UnaryDenotation upperBound) { + return new UnaryTypeHint(upperBound, variableMap); + } + + /** Create an restricted binary from the current variable map */ + public BinaryTypeHint newRestrictedBinary(UnaryDenotation firstUpperBound, UnaryDenotation secondUpperBound) { + return new BinaryTypeHint(firstUpperBound, secondUpperBound, variableMap); + } +} diff --git a/src/edu/stanford/nlp/sempre/tables/lambdadcs/UnaryDenotation.java b/src/edu/stanford/nlp/sempre/tables/lambdadcs/UnaryDenotation.java new file mode 100644 index 0000000..6521ee7 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/lambdadcs/UnaryDenotation.java @@ -0,0 +1,56 @@ +package edu.stanford.nlp.sempre.tables.lambdadcs; + +import java.util.*; + +import edu.stanford.nlp.sempre.*; +import fig.basic.*; + +/** + * Unary denotation: a set of values. + * + * @author ppasupat + */ +public abstract class UnaryDenotation implements Collection { + + @Override + public String toString() { + return toLispTree().toString(); + } + + public abstract LispTree toLispTree(); + + public abstract ListValue toListValue(KnowledgeGraph graph); + + /** return a unary denotation with all duplicates removed */ + public abstract UnaryDenotation uniqued(); + + /** intersection or union */ + public abstract UnaryDenotation merge(UnaryDenotation that, MergeFormula.Mode mode); + + /** sum, average, min, max, count */ + public abstract UnaryDenotation aggregate(AggregateFormula.Mode mode); + + /** return a new UnaryDenotation where only the values found in |upperBound| are kept */ + public abstract UnaryDenotation filter(UnaryDenotation upperBound); + + // ============================================================ + // Collection interface + // ============================================================ + + @Override public boolean add(Value e) { throw new UnsupportedOperationException("unsupported"); } + @Override public boolean addAll(Collection c) { throw new UnsupportedOperationException("unsupported"); } + @Override public void clear() { throw new UnsupportedOperationException("unsupported"); } + @Override public boolean remove(Object o) { throw new UnsupportedOperationException("unsupported"); } + @Override public boolean removeAll(Collection c) { throw new UnsupportedOperationException("unsupported"); } + @Override public boolean retainAll(Collection c) { throw new UnsupportedOperationException("unsupported"); } + @Override public Object[] toArray() { throw new UnsupportedOperationException("unsupported"); } + @Override public T[] toArray(T[] a) { throw new UnsupportedOperationException("unsupported"); } + + @Override public abstract boolean contains(Object o); + @Override public abstract boolean containsAll(Collection c); + @Override public abstract Iterator iterator(); + @Override public abstract int size(); + @Override public boolean isEmpty() { return size() == 0; } + + +} diff --git a/src/edu/stanford/nlp/sempre/tables/lambdadcs/UnaryTypeHint.java b/src/edu/stanford/nlp/sempre/tables/lambdadcs/UnaryTypeHint.java new file mode 100644 index 0000000..28b20f0 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/lambdadcs/UnaryTypeHint.java @@ -0,0 +1,61 @@ +package edu.stanford.nlp.sempre.tables.lambdadcs; + +import edu.stanford.nlp.sempre.*; + +/** + * Impose that the result is a unary and the set of values should be a subset of |upperBound|. + * + * @author ppasupat + */ +public class UnaryTypeHint extends TypeHint { + + public final UnaryDenotation upperBound; + + // Should only be called within this package + protected UnaryTypeHint(UnaryDenotation u, VariableMap map) { + upperBound = (u == null) ? InfiniteUnaryDenotation.STAR_UNARY : u; + variableMap = map; + } + + @Override + public String toString() { + return "UnaryTypeHint [" + upperBound + "] " + variableMap; + } + + /** + * Keep only the values that appear in this upperBound. + * If a value occurs multiple times, keep the multiplicity. + */ + public UnaryDenotation applyBound(UnaryDenotation denotation) { + return denotation.filter(upperBound); + } + + // ============================================================ + // Derive a new type hint + // ============================================================ + + public UnaryTypeHint withVar(String name, Value value) { + return new UnaryTypeHint(upperBound, variableMap.plus(name, value)); + } + + public BinaryTypeHint asFirstOfBinary() { + return newRestrictedBinary(upperBound, null); + } + + public BinaryTypeHint asFirstOfBinaryWithSecond(UnaryDenotation second) { + return newRestrictedBinary(upperBound, second); + } + + public BinaryTypeHint asSecondOfBinary() { + return newRestrictedBinary(null, upperBound); + } + + public BinaryTypeHint asSecondOfBinaryWithFirst(UnaryDenotation first) { + return newRestrictedBinary(first, upperBound); + } + + public UnaryTypeHint restrict(UnaryDenotation newBound) { + return newRestrictedUnary(upperBound.merge(newBound, MergeFormula.Mode.and)); + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/serialize/DummyParser.java b/src/edu/stanford/nlp/sempre/tables/serialize/DummyParser.java new file mode 100644 index 0000000..a4bb33e --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/serialize/DummyParser.java @@ -0,0 +1,40 @@ +package edu.stanford.nlp.sempre.tables.serialize; + +import java.util.*; + +import edu.stanford.nlp.sempre.*; + +public class DummyParser extends Parser { + + public DummyParser(Spec spec) { + super(spec); + } + + @Override + public ParserState newParserState(Params params, Example ex, boolean computeExpectedCounts) { + return new DummyParserState(this, params, ex, computeExpectedCounts); + } + +} + +class DummyParserState extends ParserState { + + public DummyParserState(Parser parser, Params params, Example ex, boolean computeExpectedCounts) { + super(parser, params, ex, computeExpectedCounts); + } + + @Override + public void infer() { + // Assume that the example already has all derivations. + for (Derivation deriv : ex.predDerivations) { + featurizeAndScoreDerivation(deriv); + deriv.compatibility = parser.valueEvaluator.getCompatibility(ex.targetValue, deriv.value); + predDerivations.add(deriv); + } + if (computeExpectedCounts) { + expectedCounts = new HashMap<>(); + ParserState.computeExpectedCounts(predDerivations, expectedCounts); + } + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/serialize/LoadedExampleList.java b/src/edu/stanford/nlp/sempre/tables/serialize/LoadedExampleList.java new file mode 100644 index 0000000..f820e66 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/serialize/LoadedExampleList.java @@ -0,0 +1,185 @@ +package edu.stanford.nlp.sempre.tables.serialize; + +import java.util.*; + +import edu.stanford.nlp.sempre.*; +import fig.basic.*; + +public class LoadedExampleList implements List { + private final String path; + private String group; + private int size = -1, currentIndex = -1; + private Example currentExample; + + private Iterator trees; + + public LoadedExampleList(String path, int maxSize) { + this.path = path; + LispTree metadata = resetIterator(); + LogInfo.logs("Metadata: %s", metadata); + if (!"metadata".equals(metadata.child(0).value)) + LogInfo.fails("Dataset %s does not have metadata", path); + for (int i = 1; i < metadata.children.size(); i++) { + LispTree arg = metadata.child(i); + String label = arg.child(0).value; + if ("group".equals(label)) { + group = arg.child(1).value; + } else if ("size".equals(label)) { + size = Math.min(Integer.parseInt(arg.child(1).value), maxSize); + } + } + if (group == null) LogInfo.fails("Dataset %s does not specify the group", path); + if (size < 0) LogInfo.fails("Dataset %s does not specify the size", path); + } + + /** + * Reset the LispTree iterator and return the metadata LispTree. + */ + private LispTree resetIterator() { + trees = LispTree.proto.parseFromFile(path); + currentIndex = -1; + return trees.next(); + } + + @Override public int size() { return size; } + @Override public boolean isEmpty() { return size == 0; } + + @Override + public Example get(int index) { + if (index == currentIndex) { + return currentExample; + } else if (index == currentIndex + 1) { + currentExample = readExample(trees.next()); + currentIndex++; + return currentExample; + } else if (index == 0) { + resetIterator(); + return get(index); + } + throw new RuntimeException("Can only get examples " + currentIndex + " or " + (currentIndex + 1)); + } + + private static final Set finalFields = new HashSet<>(Arrays.asList( + "id", "utterance", "targetFormula", "targetValue", "targetValues", "context")); + + private Example readExample(LispTree tree) { + Example.Builder b = new Example.Builder(); + if (!"example".equals(tree.child(0).value)) + LogInfo.fails("Not an example: %s", tree); + + // final fields + for (int i = 1; i < tree.children.size(); i++) { + LispTree arg = tree.child(i); + String label = arg.child(0).value; + if ("id".equals(label)) { + b.setId(arg.child(1).value); + } else if ("utterance".equals(label)) { + b.setUtterance(arg.child(1).value); + } else if ("targetFormula".equals(label)) { + b.setTargetFormula(Formulas.fromLispTree(arg.child(1))); + } else if ("targetValue".equals(label) || "targetValues".equals(label)) { + if (arg.children.size() != 2) + throw new RuntimeException("Expect one target value"); + b.setTargetValue(Values.fromLispTree(arg.child(1))); + } else if ("context".equals(label)) { + b.setContext(new ContextValue(arg)); + } + } + b.setLanguageInfo(new LanguageInfo()); + + Example ex = b.createExample(); + + // other fields + for (int i = 1; i < tree.children.size(); i++) { + LispTree arg = tree.child(i); + String label = arg.child(0).value; + if ("tokens".equals(label)) { + int n = arg.child(1).children.size(); + for (int j = 0; j < n; j++) + ex.languageInfo.tokens.add(arg.child(1).child(j).value); + } else if ("lemmaTokens".equals(label)) { + int n = arg.child(1).children.size(); + for (int j = 0; j < n; j++) + ex.languageInfo.lemmaTokens.add(arg.child(1).child(j).value); + } else if ("posTags".equals(label)) { + int n = arg.child(1).children.size(); + for (int j = 0; j < n; j++) + ex.languageInfo.posTags.add(arg.child(1).child(j).value); + } else if ("nerTags".equals(label)) { + int n = arg.child(1).children.size(); + for (int j = 0; j < n; j++) + ex.languageInfo.nerTags.add(arg.child(1).child(j).value); + } else if ("nerValues".equals(label)) { + int n = arg.child(1).children.size(); + for (int j = 0; j < n; j++) { + String value = arg.child(1).child(j).value; + if ("null".equals(value)) value = null; + ex.languageInfo.nerValues.add(value); + } + } else if ("derivations".equals(label)) { + ex.predDerivations = new ArrayList<>(); + for (int j = 1; j < arg.children.size(); j++) + ex.predDerivations.add(readDerivation(arg.child(j))); + } else if (!finalFields.contains(label)) { + throw new RuntimeException("Invalid example argument: " + arg); + } + } + + return ex; + } + + private Derivation readDerivation(LispTree tree) { + Derivation.Builder b = new Derivation.Builder() + .cat(Rule.rootCat).start(-1).end(-1).localFeatureVector(new FeatureVector()) + .rule(Rule.nullRule).children(new ArrayList()); + if (!"derivation".equals(tree.child(0).value)) + LogInfo.fails("Not a derivation: %s", tree); + + for (int i = 1; i < tree.children.size(); i++) { + LispTree arg = tree.child(i); + String label = arg.child(0).value; + if ("formula".equals(label)) { + b.formula(Formulas.fromLispTree(arg.child(1))); + } else if ("type".equals(label)) { + b.type(SemType.fromLispTree(arg.child(1))); + } else if ("value".equals(label)) { + b.value(Values.fromLispTree(arg.child(1))); + } else if (label.endsWith("values")) { + List values = new ArrayList<>(); + for (int j = 1; j < arg.children.size(); j++) { + values.add(Values.fromLispTree(arg.child(j))); + } + b.value(new ListValue(values)); + } else { + throw new RuntimeException("Invalid derivation argument: " + arg); + } + } + return b.createDerivation(); + } + + @Override + public Iterator iterator() { + // TODO Auto-generated method stub + throw new RuntimeException("Not implemented!"); + } + + @Override public boolean contains(Object o) { throw new RuntimeException("Not implemented!"); } + @Override public Object[] toArray() { throw new RuntimeException("Not implemented!"); } + @Override public T[] toArray(T[] a) { throw new RuntimeException("Not implemented!"); } + @Override public boolean add(Example e) { throw new RuntimeException("Not implemented!"); } + @Override public boolean remove(Object o) { throw new RuntimeException("Not implemented!"); } + @Override public boolean containsAll(Collection c) { throw new RuntimeException("Not implemented!"); } + @Override public boolean addAll(Collection c) { throw new RuntimeException("Not implemented!"); } + @Override public boolean addAll(int index, Collection c) { throw new RuntimeException("Not implemented!"); } + @Override public boolean removeAll(Collection c) { throw new RuntimeException("Not implemented!"); } + @Override public boolean retainAll(Collection c) { throw new RuntimeException("Not implemented!"); } + @Override public void clear() { throw new RuntimeException("Not implemented!"); } + @Override public Example set(int index, Example element) { throw new RuntimeException("Not implemented!"); } + @Override public void add(int index, Example element) { throw new RuntimeException("Not implemented!"); } + @Override public Example remove(int index) { throw new RuntimeException("Not implemented!"); } + @Override public int indexOf(Object o) { throw new RuntimeException("Not implemented!"); } + @Override public int lastIndexOf(Object o) { throw new RuntimeException("Not implemented!"); } + @Override public ListIterator listIterator() { throw new RuntimeException("Not implemented!"); } + @Override public ListIterator listIterator(int index) { throw new RuntimeException("Not implemented!"); } + @Override public List subList(int fromIndex, int toIndex) { throw new RuntimeException("Not implemented!"); } +} diff --git a/src/edu/stanford/nlp/sempre/tables/serialize/SerializedDataset.java b/src/edu/stanford/nlp/sempre/tables/serialize/SerializedDataset.java new file mode 100644 index 0000000..c59b309 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/serialize/SerializedDataset.java @@ -0,0 +1,59 @@ +package edu.stanford.nlp.sempre.tables.serialize; + +import java.io.*; +import java.util.*; + +import edu.stanford.nlp.sempre.*; +import fig.basic.*; + +public class SerializedDataset extends Dataset { + public static class Options { + @Option(gloss = "Base directory for dumped datasets") + public String dumpDir = null; + } + public static Options opts = new Options(); + + private final Map availableGroups = new LinkedHashMap<>(); + + public void read() { + LogInfo.begin_track_printAll("Dataset.read"); + if (Dataset.opts.trainFrac != 1) + LogInfo.warnings("Dataset.opts.trainfrac is ignored!"); + List> pathPairs = new ArrayList<>(); + if (opts.dumpDir != null) { + for (String group : new String[] {"train", "dev", "test"}) { + String filename = opts.dumpDir + "/dumped-" + group + ".gz"; + File file = new File(filename); + if (file.exists()) { + pathPairs.add(new Pair<>(group, filename)); + } + } + } else { + pathPairs.addAll(Dataset.opts.inPaths); + } + for (Pair pathPair : pathPairs) { + File file = new File(pathPair.getSecond()); + if (!file.exists() || file.isDirectory()) { + throw new RuntimeException("Error reading dataset: " + file + " does not exist."); + } + availableGroups.put(pathPair.getFirst(), pathPair.getSecond()); + LogInfo.logs("%s = %s", pathPair.getFirst(), pathPair.getSecond()); + } + LogInfo.end_track(); + } + + private static int getMaxExamplesForGroup(String group) { + int maxExamples = Integer.MAX_VALUE; + for (Pair maxPair : Dataset.opts.maxExamples) + if (maxPair.getFirst().equals(group)) + maxExamples = maxPair.getSecond(); + return maxExamples; + } + + public Set groups() { return availableGroups.keySet(); } + + public List examples(String group) { + return new LoadedExampleList(availableGroups.get(group), getMaxExamplesForGroup(group)); + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/serialize/SerializedDumper.java b/src/edu/stanford/nlp/sempre/tables/serialize/SerializedDumper.java new file mode 100644 index 0000000..f2d6fe0 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/serialize/SerializedDumper.java @@ -0,0 +1,183 @@ +package edu.stanford.nlp.sempre.tables.serialize; + +import java.io.*; +import java.util.*; + +import edu.stanford.nlp.sempre.*; +import edu.stanford.nlp.sempre.tables.TableTypeSystem; +import fig.basic.*; +import fig.exec.*; + +/** + * Dump examples and parsed derivations. + * + * @author ppasupat + */ +public class SerializedDumper implements Runnable { + public static class Options { + @Option(gloss = "do not print obviously bad denotations") + public boolean pruneBadDenotations = true; + } + public static Options opts = new Options(); + + public static void main(String[] args) { + Execution.run(args, "SerializedDumperMain", new SerializedDumper(), Master.getOptionsParser()); + } + + Builder builder; + Dataset dataset; + Params params; + PrintWriter out; + + @Override + public void run() { + builder = new Builder(); + builder.build(); + dataset = new Dataset(); + dataset.read(); + params = new Params(); + for (String group : dataset.groups()) { + String filename = Execution.getFile("dumped-" + group + ".gz"); + out = IOUtils.openOutHard(filename); + processExamples(group, dataset.examples(group)); + out.close(); + LogInfo.logs("Finished dumping to %s", filename); + StopWatchSet.logStats(); + } + } + + private void processExamples(String group, List examples) { + Evaluation evaluation = new Evaluation(); + if (examples.isEmpty()) return; + + final String prefix = "iter=0." + group; + Execution.putOutput("group", group); + LogInfo.begin_track_printAll("Processing %s: %s examples", prefix, examples.size()); + LogInfo.begin_track("Dumping metadata"); + dumpMetadata(group, examples); + LogInfo.end_track(); + LogInfo.begin_track("Examples"); + + for (int e = 0; e < examples.size(); e++) { + Example ex = examples.get(e); + LogInfo.begin_track_printAll("%s: example %s/%s: %s", prefix, e, examples.size(), ex.id); + ex.log(); + Execution.putOutput("example", e); + StopWatchSet.begin("Parser.parse"); + ParserState state = builder.parser.parse(params, ex, false); + StopWatchSet.end(); + out.printf("########## Example %s ##########\n", ex.id); + dumpExample(exampleToLispTree(state)); + LogInfo.logs("Current: %s", ex.evaluation.summary()); + evaluation.add(ex.evaluation); + LogInfo.logs("Cumulative(%s): %s", prefix, evaluation.summary()); + LogInfo.end_track(); + ex.predDerivations.clear(); // To save memory + } + + LogInfo.end_track(); + LogInfo.logs("Stats for %s: %s", prefix, evaluation.summary()); + evaluation.logStats(prefix); + evaluation.putOutput(prefix); + LogInfo.end_track(); + } + + private void dumpMetadata(String group, List examples) { + LispTree tree = LispTree.proto.newList(); + tree.addChild("metadata"); + tree.addChild(LispTree.proto.newList("group", group)); + tree.addChild(LispTree.proto.newList("size", "" + examples.size())); + tree.print(out); + out.println(); + } + + // ============================================================ + // Conversion to LispTree + // ============================================================ + + private LispTree exampleToLispTree(ParserState state) { + LispTree tree = LispTree.proto.newList(); + tree.addChild("example"); + + // Basic information + Example ex = state.ex; + if (ex.id != null) + tree.addChild(LispTree.proto.newList("id", ex.id)); + if (ex.utterance != null) + tree.addChild(LispTree.proto.newList("utterance", ex.utterance)); + if (ex.targetFormula != null) + tree.addChild(LispTree.proto.newList("targetFormula", ex.targetFormula.toLispTree())); + if (ex.targetValue != null) + tree.addChild(LispTree.proto.newList("targetValue", ex.targetValue.toLispTree())); + if (ex.context != null) + tree.addChild(ex.context.toLispTree()); + + // Language info + if (ex.languageInfo != null) { + if (ex.languageInfo.tokens != null) + tree.addChild(LispTree.proto.newList("tokens", LispTree.proto.newList(ex.languageInfo.tokens))); + if (ex.languageInfo.lemmaTokens != null) + tree.addChild(LispTree.proto.newList("lemmaTokens", LispTree.proto.newList(ex.languageInfo.lemmaTokens))); + if (ex.languageInfo.posTags != null) + tree.addChild(LispTree.proto.newList("posTags", LispTree.proto.newList(ex.languageInfo.posTags))); + if (ex.languageInfo.nerTags != null) + tree.addChild(LispTree.proto.newList("nerTags", LispTree.proto.newList(ex.languageInfo.nerTags))); + if (ex.languageInfo.nerValues != null) + tree.addChild(LispTree.proto.newList("nerValues", LispTree.proto.newList(ex.languageInfo.nerValues))); + } + + // Derivations + LispTree derivations = LispTree.proto.newList(); + derivations.addChild("derivations"); + List preds = state.predDerivations; + for (int i = 0; i < preds.size(); i++) { + Derivation deriv = preds.get(i); + if (!isPruned(deriv)) { + derivations.addChild(deriv.toLispTree()); + } + } + tree.addChild(derivations); + + return tree; + } + + // Decide whether we should skip the derivation + private boolean isPruned(Derivation derivation) { + // Check if there is at least one answer + if (derivation.value instanceof ListValue) { + ListValue list = (ListValue) derivation.value; + if (list.values.isEmpty()) return true; + } else return true; + // Check if the type is not row + if (derivation.type.meet(TableTypeSystem.ROW_SEMTYPE).isValid()) return true; + return false; + } + + // ============================================================ + // Dumping LispTree + // ============================================================ + + private void dumpExample(LispTree tree) { + out.println("(example"); + for (LispTree subtree : tree.children.subList(1, tree.children.size())) { + if (!subtree.isLeaf() && "derivations".equals(subtree.children.get(0).value)) { + if (subtree.children.size() == 1) { + out.println(" (derivations)"); + } else { + out.println(" (derivations"); + for (LispTree derivation : subtree.children.subList(1, subtree.children.size())) { + out.write(" "); + derivation.print(Integer.MAX_VALUE, Integer.MAX_VALUE, out); + out.write("\n"); + } + out.println(" )"); + } + } else { + out.write(" "); + subtree.print(Integer.MAX_VALUE, Integer.MAX_VALUE, out); + out.write("\n"); + } + } + out.println(")"); + } +} diff --git a/src/edu/stanford/nlp/sempre/tables/serialize/SerializedLoader.java b/src/edu/stanford/nlp/sempre/tables/serialize/SerializedLoader.java new file mode 100644 index 0000000..dad493f --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/serialize/SerializedLoader.java @@ -0,0 +1,26 @@ +package edu.stanford.nlp.sempre.tables.serialize; + +import edu.stanford.nlp.sempre.*; +import fig.exec.Execution; + +/** + * Load the examples and derivations from SerealizeDumper. + */ +public class SerializedLoader implements Runnable { + + public static void main(String[] args) { + Execution.run(args, "SerializedLoaderMain", new SerializedLoader(), Master.getOptionsParser()); + } + + public void run() { + Builder builder = new Builder(); + builder.build(); + + Dataset dataset = new SerializedDataset(); + dataset.read(); + + Learner learner = new Learner(builder.parser, builder.params, dataset); + learner.learn(); + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/test/CustomExample.java b/src/edu/stanford/nlp/sempre/tables/test/CustomExample.java new file mode 100644 index 0000000..43e4f76 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/test/CustomExample.java @@ -0,0 +1,245 @@ +package edu.stanford.nlp.sempre.tables.test; + +import java.io.*; +import java.util.*; +import java.util.regex.*; + +import edu.stanford.nlp.sempre.CanonicalNames; +import edu.stanford.nlp.sempre.ContextValue; +import edu.stanford.nlp.sempre.Example; +import edu.stanford.nlp.sempre.Formulas; +import edu.stanford.nlp.sempre.LanguageInfo; +import edu.stanford.nlp.sempre.Values; +import edu.stanford.nlp.sempre.tables.StringNormalizationUtils; +import edu.stanford.nlp.sempre.tables.TableTypeSystem; +import fig.basic.*; +import fig.exec.Execution; + +/** + * Custom version of Example. + * + * - Allow additional keys "warning", "error", and "alternativeFormula" + * - Allow shorthand annotation for targetFormula + * + * @author ppasupat + */ +public class CustomExample extends Example { + public static class Options { + // Format: "3-5,10,12-20" + @Option(gloss = "Filter only these examples") public String filterExamples = null; + @Option public boolean allowNoAnnotation = false; + } + public static Options opts = new Options(); + + public List warnings = new ArrayList<>(); + public List errors = new ArrayList<>(); + + public CustomExample(Example ex) { + // Copy everything from ex + super(ex.id, ex.utterance, ex.context, ex.targetFormula, ex.targetValue, ex.languageInfo); + } + + static final Set usefulTags = + new HashSet(Arrays.asList("id", "utterance", "targetFormula", "targetValue", "targetValues", "context")); + + /** + * Convert LispTree to Example with additional tags + */ + public static CustomExample fromLispTree(LispTree tree, String defaultId) { + Builder b = new Builder().setId(defaultId); + + for (int i = 1; i < tree.children.size(); i++) { + LispTree arg = tree.child(i); + String label = arg.child(0).value; + if ("id".equals(label)) { + b.setId(arg.child(1).value); + } else if ("utterance".equals(label)) { + b.setUtterance(arg.child(1).value); + } else if ("targetFormula".equals(label)) { + LispTree canonicalized = canonicalizeFormula(arg.child(1)); + b.setTargetFormula(Formulas.fromLispTree(canonicalized)); + } else if ("targetValue".equals(label) || "targetValues".equals(label)) { + if (arg.children.size() != 2) + throw new RuntimeException("Expect one target value"); + b.setTargetValue(Values.fromLispTree(arg.child(1))); + } else if ("context".equals(label)) { + b.setContext(new ContextValue(arg)); + } + } + b.setLanguageInfo(new LanguageInfo()); + + CustomExample ex = new CustomExample(b.createExample()); + boolean error = false; + + for (int i = 1; i < tree.children.size(); i++) { + LispTree arg = tree.child(i); + String label = arg.child(0).value; + if ("warning".equals(label)) { + ex.warnings.add(arg.child(1).value); + } else if ("error".equals(label)) { + error = true; + ex.errors.add(arg.child(1).value); + } else if ("alternativeFormula".equals(label)) { + // Do nothing for now + } else if (!usefulTags.contains(label)) { + throw new RuntimeException("Invalid example argument: " + arg); + } + } + + // Check formula and error + if (ex.targetFormula == null && !error && !opts.allowNoAnnotation) + throw new RuntimeException("Either error or targetFormula must be present."); + if (ex.targetFormula != null && error) + throw new RuntimeException("Cannot use error when targetFormula is present."); + + return ex; + } + + static final Map formulaMacros; + static { + formulaMacros = new HashMap<>(); + formulaMacros.put("@type", CanonicalNames.TYPE); + formulaMacros.put("@row", TableTypeSystem.ROW_TYPE); + formulaMacros.put("@next", TableTypeSystem.ROW_NEXT_VALUE.id); + formulaMacros.put("@!next", "!" + TableTypeSystem.ROW_NEXT_VALUE.id); + formulaMacros.put("@index", TableTypeSystem.ROW_INDEX_VALUE.id); + formulaMacros.put("@!index", "!" + TableTypeSystem.ROW_INDEX_VALUE.id); + formulaMacros.put("@p.num", TableTypeSystem.CELL_NUMBER_VALUE.id); + formulaMacros.put("@!p.num", "!" + TableTypeSystem.CELL_NUMBER_VALUE.id); + formulaMacros.put("@p.date", TableTypeSystem.CELL_DATE_VALUE.id); + formulaMacros.put("@!p.date", "!" + TableTypeSystem.CELL_DATE_VALUE.id); + //formulaMacros.put("@p.first", TableTypeSystem.CELL_FIRST_VALUE.id); + //formulaMacros.put("@!p.first", "!" + TableTypeSystem.CELL_FIRST_VALUE.id); + formulaMacros.put("@p.second", TableTypeSystem.CELL_SECOND_VALUE.id); + formulaMacros.put("@!p.second", "!" + TableTypeSystem.CELL_SECOND_VALUE.id); + } + + static final Pattern regexProperty = Pattern.compile("c\\.(.*)"); + static final Pattern regexReversedProperty = Pattern.compile("!c\\.(.*)"); + static final Pattern regexEntity = Pattern.compile("c_(.*)\\.(.*)"); + + /** + * Return a new LispTree representing the canonicalized version of the original formula + */ + public static LispTree canonicalizeFormula(LispTree orig) { + if (orig.isLeaf()) { + String value = orig.value; + // 45 --> (number 45) + if (StringNormalizationUtils.parseNumberStrict(value) != null) + return LispTree.proto.newList("number", value); + // value with "@" --> canonicalized name + if (value.contains("@")) { + String canonicalName = formulaMacros.get(value); + if (canonicalName == null) + throw new RuntimeException("Unrecognized macro: " + value); + return LispTree.proto.newLeaf(canonicalName); + } + // c.xxx or c_xxx.yyy --> canonicalized name + Matcher match; + if ((match = regexProperty.matcher(value)).matches()) + return LispTree.proto.newLeaf(TableTypeSystem.getPropertyName(match.group(1))); + if ((match = regexReversedProperty.matcher(value)).matches()) + return LispTree.proto.newLeaf("!" + TableTypeSystem.getPropertyName(match.group(1))); + if ((match = regexEntity.matcher(value)).matches()) + return LispTree.proto.newLeaf(TableTypeSystem.getCellName(match.group(2), match.group(1))); + if (value.contains(".")) + throw new RuntimeException("Unhandled '.': " + value); + return orig; + } else { + LispTree answer = LispTree.proto.newList(); + // Handle special cases + LispTree head = orig.child(0); + if ("date".equals(head.value)) { + for (LispTree child : orig.children) { + answer.addChild(LispTree.proto.newLeaf(child.value)); + } + } else { + for (LispTree child : orig.children) { + answer.addChild(canonicalizeFormula(child)); + } + } + return answer; + } + } + + // ============================================================ + // Read dataset + // ============================================================ + + interface ExampleProcessor { + void run(CustomExample ex); + } + + private static boolean checkFilterExamples(int n) { + if (opts.filterExamples == null || opts.filterExamples.isEmpty()) return true; + for (String range : opts.filterExamples.split(",")) { + String[] tokens = range.split("-"); + if (tokens.length == 1) + if (Integer.parseInt(tokens[0]) == n) + return true; + if (tokens.length == 2) + if (Integer.parseInt(tokens[0]) <= n && Integer.parseInt(tokens[1]) >= n) + return true; + } + return false; + } + + public static List getDataset(List> pathPairs, ExampleProcessor processor) { + LogInfo.begin_track_printAll("Dataset.read"); + Evaluation evaluation = new Evaluation(); + List examples = new ArrayList<>(); + for (Pair pathPair : pathPairs) { + String group = pathPair.getFirst(); + String path = pathPair.getSecond(); + if (!group.equals("train")) continue; + Execution.putOutput("group", group); + + LogInfo.begin_track("Reading %s", path); + Iterator trees = LispTree.proto.parseFromFile(path); + + while (trees.hasNext()) { + // Format: (example (id ...) (utterance ...) (targetFormula ...) (targetValue ...)) + LispTree tree = trees.next(); + if (!checkFilterExamples(examples.size())) { // Skip -- for debugging + examples.add(null); + continue; + } + LogInfo.begin_track("Reading Example %s", examples.size()); + if (tree.children.size() < 2 && !"example".equals(tree.child(0).value)) + throw new RuntimeException("Invalid example: " + tree); + CustomExample ex = null; + Execution.putOutput("example", examples.size()); + try { + ex = CustomExample.fromLispTree(tree, path + ":" + examples.size()); // Specify a default id if it doesn't exist + ex.preprocess(); + ex.log(); + } catch (Exception e) { + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw)); + LogInfo.warnings("Example %s: CONTAINS ERROR! %s:\n%s", ex == null ? ex : ex.id, e, sw); + } + examples.add(ex); + for (String warning : ex.warnings) LogInfo.logs("WARNING: %s", warning); + for (String error : ex.errors) LogInfo.logs("ERROR: %s", error); + if (processor != null) processor.run(ex); + LogInfo.end_track(); + if (ex != null && ex.evaluation != null) { + LogInfo.logs("Current: %s", ex.evaluation.summary()); + evaluation.add(ex.evaluation); + LogInfo.logs("Cumulative(%s): %s", group, evaluation.summary()); + } + } + LogInfo.end_track(); + LogInfo.logs("Stats for %s: %s", group, evaluation.summary()); + evaluation.logStats(group); + evaluation.putOutput(group); + } + LogInfo.end_track(); + return examples; + } + + public static List getDataset(List> pathPairs) { + return getDataset(pathPairs, null); + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/test/DPParserChecker.java b/src/edu/stanford/nlp/sempre/tables/test/DPParserChecker.java new file mode 100644 index 0000000..eebdd04 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/test/DPParserChecker.java @@ -0,0 +1,224 @@ +package edu.stanford.nlp.sempre.tables.test; + +import java.io.*; +import java.util.*; + +import edu.stanford.nlp.sempre.*; +import edu.stanford.nlp.sempre.tables.TableKnowledgeGraph; +import edu.stanford.nlp.sempre.tables.TableTypeSystem; +import edu.stanford.nlp.sempre.tables.lambdadcs.LambdaDCSExecutor; +import edu.stanford.nlp.sempre.tables.test.CustomExample.ExampleProcessor; +import fig.basic.*; +import fig.exec.Execution; + +/** + * Check 2 things: + * - Whether the annotated formula actually executes to the correct denotation. + * - Whether the formula is in the final beam of DPParser. + * + * @author ppasupat + */ +public class DPParserChecker implements Runnable { + public static class Options { + @Option(gloss = "Only check annotated formulas (Don't check DPParser beam)") + public boolean onlyCheckAnnotatedFormulas = false; + } + public static Options opts = new Options(); + + public static void main(String[] args) { + Execution.run(args, "DPParserCheckerMain", new DPParserChecker(), Master.getOptionsParser()); + } + + @Override + public void run() { + DPParserCheckerProcessor processor = new DPParserCheckerProcessor(); + CustomExample.getDataset(Dataset.opts.inPaths, processor); + processor.summarize(); + } + + static class DPParserCheckerProcessor implements ExampleProcessor { + int n = 0, annotated = 0, oracle = 0, beamHasCorrectFormula = 0, beamNoCorrectFormula = 0, noBeam = 0; + final Builder builder; + + public DPParserCheckerProcessor() { + builder = new Builder(); + builder.build(); + } + + @Override + public void run(CustomExample ex) { + n++; + String formulaFlag = "", beamFlag = ""; + if (ex.targetFormula == null) { + formulaFlag = "no"; + } else { + annotated++; + if (isAnnotatedFormulaCorrect(ex)) { + oracle++; + formulaFlag = "good"; + } else { + formulaFlag = "incorrect"; + } + } + if (!opts.onlyCheckAnnotatedFormulas) { + ParserState state = builder.parser.parse(builder.params, ex, false); + LogInfo.logs("utterance: %s", ex.utterance); + LogInfo.logs("targetFormula: %s", ex.targetFormula); + LogInfo.logs("targetValue: %s", ex.targetValue); + if (state.predDerivations.isEmpty()) { + noBeam++; + beamFlag = "no"; + } else if (ex.targetFormula != null && isCorrectFormulaOnBeam(ex, state.predDerivations)) { + beamHasCorrectFormula++; + beamFlag = "yes"; + } else { + beamNoCorrectFormula++; + beamFlag = "reach"; + } + LogInfo.logs("RESULT: %s %s %s", ex.id, formulaFlag, beamFlag); + } + } + + // See if the annotated formula is correct + boolean isAnnotatedFormulaCorrect(CustomExample ex) { + LogInfo.begin_track("isAnnotatedFormulaCorrect: Example %s", ex.id); + StopWatch watch = new StopWatch(); + watch.start(); + LogInfo.logs("TRUE: %s", ex.targetValue); + double result = 0; + try { + LogInfo.logs("Inferred Type: %s", TypeInference.inferType(ex.targetFormula)); + Value pred = builder.executor.execute(ex.targetFormula, ex.context).value; + if (pred instanceof ListValue) + pred = addOriginalStrings((ListValue) pred, (TableKnowledgeGraph) ex.context.graph); + LogInfo.logs("Example %s: %s", ex.id, ex.getTokens()); + LogInfo.logs(" targetFormula: %s", ex.targetFormula); + LogInfo.logs("TRUE: %s", ex.targetValue); + LogInfo.logs("PRED: %s", pred); + result = builder.valueEvaluator.getCompatibility(ex.targetValue, pred); + if (result != 1) { + LogInfo.warnings("TRUE != PRED. %s Either targetValue or targetFormula is wrong.", ex.id); + } + } catch (Exception e) { + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw)); + LogInfo.logs("Example %s: %s", ex.id, ex.getTokens()); + LogInfo.logs(" targetFormula: %s", ex.targetFormula); + LogInfo.logs("TRUE: %s", ex.targetValue); + LogInfo.logs("PRED: ERROR %s\n%s", e, sw); + LogInfo.warnings("TRUE != PRED. %s Something was wrong during the execution.", ex.id); + } + watch.stop(); + LogInfo.logs("Parse Time: %s", watch); + LogInfo.end_track(); + return result == 1; + } + + boolean isCorrectFormulaOnBeam(CustomExample ex, List predDerivations) { + Formula target = canonicalizeFormula(Formulas.betaReduction(ex.targetFormula)); + for (Derivation deriv : predDerivations) { + if (target.equals(canonicalizeFormula(Formulas.betaReduction(deriv.formula)))) return true; + } + return false; + } + + // Canonicalize the following: + // * !___ => (reverse ___) + // * (cell.cell.date (date ___ -1 -1)) => (cell.cell.number (number ___)) + // * (cell.cell.first (number ___)) => (cell.cell.number (number ___)) + // * variable names => x + Formula canonicalizeFormula(Formula formula) { + if (formula instanceof ValueFormula) { + ValueFormula valueF = (ValueFormula) formula; + if (valueF.value instanceof NameValue) { + String id = ((NameValue) valueF.value).id; + if (id.startsWith("!")) { + return new ReverseFormula(new ValueFormula(new NameValue(id.substring(1)))); + } else { + return new ValueFormula(new NameValue(id)); + } + } + return valueF; + } else if (formula instanceof JoinFormula) { + JoinFormula join = (JoinFormula) formula; + if (join.relation instanceof ValueFormula && join.child instanceof ValueFormula) { + Value relation = ((ValueFormula) join.relation).value, + child = ((ValueFormula) join.child).value; + if (relation.equals(TableTypeSystem.CELL_DATE_VALUE) && child instanceof DateValue) { + DateValue date = (DateValue) (((ValueFormula) join.child).value); + if (date.month == -1 && date.day == -1) { + return new JoinFormula(new ValueFormula(TableTypeSystem.CELL_NUMBER_VALUE), + new ValueFormula(new NumberValue(date.year))); + } + } + } + return new JoinFormula(canonicalizeFormula(join.relation), + canonicalizeFormula(join.child)); + } else if (formula instanceof MergeFormula) { + MergeFormula merge = (MergeFormula) formula; + Formula child1 = canonicalizeFormula(merge.child1), + child2 = canonicalizeFormula(merge.child2); + if (child1.hashCode() < child2.hashCode()) + return new MergeFormula(merge.mode, child1, child2); + else + return new MergeFormula(merge.mode, child2, child1); + } else if (formula instanceof AggregateFormula) { + AggregateFormula aggregate = (AggregateFormula) formula; + return new AggregateFormula(aggregate.mode, canonicalizeFormula(aggregate.child)); + } else if (formula instanceof SuperlativeFormula) { + SuperlativeFormula superlative = (SuperlativeFormula) formula; + return new SuperlativeFormula(superlative.mode, superlative.rank, superlative.count, + canonicalizeFormula(superlative.head), canonicalizeFormula(superlative.relation)); + } else if (formula instanceof ArithmeticFormula) { + ArithmeticFormula arithmetic = (ArithmeticFormula) formula; + return new ArithmeticFormula(arithmetic.mode, canonicalizeFormula(arithmetic.child1), + canonicalizeFormula(arithmetic.child2)); + } else if (formula instanceof VariableFormula) { + return new VariableFormula("x"); + } else if (formula instanceof MarkFormula) { + MarkFormula mark = (MarkFormula) formula; + return new MarkFormula("x", canonicalizeFormula(mark.body)); + } else if (formula instanceof ReverseFormula) { + ReverseFormula reverse = (ReverseFormula) formula; + return new ReverseFormula(canonicalizeFormula(reverse.child)); + } else if (formula instanceof LambdaFormula) { + LambdaFormula lambda = (LambdaFormula) formula; + return new LambdaFormula("x", canonicalizeFormula(lambda.body)); + } else { + throw new RuntimeException("Unsupported formula " + formula); + } + } + + // Add original strings to each NameValue in ListValue + ListValue addOriginalStrings(ListValue answers, TableKnowledgeGraph graph) { + List values = new ArrayList<>(); + for (Value value : answers.values) { + if (value instanceof NameValue) { + NameValue name = (NameValue) value; + if (name.description == null) + value = new NameValue(name.id, graph.getOriginalString(((NameValue) value).id)); + } + values.add(value); + } + return new ListValue(values); + } + + public void summarize() { + LogInfo.logs("N = %d | Annotated = %d | Oracle = %d", n, annotated, oracle); + Execution.putOutput("train.oracle.mean", oracle * 1.0 / n); + Execution.putOutput("train.correct.count", n); + if (!opts.onlyCheckAnnotatedFormulas) { + LogInfo.logs("No Beam = %d", noBeam); + LogInfo.logs("Beam has correct formula = %d", beamHasCorrectFormula); + LogInfo.logs("Beam doesn't have correct formula = %d", beamNoCorrectFormula); + Execution.putOutput("train.correct.mean", beamHasCorrectFormula * 1.0 / n); + } + if (builder.executor instanceof LambdaDCSExecutor) { + ((LambdaDCSExecutor) builder.executor).summarize(); + } + StopWatchSet.logStats(); + } + + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/test/LambdaDCSExecutorTest.java b/src/edu/stanford/nlp/sempre/tables/test/LambdaDCSExecutorTest.java new file mode 100644 index 0000000..5897dec --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/test/LambdaDCSExecutorTest.java @@ -0,0 +1,211 @@ +package edu.stanford.nlp.sempre.tables.test; + +import java.util.*; + +import fig.basic.*; +import edu.stanford.nlp.sempre.*; +import edu.stanford.nlp.sempre.tables.TableKnowledgeGraph; +import edu.stanford.nlp.sempre.tables.lambdadcs.LambdaDCSExecutor; + +import org.testng.annotations.Test; + +/** + * Test LambdaDCSExecutor: Execute a Formula on a given context graph. + * + * @author ppasupat + */ +public class LambdaDCSExecutorTest { + + // ============================================================ + // Value Checker (copied from SparqlExectutorTest) + // ============================================================ + + interface ValuesChecker { + void checkValues(List values); + } + + public static ValuesChecker size(final int expectedNumResults) { + return new ValuesChecker() { + public void checkValues(List values) { + if (values.size() != expectedNumResults) + throw new RuntimeException("Expected " + expectedNumResults + " results, but got " + values.size() + ": " + values); + } + }; + } + + public static ValuesChecker sizeAtLeast(final int expectedNumResults) { + return new ValuesChecker() { + public void checkValues(List values) { + if (values.size() < expectedNumResults) + throw new RuntimeException("Expected at least " + expectedNumResults + " results, but got " + values.size() + ": " + values); + } + }; + } + + public static ValuesChecker matches(String expected) { + final Value expectedValue = Value.fromString(expected); + return new ValuesChecker() { + public void checkValues(List values) { + if (values.size() != 1 || !values.get(0).equals(expectedValue)) + throw new RuntimeException("Expected " + expectedValue + ", but got " + values); + } + }; + } + + public static ValuesChecker matchesAll(String... expected) { + final List expectedValues = new ArrayList<>(); + for (String x : expected) expectedValues.add(Value.fromString(x)); + return new ValuesChecker() { + public void checkValues(List values) { + if (values.size() != expectedValues.size() || !expectedValues.containsAll(values)) + throw new RuntimeException("Expected " + new ListValue(expectedValues) + ", but got " + values); + } + }; + } + + public static ValuesChecker regexMatches(final String expectedPattern) { + return new ValuesChecker() { + public void checkValues(List values) { + if (values.size() != 1 || !values.get(0).toString().matches(expectedPattern)) + throw new RuntimeException("Expected " + expectedPattern + ", but got " + values); + } + }; + } + + // ============================================================ + // Processing + // ============================================================ + + LambdaDCSExecutor executor = new LambdaDCSExecutor(); + + protected static void runFormula(LambdaDCSExecutor executor, String formula, KnowledgeGraph graph) { + runFormula(executor, formula, graph, sizeAtLeast(0)); + } + + protected static void runFormula(LambdaDCSExecutor executor, String formula, KnowledgeGraph graph, ValuesChecker checker) { + ContextValue context = new ContextValue(graph); + LambdaDCSExecutor.opts.verbose = 5; + Executor.Response response = executor.execute(Formulas.fromLispTree(LispTree.proto.parseFromString(formula)), context); + LogInfo.logs("RESULT: %s", response.value); + checker.checkValues(((ListValue) response.value).values); + } + + protected static KnowledgeGraph getKnowledgeGraph(String name) { + if ("simple".equals(name)) { + return KnowledgeGraph.fromLispTree(LispTree.proto.parseFromString( + "(graph NaiveKnowledgeGraph ((number 1) (number 2) (number 3)))")); + } else if ("prez".equals(name)) { + return KnowledgeGraph.fromLispTree(LispTree.proto.parseFromString( + "(graph NaiveKnowledgeGraph " + + "(fb:en.barack_obama fb:people.person.place_of_birth fb:en.honolulu)" + + "(fb:en.barack_obama fb:people.person.profession fb:en.politician)" + + "(fb:en.barack_obama fb:people.person.weight_kg (number 82))" + + "(fb:en.george_w_bush fb:people.person.place_of_birth fb:en.new_haven)" + + "(fb:en.george_w_bush fb:people.person.profession fb:en.politician)" + + "(fb:en.george_w_bush fb:people.person.weight_kg (number 86))" + + "(fb:en.bill_clinton fb:people.person.place_of_birth fb:en.hope_arkansas)" + + "(fb:en.bill_clinton fb:people.person.profession fb:en.lawyer)" + + "(fb:en.bill_clinton fb:people.person.profession fb:en.politician)" + + "(fb:en.bill_clinton fb:people.person.weight_kg (number 100))" + + "(fb:en.nicole_kidman fb:people.person.place_of_birth fb:en.honolulu)" + + "(fb:en.nicole_kidman fb:people.person.profession fb:en.actor)" + + "(fb:en.nicole_kidman fb:people.person.weight_kg (number 58))" + + "(fb:en.morgan_freeman fb:people.person.place_of_birth fb:en.memphis)" + + "(fb:en.morgan_freeman fb:people.person.profession fb:en.actor)" + + "(fb:en.morgan_freeman fb:people.person.weight_kg (number 91))" + + "(fb:en.ronald_reagan fb:people.person.place_of_birth fb:en.tampico)" + + "(fb:en.ronald_reagan fb:people.person.profession fb:en.politician)" + + "(fb:en.ronald_reagan fb:people.person.weight_kg (number 82))" + + "(fb:en.honolulu fb:location.location.containedby fb:en.hawaii)" + + "(fb:en.memphis fb:location.location.containedby fb:en.tennessee)" + + "(fb:en.new_haven fb:location.location.containedby fb:en.connecticut)" + + "(fb:en.hope_arkansas fb:location.location.containedby fb:en.arkansas)" + + "(fb:en.tampico fb:location.location.containedby fb:en.illinois)" + + ")")); + } else if ("csv".equals(name)) { + return TableKnowledgeGraph.fromFilename("tables/toy-examples/random/nikos_machlas.csv"); + } + throw new RuntimeException("Unknown graph name: " + name); + } + + // ============================================================ + // Actual Tests + // ============================================================ + + @Test(groups = "lambdaSimple") public void lambdaOnGraphDummyTest() { + KnowledgeGraph graph = getKnowledgeGraph("simple"); + runFormula(executor, "(number 3)", graph, matches("(number 3)")); + } + + @Test(groups = "lambdaPrez") public void lambdaOnGraphBasicTest() { + KnowledgeGraph graph = getKnowledgeGraph("prez"); + runFormula(executor, "(fb:people.person.place_of_birth fb:en.honolulu)", + graph, matchesAll("(name fb:en.barack_obama)", "(name fb:en.nicole_kidman)")); + runFormula(executor, "(!fb:people.person.place_of_birth (fb:people.person.place_of_birth fb:en.honolulu))", + graph, matches("(name fb:en.honolulu)")); + runFormula(executor, "(!fb:people.person.place_of_birth fb:en.barack_obama)", + graph, matches("(name fb:en.honolulu)")); + runFormula(executor, "(and (fb:people.person.place_of_birth fb:en.honolulu) (fb:people.person.profession fb:en.actor))", + graph, matches("(name fb:en.nicole_kidman)")); + runFormula(executor, "(or (fb:people.person.place_of_birth fb:en.honolulu) (fb:people.person.profession fb:en.actor))", + graph, matchesAll("(name fb:en.barack_obama)", "(name fb:en.nicole_kidman)", "(name fb:en.morgan_freeman)")); + runFormula(executor, "(count (or (fb:people.person.place_of_birth fb:en.honolulu) (fb:people.person.profession fb:en.actor)))", + graph, matches("(number 3)")); + } + + @Test(groups = "lambdaPrez") public void lambdaOnGraphInfiniteTest() { + KnowledgeGraph graph = getKnowledgeGraph("prez"); + runFormula(executor, "(!fb:people.person.place_of_birth *)", + graph, size(5)); + runFormula(executor, "(and * (fb:people.person.place_of_birth fb:en.honolulu))", + graph, matchesAll("(name fb:en.barack_obama)", "(name fb:en.nicole_kidman)")); + runFormula(executor, "(max (!fb:people.person.weight_kg *))", + graph, matches("(number 100)")); + runFormula(executor, "(sum (!fb:people.person.weight_kg *))", + graph, matches("(number 499)")); + runFormula(executor, "(argmax 1 1 * fb:people.person.weight_kg)", + graph, matches("(name fb:en.bill_clinton)")); + runFormula(executor, "(fb:people.person.weight_kg (> (number 95)))", + graph, matches("(name fb:en.bill_clinton)")); + runFormula(executor, "(fb:people.person.weight_kg (!= (!fb:people.person.weight_kg fb:en.barack_obama)))", + graph, size(4)); + runFormula(executor, "(fb:people.person.weight_kg ((reverse >) (number 82)))", + graph, matchesAll("(name fb:en.barack_obama)", "(name fb:en.ronald_reagan)", "(name fb:en.nicole_kidman)")); + runFormula(executor, "(fb:people.person.weight_kg (and (< (number 100)) (> (number 90))))", + graph, matches("(name fb:en.morgan_freeman)")); + } + + @Test(groups = "lambdaPrez") public void lambdaOnGraphLambdaTest() { + KnowledgeGraph graph = getKnowledgeGraph("prez"); + runFormula(executor, "((lambda x (fb:people.person.place_of_birth (var x))) fb:en.honolulu)", + graph, matchesAll("(name fb:en.barack_obama)", "(name fb:en.nicole_kidman)")); + runFormula(executor, "(argmax 1 1 (fb:location.location.containedby *) (reverse (lambda x (count (fb:people.person.place_of_birth (var x))))))", + graph, matches("(name fb:en.honolulu)")); + runFormula(executor, "(and (!fb:people.person.place_of_birth *) ((reverse (lambda x (fb:people.person.place_of_birth (var x)))) fb:en.barack_obama))", + graph, matches("(name fb:en.honolulu)")); + runFormula(executor, "(and ((reverse (lambda x (fb:people.person.place_of_birth (var x)))) fb:en.barack_obama) (!fb:people.person.place_of_birth *))", + graph, matches("(name fb:en.honolulu)")); + } + + @Test(groups = "lambdaCSV") public void lambdaOnGraphCSVTest() { + KnowledgeGraph graph = getKnowledgeGraph("csv"); + runFormula(executor, "(number 3)", graph, matches("(number 3)")); + runFormula(executor, "(!fb:row.row.score (fb:row.row.opponent fb:cell_opponent.austria))", + graph, matches("(name fb:cell_score.1_2)")); + runFormula(executor, "(count (fb:row.row.result fb:cell_result.win))", + graph, matches("(number 16)")); + // Depending on tie-breaking, one of these will be correct + try { + // Return all top ties + runFormula(executor, "(argmax 1 1 (!fb:row.row.opponent (fb:type.object.type fb:type.row)) " + + "(reverse (lambda x (count (fb:row.row.opponent (var x))))))", + graph, size(5)); + } catch (Exception e) { + // Return only one item + runFormula(executor, "(count (fb:row.row.opponent (argmax 1 1 (!fb:row.row.opponent (fb:type.object.type fb:type.row)) " + + "(reverse (lambda x (count (fb:row.row.opponent (var x))))))))", + graph, matches("(number 2)")); + } + } + +} diff --git a/src/edu/stanford/nlp/sempre/tables/test/TableStatsComputer.java b/src/edu/stanford/nlp/sempre/tables/test/TableStatsComputer.java new file mode 100644 index 0000000..9557c49 --- /dev/null +++ b/src/edu/stanford/nlp/sempre/tables/test/TableStatsComputer.java @@ -0,0 +1,134 @@ +package edu.stanford.nlp.sempre.tables.test; + +import java.io.*; +import java.util.*; + +import edu.stanford.nlp.sempre.*; +import edu.stanford.nlp.sempre.FuzzyMatchFn.FuzzyMatchFnMode; +import edu.stanford.nlp.sempre.MergeFormula.Mode; +import edu.stanford.nlp.sempre.tables.StringNormalizationUtils; +import edu.stanford.nlp.sempre.tables.TableKnowledgeGraph; +import edu.stanford.nlp.sempre.tables.TableTypeSystem; +import edu.stanford.nlp.sempre.tables.test.CustomExample.ExampleProcessor; +import fig.basic.*; +import fig.exec.*; + +public class TableStatsComputer implements Runnable { + public static class Options { + @Option(gloss = "Maximum string length to consider") + public int statsMaxStringLength = 70; + } + public static Options opts = new Options(); + + public static void main(String[] args) { + Execution.run(args, "TableStatsComputerMain", new TableStatsComputer(), Master.getOptionsParser()); + } + + @Override + public void run() { + TableStatsComputerProcessor processor = new TableStatsComputerProcessor(); + CustomExample.getDataset(Dataset.opts.inPaths, processor); + processor.analyzeTables(); + processor.summarize(); + } + + static class TableStatsComputerProcessor implements ExampleProcessor { + Evaluation evaluation = new Evaluation(); + Map tables = new HashMap<>(); + Map columnStrings = new HashMap<>(), cellStrings = new HashMap<>(); + Builder builder; + + public TableStatsComputerProcessor() { + builder = new Builder(); + builder.build(); + } + + @Override + public void run(CustomExample ex) { + TableKnowledgeGraph graph = (TableKnowledgeGraph) ex.context.graph; + MapUtils.incr(tables, graph); + // Answer type + // For convenience, just use the first answer from the list + Value value = ((ListValue) ex.targetValue).values.get(0); + evaluation.add("value-number", value instanceof NumberValue); + evaluation.add("value-date", value instanceof DateValue); + evaluation.add("value-text", value instanceof DescriptionValue); + evaluation.add("value-partial-number", + value instanceof DescriptionValue && ((DescriptionValue) value).value.matches(".*[0-9].*")); + checkInTable(value, ex.context); + } + + private void checkInTable(Value value, ContextValue context) { + boolean inTable = false; + if (value instanceof DescriptionValue) { + Collection formulas = context.graph.getFuzzyMatchedFormulas(((DescriptionValue) value).value, FuzzyMatchFnMode.ENTITY); + inTable = !formulas.isEmpty(); + evaluation.add("value-text-in-table", inTable); + LogInfo.logs("value: text in table"); + } else if (value instanceof NumberValue) { + // (and (@type @cell) (@p.num ___)) + Formula formula = new MergeFormula(Mode.and, + new JoinFormula(Formula.fromString(CanonicalNames.TYPE), Formula.fromString(TableTypeSystem.CELL_GENERIC_TYPE)), + new JoinFormula(Formula.fromString(TableTypeSystem.CELL_NUMBER_VALUE.id), new ValueFormula(value))); + Value result = builder.executor.execute(formula, context).value; + inTable = result instanceof ListValue && !((ListValue) result).values.isEmpty(); + evaluation.add("value-number-in-table", inTable); + LogInfo.logs("value: number in table"); + } else if (value instanceof DateValue) { + // (and (@type @cell) (@p.num ___)) + Formula formula = new MergeFormula(Mode.and, + new JoinFormula(Formula.fromString(CanonicalNames.TYPE), Formula.fromString(TableTypeSystem.CELL_GENERIC_TYPE)), + new JoinFormula(Formula.fromString(TableTypeSystem.CELL_DATE_VALUE.id), new ValueFormula(value))); + Value result = builder.executor.execute(formula, context).value; + inTable = result instanceof ListValue && !((ListValue) result).values.isEmpty(); + evaluation.add("value-number-in-table", inTable); + LogInfo.logs("value: date in table"); + } + evaluation.add("value-any-in-table", inTable); + } + + public void analyzeTables() { + for (Map.Entry entry : tables.entrySet()) { + TableKnowledgeGraph table = entry.getKey(); + evaluation.add("count", entry.getValue()); + table.populateStats(evaluation); + for (String columnString : table.getAllColumnStrings()) + addIfOK(columnString, columnStrings); + for (String cellString : table.getAllCellStrings()) + addIfOK(cellString, cellStrings); + } + } + + void addIfOK(String x, Map collection) { + x = StringNormalizationUtils.characterNormalize(x).toLowerCase(); + if (x.length() <= TableStatsComputer.opts.statsMaxStringLength) + MapUtils.incr(collection, x); + } + + public void summarize() { + for (Map.Entry entry : columnStrings.entrySet()) { + evaluation.add("column-strings", entry.getKey(), entry.getValue()); + } + for (Map.Entry entry : cellStrings.entrySet()) { + evaluation.add("cell-strings", entry.getKey(), entry.getValue()); + } + evaluation.logStats("tables"); + dumpCollection(columnStrings, "columns"); + dumpCollection(cellStrings, "cells"); + } + + void dumpCollection(Map collection, String filename) { + List> entries = new ArrayList<>(collection.entrySet()); + Collections.sort(entries, new ValueComparator(true)); + String path = Execution.getFile(filename); + LogInfo.begin_track("Writing to %s (%d entries)", path, entries.size()); + PrintWriter out = IOUtils.openOutHard(path); + for (Map.Entry entry : entries) { + out.printf("%6d : %s\n", entry.getValue(), entry.getKey()); + } + out.close(); + LogInfo.end_track(); + } + + } +} diff --git a/src/edu/stanford/nlp/sempre/test/GrammarValidityTest.java b/src/edu/stanford/nlp/sempre/test/GrammarValidityTest.java index 66399b1..715d3aa 100644 --- a/src/edu/stanford/nlp/sempre/test/GrammarValidityTest.java +++ b/src/edu/stanford/nlp/sempre/test/GrammarValidityTest.java @@ -18,7 +18,7 @@ import static org.testng.AssertJUnit.assertEquals; */ public class GrammarValidityTest { - private String[] dataPaths = new String[] {"data/", "freebase/", "tables/", "regex/"}; + private String[] dataPaths = new String[] {"data/", "freebase/", "tables/", "regex/", "overnight/"}; @Test(groups = {"grammar"}) public void readGrammars() { diff --git a/src/edu/stanford/nlp/sempre/test/JsonTest.java b/src/edu/stanford/nlp/sempre/test/JsonTest.java index a30e307..2d2c602 100644 --- a/src/edu/stanford/nlp/sempre/test/JsonTest.java +++ b/src/edu/stanford/nlp/sempre/test/JsonTest.java @@ -53,14 +53,14 @@ public class JsonTest { LogInfo.log(S(ex)); assert exampleEquals(ex, D(S(ex), Example.class)); - ex.preprocess(new SimpleAnalyzer()); + ex.preprocess(); LogInfo.log(S(ex)); assert ex.languageInfo != null; assert !ex.languageInfo.tokens.isEmpty(); assert exampleEquals(ex, D(S(ex), Example.class)); ex = TestUtils.makeSimpleExample("1 2 3"); - ex.preprocess(new SimpleAnalyzer()); + ex.preprocess(); makeSimpleBeamParser().parse(new Params(), ex, true); String there = S(ex); Example back = D(there, Example.class); diff --git a/src/edu/stanford/nlp/sempre/test/L1RegularizationTest.java b/src/edu/stanford/nlp/sempre/test/L1RegularizationTest.java new file mode 100644 index 0000000..3fd124d --- /dev/null +++ b/src/edu/stanford/nlp/sempre/test/L1RegularizationTest.java @@ -0,0 +1,183 @@ +package edu.stanford.nlp.sempre.test; + +import java.util.*; + +import org.testng.annotations.Test; + +import static org.testng.AssertJUnit.*; +import edu.stanford.nlp.sempre.Params; + +/** + * Test lazy L1 regularization. + * + * @author ppasupat + */ +public class L1RegularizationTest { + + private static final double EPSILON = 1e-3; + + class Options { + public double initStepSize = 1.0; + public String l1Reg = "none"; + public double l1RegCoeff = 0; + public Options initStepSize(double x) { initStepSize = x; return this; } + public Options l1Reg(String x) { l1Reg = x; return this; } + public Options l1RegCoeff(double x) { l1RegCoeff = x; return this; } + } + + private Options originalOptions = null; + + private void saveOptions() { + originalOptions = new Options() + .initStepSize(Params.opts.initStepSize) + .l1Reg(Params.opts.l1Reg) + .l1RegCoeff(Params.opts.l1RegCoeff); + } + + private void loadOptions(Options options) { + Params.opts.initStepSize = options.initStepSize; + Params.opts.l1Reg = options.l1Reg; + Params.opts.l1RegCoeff = options.l1RegCoeff; + } + + private Map constructGradient(double a, double b, double c, double d) { + Map gradient = new HashMap<>(); + if (a != 0) gradient.put("a", a); + if (b != 0) gradient.put("b", b); + if (c != 0) gradient.put("c", c); + if (d != 0) gradient.put("d", d); + return gradient; + } + + @Test + public void zeroLazyL1Test() { + saveOptions(); + { + loadOptions(new Options().l1Reg("none").l1RegCoeff(0)); + Params params = new Params(); + assertEquals(0.0, params.getWeight("a"), EPSILON); + assertEquals(0.0, params.getWeight("b"), EPSILON); + params.update(constructGradient(1.0, 0, 0, 0)); + assertEquals(1.0 / Math.sqrt(1), params.getWeight("a"), EPSILON); + assertEquals(0.0, params.getWeight("b"), EPSILON); + params.update(constructGradient(1.0, 0, 0, 0)); + assertEquals(1.0 / Math.sqrt(1) + 1.0 / Math.sqrt(2), params.getWeight("a"), EPSILON); + assertEquals(0.0, params.getWeight("b"), EPSILON); + params.update(constructGradient(0.0, -2.0, 0, 0)); + assertEquals(1.0 / Math.sqrt(1) + 1.0 / Math.sqrt(2), params.getWeight("a"), EPSILON); + assertEquals(-2.0 / Math.sqrt(4), params.getWeight("b"), EPSILON); + params.update(constructGradient(1.0, 2.0, 0, 0)); + assertEquals(1.0 / Math.sqrt(1) + 1.0 / Math.sqrt(2) + 1.0 / Math.sqrt(3), params.getWeight("a"), EPSILON); + assertEquals(-2.0 / Math.sqrt(4) + 2.0 / Math.sqrt(8), params.getWeight("b"), EPSILON); + } + { + loadOptions(new Options().l1Reg("nonlazy").l1RegCoeff(0)); + Params params = new Params(); + assertEquals(0.0, params.getWeight("a"), EPSILON); + assertEquals(0.0, params.getWeight("b"), EPSILON); + params.update(constructGradient(1.0, 0, 0, 0)); + // NONLAZY will give a different result as the denominator of the AdaGrad update is incremented by 1 + assertEquals(1.0 / Math.sqrt(2), params.getWeight("a"), EPSILON); + assertEquals(0.0, params.getWeight("b"), EPSILON); + params.update(constructGradient(1.0, 0, 0, 0)); + assertEquals(1.0 / Math.sqrt(2) + 1.0 / Math.sqrt(3), params.getWeight("a"), EPSILON); + assertEquals(0.0, params.getWeight("b"), EPSILON); + params.update(constructGradient(0.0, -2.0, 0, 0)); + assertEquals(1.0 / Math.sqrt(2) + 1.0 / Math.sqrt(3), params.getWeight("a"), EPSILON); + assertEquals(-2.0 / Math.sqrt(5), params.getWeight("b"), EPSILON); + params.update(constructGradient(1.0, 2.0, 0, 0)); + assertEquals(1.0 / Math.sqrt(2) + 1.0 / Math.sqrt(3) + 1.0 / Math.sqrt(4), params.getWeight("a"), EPSILON); + assertEquals(-2.0 / Math.sqrt(5) + 2.0 / Math.sqrt(9), params.getWeight("b"), EPSILON); + } + { + loadOptions(new Options().l1Reg("lazy").l1RegCoeff(0)); + Params params = new Params(); + assertEquals(0.0, params.getWeight("a"), EPSILON); + assertEquals(0.0, params.getWeight("b"), EPSILON); + params.update(constructGradient(1.0, 0, 0, 0)); + // LAZY will give the same result as NONLAZY + assertEquals(1.0 / Math.sqrt(2), params.getWeight("a"), EPSILON); + assertEquals(0.0, params.getWeight("b"), EPSILON); + params.update(constructGradient(1.0, 0, 0, 0)); + assertEquals(1.0 / Math.sqrt(2) + 1.0 / Math.sqrt(3), params.getWeight("a"), EPSILON); + assertEquals(0.0, params.getWeight("b"), EPSILON); + params.update(constructGradient(0.0, -2.0, 0, 0)); + assertEquals(1.0 / Math.sqrt(2) + 1.0 / Math.sqrt(3), params.getWeight("a"), EPSILON); + assertEquals(-2.0 / Math.sqrt(5), params.getWeight("b"), EPSILON); + params.update(constructGradient(1.0, 2.0, 0, 0)); + assertEquals(1.0 / Math.sqrt(2) + 1.0 / Math.sqrt(3) + 1.0 / Math.sqrt(4), params.getWeight("a"), EPSILON); + assertEquals(-2.0 / Math.sqrt(5) + 2.0 / Math.sqrt(9), params.getWeight("b"), EPSILON); + } + loadOptions(originalOptions); + } + + @Test + public void nonZeroLazyL1Test() { + saveOptions(); + { + loadOptions(new Options().l1Reg("nonlazy").l1RegCoeff(1.0)); + Params params = new Params(); + assertEquals(0.0, params.getWeight("a"), EPSILON); + assertEquals(0.0, params.getWeight("b"), EPSILON); + params.update(constructGradient(2.0, 0, -3.14, 0)); + assertEquals(1.0 / Math.sqrt(5), params.getWeight("a"), EPSILON); + assertEquals(0.0, params.getWeight("b"), EPSILON); + params.update(constructGradient(1.0, 0, 0, 0)); + assertEquals(1.0 / Math.sqrt(5), params.getWeight("a"), EPSILON); + assertEquals(0.0, params.getWeight("b"), EPSILON); + params.update(constructGradient(0.0, -2.0, 0, 0)); + assertEquals(1.0 / Math.sqrt(5) - 1.0 / Math.sqrt(6), params.getWeight("a"), EPSILON); + assertEquals(-1.0 / Math.sqrt(5), params.getWeight("b"), EPSILON); + params.update(constructGradient(1.0, 2.0, 0, 0)); + assertEquals(1.0 / Math.sqrt(5) - 1.0 / Math.sqrt(6), params.getWeight("a"), EPSILON); + assertEquals(0.0, params.getWeight("b"), EPSILON); + params.update(constructGradient(0.0, 3.0, 0, 0)); + assertEquals(0.0, params.getWeight("a"), EPSILON); + assertEquals(2.0 / Math.sqrt(18), params.getWeight("b"), EPSILON); + params.update(constructGradient(0.0, 0.0, 0, 0)); + assertEquals(0.0, params.getWeight("a"), EPSILON); + assertEquals(1.0 / Math.sqrt(18), params.getWeight("b"), EPSILON); + params.update(constructGradient(-5.0, 0.0, 1.0, 0)); + assertEquals(-4.0 / Math.sqrt(32), params.getWeight("a"), EPSILON); + assertEquals(0.0, params.getWeight("b"), EPSILON); + params.update(constructGradient(0.0, 0.0, -1.0, 0)); + assertEquals(-3.0 / Math.sqrt(32), params.getWeight("a"), EPSILON); + assertEquals(0.0, params.getWeight("b"), EPSILON); + assertEquals(0.0, params.getWeight("c"), EPSILON); + } + // LAZY: Randomly access the features in between. + Random r = new Random(42); + for (double t = 1; t > 0; t -= 0.02) { + loadOptions(new Options().l1Reg("lazy").l1RegCoeff(1.0)); + Params params = new Params(); + if (r.nextDouble() < t) assertEquals(0.0, params.getWeight("a"), EPSILON); + if (r.nextDouble() < t) assertEquals(0.0, params.getWeight("b"), EPSILON); + params.update(constructGradient(2.0, 0, -3.14, 0)); + if (r.nextDouble() < t) assertEquals(1.0 / Math.sqrt(5), params.getWeight("a"), EPSILON); + if (r.nextDouble() < t) assertEquals(0.0, params.getWeight("b"), EPSILON); + params.update(constructGradient(1.0, 0, 0, 0)); + if (r.nextDouble() < t) assertEquals(1.0 / Math.sqrt(5), params.getWeight("a"), EPSILON); + if (r.nextDouble() < t) assertEquals(0.0, params.getWeight("b"), EPSILON); + params.update(constructGradient(0.0, -2.0, 0, 0)); + if (r.nextDouble() < t) assertEquals(1.0 / Math.sqrt(5) - 1.0 / Math.sqrt(6), params.getWeight("a"), EPSILON); + if (r.nextDouble() < t) assertEquals(-1.0 / Math.sqrt(5), params.getWeight("b"), EPSILON); + params.update(constructGradient(1.0, 2.0, 0, 0)); + if (r.nextDouble() < t) assertEquals(1.0 / Math.sqrt(5) - 1.0 / Math.sqrt(6), params.getWeight("a"), EPSILON); + if (r.nextDouble() < t) assertEquals(0.0, params.getWeight("b"), EPSILON); + params.update(constructGradient(0.0, 3.0, 0, 0)); + if (r.nextDouble() < t) assertEquals(0.0, params.getWeight("a"), EPSILON); + if (r.nextDouble() < t) assertEquals(2.0 / Math.sqrt(18), params.getWeight("b"), EPSILON); + params.update(constructGradient(0.0, 0.0, 0, 0)); + if (r.nextDouble() < t) assertEquals(0.0, params.getWeight("a"), EPSILON); + if (r.nextDouble() < t) assertEquals(1.0 / Math.sqrt(18), params.getWeight("b"), EPSILON); + params.update(constructGradient(-5.0, 0.0, 1.0, 0)); + if (r.nextDouble() < t) assertEquals(-4.0 / Math.sqrt(32), params.getWeight("a"), EPSILON); + if (r.nextDouble() < t) assertEquals(0.0, params.getWeight("b"), EPSILON); + params.update(constructGradient(0.0, 0.0, -1.0, 0)); + if (r.nextDouble() < t) assertEquals(-3.0 / Math.sqrt(32), params.getWeight("a"), EPSILON); + if (r.nextDouble() < t) assertEquals(0.0, params.getWeight("b"), EPSILON); + if (r.nextDouble() < t) assertEquals(0.0, params.getWeight("c"), EPSILON); + } + loadOptions(originalOptions); + } +} diff --git a/src/edu/stanford/nlp/sempre/test/ParserTest.java b/src/edu/stanford/nlp/sempre/test/ParserTest.java index 8dc7884..400f3d6 100644 --- a/src/edu/stanford/nlp/sempre/test/ParserTest.java +++ b/src/edu/stanford/nlp/sempre/test/ParserTest.java @@ -35,7 +35,7 @@ public class ParserTest { } private static void checkNumDerivations(Parser parser, Params params, String utterance, String targetValue, int numExpected) { - Parser.opts.verbose = 1; + Parser.opts.verbose = 5; Example ex = TestUtils.makeSimpleExample(utterance, targetValue != null ? Value.fromString(targetValue) : null); ParserState state = parser.parse(params, ex, targetValue != null); diff --git a/src/edu/stanford/nlp/sempre/test/TestUtils.java b/src/edu/stanford/nlp/sempre/test/TestUtils.java index cb44050..917f6be 100644 --- a/src/edu/stanford/nlp/sempre/test/TestUtils.java +++ b/src/edu/stanford/nlp/sempre/test/TestUtils.java @@ -75,7 +75,7 @@ public final class TestUtils { .setUtterance(utterance) .setTargetValue(targetValue) .createExample(); - ex.preprocess(LanguageAnalyzer.getSingleton()); + ex.preprocess(); return ex; } } diff --git a/tables/README.md b/tables/README.md new file mode 100644 index 0000000..5393336 --- /dev/null +++ b/tables/README.md @@ -0,0 +1 @@ +This directory contains all the files relevant to semantic parsing on tables. diff --git a/tables/grammars/combined.grammar b/tables/grammars/combined.grammar new file mode 100644 index 0000000..7381cd5 --- /dev/null +++ b/tables/grammars/combined.grammar @@ -0,0 +1,311 @@ +# (Combined) 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 +# (@p.num (number 42)) / (@p.date (date 2012 12 21)) +(for @property (@p.num @p.date) + (rule $Property (nothing) (ConstantFn @property)) +) +(when second + (rule $Property (nothing) (ConstantFn @p.second)) +) + +################################################################ +# Composition + +# $RowSet = list of rows (type = fb:row.row) +# $ValueSet = list of values (NameValue or other primitives) +# $SingleValue = number representing count (may not make sense to compose it with other things) + +################################ +# Generic RowSet +# (@type @row) +(rule $RowSet (nothing) (ConstantFn (@type @row))) + +################################ +# Anchored ValueSet +# c_name.barack_obama +(rule $ValueSet ($Entity) (IdentityFn)) + +# [TAG] alternative: "X or Y" questions +(when alternative + # (or c_name.barack_obama c_name.bill_clinton) + (rule $ValueSet ($Entity $Entity) + (lambda e1 (lambda e2 (or (var e1) (var e2)))) + ) +) + +# [TAG] t-alternative +(when t-alternative + (rule $AnchoredOr ($LEMMA_TOKEN) (FilterTokenFn lemma and or) (anchored 1)) + (rule $ValueSet ($AnchoredOr $Entity $Entity) + (lambda a (lambda e1 (lambda e2 (or (var e1) (var e2))))) + ) +) + +################################ +# Join +# (c.name c_name.barack_obama) +(rule $RowSet ($Binary $ValueSet) (lambda b (lambda v ((var b) (var v))))) +# (!c.name (...[rows]...)) --> c_name.barack_obama +(rule $ValueSet ($Binary $RowSet) (lambda b (lambda r ((@R (var b)) (var r))))) +# (c.height (@p.num (number 180))) +(rule $RowSet ($Binary $Property $ValueSet) + (lambda b (lambda p (lambda v ((var b) ((var p) (var v)))))) +) +# (@!p.num (!c.height (...[rows]...))) --> (number 180) +(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 + # (@next (...[rows]...)) --> ...[rows]... + (for @movement (@next @!next) + # HACK: Added 'nothing' to prevent unary cycle + (rule $RowSet (nothing $RowSet) (lambda r (@movement (var r)))) + ) +) + +# [TAG] t-movement +(when t-movement + (rule $AnchoredMovement ($LEMMA_TOKEN) (FilterTokenFn lemma next previous after before above below) (anchored 1)) + (for @movement (@next @!next) + (rule $RowSet ($AnchoredMovement $RowSet) (lambda m (lambda r (@movement (var r))))) + ) +) + +# [TAG] comparison: "at least" / "more than" +(when comparison + # (c.height (@p.num (>= (number 180)))) + (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 + # (c.name (!= c_name.barack_obama)) + (rule $RowSet ($Binary $Entity) (lambda b (lambda e ((var b) (!= (var e)))))) + # (c.height (@p.num (!= (number 180)))) + (rule $RowSet ($Binary $Property $Entity) + (lambda b (lambda p (lambda e ((var b) ((var p) (!= (var e))))))) + ) +) + +# [TAG] t-comparison +(when t-comparison + (rule $AnchoredMore ($LEMMA_TOKEN) (FilterTokenFn lemma more than least above after) (anchored 1)) + (for @comparison (> >=) + (rule $RowSet ($AnchoredMore $Binary $Property $Entity) + (lambda a (lambda b (lambda p (lambda e ((var b) ((var p) (@comparison (var e)))))))) + ) + ) + (rule $AnchoredLess ($LEMMA_TOKEN) (FilterTokenFn lemma less than most below before) (anchored 1)) + (for @comparison (< <=) + (rule $RowSet ($AnchoredLess $Binary $Property $Entity) + (lambda a (lambda b (lambda p (lambda e ((var b) ((var p) (@comparison (var e)))))))) + ) + ) +) + +################################ +# Aggregate + +# [TAG] count +# (count (...[rows]...)) --> (number 4) +(when count + (rule $SingleValue ($RowSet) (lambda r (count (var r)))) +) + +# [TAG] t-count +(when t-count + (rule $AnchoredCount ($LEMMA_TOKEN) (FilterTokenFn lemma how many total number) (anchored 1)) + (rule $SingleValue ($AnchoredCount $RowSet) (lambda a (lambda r (count (var r))))) +) + +# [TAG] aggregate: "sum" / "average" / "maximum" +# (max (...[rows]...)) --> (number 42) +(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)))) +) + +# [TAG] t-aggregate +(when t-aggregate + (rule $AnchoredSum ($LEMMA_TOKEN) (FilterTokenFn lemma all combine total) (anchored 1)) + (rule $SingleValue ($AnchoredSum $ValueSet) (lambda a (lambda r (sum (var r))))) + (rule $AnchoredAvg ($LEMMA_TOKEN) (FilterTokenFn lemma average) (anchored 1)) + (rule $SingleValue ($AnchoredAvg $ValueSet) (lambda a (lambda r (avg (var r))))) +) + +################################ +# Functions (used in superlative and arithmetic) + +# Function on row set -- used in: +# (argmax 1 1 (@type @row) (reverse ___)) +# ___ = (lambda r (@!p.num (!c.height (var r)))) +# "highest" +(rule $FnOnRow ($Binary $Property) + (lambda b (lambda p (lambda r ((@R (var p)) ((@R (var b)) (var r)))))) +) + +# Function on value set -- used in: +# (argmax 1 1 (!c.name (@type @row)) (reverse ___)) +# ___ = (lambda v (@!p.num (!c.height (c.name (var v))))) +# "which person is the highest" +(rule $FnOnValue ($Binary $Binary $Property) + (lambda b1 (lambda b2 (lambda p (lambda v ((@R (var p)) ((@R (var b2)) ((var b1) (var v)))))))) +) + +(when count + # (argmax 1 1 (!c.name (@type @row)) (reverse ___)) + # ___ = (lambda v (count (c.name (var v)))) + # "which name occurs the most often" / "who won the most awards" + (rule $FnOnValue ($Binary) + (lambda b (lambda v (count ((var b) (var v))))) + ) + # (argmax 1 1 (@!p.num (!c.height (@type @row))) (reverse ___)) + # ___ = (lambda v (count (c.height (@p.num (var v))))) + # "which height occurs the most often" + (rule $FnOnValue ($Binary $Property) + (lambda b (lambda p (lambda v (count ((var b) ((var p) (var v))))))) + ) +) + +(when t-count + (rule $FnOnValue ($AnchoredCount $Binary) + (lambda t (lambda b (lambda v (count ((var b) (var v)))))) + ) + (rule $FnOnValue ($AnchoredCount $Binary $Property) + (lambda t (lambda b (lambda p (lambda v (count ((var b) ((var p) (var v)))))))) + ) +) + +################################ +# Superlative + +# [TAG] superlative: "first", "last", "biggest", "lowest" +(when superlative + (for @argm (argmax argmin) + # "first", "last" + (rule $RowSet (nothing $RowSet) (lambda r (@argm 1 1 (var r) @index))) + # Generic argmax / argmin on rows or values + (rule $RowSet ($RowSet $FnOnRow) (lambda r (lambda f (@argm 1 1 (var r) (@R (var f)))))) + (when u-superlative + (rule $ValueSet ($Unary $FnOnValue) (lambda u (lambda f (@argm 1 1 (var u) (reverse (var f)))))) + (rule $ValueSet ($Entity $Entity $FnOnValue) + (lambda e1 (lambda e2 (lambda f (@argm 1 1 (or (var e1) (var e2)) (reverse (var f)))))) + ) + ) + (when v-superlative + (rule $ValueSet ($ValueSet $FnOnValue) (lambda v (lambda f (@argm 1 1 (var v) (reverse (var f)))))) + ) + ) +) + +# [TAG] t-superlative: only trigger superlatives on appropriate anchors +(when t-superlative + # Anchors + (rule $SuperlativeTrigger ($LEMMA_TOKEN) (FilterPosTagFn token JJR JJS RBR RBS) (anchored 1)) + (rule $SuperlativeTrigger ($LEMMA_TOKEN) (FilterTokenFn lemma top first bottom last) (anchored 1)) + # Superlatives + (for @argm (argmax argmin) + (rule $RowSet ($SuperlativeTrigger $RowSet) (lambda t (lambda r (@argm 1 1 (var r) @index)))) + # Generic argmax / argmin on rows or values + (rule $RowSet ($SuperlativeTrigger $RowSet $FnOnRow) (lambda t (lambda r (lambda f (@argm 1 1 (var r) (@R (var f))))))) + (when u-superlative + (rule $ValueSet ($SuperlativeTrigger $Unary $FnOnValue) (lambda t (lambda u (lambda f (@argm 1 1 (var u) (reverse (var f))))))) + (rule $ValueSet ($SuperlativeTrigger $Entity $Entity $FnOnValue) + (lambda t (lambda e1 (lambda e2 (lambda f (@argm 1 1 (or (var e1) (var e2)) (reverse (var f))))))) + ) + ) + (when v-superlative + (rule $ValueSet ($SuperlativeTrigger $ValueSet $FnOnValue) (lambda t (lambda v (lambda f (@argm 1 1 (var v) (@R (var f))))))) + ) + ) +) + +################################ +# Arithmetic + +# [TAG] arithmetic: "How much taller is Barack Obama than Bill Clinton" +# (- (... (c_name.barack_obama)) (... (c_name.bill_clinton))) +(when arithmetic + (rule $SingleValue ($FnOnValue $Entity $Entity) + (lambda f (lambda e1 (lambda e2 (- ((var f) (var e1)) ((var f) (var e2)))))) + ) +) + +# [TAG] t-arithmetic +(when t-arithmetic + (rule $AnchoredSub ($LEMMA_TOKEN) (FilterTokenFn lemma difference between and much) (anchored 1)) + (rule $SingleValue ($AnchoredSub $FnOnValue $Entity $Entity) + (lambda a (lambda f (lambda e1 (lambda e2 (- ((var f) (var e1)) ((var f) (var e2))))))) + ) +) + +################################ +# Merge + +# [TAG] merge: "bigger than 10 and is from Russia" +# Warning: Without pruning, this can blow up the search space! +(when merge + (rule $RowSet ($RowSet $RowSet) + (lambda r1 (lambda r2 (and (var r1) (var r2)))) + ) +) + +################################ +# ROOT +(rule $ROOT ($ValueSet) (IdentityFn)) +(rule $ROOT ($SingleValue) (IdentityFn)) + +################################################################ +# For debugging: + +(when debug + (rule $Any ($ValueSet) (IdentityFn)) + (rule $Any ($RowSet) (IdentityFn)) + (rule $Any ($SingleValue) (IdentityFn)) + (rule $ROOT ($Any) (IdentityFn)) +) diff --git a/tables/grammars/generic.grammar b/tables/grammars/generic.grammar new file mode 100644 index 0000000..40648fa --- /dev/null +++ b/tables/grammars/generic.grammar @@ -0,0 +1,174 @@ +# 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) + +################################################################ +# 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)) + +################################ +# Property +# (@p.num (number 42)) / (@p.date (date 2012 12 21)) +(for @property (@p.num @p.date) + (rule $Property (nothing) (ConstantFn @property)) +) + +################################################################ +# Composition + +# $RowSet = list of rows (type = fb:row.row) +# $ValueSet = list of values (NameValue or other primitives) +# $SingleValue = number representing count (may not make sense to compose it with other things) + +################################ +# Generic Rows +# (@type @row) +(rule $RowSet (nothing) (ConstantFn (@type @row))) + +################################ +# Join +# c_name.barack_obama +(rule $ValueSet ($Entity) (IdentityFn)) +# (or c_name.barack_obama c_name.bill_clinton) +(rule $ValueSet ($Entity $Entity) (lambda e1 (lambda e2 (or (var e1) (var e2))))) +# (c.name c_name.barack_obama) +(rule $RowSet ($Binary $ValueSet) (lambda b (lambda v ((var b) (var v))))) +# (!c.name (...[rows]...)) --> c_name.barack_obama +(rule $ValueSet ($Binary $RowSet) (lambda b (lambda r ((@R (var b)) (var r))))) +# (c.height (@p.num (number 180))) +(rule $RowSet ($Binary $Property $ValueSet) + (lambda b (lambda p (lambda v ((var b) ((var p) (var v)))))) +) +# (@!p.num (!c.height (...[rows]...))) --> (number 180) +(rule $ValueSet ($Binary $Property $RowSet) + (lambda b (lambda p (lambda r ((@R (var p)) ((@R (var b)) (var r)))))) +) +# (@next (...[rows]...)) --> ...[rows]... +(for @movement (@next @!next) + # HACK: Added 'nothing' to prevent unary cycle + (rule $RowSet (nothing $RowSet) (lambda r (@movement (var r)))) +) + +(when comparison + # (c.name (!= c_name.barack_obama)) + (rule $RowSet ($Binary $Entity) + (lambda b (lambda e ((var b) (!= (var e))))) + ) + # (c.height (@p.num (>= (number 180)))) + (for @comparison (!= < > <= >=) + (rule $RowSet ($Binary $Property $Entity) + (lambda b (lambda p (lambda e ((var b) ((var p) (@comparison (var e))))))) + ) + ) +) + +################################ +# Aggregate + +# (count (...[rows]...)) --> (number 4) +(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 + +# Function on row set -- used in: +# (argmax 1 1 (@type @row) (reverse ___)) +# ___ = (lambda r (@!p.num (!c.height (var r)))) +# "highest" +(rule $FnOnRow ($Binary $Property) + (lambda b (lambda p (lambda r ((@R (var p)) ((@R (var b)) (var r)))))) +) + +# Function on value set -- used in: +# (argmax 1 1 (!c.name (@type @row)) (reverse ___)) +# ___ = (lambda v (count (c.name (var v)))) +# "which name occurs the most often" +(rule $FnOnValue ($Binary) + (lambda b (lambda v (count ((var b) (var v))))) +) +# (argmax 1 1 (@!p.num (!c.height (@type @row))) (reverse ___)) +# ___ = (lambda v (count (c.height (@p.num (var v))))) +# "which height occurs the most often" +(rule $FnOnValue ($Binary $Property) + (lambda b (lambda p (lambda v (count ((var b) ((var p) (var v))))))) +) +# (argmax 1 1 (!c.name (@type @row)) (reverse ___)) +# ___ = (lambda v (@!p.num (!c.height (c.name (var v))))) +# "which person is the highest" +(rule $FnOnValue ($Binary $Binary $Property) + (lambda b1 (lambda b2 (lambda p (lambda v ((@R (var p)) ((@R (var b2)) ((var b1) (var v)))))))) +) + +# Actual declaration for argmin and argmax +(for @argm (argmax argmin) + # "first", "last" + (rule $RowSet (nothing $RowSet) (lambda r (@argm 1 1 (var r) @index))) + # Generic argmax / argmin on rows or values + (rule $RowSet ($RowSet $FnOnRow) (lambda r (lambda f (@argm 1 1 (var r) (reverse (var f)))))) + (rule $ValueSet ($ValueSet $FnOnValue) (lambda v (lambda f (@argm 1 1 (var v) (reverse (var f)))))) +) + +################################ +# Arithmetic + +# (- (... (c_name.barack_obama)) (... (c_name.bill_clinton))) +# "How much taller is Barack Obama than Bill Clinton" +(when arithmetic + (rule $SingleValue ($FnOnValue $Entity $Entity) + (lambda f (lambda e1 (lambda e2 (- ((var f) (var e1)) ((var f) (var e2)))))) + ) +) + +################################ +# Merge +# Warning: Without pruning, this can blow up the search space! +(when merge + (rule $RowSet ($RowSet $RowSet) (lambda r1 (lambda r2 (and (var r1) (var r2))))) +) + +################################ +# ROOT +(rule $ROOT ($ValueSet) (IdentityFn)) +(rule $ROOT ($SingleValue) (IdentityFn)) + +################################################################ +# For debugging: + +(when debug + (rule $Any ($ValueSet) (IdentityFn)) + (rule $Any ($RowSet) (IdentityFn)) + (rule $Any ($SingleValue) (IdentityFn)) + (rule $ROOT ($Any) (IdentityFn)) +) diff --git a/tables/grammars/restrict.grammar b/tables/grammars/restrict.grammar new file mode 100644 index 0000000..ca55fa2 --- /dev/null +++ b/tables/grammars/restrict.grammar @@ -0,0 +1,126 @@ +# Restrictive Grammar +# Use fixed patterns with limited number of layers of compositionality. + +################################################################ +# Macros + +(def @R reverse) +(def @type fb:type.object.type) +(def @any fb:type.any) +(def @row fb:type.row) +(def @cell fb:type.cell) +(def @next fb:row.row.next) +(def @!next fb:row.row.next) +(def @index fb:row.row.index) +(def @p.num fb:cell.cell.number) +(def @!p.num !fb:cell.cell.number) + +################################################################ +# Lexicon + +################################ +# TokenSpan +(rule $TokenSpan ($PHRASE) (FilterSpanLengthFn 1) (anchored 1)) + +################################ +# Entity, Unary, Binary (using FuzzyMatchFn) +(rule $Entity ($TokenSpan) (FuzzyMatchFn entity) (anchored 1)) +(rule $Num ($PHRASE) (NumberFn) (anchored 1)) +#(rule $Entity ($PHRASE) (DateFn) (anchored 1)) +(rule $Binary ($TokenSpan) (FuzzyMatchFn binary) (anchored 1)) + +################################ +# Create binary from thin air +(rule $Binary (nothing) (FuzzyMatchFn any binary)) +(rule $Num (nothing) (ConstantFn (number 0))) + +################################################################ +# Composition Patterns + +# (!b1 (b2 e)) +(rule $ROOT ($Binary $Binary $Entity) (lambda b1 (lambda b2 (lambda e + ((@R (var b1)) ((var b2) (var e))) +)))) + +# (!b1 (next (b2 e))) +(for @movement (@next @!next) + (rule $ROOT ($Binary $Binary $Entity) (lambda b1 (lambda b2 (lambda e + ((@R (var b1)) (@movement ((var b2) (var e)))) + )))) +) + +# (count (type row)) +(rule $ROOT (nothing) (ConstantFn (count (@type @row)))) + +# (count (b e)) +(rule $ROOT ($Binary $Entity) (lambda b (lambda e + (count ((var b) (var e))) +))) + +# (count (!b1 (b2 e))) +(rule $ROOT ($Binary $Binary $Entity) (lambda b1 (lambda b2 (lambda e + (count ((@R (var b1)) ((var b2) (var e)))) +)))) + +# (count (and (!b (type row)) (p.num (> num)))) +#(for @comparison (< > <= >=) +# (rule $ROOT ($Binary $Num) (lambda b (lambda num +# (count (and ((@R (var b)) (@type @row)) (@p.num (@comparison (var num))))) +# ))) +#) + +# (count (and (type row) (b (p.num (> num))))) +(for @comparison (< > <= >=) + (rule $ROOT ($Binary $Num) (lambda b (lambda num + (count (and (@type @row) ((var b) (@p.num (@comparison (var num)))))) + ))) +) + +# (count (and (!b1 (b2 e)) (p.num (> num)))) +#(for @comparison (< > <= >=) +# (rule $ROOT ($Binary $Binary $Entity $Num) (lambda b1 (lambda b2 (lambda e (lambda num +# (count (and ((@R (var b1)) ((var b2) (var e))) (@p.num (@comparison (var num))))) +# ))))) +#) + +# (count (and (b1 e) (b2 (p.num (> num))))) +(for @comparison (< > <= >=) + (rule $ROOT ($Binary $Binary $Entity $Num) (lambda b1 (lambda b2 (lambda e (lambda num + (count (and ((var b1) (var e)) ((var b2) (@p.num (@comparison (var num)))))) + ))))) +) + +# (argmax 1 1 (!b1 (type row)) (reverse (lambda x (@!p.num (!b2 (b1 x)))))) +(for @f (argmax argmin) + (rule $ROOT ($Binary $Binary) (lambda b1 (lambda b2 + (@f 1 1 ((@R (var b1)) (@type @row)) (@R (lambda x (@!p.num ((@R (var b2)) ((var b1) (var x))))))) + ))) +) + +# (argmax 1 1 (!b1 (type row)) (reverse (lambda x (count (b1 x))))) +(for @f (argmax argmin) + (rule $ROOT ($Binary $Binary) (lambda b1 + (@f 1 1 ((@R (var b1)) (@type @row)) (@R (lambda x (count ((var b1) (var x)))))) + )) +) + +# (!b1 (argmax 1 1 (and (b2 e) (type row)) index) +(for @f (argmax argmin) + (rule $ROOT ($Binary $Binary $Entity) (lambda b1 (lambda b2 (lambda e + ((@R (var b1)) (@f 1 1 (and ((var b2) (var e)) (@type @row)) @index)) + )))) +) + +# (max (@!p.num (!b (type row)))) +(for @f (max min sum) + (rule $ROOT ($Binary) (lambda b + (@f (@!p.num ((@R (var b)) (@type @row)))) + )) +) + +# (max (@!p.num (!b1 (b2 e)))) +(for @f (max min sum) + (rule $ROOT ($Binary $Binary $Entity) (lambda b1 (lambda b2 (lambda e + (@f (@!p.num ((@R (var b1)) ((var b2) (var e))))) + )))) +) diff --git a/tables/grammars/simple.grammar b/tables/grammars/simple.grammar new file mode 100644 index 0000000..922294b --- /dev/null +++ b/tables/grammars/simple.grammar @@ -0,0 +1,106 @@ +# Simple Grammar +# Use only joins, next, and maybe superlatives + +################################################################ +# 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) + +################################################################ +# 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)) + +################################ +# Property +# (@p.num (number 42)) / (@p.date (date 2012 12 21)) +(for @property (@p.num @p.date) + (rule $Property (nothing) (ConstantFn @property)) +) + +################################################################ +# Composition + +# $RowSet = list of rows (type = fb:row.row) +# $ValueSet = list of values (NameValue or other primitives) + +################################ +# Generic Rows +# (@type @row) +(rule $RowSet (nothing) (ConstantFn (@type @row))) + +################################ +# Join +# c_name.barack_obama +(rule $ValueSet ($Entity) (IdentityFn)) +# (c.name c_name.barack_obama) +(rule $RowSet ($Binary $ValueSet) (lambda b (lambda v ((var b) (var v))))) +# (!c.name (...[rows]...)) --> c_name.barack_obama +(rule $ValueSet ($Binary $RowSet) (lambda b (lambda r ((@R (var b)) (var r))))) +# (c.height (@p.num (number 180))) +(rule $RowSet ($Binary $Property $ValueSet) + (lambda b (lambda p (lambda v ((var b) ((var p) (var v)))))) +) +# (@!p.num (!c.height (...[rows]...))) --> (number 180) +(rule $ValueSet ($Binary $Property $RowSet) + (lambda b (lambda p (lambda r ((@R (var p)) ((@R (var b)) (var r)))))) +) + +(when movement + # (@next (...[rows]...)) --> ...[rows]... + (for @movement (@next @!next) + # HACK: Added 'nothing' to prevent unary cycle + (rule $RowSet (nothing $RowSet) (lambda r (@movement (var r)))) + ) +) + +################################ +# Superlative + +(when superlative + (for @argm (argmax argmin) + # "first", "last" + (rule $RowSet (nothing $RowSet) (lambda r (@argm 1 1 (var r) @index))) + # Generic argmax / argmin on rows or values + # (argmax 1 1 (@type @row) (reverse ___)) + # ___ = (lambda r (@!p.num (!c.height (var r)))) + # "highest" + (rule $RowSet ($RowSet $Binary $Property) + (lambda r (lambda b (lambda p (@argm 1 1 (var r) (reverse (lambda x ((@R (var p)) ((@R (var b)) (var x) )))))))) + ) + ) +) + +################################ +# ROOT +(rule $ROOT ($ValueSet) (IdentityFn)) + +################################################################ +# For debugging: + +(when debug + (rule $Any ($ValueSet) (IdentityFn)) + (rule $Any ($RowSet) (IdentityFn)) + (rule $ROOT ($Any) (IdentityFn)) +) diff --git a/tables/grammars/tiny.grammar b/tables/grammars/tiny.grammar new file mode 100644 index 0000000..184f97e --- /dev/null +++ b/tables/grammars/tiny.grammar @@ -0,0 +1,107 @@ +# Tiny grammar for semantic parsing on tables. +# Use emnlp2013.grammar as the base grammar. + +################################################################ +# Macros + +(def @R reverse) +(def @type fb:type.object.type) +(def @row fb:type.row) +(def @cell fb:type.cell) +(def @any fb:type.any) + +################################################################ +# Lexicon + +################################ +# Padding: can skip these sequences +(rule $Padding ($PHRASE) (IdentityFn)) + +################################ +# TokenSpan +(rule $TokenSpan ($PHRASE) (FilterSpanLengthFn 1)) + +################################ +# Entity, Unary, Binary (using FuzzyMatchFn) +(rule $Entity ($TokenSpan) (FuzzyMatchFn entity)) +(rule $Entity ($PHRASE) (NumberFn)) +(rule $Entity ($PHRASE) (DateFn)) +(rule $Binary ($TokenSpan) (FuzzyMatchFn binary)) + +################################ +# Hard coded rules +(rule $Operator (how many) (ConstantFn (lambda x (count (var x))))) +(rule $Binary (after) (ConstantFn fb:row.row.next)) +(rule $Binary (before) (ConstantFn fb:row.row.next)) + +# (argmax 1 1 (!r.nation *) (@R (lambda c (!r.total (r.nation (var c)))))) +# (argmax 1 1 (!x *) (@R (lambda c (!y (x (var c))))) + +(rule $DoubleOperator (most) (ConstantFn + (lambda x (lambda y + (argmax 1 1 + ((@R (var x)) *) + (@R (lambda c ((@R (var y)) ((var x) (var c))))) + ) + )) + (-> (-> @cell @row) (-> (-> @cell @row) @cell)) +)) +(rule $DoubleOperator (most) (ConstantFn + (lambda x (lambda y + (argmax 1 1 + ((@R (var x)) *) + (@R (lambda c (count ((@R (var y)) ((var x) (var c)))))) + ) + )) + (-> (-> @cell @row) (-> (-> @cell @row) @cell)) +)) +#(rule $Operator (most length) (ConstantFn +# (lambda x +# (argmax 1 1 +# ((@R (var x)) *) +# (@R (lambda c (!fb:row.row.length ((var x) (var c))))) +# ) +# ) +# (-> (-> @cell @row) @cell) +#)) +#(rule $Operator (title with the most) (ConstantFn +# (lambda y +# (argmax 1 1 +# (!fb:row.row.title *) +# (@R (lambda c ((@R (var y)) (fb:row.row.title (var c))))) +# ) +# ) +# (-> (-> @cell @row) @cell) +#)) + +################################################################ +# Composition + +################################ +# Join +(rule $Set ($Entity) (IdentityFn)) +(rule $Set ($Binary ($Padding optional) $Set) (JoinFn binary,unary unaryCanBeArg0 unaryCanBeArg1)) +(rule $Set ($Set ($Padding optional) $Binary) (JoinFn unary,binary unaryCanBeArg0 unaryCanBeArg1)) +(rule $Set ($Set ($Padding optional) $Set) (MergeFn and)) + +################################ +# Aggregation / Superlative +(rule $Set ($Operator ($Padding optional) $Set) (JoinFn binary,unary unaryCanBeArg1 betaReduce)) +(rule $Set ($Set ($Padding optional) $Operator) (JoinFn unary,binary unaryCanBeArg1 betaReduce)) +(rule $Set ($Operator ($Padding optional) $Binary) (JoinFn binary,unary unaryCanBeArg1 betaReduce)) +(rule $Set ($Binary ($Padding optional) $Operator) (JoinFn unary,binary unaryCanBeArg1 betaReduce)) +(rule $Operator ($Binary ($Padding optional) $DoubleOperator) (JoinFn unary,binary unaryCanBeArg1 betaReduce)) +(rule $Operator ($DoubleOperator ($Padding optional) $Binary) (JoinFn binary,unary unaryCanBeArg1 betaReduce)) + +################################ +# ROOT +(rule $ROOT (($Padding optional) $Set ($Padding optional)) (IdentityFn)) + +################################################################ +# For debugging: + +(rule $Any ($Set) (IdentityFn)) +(rule $Any ($Binary) (IdentityFn)) +(rule $Any ($Operator) (IdentityFn)) +(rule $Any ($DoubleOperator) (IdentityFn)) +#(rule $ROOT ($Any) (IdentityFn)) diff --git a/tables/toy-examples/random/nikos_machlas.csv b/tables/toy-examples/random/nikos_machlas.csv new file mode 100644 index 0000000..356e041 --- /dev/null +++ b/tables/toy-examples/random/nikos_machlas.csv @@ -0,0 +1,19 @@ +"#","Date","Venue","Opponent","Score","Result","Competition" +"1.","10 March 1993","Wien, Austria","Austria","1–2","Loss","Friendly" +"2.","12 October 1993","Luxembourg City, Luxembourg","Luxembourg","1–3","Win","1994 FIFA World Cup qualifier" +"3.","17 November 1993","Athens, Greece","Russia","1–0","Win","1994 FIFA World Cup qualifier" +"4.","27 April 1994","Athens, Greece","Saudi Arabia","5–1","Win","Friendly" +"5.","27 April 1994","Athens, Greece","Saudi Arabia","5–1","Win","Friendly" +"6.","12 October 1994","Thessaloniki, Greece","Finland","4–0","Win","UEFA Euro 1996 qualifier" +"7.","12 October 1994","Thessaloniki, Greece","Finland","4–0","Win","UEFA Euro 1996 qualifier" +"8.","16 November 1994","Athens, Greece","San Marino","2–0","Win","UEFA Euro 1996 qualifier" +"9.","15 November 1995","Heraklion, Greece","Faroe Islands","5–0","Win","UEFA Euro 1996 qualifier" +"10.","19 August 1997","Thessaloniki, Greece","Cyprus","2–1","Win","Friendly" +"11.","6 September 1997","Ljubljana, Slovenia","Slovenia","0–3","Win","1998 FIFA World Cup qualifier" +"12.","6 September 1998","Athens, Greece","Slovenia","2–2","Draw","UEFA Euro 2000 qualifier" +"13.","14 October 1998","Athens, Greece","Georgia","3–0","Win","UEFA Euro 2000 qualifier" +"14.","5 June 1999","Tbilisi, Georgia","Georgia","1–2","Win","UEFA Euro 2000 qualifier" +"15.","13 November 1999","Kilkis, Greece","Nigeria","2–0","Win","Friendly" +"16.","13 November 1999","Kilkis, Greece","Nigeria","2–0","Win","Friendly" +"17.","2 June 2001","Heraklion, Greece","Albania","1–0","Win","2002 FIFA World Cup qualifier" +"18.","10 November 2001","Athens, Greece","Estonia","4–2","Win","Friendly" diff --git a/tables/view b/tables/view new file mode 100755 index 0000000..9826943 --- /dev/null +++ b/tables/view @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +if [ $# -eq 1 ]; then + filename=${1%.csv}.table +elif [ $# -eq 2 ]; then + filename=csv/${1}-csv/${2}.table +else + echo "Usage: ./tables/view csv/XXX-csv/YYY.csv OR ./tables/view XXX YYY" + exit 1 +fi +[ -f filename ] || filename=lib/data/tables/$filename +view -c "set nowrap" $filename