package edu.stanford.nlp.sempre.freebase; import edu.stanford.nlp.sempre.freebase.FbFormulasInfo.BinaryFormulaInfo; import edu.stanford.nlp.sempre.MergeFormula.Mode; import fig.basic.LispTree; import fig.basic.LogInfo; import fig.basic.Option; import edu.stanford.nlp.sempre.LanguageInfo.DependencyEdge; import edu.stanford.nlp.sempre.*; import java.io.IOException; import java.util.*; /** * Bridge between two derivations by type-raising one of them. * @author jonathanberant */ public class BridgeFn extends SemanticFn { private static final Formula intFormula = Formulas.fromLispTree(LispTree.proto.parseFromString("(fb:type.object.type fb:type.int)")); private static final Formula floatFormula = Formulas.fromLispTree(LispTree.proto.parseFromString("(fb:type.object.type fb:type.float)")); public static class Options { @Option (gloss = "Verbose") public int verbose = 0; @Option (gloss = "Whether to have binary predicate features (ovrefits on small data)") public boolean useBinaryPredicateFeatures = true; @Option (gloss = "Whether to filter bad domains such as user and common") public boolean filterBadDomain = true; } public static Options opts = new Options(); private FbFormulasInfo fbFormulaInfo = null; private String description; private boolean headFirst; private TextToTextMatcher textToTextMatcher; public void init(LispTree tree) { super.init(tree); if (tree.children.size() != 3) throw new RuntimeException("Number of children is: " + tree.children.size()); if (!tree.child(2).value.equals("headFirst") && !tree.child(2).value.equals("headLast")) throw new RuntimeException("Bad argument for head position: " + tree.child(2).value); if (!tree.child(1).value.equals("unary") && !tree.child(1).value.equals("inject") && !tree.child(1).value.equals("entity")) throw new RuntimeException("Bad description: " + tree.child(1).value); this.description = tree.child(1).value; headFirst = tree.child(2).value.equals("headFirst"); } public BridgeFn() { fbFormulaInfo = FbFormulasInfo.getSingleton(); textToTextMatcher = TextToTextMatcher.getSingleton(); } @Override public DerivationStream call(Example ex, Callable c) { try { switch (description) { case "unary": return bridgeUnary(ex, c); case "inject": return injectIntoCvt(ex, c); case "entity": return bridgeEntity(ex, c); default: throw new RuntimeException("Invalid (expected unary, inject, or entity): " + description); } } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } } @Override public void sortOnFeedback(Params params) { LogInfo.begin_track("Learner.BridgeFeedback"); FbFormulasInfo fbFormulasInfo = FbFormulasInfo.getSingleton(); Comparator<Formula> feedbackComparator = fbFormulasInfo.new FormulaByFeaturesComparator(params); fbFormulasInfo.sortType2ToBinaryMaps(feedbackComparator); LogInfo.end_track(); } private boolean isCvt(Derivation headDeriv) { if (!(headDeriv.formula instanceof JoinFormula)) return false; JoinFormula join = (JoinFormula) headDeriv.formula; return join.relation instanceof LambdaFormula || join.child instanceof JoinFormula || join.child instanceof MergeFormula; } // Return all the entity supertypes of |type|. // TODO(joberant): make this more efficient. private Set<String> getSupertypes(SemType type, Set<String> supertypes) { if (type instanceof AtomicSemType) supertypes.addAll(SemTypeHierarchy.singleton.getSupertypes(((AtomicSemType) type).name)); else if (type instanceof UnionSemType) for (SemType baseType : ((UnionSemType) type).baseTypes) getSupertypes(baseType, supertypes); else { // TODO(joberant): FIXME HACK for when passing binary into lambda formula and // getSuperTypes doesn't work getSupertypes(SemType.fromString("topic"), supertypes); // throw new RuntimeException("Unexpected type (must be unary): " + type); } return supertypes; } private DerivationStream bridgeUnary(Example ex, Callable c) throws IOException { assert ex != null; // Example (headFirst = false): modifier[Hanks] head[movies] Derivation headDeriv = headFirst ? c.child(0) : c.child(1); Derivation modifierDeriv = !headFirst ? c.child(0) : c.child(1); Set<String> headTypes = getSupertypes(headDeriv.type, new HashSet<>()); Set<String> modifierTypes = getSupertypes(modifierDeriv.type, new HashSet<>()); ArrayList<BridgingInfo> bridgingInfoList = new ArrayList<>(); for (String modifierType : modifierTypes) { // For each head type... List<Formula> binaries = fbFormulaInfo.getBinariesForType2(modifierType); for (Formula binary : binaries) { // For each possible binary... if (opts.filterBadDomain && badDomain(binary)) continue; BinaryFormulaInfo binaryInfo = fbFormulaInfo.getBinaryInfo(binary); if (opts.verbose >= 3) LogInfo.logs("%s => %s", modifierType, binary); if (headTypes.contains(binaryInfo.expectedType1)) { BridgingInfo bridgingInfo = new BridgingInfo(ex, c, binaryInfo, headFirst, headDeriv, modifierDeriv); bridgingInfoList.add(bridgingInfo); } } } Collections.sort(bridgingInfoList); return new LazyBridgeFnDerivs(bridgingInfoList); } // bridge without a unary - simply by looking at binaries leading to the entity and string matching binary description to example tokens/lemmas/stems private DerivationStream bridgeEntity(Example ex, Callable c) throws IOException { assert ex != null; Derivation modifierDeriv = c.child(0); Set<String> modifierTypes = getSupertypes(modifierDeriv.type, new HashSet<>()); ArrayList<BridgingInfo> bridgingInfoList = new ArrayList<>(); if (opts.verbose >= 1) LogInfo.logs("bridgeEntity: %s | %s", modifierDeriv, modifierTypes); for (String modifierType : modifierTypes) { // For each head type... List<Formula> binaries = fbFormulaInfo.getBinariesForType2(modifierType); for (Formula binary : binaries) { // For each possible binary... if (opts.filterBadDomain && badDomain(binary)) continue; BinaryFormulaInfo binaryInfo = fbFormulaInfo.getBinaryInfo(binary); if (opts.verbose >= 3) LogInfo.logs("%s => %s", modifierType, binary); BridgingInfo bridgingInfo = new BridgingInfo(ex, c, binaryInfo, headFirst, null, modifierDeriv); bridgingInfoList.add(bridgingInfo); } } Collections.sort(bridgingInfoList); return new LazyBridgeFnDerivs(bridgingInfoList); } private boolean badDomain(String binary) { return binary.contains("fb:user.") || binary.contains("fb:base.") || binary.contains("fb:dataworld.") || binary.contains("fb:type.") || binary.contains("fb:common.") || binary.contains("fb:freebase."); } private boolean badDomain(Formula formula) { if (formula instanceof VariableFormula) return false; if (formula instanceof ValueFormula) { return badDomain(formula.toString()); } if (formula instanceof JoinFormula) { JoinFormula jFormula = (JoinFormula) formula; return badDomain(jFormula.relation) || badDomain(jFormula.child); } if (formula instanceof LambdaFormula) { LambdaFormula lambdaFormula = (LambdaFormula) formula; return badDomain(lambdaFormula.body); } if (formula instanceof ReverseFormula) { ReverseFormula reverseFormula = (ReverseFormula) formula; return badDomain(reverseFormula.child); } if (formula instanceof NotFormula) { NotFormula notFormula = (NotFormula) formula; return badDomain(notFormula.child); } throw new RuntimeException("Binary has formula type that is not supported"); } // generate from example array of content word tokens/lemmas/stems that are not dominated by child derivations private List<List<String>> generateExampleInfo(Example ex, Callable c) { List<String> tokens = new ArrayList<>(); List<String> posTags = new ArrayList<>(); List<String> lemmas = new ArrayList<>(); List<List<String>> res = new ArrayList<>(); res.add(tokens); res.add(posTags); res.add(lemmas); Derivation modifierDeriv = headFirst ? c.child(1) : c.child(0); for (int i = 0; i < ex.languageInfo.tokens.size(); ++i) { if (i >= modifierDeriv.start && i < modifierDeriv.end) { // do not consider the modifier words { continue; } tokens.add(ex.languageInfo.tokens.get(i)); posTags.add(ex.languageInfo.posTags.get(i)); lemmas.add(ex.languageInfo.lemmaTokens.get(i)); } return res; } private DerivationStream injectIntoCvt(Example ex, Callable c) { assert ex != null; if (opts.verbose >= 2) LogInfo.logs("child1=%s, child2=%s", ex.phrase(c.child(0).start, c.child(0).end), ex.phrase(c.child(1).start, c.child(1).end)); // Example: modifier[Braveheart] head[Mel Gibson plays in] Derivation headDeriv = headFirst ? c.child(0) : c.child(1); if (!isCvt(headDeriv)) // only works on cvts return new LazyBridgeFnDerivs(new ArrayList<>()); Derivation modifierDeriv = !headFirst ? c.child(0) : c.child(1); JoinFormula headFormula = (JoinFormula) Formulas.betaReduction(headDeriv.formula); // find the type of the cvt node Set<String> headTypes = Collections.singleton(fbFormulaInfo.getBinaryInfo(headFormula.relation).expectedType2); Set<String> modifierTypes = getSupertypes(modifierDeriv.type, new HashSet<>()); ArrayList<BridgingInfo> bridgingInfoList = new ArrayList<>(); for (String modifierType : modifierTypes) { List<Formula> binaries = fbFormulaInfo.getAtomicBinariesForType2(modifierType); // here we use atomic binaries since we inject into a CVT for (Formula binary : binaries) { // For each possible binary... if (opts.filterBadDomain && badDomain(binary)) continue; BinaryFormulaInfo info = fbFormulaInfo.getBinaryInfo(binary); if (headTypes.contains(info.expectedType1)) { BridgingInfo bridgingInfo = new BridgingInfo(ex, c, info, headFirst, headDeriv, modifierDeriv); bridgingInfoList.add(bridgingInfo); } } } Collections.sort(bridgingInfoList); return new LazyBridgeFnDerivs(bridgingInfoList); } // Checks whether "var" is used as a binary in "formula" private boolean varIsBinary(Formula formula, String var) { boolean isBinary = false; LispTree tree = formula.toLispTree(); VariableFormula vf = new VariableFormula(var); for (LispTree child : tree.children) { if (child.isLeaf()) continue; if (child.children.size() == 2 && vf.equals(Formulas.fromLispTree(child.child(0)))) { isBinary = true; break; } if (varIsBinary(Formulas.fromLispTree(child), var)) { isBinary = true; break; } } return isBinary; } private Formula buildBridge(Formula headFormula, Formula modifierFormula, Formula binary) { // Handle cases like "what state has the most cities" where "has the" is mapped // to "contains" predicate via bridging but "most" triggers a nested lambda w/ // argmax on a count relation // (Corresponds to $MetaMetaOperator in grammar) if (modifierFormula instanceof LambdaFormula) { LambdaFormula lf = (LambdaFormula) modifierFormula; if (varIsBinary(lf, lf.var)) { Formula newBinary = Formulas.lambdaApply(lf, binary); if (newBinary instanceof LambdaFormula) { return Formulas.lambdaApply((LambdaFormula) newBinary, headFormula); } } } Formula join = new JoinFormula(binary, modifierFormula); Formula merge = new MergeFormula(Mode.and, headFormula, join); // Don't merge on ints and floats return (headFormula.equals(intFormula) || headFormula.equals(floatFormula)) ? join : merge; } private Formula buildBridgeFromCvt(JoinFormula headFormula, Formula modifierFormula, Formula binary) { Formula join = new JoinFormula(binary, modifierFormula); Formula merge = new MergeFormula(Mode.and, headFormula.child, join); return new JoinFormula(headFormula.relation, merge); } public static FeatureVector getBinaryBridgeFeatures(BinaryFormulaInfo bInfo) { FeatureVector features = new FeatureVector(); if (opts.useBinaryPredicateFeatures) features.add("BridgeFn", "binary=" + bInfo.formula); features.add("BridgeFn", "domain=" + bInfo.extractDomain(bInfo.formula)); features.addWithBias("BridgeFn", "popularity", Math.log(bInfo.popularity + 1)); return features; } /** * Lazy iterator for bridging - we always have the next derivation ready since it is possible that * items in the list do not produce a derivation */ public class LazyBridgeFnDerivs extends MultipleDerivationStream { private ArrayList<BridgingInfo> bridgingInfoList; private int currIndex = 0; public LazyBridgeFnDerivs(ArrayList<BridgingInfo> bridgingInfoList) { this.bridgingInfoList = bridgingInfoList; } @Override public int estimatedSize() { return bridgingInfoList.size() - currIndex; } @Override public Derivation createDerivation() { if (currIndex == bridgingInfoList.size()) return null; Derivation res; switch (description) { case "unary": res = nextUnary(); break; case "inject": res = nextInject(); break; case "entity": res = nextEntity(); break; default: throw new RuntimeException("Bad description " + description); } if (opts.verbose >= 2) LogInfo.logs("mode=%s,deriv=%s", description, res); return res; } // not every BridgingInfo produces a derivation so we iterate until we find one private Derivation nextEntity() { if (opts.verbose >= 3) LogInfo.begin_track("Compute next entity"); BridgingInfo currBridgingInfo = bridgingInfoList.get(currIndex++); if (opts.verbose >= 2) LogInfo.logs("BridgeFn.nextEntity: binary=%s, popularity=%s", currBridgingInfo.bInfo.formula, currBridgingInfo.bInfo.popularity); List<List<String>> exampleInfo = generateExampleInfo(currBridgingInfo.ex, currBridgingInfo.c); // this is not done in text to text matcher so done here Formula join = new JoinFormula(currBridgingInfo.bInfo.formula, currBridgingInfo.modifierDeriv.formula); Derivation res = new Derivation.Builder() .withCallable(currBridgingInfo.c) .formula(join) .type(SemType.newAtomicSemType(currBridgingInfo.bInfo.expectedType1)) .createDerivation(); if (SemanticFn.opts.trackLocalChoices) { res.addLocalChoice( String.format( "BridgeFn: entity %s --> %s %s", currBridgingInfo.bInfo.formula, currBridgingInfo.modifierDeriv.startEndString(currBridgingInfo.ex.getTokens()), currBridgingInfo.modifierDeriv.formula)); } if (opts.verbose >= 2) LogInfo.logs("BridgeStringFn: %s", join); // features res.addFeature("BridgeFn", "entity"); res.addFeatures(getBinaryBridgeFeatures(currBridgingInfo.bInfo)); // Adds dependencies for every bridging relation/entity if (FeatureExtractor.containsDomain("dependencyBridge")) { addBridgeDependency(res, currBridgingInfo, "entity"); } FeatureVector textMatchFeatures = textToTextMatcher.extractFeatures( exampleInfo.get(0), exampleInfo.get(1), exampleInfo.get(2), new HashSet<>(currBridgingInfo.bInfo.descriptions)); res.addFeatures(textMatchFeatures); if (opts.verbose >= 3) LogInfo.end_track(); return res; } /* Adds dependencies for every bridging relation/entity. */ private void addBridgeDependency(Derivation res, BridgingInfo currBridgingInfo, String type) { List<List<DependencyEdge>> deps = currBridgingInfo.ex.languageInfo.dependencyChildren; Derivation entityDeriv = currBridgingInfo.modifierDeriv; for (int currWord = 0; currWord < deps.size(); currWord++) { if (entityDeriv.containsIndex(currWord)) { continue; } for (DependencyEdge e : deps.get(currWord)) { if (entityDeriv.containsIndex(e.modifier)) { res.addFeature("dependencyBridge", "type=" + type + "," + e.label + " -- " + "relation=" + currBridgingInfo.bInfo.formula); } } } } private Derivation nextInject() { BridgingInfo currBridgingInfo = bridgingInfoList.get(currIndex++); if (opts.verbose >= 2) LogInfo.logs("BridgingFn.nextInject: binary=%s, popularity=%s", currBridgingInfo.bInfo.formula, currBridgingInfo.bInfo.popularity); JoinFormula headFormula = (JoinFormula) Formulas.betaReduction(currBridgingInfo.headDeriv.formula); Formula bridgedFormula = buildBridgeFromCvt(headFormula, currBridgingInfo.modifierDeriv.formula, currBridgingInfo.bInfo.formula); Derivation res = new Derivation.Builder() .withCallable(currBridgingInfo.c) .formula(bridgedFormula) .type(currBridgingInfo.headDeriv.type) .createDerivation(); if (SemanticFn.opts.trackLocalChoices) { res.addLocalChoice( String.format( "BridgeFn: %s %s --> %s %s --> %s %s", currBridgingInfo.headDeriv.startEndString(currBridgingInfo.ex.getTokens()), currBridgingInfo.headDeriv.formula, currBridgingInfo.ex.getTokens().subList(currBridgingInfo.c.child(0).end, currBridgingInfo.c.child(1).start), currBridgingInfo.bInfo.formula, currBridgingInfo.modifierDeriv.startEndString(currBridgingInfo.ex.getTokens()), currBridgingInfo.modifierDeriv.formula)); } if (opts.verbose >= 3) LogInfo.logs("BridgeFn: injecting %s to %s --> %s ", currBridgingInfo.modifierDeriv.formula, headFormula, bridgedFormula); String headModifierOrder = headFirst ? "head-modifier" : "modifier-head"; res.addFeature("BridgeFn", "inject_order=" + headModifierOrder + "," + "pos=" + currBridgingInfo.ex.languageInfo.getCanonicalPos(currBridgingInfo.headDeriv.start) + "-" + currBridgingInfo.ex.languageInfo.getCanonicalPos(currBridgingInfo.modifierDeriv.start) ); res.addFeature("BridgeFn", "binary=" + currBridgingInfo.bInfo.formula); res.addFeature("BridgeFn", "domain=" + currBridgingInfo.bInfo.extractDomain(currBridgingInfo.bInfo.formula)); res.addFeatureWithBias("BridgeFn", "popularity", Math.log(currBridgingInfo.bInfo.popularity + 1)); /* Adds dependencies for every bridging relation/entity */ if (FeatureExtractor.containsDomain("dependencyBridge")) { addBridgeDependency(res, currBridgingInfo, "inject"); } return res; } private Derivation nextUnary() { BridgingInfo currBridgingInfo = bridgingInfoList.get(currIndex++); if (opts.verbose >= 2) LogInfo.logs("BridgingFn.nextUnary: binary=%s, popularity=%s", currBridgingInfo.bInfo.formula, currBridgingInfo.bInfo.popularity); Formula bridgedFormula = buildBridge(currBridgingInfo.headDeriv.formula, currBridgingInfo.modifierDeriv.formula, currBridgingInfo.bInfo.formula); Derivation res = new Derivation.Builder() .withCallable(currBridgingInfo.c) .formula(bridgedFormula) .type(currBridgingInfo.headDeriv.type) .createDerivation(); if (SemanticFn.opts.trackLocalChoices) { res.addLocalChoice( String.format( "BridgeFn: %s %s --> %s %s --> %s %s", currBridgingInfo.headDeriv.startEndString(currBridgingInfo.ex.getTokens()), currBridgingInfo.headDeriv.formula, currBridgingInfo.ex.getTokens().subList(currBridgingInfo.c.child(0).end, currBridgingInfo.c.child(1).start), currBridgingInfo.bInfo.formula, currBridgingInfo.modifierDeriv.startEndString(currBridgingInfo.ex.getTokens()), currBridgingInfo.modifierDeriv.formula)); } // Add features res.addFeature("BridgeFn", "unary"); res.addFeatures(getBinaryBridgeFeatures(currBridgingInfo.bInfo)); // head modifier POS tags String headModifierOrder = headFirst ? "head-modifier" : "modifier-head"; res.addFeature("BridgeFn", "order=" + headModifierOrder + "," + "pos=" + currBridgingInfo.ex.languageInfo.getCanonicalPos(currBridgingInfo.headDeriv.start) + "-" + currBridgingInfo.ex.languageInfo.getCanonicalPos(currBridgingInfo.modifierDeriv.start) ); List<List<String>> exampleInfo = generateExampleInfo(currBridgingInfo.ex, currBridgingInfo.c); // this is not done in text to text matcher so done here FeatureVector vector = textToTextMatcher.extractFeatures( exampleInfo.get(0), exampleInfo.get(1), exampleInfo.get(2), new HashSet<>(currBridgingInfo.bInfo.descriptions)); res.addFeatures(vector); /* Adds dependencies for every bridging relation/entity */ if (FeatureExtractor.containsDomain("dependencyBridge")) { addBridgeDependency(res, currBridgingInfo, "unary"); } return res; } @Override public void remove() { throw new RuntimeException("Does not support remove"); } } public class BridgingInfo implements Comparable<BridgingInfo> { public final Example ex; public final Callable c; public final BinaryFormulaInfo bInfo; public final boolean headFirst; public final Derivation headDeriv; public final Derivation modifierDeriv; public BridgingInfo(Example ex, Callable c, BinaryFormulaInfo bInfo, boolean headFirst, Derivation headDeriv, Derivation modifierDeriv) { this.ex = ex; this.c = c; this.bInfo = bInfo; this.headFirst = headFirst; this.headDeriv = headDeriv; this.modifierDeriv = modifierDeriv; } @Override public int compareTo(BridgingInfo o) { return fbFormulaInfo.compare(this.bInfo.formula, o.bInfo.formula); } } }