Skip to content

Commit

Permalink
Improve annealer performance by about 4-fold.
Browse files Browse the repository at this point in the history
...as measured on tuning readability:

    Got to 14.74 4x faster.
    Got to 11.97 in 7 vs. 27 minutes (3.8x faster).
    Got to 11.85 in 8 vs. 69 minutes (8.6x faster).

We do this by adding caching of solution costs (hit rate ≈ 90% for the sort of single-coefficient-at-a-time nudging we've often used, 3% for the coeff-swapping approach of the bin-packing problem). We also make sure to report the best-encountered solution rather than merely the one that's current when time runs out.

At the moment, the caching leaks memory since entries never expire, but the leakage is bounded by the max iterations of the annealer and is insignificant in practice. It is an open question why cache hit rate for readability is near zero: comparable single-coeff-nudgers yield a higher rate, and we wouldn't expect such a speedup for a low one. Stochasticity could be a factor.

Also allow the annealer hyperparameters to be modified through constructor params. I haven't yet found any that are both faster and better, but various useful balances are achievable, like [10643.296506954695, 113.30886882813805, 0.023803813406736214, 4.231731642265747], which finds solutions about half as good but finds them 32x faster.
  • Loading branch information
erikrose committed Dec 22, 2017
1 parent 84ea3af commit 8aefff5
Showing 1 changed file with 26 additions and 9 deletions.
35 changes: 26 additions & 9 deletions optimizers.js
@@ -1,7 +1,7 @@
const {readFileSync} = require('fs');
const {basename, join} = require('path');

const {dirsIn, staticDom} = require('./utils');
const {dirsIn, setDefault, staticDom} = require('./utils');


// This is based on public-domain code from
Expand All @@ -21,11 +21,11 @@ const {dirsIn, staticDom} = require('./utils');
* as the algorithim progresses.
*/
class Annealer {
constructor() {
this.INITIAL_TEMPERATURE = 5000;
this.COOLING_STEPS = 5000;
this.COOLING_FRACTION = 0.95;
this.STEPS_PER_TEMP = 1000;
constructor(initialTemperature = 5000, coolingSteps = 5000, coolingFraction = .95, stepsPerTemp = 1000) {
this.INITIAL_TEMPERATURE = initialTemperature;
this.COOLING_STEPS = coolingSteps;
this.COOLING_FRACTION = coolingFraction;
this.STEPS_PER_TEMP = stepsPerTemp;
this.BOLTZMANNS = 1.3806485279e-23;
}

Expand All @@ -38,21 +38,36 @@ class Annealer {
anneal() {
let temperature = this.INITIAL_TEMPERATURE;
let currentSolution = this.initialSolution();
let bestSolution = currentSolution;
let currentCost = this.solutionCost(currentSolution);
let bestCost = currentCost;
let m = 0;
let n = 0;
let hits = 0, misses = 0;
const seenSolutions = new Map(); // solution => cost
for (let i = 0; i < this.COOLING_STEPS; i++) {
console.log('Cooling step', i, 'of', this.COOLING_STEPS, '...');
const startCost = currentCost;
for (let j = 0; j < this.STEPS_PER_TEMP; j++) {
let newSolution = this.randomTransition(currentSolution);
let newCost = this.solutionCost(newSolution);
if (seenSolutions.has(newSolution.toString())) {
hits += 1;
} else {
misses += 1;
}
let newCost = setDefault(seenSolutions, newSolution.toString(), () => this.solutionCost(newSolution));

if (newCost < currentCost) {
// Always take improvements.
currentCost = newCost;
currentSolution = newSolution;
console.log('New best solution is ', newSolution, ' with fitness ', newCost);
if (newCost < bestCost) {
bestCost = newCost;
bestSolution = newSolution;
console.log('New best solution is ', newSolution, ' with cost ', newCost);
}
} else {
// Sometimes take non-improvements.
const minusDelta = currentCost - newCost;
const merit = Math.exp(minusDelta / (this.BOLTZMANNS * temperature));
if (merit > Math.random()) {
Expand All @@ -68,7 +83,9 @@ class Annealer {
temperature *= this.COOLING_FRACTION;
}
console.log('Iterations:', n, 'using', m, 'jumps.');
return currentSolution;
console.log('Cache hits', hits, 'misses', misses);
console.log('Cache hit rate', hits/(hits + misses));
return bestSolution;
}

/**
Expand Down

0 comments on commit 8aefff5

Please sign in to comment.