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.UnaryFormulaInfo;
import edu.stanford.nlp.sempre.freebase.lexicons.EntrySource;
import edu.stanford.nlp.sempre.freebase.lexicons.LexicalEntry.LexiconValue;
import edu.stanford.nlp.sempre.freebase.lexicons.LexicalEntry.UnaryLexicalEntry;
import fig.basic.LogInfo;
import fig.basic.MapUtils;
import fig.basic.Option;
import fig.basic.StopWatchSet;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;
/**
* lexicon for unaries: "city"-->fb:location.citytown
* Loads the lexicon from a file and has some features
*/
public final class UnaryLexicon {
public static class Options {
@Option(gloss = "Number of results return by the lexicon")
public int maxEntries = 1000;
@Option(gloss = "Path to unary lexicon file")
public String unaryLexiconFilePath = "lib/fb_data/7/unaryInfoStringAndAlignment.txt";
@Option(gloss = "Threshold for filtering unaries")
public int unaryFilterThreshold = 5;
@Option(gloss = "Verbosity") public int verbose = 0;
}
public static Options opts = new Options();
private static UnaryLexicon unaryLexicon;
public static UnaryLexicon getInstance() {
if (unaryLexicon == null) unaryLexicon = new UnaryLexicon();
return unaryLexicon;
}
private Map<String, List<UnaryLexicalEntry>> lexemeToEntryList = new HashMap<>();
public static final String INTERSECTION = "intersection";
public static final String NL_SIZE = "nl_size";
public static final String FB_SIZE = "fb_size";
private UnaryLexicon() {
if (Strings.isNullOrEmpty(opts.unaryLexiconFilePath))
throw new RuntimeException("Missing unary lexicon file");
read();
sortLexiconEntries();
}
private void sortLexiconEntries() {
Comparator<UnaryLexicalEntry> comparator = new UnaryLexicalEntryComparator();
for (List<UnaryLexicalEntry> uEntries: lexemeToEntryList.values()) {
Collections.sort(uEntries, comparator);
}
}
private void read() {
LogInfo.begin_track("Loading unary lexicon file " + opts.unaryLexiconFilePath);
for (String line : IOUtils.readLines(opts.unaryLexiconFilePath)) {
LexiconValue lv = Json.readValueHard(line, LexiconValue.class);
addEntry(lv.lexeme, lv.source, lv.formula, lv.features);
}
LogInfo.log("Number of lexemes: " + lexemeToEntryList.size());
LogInfo.end_track();
}
private void addEntry(String nl, String source, Formula formula, Map<String, Double> featureMap) {
FbFormulasInfo ffi = FbFormulasInfo.getSingleton();
if (ffi.getUnaryInfo(formula) != null) {
UnaryFormulaInfo uInfo = ffi.getUnaryInfo(formula);
UnaryLexicalEntry uEntry = new UnaryLexicalEntry(nl, nl, new TreeSet<>(uInfo.descriptions), formula, EntrySource.parseSourceDesc(source),
uInfo.popularity, new TreeMap<>(featureMap), uInfo.types);
MapUtils.addToList(lexemeToEntryList, nl, uEntry);
} else {
if (opts.verbose >= 3) LogInfo.warnings("Missing info for unary: %s ", formula);
}
}
public void save(String outFile) throws IOException {
PrintWriter writer = IOUtils.getPrintWriter(outFile);
for (String nl : lexemeToEntryList.keySet()) {
for (UnaryLexicalEntry uEntry : lexemeToEntryList.get(nl)) {
LexiconValue lv = new LexiconValue(nl, uEntry.formula, uEntry.source.toString(), uEntry.alignmentScores);
writer.println(Json.writeValueAsStringHard(lv));
}
}
writer.close();
}
public List<UnaryLexicalEntry> lookupEntries(String textDesc) throws IOException {
List<UnaryLexicalEntry> entries = lexemeToEntryList.get(textDesc.toLowerCase());
if (entries != null) {
List<UnaryLexicalEntry> res = new ArrayList<>();
for (int i = 0; i < Math.min(entries.size(), opts.maxEntries); ++i) {
if (valid(entries.get(i)))
res.add(entries.get(i));
}
return res;
}
return Collections.emptyList();
}
/** Checks if an entry is valid (e.g. we filter if intersection is too small) */
private boolean valid(UnaryLexicalEntry lexicalEntry) {
return (lexicalEntry.source != EntrySource.ALIGNMENT ||
MapUtils.getDouble(lexicalEntry.alignmentScores, INTERSECTION, 0.0) >= opts.unaryFilterThreshold);
}
public void sortLexiconByFeedback(Params params) {
StopWatchSet.begin("UnaryLexicon.sortLexiconByFeedback");
LogInfo.log("Number of entries: " + lexemeToEntryList.size());
UnaryLexEntrybyFeaturesComparator comparator = new UnaryLexEntrybyFeaturesComparator(params);
for (String lexeme : lexemeToEntryList.keySet()) {
Collections.sort(lexemeToEntryList.get(lexeme), comparator);
if (LexiconFn.opts.verbose > 0) {
LogInfo.logs("Sorted list for lexeme=%s", lexeme);
for (UnaryLexicalEntry uEntry : lexemeToEntryList.get(lexeme)) {
FeatureVector fv = new FeatureVector();
LexiconFn.getUnaryEntryFeatures(uEntry, fv);
LogInfo.logs("Entry=%s, dotprod=%s", uEntry, fv.dotProduct(comparator.params));
}
}
}
StopWatchSet.end();
}
public class UnaryLexEntrybyFeaturesComparator implements Comparator<UnaryLexicalEntry> {
public final Params params;
public UnaryLexEntrybyFeaturesComparator(Params params) {
this.params = params;
}
@Override
public int compare(UnaryLexicalEntry entry1, UnaryLexicalEntry entry2) {
FeatureVector features1 = new FeatureVector();
FeatureVector features2 = new FeatureVector();
LexiconFn.getUnaryEntryFeatures(entry1, features1);
LexiconFn.getUnaryEntryFeatures(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 entry1Intersection = MapUtils.getDouble(entry1.alignmentScores, INTERSECTION, 0.0);
double entry2Intersection = MapUtils.getDouble(entry2.alignmentScores, INTERSECTION, 0.0);
if (entry1Intersection > entry2Intersection)
return -1;
if (entry1Intersection < entry2Intersection)
return 1;
if (entry1.popularity > entry2.popularity)
return -1;
if (entry1.popularity < entry2.popularity)
return 1;
return 0;
}
}
public static class UnaryLexicalEntryComparator implements Comparator<UnaryLexicalEntry> {
public static final String INTERSECTION = "intersection";
@Override
public int compare(UnaryLexicalEntry entry1, UnaryLexicalEntry entry2) {
double entry1Intersection = MapUtils.getDouble(entry1.alignmentScores, INTERSECTION, 0.0);
double entry2Intersection = MapUtils.getDouble(entry2.alignmentScores, INTERSECTION, 0.0);
if (entry1Intersection > entry2Intersection)
return -1;
if (entry1Intersection < entry2Intersection)
return 1;
if (entry1.popularity > entry2.popularity)
return -1;
if (entry1.popularity < entry2.popularity)
return 1;
// todo - 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;
}
}
}