Skip to content

Instantly share code, notes, and snippets.

@bricof
Last active April 21, 2017 13:32
Show Gist options
  • Save bricof/3197c85d55df98a7defa4a70ecb7cd75 to your computer and use it in GitHub Desktop.
Save bricof/3197c85d55df98a7defa4a70ecb7cd75 to your computer and use it in GitHub Desktop.
Latent Value Learning with Extras

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.

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)
}
<!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