package edu.stanford.nlp.sempre.freebase;
import com.google.common.base.Strings;
import edu.stanford.nlp.io.IOUtils;
import edu.stanford.nlp.sempre.*;
import edu.stanford.nlp.sempre.freebase.FbFormulasInfo.BinaryFormulaInfo;
import edu.stanford.nlp.sempre.freebase.lexicons.EntrySource;
import edu.stanford.nlp.sempre.freebase.lexicons.LexicalEntry.BinaryLexicalEntry;
import edu.stanford.nlp.sempre.freebase.lexicons.LexicalEntry.LexiconValue;
import edu.stanford.nlp.sempre.freebase.lexicons.normalizers.EntryNormalizer;
import edu.stanford.nlp.sempre.freebase.lexicons.normalizers.PrepDropNormalizer;
import fig.basic.*;
import fig.exec.Execution;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;
/**
* Lexicon for binary predicates, "born" --> fb:people.person.place_of_birth
* @author jonathanberant
*/
public final class BinaryLexicon {
public static class Options {
@Option(gloss = "Number of results return by the lexicon")
public int maxEntries = 1000;
@Option(gloss = "Path to binary lexicon files")
public String binaryLexiconFilesPath = "lib/fb_data/7/binaryInfoStringAndAlignment.txt";
@Option(gloss = "Verbosity") public int verbose = 0;
@Option(gloss = "Alignment score to sort by")
public String keyToSortBy = INTERSECTION;
}
private static BinaryLexicon binaryLexicon;
public static BinaryLexicon getInstance() {
if (binaryLexicon == null)
try {
binaryLexicon = new BinaryLexicon();
} catch (IOException e) {
throw new RuntimeException(e);
}
return binaryLexicon;
}
public static Options opts = new Options();
private EntryNormalizer lexiconLoadingNormalizer;
private FbFormulasInfo fbFormulasInfo;
public static final String INTERSECTION = "Intersection_size_typed";
Map<String, List<BinaryLexicalEntry>> lexemeToEntryList = new HashMap<>();
private BinaryLexicon() throws IOException {
if (Strings.isNullOrEmpty(opts.binaryLexiconFilesPath))
throw new RuntimeException("Missing unary lexicon file");
fbFormulasInfo = FbFormulasInfo.getSingleton();
// if we omit prepositions then the lexicon normalizer does that, otherwise, it is a normalizer that does nothing
lexiconLoadingNormalizer = new PrepDropNormalizer(); // the alignment lexicon already contains stemmed stuff so just need to drop prepositions
read(opts.binaryLexiconFilesPath);
}
private void read(String lexiconFile) throws IOException {
LogInfo.begin_track_printAll("Loading binary lexicon file " + lexiconFile);
for (String line : IOUtils.readLines(lexiconFile)) {
LexiconValue lv = Json.readValueHard(line, LexiconValue.class);
String lexemeKey = lv.lexeme;
String normalizedLexemeKey = lexiconLoadingNormalizer.normalize(lexemeKey);
// add lexeme and normalized lexeme
addEntryToMap(lexemeKey, lv);
if (!lexemeKey.equals(normalizedLexemeKey)) {
addEntryToMap(normalizedLexemeKey, lv);
}
}
sortLexiconEntries();
LogInfo.log("Number of entries: " + lexemeToEntryList.size());
LogInfo.end_track();
}
public void addEntryToMap(String lexemeKey, LexiconValue lv) {
List<BinaryLexicalEntry> bEntries = buildEntry(lv, lexemeKey);
for (BinaryLexicalEntry bEntry : bEntries)
MapUtils.addToList(lexemeToEntryList, lexemeKey, bEntry);
}
private void sortLexiconEntries() {
for (List<BinaryLexicalEntry> entries: lexemeToEntryList.values()) {
Collections.sort(entries, new BinaryLexEntryByCounterComparator());
}
}
public List<BinaryLexicalEntry> buildEntry(LexiconValue lexValue, String lexemeKey) {
EntrySource source = EntrySource.parseSourceDesc(lexValue.source);
BinaryFormulaInfo info = fbFormulasInfo.getBinaryInfo(lexValue.formula);
if (!validBinaryFormula(lexValue.formula))
return Collections.emptyList();
if (info == null) {
if (opts.verbose >= 3)
LogInfo.log("BinaryLexicon: skipping entry since there is no info for formula: " + lexValue.formula.toString());
return Collections.emptyList();
}
// get alignment features
Map<String, Double> alignmentScores = new TreeMap<>(lexValue.features);
if (fbFormulasInfo.isCvtFormula(info) && source == EntrySource.STRING_MATCH) {
List<BinaryLexicalEntry> entries = new ArrayList<>();
for (BinaryFormulaInfo cvtInfo : fbFormulasInfo.getCvtExpansions(info)) {
entries.add(
new BinaryLexicalEntry(
lexemeKey, lexemeKey, new HashSet<>(cvtInfo.descriptions), cvtInfo.formula, source,
cvtInfo.popularity, cvtInfo.expectedType1, cvtInfo.expectedType2, cvtInfo.unitId, cvtInfo.unitDesc, alignmentScores, lexValue.lexeme));
}
return entries;
} else {
BinaryLexicalEntry entry = new BinaryLexicalEntry(
lexemeKey, lexemeKey, new HashSet<>(info.descriptions), lexValue.formula, source,
info.popularity, info.expectedType1, info.expectedType2, info.unitId, info.unitDesc, alignmentScores, lexValue.lexeme);
return Collections.singletonList(entry);
}
}
public List<BinaryLexicalEntry> lookupEntries(String textDesc) throws IOException {
List<BinaryLexicalEntry> entries = lexemeToEntryList.get(textDesc.toLowerCase());
if (entries != null) {
List<BinaryLexicalEntry> res = new ArrayList<>();
for (int i = 0; i < Math.min(entries.size(), opts.maxEntries); ++i) {
res.add(entries.get(i));
}
return res;
}
return Collections.emptyList();
}
/** If the property has a reverse, keep it if it reversed*/
public boolean validBinaryFormula(Formula formula) {
if (fbFormulasInfo.hasOpposite(formula)) {
boolean valid = fbFormulasInfo.isReversed(formula);
if (opts.verbose >= 3) {
if (!valid)
LogInfo.logs("BinaryLexicon: invalid formula: %s", formula);
else
LogInfo.logs("BinaryLexicon: valid formula: %s", formula);
}
return valid;
}
return true;
}
public void updateLexicon(Pair<String, Formula> lexemeFormulaPair, int support) {
StopWatchSet.begin("BinaryLexicon.updateLexicon");
if (opts.verbose > 0)
LogInfo.logs("Pair=%s, score=%s", lexemeFormulaPair, support);
boolean exists = false;
String lexeme = lexemeFormulaPair.getFirst();
Formula formula = lexemeFormulaPair.getSecond();
List<BinaryLexicalEntry> bEntries = MapUtils.get(lexemeToEntryList, lexeme, Collections.<BinaryLexicalEntry>emptyList());
for (BinaryLexicalEntry bEntry : bEntries) {
if (bEntry.formula.equals(formula)) {
bEntry.alignmentScores.put("Feedback", (double) support);
if (opts.verbose > 0)
LogInfo.logs("Entry exists: %s", bEntry);
exists = true;
break;
}
}
if (!exists) {
BinaryFormulaInfo bInfo = fbFormulasInfo.getBinaryInfo(formula);
if (bInfo == null) {
LogInfo.warnings("BinaryLexicon.updateLexicon: no binary info for %s", formula);
return;
}
BinaryLexicalEntry newEntry =
new BinaryLexicalEntry(
lexeme, lexeme, new HashSet<>(bInfo.descriptions), bInfo.formula, EntrySource.FEEDBACK,
bInfo.popularity, bInfo.expectedType1, bInfo.expectedType2, bInfo.unitId, bInfo.unitDesc, new HashMap<>(), lexeme);
MapUtils.addToList(lexemeToEntryList, lexeme, newEntry);
newEntry.alignmentScores.put("Feedback", (double) support);
LogInfo.logs("Adding new binary entry=%s", newEntry);
}
StopWatchSet.end();
}
public void sortLexiconByFeedback(Params params) {
StopWatchSet.begin("BinaryLexicon.sortLexiconByFeedback");
LogInfo.log("Number of entries: " + lexemeToEntryList.size());
BinaryLexEntrybyFeaturesComparator comparator =
new BinaryLexEntrybyFeaturesComparator(params);
for (String lexeme : lexemeToEntryList.keySet()) {
Collections.sort(lexemeToEntryList.get(lexeme), comparator);
if (opts.verbose > 1) {
LogInfo.logs("Sorted list for lexeme=%s", lexeme);
for (BinaryLexicalEntry bEntry : lexemeToEntryList.get(lexeme)) {
FeatureVector fv = new FeatureVector();
LexiconFn.getBinaryEntryFeatures(bEntry, fv);
LogInfo.logs("Entry=%s, dotprod=%s", bEntry, fv.dotProduct(comparator.params));
}
}
}
try {
// Output the lexicon to the execution directory.
String path = Execution.getFile("lexicon");
if (path != null) {
PrintWriter writer = fig.basic.IOUtils.openOut(path);
for (String lexeme : lexemeToEntryList.keySet()) {
writer.println(lexeme + "\t" + lexemeToEntryList.get(lexeme));
}
writer.flush();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
StopWatchSet.end();
}
public class BinaryLexEntrybyFeaturesComparator implements Comparator<BinaryLexicalEntry> {
public final Params params;
public BinaryLexEntrybyFeaturesComparator(Params params) {
this.params = params;
}
@Override
public int compare(BinaryLexicalEntry entry1, BinaryLexicalEntry entry2) {
FeatureVector features1 = new FeatureVector();
FeatureVector features2 = new FeatureVector();
LexiconFn.getBinaryEntryFeatures(entry1, features1);
LexiconFn.getBinaryEntryFeatures(entry2, features2);
double score1 = features1.dotProduct(params);
double score2 = features2.dotProduct(params);
if (score1 > score2) return -1;
if (score1 < score2) return +1;
// back off to usual thing
double entry1Score = MapUtils.getDouble(entry1.alignmentScores, opts.keyToSortBy, 0.0);
double entry2Score = MapUtils.getDouble(entry2.alignmentScores, opts.keyToSortBy, 0.0);
if (entry1Score > entry2Score)
return -1;
if (entry1Score < entry2Score)
return +1;
if (entry1.popularity > entry2.popularity)
return -1;
if (entry1.popularity < entry2.popularity)
return +1;
return 0;
}
}
public class BinaryLexEntryByCounterComparator implements Comparator<BinaryLexicalEntry> {
@Override
public int compare(BinaryLexicalEntry entry1, BinaryLexicalEntry entry2) {
double entry1Score = MapUtils.getDouble(entry1.alignmentScores, opts.keyToSortBy, 0.0);
double entry2Score = MapUtils.getDouble(entry2.alignmentScores, opts.keyToSortBy, 0.0);
if (entry1Score > entry2Score)
return -1;
if (entry1Score < entry2Score)
return +1;
if (entry1.popularity > entry2.popularity)
return -1;
if (entry1.popularity < entry2.popularity)
return +1;
// to do - this is to break ties - make more efficient
int stringComparison = entry1.formula.toString().compareTo(entry2.formula.toString());
if (stringComparison < 0)
return -1;
if (stringComparison > 0)
return +1;
return 0;
}
}
}