package edu.stanford.nlp.sempre; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.type.TypeReference; import com.google.common.collect.Lists; import fig.basic.*; import fig.exec.Execution; import fig.prob.SampleUtils; import java.io.*; import java.util.*; /** * A dataset contains a set of examples, which are keyed by group (e.g., train, * dev, test). * * @author Percy Liang */ public class Dataset { public static class Options { @Option(gloss = "Paths to read input files (format: <group>:<file>)") public ArrayList<Pair<String, String>> inPaths = new ArrayList<Pair<String, String>>(); @Option(gloss = "Maximum number of examples to read") public ArrayList<Pair<String, Integer>> maxExamples = new ArrayList<Pair<String, Integer>>(); // Training file gets split into: // | trainFrac --> | | <-- devFrac | @Option(gloss = "Fraction of trainExamples (from the beginning) to keep for training") public double trainFrac = 1; @Option(gloss = "Fraction of trainExamples (from the end) to keep for development") public double devFrac = 0; @Option(gloss = "Used to randomly divide training examples") public Random splitRandom = new Random(1); @Option(gloss = "whether to split dev from train") public boolean splitDevFromTrain = true; @Option(gloss = "Only keep examples which have at most this number of tokens") public int maxTokens = Integer.MAX_VALUE; @Option(gloss = "Path to a knowledge graph that will be uploaded as global context") public String globalGraphPath; } public static Options opts = new Options(); // Group id -> examples in that group private LinkedHashMap<String, List<Example>> allExamples = new LinkedHashMap<String, List<Example>>(); // General statistics about the examples. private final HashSet<String> tokenTypes = new HashSet<String>(); private final StatFig numTokensFig = new StatFig(); // For each example, number of tokens public Set<String> groups() { return allExamples.keySet(); } public List<Example> examples(String group) { return allExamples.get(group); } /** For JSON. */ static class GroupInfo { @JsonProperty final String group; @JsonProperty final List<Example> examples; String path; // Optional, used if this was read from a path. @JsonCreator public GroupInfo(@JsonProperty("group") String group, @JsonProperty("examples") List<Example> examples) { this.group = group; this.examples = examples; } } /** For JSON. */ @JsonProperty("groups") public List<GroupInfo> getAllGroupInfos() { List<GroupInfo> all = Lists.newArrayList(); for (Map.Entry<String, List<Example>> entry : allExamples.entrySet()) all.add(new GroupInfo(entry.getKey(), entry.getValue())); return all; } /** For JSON. */ // Allows us to creates dataset from arbitrary JSON, not requiring a // path from which to read. @JsonCreator public static Dataset fromGroupInfos(@JsonProperty("groups") List<GroupInfo> groups) { Dataset d = new Dataset(); d.readFromGroupInfos(groups); return d; } public void read() { readFromPathPairs(opts.inPaths); } public void readFromPathPairs(List<Pair<String, String>> pathPairs) { // Try to detect whether we need JSON. for (Pair<String, String> pathPair : pathPairs) { if (pathPair.getSecond().endsWith(".json")) { readJsonFromPathPairs(pathPairs); return; } } readLispTreeFromPathPairs(pathPairs); updateGlobalContext(); } private void updateGlobalContext() { if (opts.globalGraphPath != null) { KnowledgeGraph graph = NaiveKnowledgeGraph.fromFile(opts.globalGraphPath); for (String group : allExamples.keySet()) { for (Example ex : allExamples.get(group)) { ex.setContext(new ContextValue(graph)); } } } } private void readJsonFromPathPairs(List<Pair<String, String>> pathPairs) { List<GroupInfo> groups = Lists.newArrayListWithCapacity(pathPairs.size()); for (Pair<String, String> pathPair : pathPairs) { String group = pathPair.getFirst(); String path = pathPair.getSecond(); List<Example> examples = Json.readValueHard( IOUtils.openInHard(path), new TypeReference<List<Example>>() { }); GroupInfo gi = new GroupInfo(group, examples); gi.path = path; groups.add(gi); } readFromGroupInfos(groups); } private void readFromGroupInfos(List<GroupInfo> groupInfos) { LogInfo.begin_track_printAll("Dataset.read"); for (GroupInfo groupInfo : groupInfos) { int maxExamples = getMaxExamplesForGroup(groupInfo.group); List<Example> examples = allExamples.get(groupInfo.group); if (examples == null) allExamples.put(groupInfo.group, examples = new ArrayList<Example>()); readHelper(groupInfo.examples, maxExamples, examples, groupInfo.path); } if (opts.splitDevFromTrain) splitDevFromTrain(); collectStats(); LogInfo.end_track(); } private void splitDevFromTrain() { // Split original training examples randomly into train and dev. List<Example> origTrainExamples = allExamples.get("train"); if (origTrainExamples != null) { int split1 = (int) (opts.trainFrac * origTrainExamples.size()); int split2 = (int) ((1 - opts.devFrac) * origTrainExamples.size()); int[] perm = SampleUtils.samplePermutation(opts.splitRandom, origTrainExamples.size()); List<Example> trainExamples = new ArrayList<Example>(); allExamples.put("train", trainExamples); List<Example> devExamples = allExamples.get("dev"); if (devExamples == null) { // Preserve order LinkedHashMap<String, List<Example>> newAllExamples = new LinkedHashMap<>(); for (Map.Entry<String, List<Example>> entry : allExamples.entrySet()) { newAllExamples.put(entry.getKey(), entry.getValue()); if (entry.getKey().equals("train")) newAllExamples.put("dev", devExamples = new ArrayList<>()); } allExamples = newAllExamples; } for (int i = 0; i < split1; i++) trainExamples.add(origTrainExamples.get(perm[i])); for (int i = split2; i < origTrainExamples.size(); i++) devExamples.add(origTrainExamples.get(perm[i])); } } private void readHelper(List<Example> incoming, int maxExamples, List<Example> examples, String path) { if (examples.size() >= maxExamples) return; int i = 0; for (Example ex : incoming) { if (examples.size() >= maxExamples) break; if (ex.id == null) { String id = (path != null ? path : "<nopath>") + ":" + i; ex = new Example.Builder().withExample(ex).setId(id).createExample(); } i++; ex.preprocess(); // Skip example if too long if (ex.numTokens() > opts.maxTokens) continue; LogInfo.logs("Example %s (%d): %s => %s", ex.id, examples.size(), ex.getTokens(), ex.targetValue); examples.add(ex); numTokensFig.add(ex.numTokens()); for (String token : ex.getTokens()) tokenTypes.add(token); } } private void readLispTreeFromPathPairs(List<Pair<String, String>> pathPairs) { LogInfo.begin_track_printAll("Dataset.read"); for (Pair<String, String> pathPair : pathPairs) { String group = pathPair.getFirst(); String path = pathPair.getSecond(); int maxExamples = getMaxExamplesForGroup(group); List<Example> examples = allExamples.get(group); if (examples == null) allExamples.put(group, examples = new ArrayList<Example>()); readLispTreeHelper(path, maxExamples, examples); } if (opts.splitDevFromTrain) splitDevFromTrain(); LogInfo.end_track(); } private void readLispTreeHelper(String path, int maxExamples, List<Example> examples) { if (examples.size() >= maxExamples) return; LogInfo.begin_track("Reading %s", path); Iterator<LispTree> trees = LispTree.proto.parseFromFile(path); int n = 0; while (examples.size() < maxExamples && trees.hasNext()) { // Format: (example (id ...) (utterance ...) (targetFormula ...) (targetValue ...)) LispTree tree = trees.next(); if (tree.children.size() < 2 || !"example".equals(tree.child(0).value)) { if ("metadata".equals(tree.child(0).value)) continue; throw new RuntimeException("Invalid example: " + tree); } Example ex = Example.fromLispTree(tree, path + ":" + n); // Specify a default id if it doesn't exist n++; ex.preprocess(); // Skip example if too long if (ex.numTokens() > opts.maxTokens) continue; LogInfo.logs("Example %s (%d): %s => %s", ex.id, examples.size(), ex.getTokens(), ex.targetValue); examples.add(ex); numTokensFig.add(ex.numTokens()); for (String token : ex.getTokens()) tokenTypes.add(token); } LogInfo.end_track(); } private void collectStats() { LogInfo.begin_track_printAll("Dataset stats"); Execution.putLogRec("numTokenTypes", tokenTypes.size()); Execution.putLogRec("numTokensPerExample", numTokensFig); for (Map.Entry<String, List<Example>> e : allExamples.entrySet()) Execution.putLogRec("numExamples." + e.getKey(), e.getValue().size()); LogInfo.end_track(); } public static int getMaxExamplesForGroup(String group) { int maxExamples = Integer.MAX_VALUE; for (Pair<String, Integer> maxPair : opts.maxExamples) if (maxPair.getFirst().equals(group)) maxExamples = maxPair.getSecond(); return maxExamples; } public static void appendExampleToFile(String path, Example ex) { // JSON is an annoying format because we can't just append. // So currently we have to read the entire file in and write it out. List<Example> examples; if (new File(path).exists()) { examples = Json.readValueHard( IOUtils.openInHard(path), new TypeReference<List<Example>>() { }); } else { examples = new ArrayList<Example>(); } examples.add(ex); Json.prettyWriteValueHard(new File(path), examples); } }