package edu.stanford.nlp.sempre.tables.features; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.google.common.base.Function; import edu.stanford.nlp.sempre.*; import edu.stanford.nlp.sempre.tables.ScopedFormula; import edu.stanford.nlp.sempre.tables.serialize.LazyLoadedExampleList; import fig.basic.LogInfo; import fig.basic.Option; /** * Extract features of the form (n-gram, logical form fragment) * * @author ppasupat */ public class PhraseFragmentFeatureComputer implements FeatureComputer { public static class Options { @Option(gloss = "verbosity") public int verbose = 0; @Option(gloss = "number of recursion levels") public int recursionLevels = 1; @Option(gloss = "Define unlexicalized phrase-fragment features") public boolean unlexicalizedPhraseFragment = true; @Option(gloss = "Define lexicalized phrase-fragment features") public boolean lexicalizedPhraseFragment = true; @Option(gloss = "Forbid type of predicate (%row.row) that comes alone") public boolean forbidSingletonPredicateType = true; } public static Options opts = new Options(); @Override public void extractLocal(Example ex, Derivation deriv) { if (!FeatureExtractor.containsDomain("phrase-fragment")) return; List<PhraseInfo> phraseInfos = PhraseInfo.getPhraseInfos(ex); extractPhraseFragment(ex, deriv, phraseInfos); } private void extractPhraseFragment(Example ex, Derivation deriv, List<PhraseInfo> phraseInfos) { Formula formula = deriv.formula; if (opts.verbose >= 1) { List<Formula> children = new ArrayList<>(); for (Derivation child : deriv.children) children.add(child.formula); LogInfo.logs("%s => %s", formula, children); } // For formulas from SerializedParser, we need to define all features in one pass if (LazyLoadedExampleList.SERIALIZED_ROOT.equals(deriv.cat)) { extractPhraseFragment(ex, deriv, phraseInfos, formula, opts.recursionLevels, "<R>@ROOT"); formula.forEach(new Function<Formula, Boolean>() { @Override public Boolean apply(Formula subformula) { if (!(subformula instanceof ReverseFormula || subformula instanceof LambdaFormula)) extractPhraseFragment(ex, deriv, phraseInfos, subformula, opts.recursionLevels, "<R>"); return false; } }); return; } // Special for ROOT if (Rule.rootCat.equals(deriv.cat)) { extractPhraseFragment(ex, deriv, phraseInfos, formula, opts.recursionLevels, "<R>@ROOT"); } // Check type raising (formula == the single child); do not define features here if (deriv.children.size() == 1 && formula.equals(deriv.children.get(0).formula)) { if (opts.verbose >= 1) LogInfo.logs("TYPE RAISE [%s]: %s", deriv.rule, formula); return; } // Define features based on the formula type extractPhraseFragment(ex, deriv, phraseInfos, formula, opts.recursionLevels, "<R>"); } private void extractPhraseFragment(Example ex, Derivation deriv, List<PhraseInfo> phraseInfos, Formula formula, int level, String fragment, Value... placeholders) { if (level < 0) throw new RuntimeException("Level = " + level + " < 0"); // Look at the formula type int N = placeholders.length; if (formula instanceof ValueFormula) { Value value = ((ValueFormula<?>) formula).value; if (value instanceof NumberValue) { if (!opts.forbidSingletonPredicateType || !"<R>".equals(fragment)) extractPhraseFragment(ex, deriv, phraseInfos, R(fragment, "(number *)"), placeholders); extractPhraseFragment(ex, deriv, phraseInfos, R(fragment, "#" + N), C(placeholders, value)); } else if (value instanceof DateValue) { if (!opts.forbidSingletonPredicateType || !"<R>".equals(fragment)) extractPhraseFragment(ex, deriv, phraseInfos, R(fragment, "(date *)"), placeholders); extractPhraseFragment(ex, deriv, phraseInfos, R(fragment, "#" + N), C(placeholders, value)); } else if (value instanceof NameValue) { if (!opts.forbidSingletonPredicateType || !"<R>".equals(fragment)) extractPhraseFragment(ex, deriv, phraseInfos, R(fragment, getType((NameValue) value)), placeholders); extractPhraseFragment(ex, deriv, phraseInfos, R(fragment, "#" + N), C(placeholders, value)); } } else if (formula instanceof JoinFormula) { JoinFormula join = (JoinFormula) formula; Formula relation = join.relation; if (relation instanceof ReverseFormula && ((ReverseFormula) relation).child instanceof ValueFormula) { Value relationValue = ((ValueFormula<?>) ((ReverseFormula) relation).child).value; extractPhraseFragment(ex, deriv, phraseInfos, R(fragment, "(!#" + N + " *)"), C(placeholders, relationValue)); if (level > 0) extractPhraseFragment(ex, deriv, phraseInfos, join.child, level - 1, R(fragment, "(!#" + N + " <R>)"), C(placeholders, relationValue)); } else if (relation instanceof ValueFormula) { Value relationValue = ((ValueFormula<?>) relation).value; extractPhraseFragment(ex, deriv, phraseInfos, R(fragment, "(#" + N + " *)"), C(placeholders, relationValue)); if (level > 0) extractPhraseFragment(ex, deriv, phraseInfos, join.child, level - 1, R(fragment, "(#" + N + " <R>)"), C(placeholders, relationValue)); } else { throw new RuntimeException("[Phrase-Fragment] Unrecognized JoinFormula: " + formula); } } else if (formula instanceof MergeFormula) { MergeFormula merge = (MergeFormula) formula; extractPhraseFragment(ex, deriv, phraseInfos, R(fragment, "(" + merge.mode + " * *)"), placeholders); if (level > 0) { extractPhraseFragment(ex, deriv, phraseInfos, merge.child1, level - 1, R(fragment, "(" + merge.mode + " <R> *)"), placeholders); extractPhraseFragment(ex, deriv, phraseInfos, merge.child2, level - 1, R(fragment, "(" + merge.mode + " <R> *)"), placeholders); } } else if (formula instanceof AggregateFormula) { AggregateFormula aggregate = (AggregateFormula) formula; extractPhraseFragment(ex, deriv, phraseInfos, R(fragment, "(" + aggregate.mode + " *)"), placeholders); if (level > 0) extractPhraseFragment(ex, deriv, phraseInfos, aggregate.child, level - 1, R(fragment, "(" + aggregate.mode + " <R>)"), placeholders); } else if (formula instanceof SuperlativeFormula) { SuperlativeFormula superlative = (SuperlativeFormula) formula; extractPhraseFragment(ex, deriv, phraseInfos, R(fragment, "(" + superlative.mode + " * *)"), placeholders); if (level > 0) { extractPhraseFragment(ex, deriv, phraseInfos, superlative.head, level - 1, R(fragment, "(" + superlative.mode + " <R> *)"), placeholders); if (superlative.relation instanceof ReverseFormula && ((ReverseFormula) superlative.relation).child instanceof LambdaFormula) { // (argmax 1 1 * (reverse (lambda (var x) ...))) Formula lambdaBody = ((LambdaFormula) ((ReverseFormula) superlative.relation).child).body; extractPhraseFragment(ex, deriv, phraseInfos, lambdaBody, level - 1, R(fragment, "(" + superlative.mode + " * R<R>)"), placeholders); } else if (superlative.relation instanceof LambdaFormula) { // (argmax 1 1 * (lambda (var x) ...)) Formula lambdaBody = ((LambdaFormula) superlative.relation).body; extractPhraseFragment(ex, deriv, phraseInfos, lambdaBody, level - 1, R(fragment, "(" + superlative.mode + " * L<R>)"), placeholders); } else { // (argmax 1 1 * ...), such as (argmax 1 1 * @index) extractPhraseFragment(ex, deriv, phraseInfos, superlative.relation, level - 1, R(fragment, "(" + superlative.mode + " * <R>)"), placeholders); } } } else if (formula instanceof ArithmeticFormula) { ArithmeticFormula arithmetic = (ArithmeticFormula) formula; extractPhraseFragment(ex, deriv, phraseInfos, R(fragment, "(" + arithmetic.mode + " * *)"), placeholders); if (level > 0) { extractPhraseFragment(ex, deriv, phraseInfos, arithmetic.child1, level - 1, R(fragment, "(" + arithmetic.mode + " <R> *)"), placeholders); extractPhraseFragment(ex, deriv, phraseInfos, arithmetic.child2, level - 1, R(fragment, "(" + arithmetic.mode + " <R> *)"), placeholders); } } else if (formula instanceof VariableFormula) { extractPhraseFragment(ex, deriv, phraseInfos, R(fragment, "%x"), placeholders); } else if (formula instanceof ScopedFormula) { ScopedFormula scoped = (ScopedFormula) formula; if (level > 0) { extractPhraseFragment(ex, deriv, phraseInfos, scoped.head, level - 1, R(fragment, "(scoped <R> *)"), placeholders); if (scoped.relation instanceof LambdaFormula) { Formula lambdaBody = ((LambdaFormula) scoped.relation).body; extractPhraseFragment(ex, deriv, phraseInfos, lambdaBody, level - 1, R(fragment, "(scoped * L<R>)"), placeholders); } else { extractPhraseFragment(ex, deriv, phraseInfos, scoped.relation, level - 1, R(fragment, "(scoped * <R>)"), placeholders); } } } else if (formula instanceof MarkFormula) { // TODO: Handle mark formula } else if (formula instanceof ReverseFormula) { throw new RuntimeException("[Phrase-Fragment] ReverseFormula not handled: " + formula); } else if (formula instanceof LambdaFormula) { throw new RuntimeException("[Phrase-Fragment] LambdaFormula not handled: " + formula); } else { throw new RuntimeException("[Phrase-Fragment] Cannot handle formula " + formula); } } private String R(String fragment, String recursedFragment) { return fragment.replace("<R>", recursedFragment); } private Value[] C(Value[] oldValues, Value newValue) { Value[] combined = new Value[oldValues.length + 1]; for (int i = 0; i < oldValues.length; i++) combined[i] = oldValues[i]; combined[oldValues.length] = newValue; return combined; } private static final Pattern UNARY_PATTERN = Pattern.compile("^fb:([^.]*)\\.[^.]*$"); private static final Pattern BINARY_PATTERN = Pattern.compile("^fb:([^.]*\\.[^.]*)\\.[^.]*$"); private String getType(NameValue name) { String id = name.id; if (CanonicalNames.COMPARATORS.contains(id)) return "%COMP"; if (CanonicalNames.COLON.equals(id)) return "%COLON"; if (CanonicalNames.isUnary(id)) { Matcher matcher = UNARY_PATTERN.matcher(id); if (matcher.matches()) return "%" + matcher.group(1); } else if (CanonicalNames.isBinary(id)) { Matcher matcher = BINARY_PATTERN.matcher(id); if (matcher.matches()) return "%" + matcher.group(1); } throw new RuntimeException("[getType] Unhandled NameValue: " + name); } private void extractPhraseFragment(Example ex, Derivation deriv, List<PhraseInfo> phraseInfos, String fragment, Value... placeholders) { if (opts.verbose >= 1) LogInfo.logs(">>> %s %s", fragment, Arrays.asList(placeholders)); String[] placeholderStrings = new String[placeholders.length], originalStrings = new String[placeholders.length]; for (int i = 0; i < placeholders.length; i++) { Value placeholder = placeholders[i]; if (placeholder instanceof NameValue) { placeholderStrings[i] = ((NameValue) placeholder).id.replace("fb:", ""); originalStrings[i] = PredicateInfo.getOriginalString(((NameValue) placeholder).id, ex); } else if (placeholder instanceof NumberValue) { originalStrings[i] = placeholderStrings[i] = "" + ((NumberValue) placeholder).value; } else if (placeholder instanceof DateValue) { originalStrings[i] = placeholderStrings[i] = ((DateValue) placeholder).isoString(); } else { throw new RuntimeException("[placeholder] Unhandled Value: " + placeholder); } } String defaultFragment = fragment; for (int i = 0; i < placeholderStrings.length; i++) { defaultFragment = defaultFragment.replace("#" + i, placeholderStrings[i]); } String[] placeholderStringsUnlex = new String[placeholders.length]; for (PhraseInfo phraseInfo : phraseInfos) { if (opts.lexicalizedPhraseFragment) deriv.addFeature("p-f", phraseInfo.lemmaText + ";" + defaultFragment); if (opts.unlexicalizedPhraseFragment) { boolean matched = false; for (int i = 0; i < placeholders.length; i++) { Value placeholder = placeholders[i]; if (placeholder instanceof NameValue) { if (originalStrings[i] != null && originalStrings[i].equals(phraseInfo.lemmaText)) { placeholderStringsUnlex[i] = "$"; matched = true; } else { placeholderStringsUnlex[i] = placeholderStrings[i]; } } else if (placeholder instanceof NumberValue || placeholder instanceof DateValue) { if (originalStrings[i] != null && originalStrings[i].equals(phraseInfo.normalizedNerSpan)) { placeholderStringsUnlex[i] = "$"; matched = true; } else { placeholderStringsUnlex[i] = placeholderStrings[i]; } } else { throw new RuntimeException("[placeholder] Unhandled Value: " + placeholder); } } if (matched) { String unlexFragment = fragment; for (int i = 0; i < placeholderStrings.length; i++) { unlexFragment = unlexFragment.replace("#" + i, placeholderStringsUnlex[i]); } deriv.addFeature("p-fu", unlexFragment); } } } } }