package edu.stanford.nlp.sempre;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Joiner;
import com.google.common.collect.Sets;
import fig.basic.Evaluation;
import fig.basic.LispTree;
import fig.basic.LogInfo;
import java.util.*;
/**
* An example corresponds roughly to an input-output pair, the basic unit which
* we make predictions on. The Example object stores both the input,
* preprocessing, and output of the parser.
*
* @author Percy Liang
* @author Roy Frostig
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Example {
//// Information from the input file.
// Unique identifier for this example.
@JsonProperty public final String id;
// Input utterance
@JsonProperty public final String utterance;
// Context
@JsonProperty public ContextValue context;
// What we should try to predict.
@JsonProperty public Formula targetFormula; // Logical form (e.g., database query)
public List<Formula> alternativeFormulas; // Alternative logical form (less canonical)
@JsonProperty public Value targetValue; // Denotation (e.g., answer)
//// Information after preprocessing (e.g., tokenization, POS tagging, NER, syntactic parsing, etc.).
public LanguageInfo languageInfo = null;
//// Output of the parser.
// Predicted derivations (sorted by score).
public List<Derivation> predDerivations;
// Temporary state while parsing an Example (see Derivation.java for analogous structure).
private Map<String, Object> tempState;
// Statistics relating to processing the example.
public Evaluation evaluation;
public static class Builder {
private String id;
private String utterance;
private ContextValue context;
private Formula targetFormula;
private Value targetValue;
private LanguageInfo languageInfo;
public Builder setId(String id) { this.id = id; return this; }
public Builder setUtterance(String utterance) { this.utterance = utterance; return this; }
public Builder setContext(ContextValue context) { this.context = context; return this; }
public Builder setTargetFormula(Formula targetFormula) { this.targetFormula = targetFormula; return this; }
public Builder setTargetValue(Value targetValue) { this.targetValue = targetValue; return this; }
public Builder setLanguageInfo(LanguageInfo languageInfo) { this.languageInfo = languageInfo; return this; }
public Builder withExample(Example ex) {
setId(ex.id);
setUtterance(ex.utterance);
setContext(ex.context);
setTargetFormula(ex.targetFormula);
setTargetValue(ex.targetValue);
return this;
}
public Example createExample() {
return new Example(id, utterance, context, targetFormula, targetValue, languageInfo);
}
}
@JsonCreator
public Example(@JsonProperty("id") String id,
@JsonProperty("utterance") String utterance,
@JsonProperty("context") ContextValue context,
@JsonProperty("targetFormula") Formula targetFormula,
@JsonProperty("targetValue") Value targetValue,
@JsonProperty("languageInfo") LanguageInfo languageInfo) {
this.id = id;
this.utterance = utterance;
this.context = context;
this.targetFormula = targetFormula;
this.targetValue = targetValue;
this.languageInfo = languageInfo;
}
// Accessors
public String getId() { return id; }
public String getUtterance() { return utterance; }
public int numTokens() { return languageInfo.tokens.size(); }
public List<Derivation> getPredDerivations() { return predDerivations; }
public void setContext(ContextValue context) { this.context = context; }
public void setTargetFormula(Formula targetFormula) { this.targetFormula = targetFormula; }
public void setAlternativeFormulas(List<Formula> alternativeFormulas) { this.alternativeFormulas = alternativeFormulas; }
public void addAlternativeFormula(Formula alternativeFormula) {
if (this.alternativeFormulas == null)
this.alternativeFormulas = new ArrayList<>();
this.alternativeFormulas.add(alternativeFormula);
}
public void setTargetValue(Value targetValue) { this.targetValue = targetValue; }
public String spanString(int start, int end) {
return String.format("%d:%d[%s]", start, end, start != -1 ? phraseString(start, end) : "...");
}
public String phraseString(int start, int end) {
return Joiner.on(' ').join(languageInfo.tokens.subList(start, end));
}
// Return a string representing the tokens between start and end.
public List<String> getTokens() { return languageInfo.tokens; }
public List<String> getLemmaTokens() { return languageInfo.lemmaTokens; }
public String token(int i) { return languageInfo.tokens.get(i); }
public String lemmaToken(int i) { return languageInfo.lemmaTokens.get(i); }
public String posTag(int i) { return languageInfo.posTags.get(i); }
public String phrase(int start, int end) { return languageInfo.phrase(start, end); }
public String lemmaPhrase(int start, int end) { return languageInfo.lemmaPhrase(start, end); }
public String toJson() { return Json.writeValueAsStringHard(this); }
public static Example fromJson(String json) { return Json.readValueHard(json, Example.class); }
public static Example 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 ("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)) {
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();
for (int i = 1; i < tree.children.size(); i++) {
LispTree arg = tree.child(i);
String label = arg.child(0).value;
if ("tokens".equals(label)) {
for (LispTree child : arg.child(1).children)
ex.languageInfo.tokens.add(child.value);
} else if ("lemmaTokens".equals(label)) {
for (LispTree child : arg.child(1).children)
ex.languageInfo.lemmaTokens.add(child.value);
} else if ("posTags".equals(label)) {
for (LispTree child : arg.child(1).children)
ex.languageInfo.posTags.add(child.value);
} else if ("nerTags".equals(label)) {
for (LispTree child : arg.child(1).children)
ex.languageInfo.nerTags.add(child.value);
} else if ("nerValues".equals(label)) {
for (LispTree child : arg.child(1).children)
ex.languageInfo.nerValues.add("null".equals(child.value) ? null : child.value);
} else if ("alternativeFormula".equals(label)) {
ex.addAlternativeFormula(Formulas.fromLispTree(arg.child(1)));
} else if ("evaluation".equals(label)) {
ex.evaluation = Evaluation.fromLispTree(arg.child(1));
} else if ("predDerivations".equals(label)) {
// Featurized
ex.predDerivations = new ArrayList<>();
for (int j = 1; j < arg.children.size(); j++)
ex.predDerivations.add(derivationFromLispTree(arg.child(j)));
} else if ("rawDerivations".equals(label) || "derivations".equals(label)) {
// Unfeaturized
ex.predDerivations = new ArrayList<>();
for (int j = 1; j < arg.children.size(); j++)
ex.predDerivations.add(rawDerivationFromLispTree(arg.child(j)));
} else if (!Sets.newHashSet("id", "utterance", "targetFormula", "targetValue", "targetValues", "context", "original").contains(label)) {
throw new RuntimeException("Invalid example argument: " + arg);
}
}
return ex;
}
public void preprocess() {
this.languageInfo = LanguageAnalyzer.getSingleton().analyze(this.utterance);
this.targetValue = TargetValuePreprocessor.getSingleton().preprocess(this.targetValue);
}
public void log() {
LogInfo.begin_track("Example: %s", utterance);
LogInfo.logs("Tokens: %s", getTokens());
LogInfo.logs("Lemmatized tokens: %s", getLemmaTokens());
LogInfo.logs("POS tags: %s", languageInfo.posTags);
LogInfo.logs("NER tags: %s", languageInfo.nerTags);
LogInfo.logs("NER values: %s", languageInfo.nerValues);
if (context != null)
LogInfo.logs("context: %s", context);
if (targetFormula != null)
LogInfo.logs("targetFormula: %s", targetFormula);
if (targetValue != null)
LogInfo.logs("targetValue: %s", targetValue);
LogInfo.logs("Dependency children: %s", languageInfo.dependencyChildren);
LogInfo.end_track();
}
public void logWithoutContext() {
LogInfo.begin_track("Example: %s", utterance);
LogInfo.logs("Tokens: %s", getTokens());
LogInfo.logs("Lemmatized tokens: %s", getLemmaTokens());
LogInfo.logs("POS tags: %s", languageInfo.posTags);
LogInfo.logs("NER tags: %s", languageInfo.nerTags);
LogInfo.logs("NER values: %s", languageInfo.nerValues);
if (targetFormula != null)
LogInfo.logs("targetFormula: %s", targetFormula);
if (targetValue != null)
LogInfo.logs("targetValue: %s", targetValue);
LogInfo.logs("Dependency children: %s", languageInfo.dependencyChildren);
LogInfo.end_track();
}
public List<Derivation> getCorrectDerivations() {
List<Derivation> res = new ArrayList<>();
for (Derivation deriv : predDerivations) {
if (deriv.compatibility == Double.NaN)
throw new RuntimeException("Compatibility is not set");
if (deriv.compatibility > 0)
res.add(deriv);
}
return res;
}
public LispTree toLispTree(boolean outputPredDerivations) {
LispTree tree = LispTree.proto.newList();
tree.addChild("example");
if (id != null)
tree.addChild(LispTree.proto.newList("id", id));
if (utterance != null)
tree.addChild(LispTree.proto.newList("utterance", utterance));
if (targetFormula != null)
tree.addChild(LispTree.proto.newList("targetFormula", targetFormula.toLispTree()));
if (targetValue != null)
tree.addChild(LispTree.proto.newList("targetValue", targetValue.toLispTree()));
if (languageInfo != null) {
if (languageInfo.tokens != null)
tree.addChild(LispTree.proto.newList("tokens", LispTree.proto.newList(languageInfo.tokens)));
if (languageInfo.posTags != null)
tree.addChild(LispTree.proto.newList("posTags", Joiner.on(' ').join(languageInfo.posTags)));
if (languageInfo.nerTags != null)
tree.addChild(LispTree.proto.newList("nerTags", Joiner.on(' ').join(languageInfo.nerTags)));
}
if (evaluation != null)
tree.addChild(LispTree.proto.newList("evaluation", evaluation.toLispTree()));
if (predDerivations != null && outputPredDerivations) {
LispTree list = LispTree.proto.newList();
list.addChild("predDerivations");
for (Derivation deriv : predDerivations)
list.addChild(derivationToLispTree(deriv));
tree.addChild(list);
}
return tree;
}
/**
* Parse a featurized derivation.
*
* Format:
* ({compatibility} {prob} {score} {value|null} {formula} {features})
* where {features} = (({key} {value}) ({key} {value}) ...)
*/
public static Derivation derivationFromLispTree(LispTree item) {
Derivation.Builder b = new Derivation.Builder()
.cat(Rule.rootCat)
.start(-1)
.end(-1)
.rule(Rule.nullRule)
.children(new ArrayList<Derivation>());
int i = 0;
b.compatibility(Double.parseDouble(item.child(i++).value));
b.prob(Double.parseDouble(item.child(i++).value));
b.score(Double.parseDouble(item.child(i++).value));
LispTree valueTree = item.child(i++);
if (!valueTree.isLeaf() || !"null".equals(valueTree.value))
b.value(Values.fromLispTree(valueTree));
b.formula(Formulas.fromLispTree(item.child(i++)));
FeatureVector fv = new FeatureVector();
LispTree features = item.child(i++);
for (int j = 0; j < features.children.size(); j++)
fv.addFromString(features.child(j).child(0).value, Double.parseDouble(features.child(j).child(1).value));
b.localFeatureVector(fv);
return b.createDerivation();
}
public static LispTree derivationToLispTree(Derivation deriv) {
LispTree item = LispTree.proto.newList();
item.addChild(deriv.compatibility + "");
item.addChild(deriv.prob + "");
item.addChild(deriv.score + "");
if (deriv.value != null)
item.addChild(deriv.value.toLispTree());
else
item.addChild("null");
item.addChild(deriv.formula.toLispTree());
HashMap<String, Double> features = new HashMap<>();
deriv.incrementAllFeatureVector(1, features);
item.addChild(LispTree.proto.newList(features));
return item;
}
/**
* Parse a LispTree with the format created by deriv.toLispTree().
* Due to the complexity, rules and children are not parsed.
*
* Format:
* (derivation [(formula {formula})] [(value {value})] [(type {type})]
* [(canonicalUtterance {canonicalUtterance})])
* @param item
* @return
*/
public static Derivation rawDerivationFromLispTree(LispTree item) {
Derivation.Builder b = new Derivation.Builder()
.cat(Rule.rootCat)
.start(-1).end(-1)
.rule(Rule.nullRule)
.children(new ArrayList<Derivation>());
for (int i = 1; i < item.children.size(); i++) {
LispTree arg = item.child(i);
String label = arg.child(0).value;
if ("formula".equals(label)) {
b.formula(Formulas.fromLispTree(arg.child(1)));
} else if ("value".equals(label)) {
b.value(Values.fromLispTree(arg.child(1)));
} else if ("type".equals(label)) {
b.type(SemType.fromLispTree(arg.child(1)));
} else if ("canonicalUtterance".equals(label)) {
b.canonicalUtterance(arg.child(1).value);
} else {
throw new RuntimeException("Invalid example argument: " + arg);
}
}
return b.createDerivation();
}
public static LispTree rawDerivationToLispTree(Derivation deriv) {
return deriv.toLispTree();
}
public Map<String, Object> getTempState() {
// Create the tempState if it doesn't exist.
if (tempState == null)
tempState = new HashMap<String, Object>();
return tempState;
}
public void clearTempState() {
tempState = null;
}
/**
* Clean up things to save memory
*/
public void clean() {
predDerivations.clear();
if (context.graph != null)
context.graph.clean();
}
}