This is an extended version of the simpler Latent Value Learning example, adding uncertainty bands, histograms and a control panel. See the readme of the simpler example for algorithm and simulation details.
Last active
April 21, 2017 13:32
-
-
Save bricof/3197c85d55df98a7defa4a70ecb7cd75 to your computer and use it in GitHub Desktop.
Latent Value Learning with Extras
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function animated_learning() { | |
var svg = d3.select("#animation").select("svg") | |
var x = d3.scaleLinear().range([200,500]).domain([0,1]) | |
var learning_rate = 0.2 | |
var hist_n = 15 | |
var As = [] | |
var Bs = [] | |
var As_visible = [] | |
var Bs_visible = [] | |
var n_As, n_Bs, n_pairs, n_As_visible, n_Bs_visible, show_histogram, show_uncertainty | |
function restart_animation() { | |
// read simulation control values | |
n_As = +document.getElementById('n_As').value | |
n_Bs = +document.getElementById('n_Bs').value | |
n_pairs = +document.getElementById('n_pairs').value | |
n_As_visible = +document.getElementById('n_As_visible').value | |
n_Bs_visible = +document.getElementById('n_Bs_visible').value | |
show_uncertainty = document.getElementById('show_uncertainty').checked | |
show_histogram = document.getElementById('show_histogram').checked | |
sim_speed = +document.getElementById('sim_speed').value | |
// create elements with latent values and initial positions | |
As = [] | |
for (var i=0; i < n_As; i++){ | |
As.push({id: i, latent_value: Math.random(), | |
current_position: 0.5, next_position: 0.5, | |
current_n_tests: 0, next_n_tests: 0}) | |
} | |
Bs = [] | |
for (var i=0; i < n_Bs; i++){ | |
Bs.push({id: i, latent_value: Math.random(), | |
current_position: 0.5, next_position: 0.5, | |
current_n_tests: 0, next_n_tests: 0}) | |
} | |
// construct circles | |
As_visible = As.slice(0, n_As_visible) | |
Bs_visible = Bs.slice(0, n_Bs_visible) | |
svg.selectAll(".A").remove() | |
svg.selectAll(".B").remove() | |
svg.selectAll(".A").data(As_visible, function(d){ return d.id; }) | |
.enter().append("circle") | |
.attr("class", "A A-color") | |
.attr("cx", 350) | |
.attr("cy", 230) | |
.attr("r", 3) | |
svg.selectAll(".B").data(Bs_visible, function(d){ return d.id; }) | |
.enter().append("circle") | |
.attr("class", "B B-color") | |
.attr("cx", 350) | |
.attr("cy", 270) | |
.attr("r", 3) | |
} | |
function animation_step(){ | |
// *** SIMULATION *** | |
var pairs = [] | |
for (var i=0; i < n_pairs; i++) { | |
var A_id = Math.floor(Math.random()*n_As) | |
// pair selection a stochastic function of distance from respective current positions | |
var weights = Bs.map(function(d){ return d.current_position - As[A_id].current_position; }) | |
var cum_weights = [] | |
weights.reduce(function(a,b,i) { cum_weights[i] = {v: a+b, id:i}; return a + b; },0) | |
cum_weights = cum_weights.sort(function(a,b){ return a.v > b.v; }) | |
var B_id = Math.floor(Math.random()*n_Bs) | |
if (cum_weights[cum_weights.length - 1].v != 0) { | |
var sel_random = Math.random() * cum_weights[cum_weights.length - 1].v | |
var sel = cum_weights.find(function(d){ return d.v >= sel_random; }) | |
if (!(sel == null)) { | |
B_id = sel.id | |
} | |
} | |
pairs.push({A_id: A_id, B_id: B_id}) | |
// for uncertainty calc | |
As[A_id].next_n_tests += 1 | |
Bs[B_id].next_n_tests += 1 | |
// big = 1, small = -1 | |
var feedback = -1 + 2 * (As[A_id].latent_value > Bs[B_id].latent_value) | |
// use feedback if it contradicts current | |
if ((feedback == -1) && (As[A_id].current_position <= Bs[B_id].current_position)) { | |
As[A_id].next_position = As[A_id].current_position + Math.random() * learning_rate | |
Bs[B_id].next_position = Bs[B_id].current_position - Math.random() * learning_rate | |
} | |
if ((feedback == 1) && (As[A_id].current_position >= Bs[B_id].current_position)) { | |
As[A_id].next_position = As[A_id].current_position - Math.random() * learning_rate | |
Bs[B_id].next_position = Bs[B_id].current_position + Math.random() * learning_rate | |
} | |
As[A_id].next_position = Math.min(1, Math.max(0, As[A_id].next_position)) | |
Bs[B_id].next_position = Math.min(1, Math.max(0, Bs[B_id].next_position)) | |
} | |
// *** SVG ANIMATION *** | |
var delay = 400 * 25 / sim_speed | |
var move = 500 * 25 / sim_speed | |
var wait = 300 * 25 / sim_speed | |
// histograms | |
var A_hist_dy = 200 / n_As | |
var A_hist = [] | |
for (var i=0; i < hist_n; i++) { | |
var this_count = 0 | |
As.forEach(function(d){ | |
if ((d.current_position >= i/hist_n) && (d.current_position < (i+1)/hist_n)) { | |
this_count += 1 | |
} | |
}) | |
A_hist.push(this_count) | |
} | |
var B_hist_dy = 200 / n_Bs | |
var B_hist = [] | |
for (var i=0; i < hist_n; i++) { | |
var this_count = 0 | |
Bs.forEach(function(d){ | |
if ((d.current_position >= i/hist_n) && (d.current_position < (i+1)/hist_n)) { | |
this_count += 1 | |
} | |
}) | |
B_hist.push(this_count) | |
} | |
// prep for visible parts of sim | |
As_visible = As.slice(0, n_As_visible) | |
Bs_visible = Bs.slice(0, n_Bs_visible) | |
pairs = pairs.filter(function(d){ | |
var keep = true | |
if (d.A_id >= n_As_visible) { keep = false; } | |
if (d.B_id >= n_Bs_visible) { keep = false; } | |
return keep | |
}) | |
// draw and animate pair lines | |
svg.selectAll(".pair").remove() | |
svg.selectAll(".pair").data(pairs).enter().append("line") | |
.attr("class", "pair") | |
.attr("y1", 230) | |
.attr("y2", 270) | |
.style("stroke", "#000") | |
.style("stroke-width", 0.25) | |
.style("fill", "none") | |
.attr("x1", function(d){ return x(As[d.A_id].current_position); }) | |
.attr("x2", function(d){ return x(Bs[d.B_id].current_position); }) | |
.transition().delay(delay).duration(move) | |
.attr("x1", function(d){ return x(As[d.A_id].next_position); }) | |
.attr("x2", function(d){ return x(Bs[d.B_id].next_position); }) | |
// animate circles | |
svg.selectAll(".A") | |
.transition().delay(delay).duration(move) | |
.attr("cx", function(d){ return x(d.next_position); }) | |
svg.selectAll(".B") | |
.transition().delay(delay).duration(move) | |
.attr("cx", function(d){ return x(d.next_position); }) | |
// animate uncertainty bands (if applicable) | |
svg.selectAll(".A_uncertainty").remove() | |
svg.selectAll(".B_uncertainty").remove() | |
if (show_uncertainty) { | |
svg.selectAll(".A_uncertainty").data(As_visible, function(d){ return d.id; }) | |
.enter().append("rect") | |
.attr("class", "A_uncertainty A-color") | |
.style("opacity", 0.25) | |
.attr("y", 227) | |
.attr("height", 6) | |
.attr("x", function(d){ return x(d.current_position) - 150 / (1 + d.current_n_tests); }) | |
.attr("width", function(d){ return 300 / (1 + d.current_n_tests); }) | |
.transition().delay(delay).duration(move) | |
.attr("x", function(d){ return x(d.next_position) - 150 / (1 + d.next_n_tests); }) | |
.attr("width", function(d){ return 300 / (1 + d.next_n_tests); }) | |
svg.selectAll(".B_uncertainty").data(Bs_visible, function(d){ return d.id; }) | |
.enter().append("rect") | |
.attr("class", "B_uncertainty B-color") | |
.style("opacity", 0.25) | |
.attr("y", 267) | |
.attr("height", 6) | |
.attr("x", function(d){ return x(d.current_position) - 150 / (1 + d.current_n_tests); }) | |
.attr("width", function(d){ return 300 / (1 + d.current_n_tests); }) | |
.transition().delay(delay).duration(move) | |
.attr("x", function(d){ return x(d.next_position) - 150 / (1 + d.next_n_tests); }) | |
.attr("width", function(d){ return 300 / (1 + d.next_n_tests); }) | |
} | |
// animate histogram (if applicable) | |
svg.selectAll(".A_hist").remove() | |
svg.selectAll(".B_hist").remove() | |
if (show_histogram) { | |
svg.selectAll(".A_hist").data(A_hist).enter().append("rect") | |
.attr("class", "A_hist A-color") | |
.style("opacity", 0.25) | |
.attr("x", function(d,i){ return x(i/hist_n); }) | |
.attr("y", function(d){ return 220 - A_hist_dy * d; }) | |
.attr("width", 300 / hist_n) | |
.attr("height", function(d){ return A_hist_dy * d; }) | |
svg.selectAll(".B_hist").data(B_hist).enter().append("rect") | |
.attr("class", "B_hist B-color") | |
.style("opacity", 0.25) | |
.attr("x", function(d,i){ return x(i/hist_n); }) | |
.attr("y", 280) | |
.attr("width", 300 / hist_n) | |
.attr("height", function(d){ return B_hist_dy * d; }) | |
} | |
// *** end of svg animation code *** | |
// prep next timestep | |
As.forEach(function(d){ | |
d.current_position = d.next_position | |
d.current_n_tests = d.next_n_tests | |
}) | |
Bs.forEach(function(d){ | |
d.current_position = d.next_position | |
d.current_n_tests = d.next_n_tests | |
}) | |
anim_interval = d3.timeout(animation_step, (delay + move + wait)) | |
} | |
// control elements on-change behaviors | |
d3.select("#animated_learning_restart").on("click", function(){ | |
restart_animation() | |
}) | |
d3.select("#show_uncertainty").on("click", function(){ | |
show_uncertainty = document.getElementById('show_uncertainty').checked | |
}) | |
d3.select("#show_histogram").on("click", function(){ | |
show_histogram = document.getElementById('show_histogram').checked | |
}) | |
d3.select("#sim_speed").on("change", function(){ | |
sim_speed = +document.getElementById('sim_speed').value | |
}) | |
// start simulation / animation | |
restart_animation() | |
anim_interval = d3.timeout(animation_step, 500) | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<style> | |
#animation { | |
position: relative; | |
display: inline-block; | |
width: 660px; | |
} | |
#controls { | |
position: fixed; | |
display: inline-block; | |
width: 300px; | |
padding-top: 120px; | |
} | |
.A-color { | |
fill: #4B90A6; | |
} | |
.B-color { | |
fill: #F3A54A; | |
} | |
</style> | |
<body> | |
<div id="animation"> | |
<svg width="660" height="500" viewBox="120 50 580 450"> | |
<line style="stroke:#000; stroke-width: 0.75; fill: none;" y2="250" y1="250" x2="520" x1="180"></line> | |
<text class="A-color" text-anchor="middle" font-size="12px" y="200" x="350">A elements</text> | |
<text class="B-color" text-anchor="middle" font-size="12px" y="310" x="350">B elements</text> | |
</svg> | |
</div> | |
<div id="controls"> | |
<input id="n_As_visible" size=4 value="10" /><small> A elements visible</small><br> | |
<input id="n_Bs_visible" size=4 value="10" /><small> B elements visible</small><br> | |
<input id="n_As" size=4 value="10" /><small> A elements simulated</small><br> | |
<input id="n_Bs" size=4 value="10" /><small> B elements simulated</small><br> | |
<input id="n_pairs" size=4 value="8" /><small> pairs per iteration</small><br> | |
<br> | |
<input id="show_uncertainty" type="checkbox" /><small> show uncertainty bands</small><br> | |
<input id="show_histogram" type="checkbox" /><small> show histograms</small><br> | |
<br> | |
<input id="sim_speed" type="range" min="0" max="100" value="25"><small> simulation speed</small><br> | |
<br> | |
<button id="animated_learning_restart">restart animation</button> | |
</div> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<script src="animated-learning.js"></script> | |
<script> | |
animated_learning() | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment