package edu.stanford.nlp.sempre; import com.google.common.base.Splitter; import com.google.common.collect.Lists; import fig.basic.*; import java.io.BufferedReader; import java.io.IOException; import java.io.PrintWriter; import java.util.*; /** * Params contains the parameters of the model. Currently consists of a map from * features to weights. * * @author Percy Liang */ public class Params { public static class Options { @Option(gloss = "By default, all features have this weight") public double defaultWeight = 0; @Option(gloss = "Randomly initialize the weights") public boolean initWeightsRandomly = false; @Option(gloss = "Randomly initialize the weights") public Random initRandom = new Random(1); @Option(gloss = "Initial step size") public double initStepSize = 1; @Option(gloss = "How fast to reduce the step size") public double stepSizeReduction = 0; @Option(gloss = "Use the AdaGrad algorithm (different step size for each coordinate)") public boolean adaptiveStepSize = true; @Option(gloss = "Use dual averaging") public boolean dualAveraging = false; @Option(gloss = "Whether to do lazy l1 reg updates") public String l1Reg = "none"; @Option(gloss = "L1 reg coefficient") public double l1RegCoeff = 0d; @Option(gloss = "Lazy L1 full update frequency") public int lazyL1FullUpdateFreq = 5000; } public static Options opts = new Options(); public enum L1Reg { LAZY, NONLAZY, NONE; } private L1Reg parseReg(String l1Reg) { if ("lazy".equals(l1Reg)) return L1Reg.LAZY; if ("nonlazy".equals(l1Reg)) return L1Reg.NONLAZY; if ("none".equals(l1Reg)) return L1Reg.NONE; throw new RuntimeException("not legal l1reg"); } private L1Reg l1Reg = parseReg(opts.l1Reg); // Discriminative weights private Map<String, Double> weights = new HashMap<>(); // For AdaGrad Map<String, Double> sumSquaredGradients = new HashMap<>(); // For dual averaging Map<String, Double> sumGradients = new HashMap<>(); // Number of stochastic updates we've made so far (for determining step size). int numUpdates; // for lazy l1-reg update Map<String, Integer> l1UpdateTimeMap = new HashMap<>(); // Initialize the weights public void init(List<Pair<String, Double>> initialization) { if (!weights.isEmpty()) throw new RuntimeException("Initialization is not legal when there are non-zero weights"); for (Pair<String, Double> pair: initialization) weights.put(pair.getFirst(), pair.getSecond()); } // Read parameters from |path|. public void read(String path) { LogInfo.begin_track("Reading parameters from %s", path); try { BufferedReader in = IOUtils.openIn(path); String line; while ((line = in.readLine()) != null) { String[] pair = Lists.newArrayList(Splitter.on('\t').split(line)).toArray(new String[2]); weights.put(pair[0], Double.parseDouble(pair[1])); } in.close(); } catch (IOException e) { throw new RuntimeException(e); } LogInfo.logs("Read %s weights", weights.size()); LogInfo.end_track(); } // Read parameters from |path|. public void read(String path, String prefix) { LogInfo.begin_track("Reading parameters from %s", path); try { BufferedReader in = IOUtils.openIn(path); String line; while ((line = in.readLine()) != null) { String[] pair = Lists.newArrayList(Splitter.on('\t').split(line)).toArray(new String[2]); weights.put(pair[0], Double.parseDouble(pair[1])); weights.put(prefix + pair[0], Double.parseDouble(pair[1])); } in.close(); } catch (IOException e) { throw new RuntimeException(e); } LogInfo.logs("Read %s weights", weights.size()); LogInfo.end_track(); } // Update weights by adding |gradient| (modified appropriately with step size). public synchronized void update(Map<String, Double> gradient) { for (Map.Entry<String, Double> entry : gradient.entrySet()) { String f = entry.getKey(); double g = entry.getValue(); if (g * g == 0) continue; // In order to not divide by zero if (l1Reg == L1Reg.LAZY) lazyL1Update(f); double stepSize = computeStepSize(f, g); if (opts.dualAveraging) { if (!opts.adaptiveStepSize && opts.stepSizeReduction != 0) throw new RuntimeException("Dual averaging not supported when " + "step-size changes across iterations for " + "features for which the gradient is zero"); MapUtils.incr(sumGradients, f, g); MapUtils.set(weights, f, stepSize * sumGradients.get(f)); } else { if (stepSize * g == Double.POSITIVE_INFINITY || stepSize * g == Double.NEGATIVE_INFINITY) { LogInfo.logs("WEIRD FEATURE UPDATE: feature=%s, currentWeight=%s, stepSize=%s, gradient=%s", f, getWeight(f), stepSize, g); throw new RuntimeException("Gradient absolute value is too large or too small"); } MapUtils.incr(weights, f, stepSize * g); if (l1Reg == L1Reg.LAZY) l1UpdateTimeMap.put(f, numUpdates); } } // non lazy implementation goes over all weights if (l1Reg == L1Reg.NONLAZY) { Set<String> features = new HashSet<String>(weights.keySet()); for (String f : features) { double stepSize = computeStepSize(f, 0d); // no update for gradient here double update = opts.l1RegCoeff * -Math.signum(MapUtils.getDouble(weights, f, opts.defaultWeight)); clipUpdate(f, stepSize * update); } } numUpdates++; if (l1Reg == L1Reg.LAZY && opts.lazyL1FullUpdateFreq > 0 && numUpdates % opts.lazyL1FullUpdateFreq == 0) { LogInfo.begin_track("Fully apply L1 regularization."); finalizeWeights(); System.gc(); LogInfo.end_track(); } } private double computeStepSize(String feature, double gradient) { if (opts.adaptiveStepSize) { MapUtils.incr(sumSquaredGradients, feature, gradient * gradient); // ugly - adding one to the denominator when using l1 reg. if (l1Reg != L1Reg.NONE) return opts.initStepSize / (Math.sqrt(sumSquaredGradients.get(feature) + 1)); else return opts.initStepSize / Math.sqrt(sumSquaredGradients.get(feature)); } else { return opts.initStepSize / Math.pow(numUpdates, opts.stepSizeReduction); } } /* * If the update changes the sign, remove the feature */ private void clipUpdate(String f, double update) { double currWeight = MapUtils.getDouble(weights, f, 0); if (currWeight == 0) return; if (currWeight * (currWeight + update) < 0.0) { weights.remove(f); } else { MapUtils.incr(weights, f, update); } } private void lazyL1Update(String f) { if (MapUtils.getDouble(weights, f, 0.0) == 0) return; // For pre-initialized weights, which have no updates yet if (sumSquaredGradients.get(f) == null || l1UpdateTimeMap.get(f) == null) { l1UpdateTimeMap.put(f, numUpdates); sumSquaredGradients.put(f, 0.0); return; } int numOfIter = numUpdates - MapUtils.get(l1UpdateTimeMap, f, 0); if (numOfIter == 0) return; if (numOfIter < 0) throw new RuntimeException("l1UpdateTimeMap is out of sync."); double stepSize = (numOfIter * opts.initStepSize) / (Math.sqrt(sumSquaredGradients.get(f) + 1)); double update = -opts.l1RegCoeff * Math.signum(MapUtils.getDouble(weights, f, 0.0)); clipUpdate(f, stepSize * update); if (weights.containsKey(f)) l1UpdateTimeMap.put(f, numUpdates); else l1UpdateTimeMap.remove(f); } public synchronized double getWeight(String f) { if (l1Reg == L1Reg.LAZY) lazyL1Update(f); if (opts.initWeightsRandomly) return MapUtils.getDouble(weights, f, 2 * opts.initRandom.nextDouble() - 1); else { return MapUtils.getDouble(weights, f, opts.defaultWeight); } } public synchronized Map<String, Double> getWeights() { finalizeWeights(); return weights; } public void write(PrintWriter out) { write(null, out); } public void write(String prefix, PrintWriter out) { List<Map.Entry<String, Double>> entries = Lists.newArrayList(weights.entrySet()); Collections.sort(entries, new ValueComparator<String, Double>(true)); for (Map.Entry<String, Double> entry : entries) { double value = entry.getValue(); out.println((prefix == null ? "" : prefix + "\t") + entry.getKey() + "\t" + value); } } public void write(String path) { LogInfo.begin_track("Params.write(%s)", path); PrintWriter out = IOUtils.openOutHard(path); write(out); out.close(); LogInfo.end_track(); } public void log() { LogInfo.begin_track("Params"); List<Map.Entry<String, Double>> entries = Lists.newArrayList(weights.entrySet()); Collections.sort(entries, new ValueComparator<String, Double>(true)); for (Map.Entry<String, Double> entry : entries) { double value = entry.getValue(); LogInfo.logs("%s\t%s", entry.getKey(), value); } LogInfo.end_track(); } public synchronized void finalizeWeights() { if (l1Reg == L1Reg.LAZY) { Set<String> features = new HashSet<>(weights.keySet()); for (String f : features) lazyL1Update(f); } } public Params copyParams() { Params result = new Params(); for (String feature : this.getWeights().keySet()) { result.weights.put(feature, this.getWeight(feature)); } return result; } // copy params starting with prefix and drop the prefix public Params copyParamsByPrefix(String prefix) { Params result = new Params(); for (String feature : this.getWeights().keySet()) { if (feature.startsWith(prefix)) { String newFeature = feature.substring(prefix.length()); result.weights.put(newFeature, this.getWeight(feature)); } } return result; } public boolean isEmpty() { return weights.size() == 0; } public Params getRandomWeightParams() { Random rand = new Random(); Params result = new Params(); for (String feature : this.getWeights().keySet()) { result.weights.put(feature, 2 * rand.nextDouble() - 1); // between -1 and 1 } return result; } }