UNPKG

aiom_pack

Version:

Framework for interdependent (mcmc-like) behavioral experiments

120 lines (103 loc) 4.55 kB
// different kinds of gatekeepers here const sampling = require('./sampling'); const transformer = require('./transformer'); const { pool } = require('../core/database'); const lower_bound = Number(process.env.lower_bound); const upper_bound = Number(process.env.upper_bound); var n = require("numeric"); var sqrt2PI = Math.sqrt(Math.PI * 2); /** * Numerically stable log-sum-exp. * @param {number[]} arr Array of numbers (logarithms). * @returns {number} log(sum(exp(arr_i))). */ function logSumExp(arr) { if (arr.length === 0) return -Infinity; const maxVal = Math.max(...arr); if (maxVal === -Infinity) return -Infinity; let sumExp = 0; for (let i = 0; i < arr.length; i++) { sumExp += Math.exp(arr[i] - maxVal); } return maxVal + Math.log(sumExp); } function GaussianKDE(parameters) { this.tree_data = parameters.tree_data; this.bandwidth = parameters.bandwidth; this.dimensionality = parameters.dimensionality; this.n_samples = parameters.n_samples; let mean = new Array(this.dimensionality).fill(0); for (let i = 0; i < this.tree_data.length; i++) { for (let j = 0; j < this.dimensionality; j++) { mean[j] += this.tree_data[i][j]; } } for (let j = 0; j < this.dimensionality; j++) { mean[j] /= this.tree_data.length; } this.mean = mean; } /** * Evaluates the density function of the gaussian at the given point */ GaussianKDE.prototype.density = function(x) { if (x.length !== this.dimensionality) { throw new Error(`Input point dimensionality (${x.length}) does not match model dimensionality (${this.dimensionality}).`); } const logKernelConstant = -this.dimensionality * Math.log(this.bandwidth) - (this.dimensionality / 2) * Math.log(2 * Math.PI); const logKernelTerms = this.tree_data.map(trainSample => { let squaredDistance = 0; for (let j = 0; j < this.dimensionality; j++) { const diff = x[j] - trainSample[j]; squaredDistance += diff * diff; } const expArgument = -0.5 * squaredDistance / (this.bandwidth * this.bandwidth); return expArgument + logKernelConstant; }); const logLikelihoodSum = logSumExp(logKernelTerms); if (logLikelihoodSum === -Infinity) { return -Infinity; } return logLikelihoodSum - Math.log(this.n_samples); }; // the basic customized gatekeepers GaussianKDE.prototype.acceptance = function(current, proposal, temperature=1.0) { const density_current = this.density(current); const density_proposal = this.density(proposal); return Math.exp(density_proposal/temperature) / (Math.exp(density_current/temperature) + Math.exp(density_proposal/temperature)) } GaussianKDE.prototype.processing = async function(current_state, proposal, table_name, proposal_cov) { var trial_number; var proposal = proposal; var acceptance_rate = this.acceptance(current_state, proposal); while (Math.random() > acceptance_rate) { trial_number = await pool.query( `UPDATE ${table_name} SET picked = true, gatekeeper = true WHERE id = ( SELECT id FROM ${table_name} ORDER BY id DESC OFFSET 1 LIMIT 1) RETURNING trial;`, ); // console.log(trial_number.rows[0].trial); proposal = transformer.limit_array_in_range(sampling.gaussian_array(current_state, proposal_cov), min=lower_bound, max=upper_bound); acceptance_rate = this.acceptance(current_state, proposal); await pool.query( `INSERT INTO ${table_name} (trial, choices) VALUES ($1, $2), ($3, $4)`, [trial_number.rows[0].trial+1, JSON.stringify(current_state), trial_number.rows[0].trial+1, JSON.stringify(proposal)] ); } // console.log(proposal); return proposal; } GaussianKDE.prototype.sampling = function() { // 1. Pick a Kernel (one of the original data points) const randomIndex = Math.floor(Math.random() * this.n_samples); const selectedKernelCenter = this.tree_data[randomIndex]; // 2. Sample from that Kernel (add Gaussian noise scaled by bandwidth) const newSample = []; for (let i = 0; i < this.dimensionality; i++) { const noise = sampling.gaussianRandom() * this.bandwidth; newSample.push(selectedKernelCenter[i] + noise); } return newSample; } module.exports = {GaussianKDE};