Published
Edited
Jan 1, 2022
Importers
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
dim = features.length
Insert cell
size = columns*rows
Insert cell
viewof weights = {
/* updates on button press */ weights_button;
return tensor( () => {
// return tf.randomUniform([size,dim]) // values would change every time size and dim change
// instead extract values from the weights pool W according to size and selected features
return !dim? tf.zeros([size,0]): // if no feature is selected defaults to empty matrix
tf.concat(features.map( d => W.slice([0,0],[size]).split(max_dim,1)[F.indexOf(d)] ), 1)

}).view(this,"weights")
}
Insert cell
Insert cell
Insert cell
Insert cell
viewof W = {
/* updates on button press */ W_button;

return tensor( () => {

// pool of random values for all possible features F
return tf.randomUniform([max_size, max_dim])
}).view(this, "W")

}
Insert cell
Insert cell
input_values = values_from_color(d3.color(color_picker)) // gets only selected features
Insert cell
viewof ranges = tensor( () => {

// min and max value for each feature
return tf.tensor(features.map( d => d == "opacity"? [0, 1]: [0, 255] ))

}).view(this, "ranges")
Insert cell
viewof input = tensor( () => {

// scale the value to be in a 0-to-1 range
const [min,max] = ranges.transpose().split(2,0);
return tf.tensor(input_values).sub(min).div(max.sub(min))
}).view(this, "input")
Insert cell
Insert cell
dissimilarity = (a,b) => tf.squaredDifference(a,b).sqrt().mean(1)
Insert cell
viewof dissimilarities = tensor( () => {

// Euclidean distance (mean)
return !dim? tf.zeros([size]): // default to zero
dissimilarity(input,weights)
}).view(this, "dissimililarities")
Insert cell
viewof bmu_index = tensor( () => {
// the best matching unit is the one with the most similar (i.e. least dissimilar) values
return dissimilarities.argMin()
}).view(this, "bmu_index")
Insert cell
Insert cell
viewof odd_r = tensor( () => {
// indexes as in a nested loop (first by rows and then by columns)
const x = tf.range(0,rows).reshape([rows,1]).tile([1,columns]).flatten()
const y = tf.range(0,columns).tile([rows])
return tf.stack([x,y],1) // trivial coordinate system does not feature easy distances

}).view(this, "odd_r")
Insert cell
viewof cubic = tensor( () => {
const [x,y] = odd_r.unstack(1) // undo

return tf.stack([tf.ceil(x.div(2)).add(y).sub(x),tf.ceil(x.div(2)).mul(-1).sub(y),x],1) // cubic is better

}).view(this, "cubic")
Insert cell
viewof bmu_cc = tensor( () => {

return cubic.gather(bmu_index)
}).view(this, "bmu_cc")
Insert cell
viewof distances = tensor( () => {
return tf.sub(cubic,bmu_cc).abs().max(1)

}).view(this, "distances")
Insert cell
Insert cell
Insert cell
viewof update_matrix = tensor( () => {

// each step all the weights that fall in the distance range from the bmu are updated
const neighbors = distances.lessEqual(range)
return neighbors.reshape([size,1]).tile([1,dim]) // tile to match weights matrix
}).view(this, "update_matrix")
Insert cell
viewof weights_updated = tensor( () => {

const delta = weights.sub(input).mul(learning_rate)
const w = weights.sub(delta)
return w.where(update_matrix, weights)
}).view(this, "weights_updated")
Insert cell
step_updater = {

while(mutable steps_to_do > 0){

viewof weights.value = tensor ( () => tf.clone(weights_updated) ).over(viewof weights.value);
viewof weights.dispatchEvent(new CustomEvent("input"));
viewof color_picker.value = random_color().toString();
viewof color_picker.dispatchEvent(new CustomEvent("input"));
++mutable current_step;
--mutable steps_to_do;

await Promises.delay(10);
}

}
Insert cell
mutable steps_to_do = { next_step_button; return typeof this == "undefined"? 0: 1 }
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
hex = [[0,-1],[sqrt3/2,0.5],[0,1],[-sqrt3/2,0.5],[-sqrt3/2,-0.5],[0,-1],[sqrt3/2,-0.5]]
Insert cell
grid_updater = {
const height = width * width_to_height;
const svg = d3.select(grid)
svg.attr("width", width)
.attr("height", height)
const radius = d3.min([width/((columns + 0.5) * sqrt3), height/((rows + 1/3) * 1.5)]);
let centers = [];
for (var i = 0; i < rows; i++) {
for (var j = 0; j < columns; j++) {
centers.push({x: (i%2? j+1: j+0.5) *radius *sqrt3, y: (i+2/3) *radius *1.5});
}
}
const scaling = i => scale_by_similarity? similarities[i]: 1
const rotate_range = (highlight_bmu && show_range)
const rngscl = rotate_range? sqrt3*(3*range+2)/3 : scaling(bmu)
const groups = ["units","range"]
if(highlight_bmu && show_distances) groups.push("distances")

const g = svg.selectAll("g").data(groups, d => d).join("g")
const units = centers.map( (d,i) => ({center:d,color:colors[i]}) )
g.filter( d => d == "units" ).selectAll("path")
.data(units).join("path")
.transition()
.attr("transform", d => `translate(${d.center.x},${d.center.y})`)
.attr("d", (d,i) =>`m${hex.map( line => line.map( value => value*radius*scaling(i)).join(',')).join('l')}z`)
.attr("fill", d => d.color )
.attr("opacity", (d,i) => highlight_bmu && i != bmu? "opacity" in features? 1/2: 1/3: 1 )
.attr("stroke", (d,i) => scale_by_similarity || highlight_bmu && i == bmu? "black": "white")
g.filter( d => d == "distances" ).selectAll("text")
.data(centers).join("text")
.attr("x", d => d.x)
.attr("y", d => d.y)
.data(distances_data).join()
.text( d => "" + d )
.attr("font-size",(d,i) => radius/2)
.attr("text-anchor","middle")
.attr("dominant-baseline","central")

g.filter( d => d == "range" ).selectAll("path")
.data([centers[bmu]]).join("path")
.attr("fill","none")
.transition()
.attr("transform", d => `translate(${d.x},${d.y}) ${ rotate_range? " rotate(30)": "" }` )
.attr("d", (d,i) =>`m${hex.map( line => line.map( value => value*radius*rngscl ).join(',')).join('l')}z`)
.attr("stroke", highlight_bmu? "black": "none")

}
Insert cell
Insert cell
similarities = tensor( () => tf.sub(1.0, show_updated_weights? (!dim? tf.zeros([size]): dissimilarity(input,weights_updated)): dissimilarities) ).data()
Insert cell
colors = d3.range(size).map( // for each unit
i => color_from_values(d3.range(dim).map( // for each feature
j => color_components[i*dim+j] // scan the array as the [size,dim]-matrix it should be
) /* all features are combined in to a color ... */).toString() // ... string
)
Insert cell
color_components = tensor( () => {
const [min,max] = ranges.transpose().split(2,0);
return (show_updated_weights? weights_updated: weights).mul(max.sub(min)).add(min) // rescale values
}).data()
Insert cell
distances_data = distances.data() // implicit await
Insert cell
bmu = bmu_index.data() // implicit await
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more