function kernel(a, b) {
/*
Kernel for mapping a, b into polynomial features
*/
return [1, a, b, a * a, b * b, a * b];
}
function sigmoid(x) {
/*
Logistic sigmoid function
*/
return 1.0 / (1.0 + Math.exp(-x));
};
function gradientDescent(theta, gradient, alpha) {
/*
http://en.wikipedia.org/wiki/Gradient_descent
*/
return numeric.sub(theta, numeric.mul(gradient(theta), alpha));
}
function render(gfx, theta, X, y) {
/*
Render graph and theta boundaries
*/
var thetaT = numeric.transpose(theta);
gfx.scale(10);
// Render background
for (var i = 0; i < 40; i++) {
for (var j = 0; j < 40; j++) {
var a = j / 20 - 1;
var b = i / 20 - 1;
var x = numeric.transpose([kernel(a, b)]);
var value = sigmoid(numeric.dot(thetaT, x)[0][0]);
gfx.stroke(255 * value, 100, 255 * (1 - value));
gfx.point(j, i);
}
}
gfx.scale(0.1);
gfx.stroke(0);
// Render points
for (var i = 0; i < X.length; i++) {
if (y[i][0] > 0.5) {
gfx.fill(255, 100, 50);
} else {
gfx.fill(50, 100, 255);
}
gfx.ellipse((X[i][1] + 1) * 200, (X[i][2] + 1) * 200, 5, 5);
}
}
window.onload = (function() {
// Alpha input
var alpha = document.getElementById('alpha').value;
var alphaInput = document.getElementById('alpha');
alphaInput.onchange = function() {
alpha = parseFloat(alphaInput.value);
};
// Lambda input
var lambda = document.getElementById('lambda').value;
var lambdaInput = document.getElementById('lambda');
lambdaInput.onchange = function() {
lambda = parseFloat(lambdaInput.value);
};
// Group input
var group = document.getElementById('group').value;
var groupInput = document.getElementById('group');
groupInput.onchange = function() {
group = parseFloat(groupInput.value);
};
// Initialize theta to zero-vector
var theta = numeric.rep([kernel(0, 0).length, 1], 0);
// X is feature matrix, y is group vector
var X = [], y = [];
// Gradient function for regularized logistic regression
var gradient = function(theta) {
var H = numeric.dot(X, theta);
for (var i = 0; i < H.length; i++) {
for (var j = 0; j < H[i].length; j++) {
H[i][j] = sigmoid(H[i][j]);
}
}
var regularization = numeric.mul(theta, lambda / X.length);
regularization[0][0] = 0.0;
var grad = numeric.dot(numeric.transpose(X), numeric.sub(H, y));
grad = numeric.div(grad, X.length);
return numeric.add(grad, regularization);
}
// Canvas for rendering
var canvas = document.getElementById('canvas');
var gfx = new Processing(canvas);
canvas.onclick = function(evt) {
if (typeof evt.offsetX == 'undefined'){
evt.offsetX = evt.layerX - canvas.offsetLeft;
}
if (typeof evt.offsetY == 'undefined'){
evt.offsetY = evt.layerY - canvas.offsetTop;
}
var a = (evt.offsetX / 200) - 1;
var b = (evt.offsetY / 200) - 1;
X.push(kernel(a, b));
y.push([group]);
};
gfx.size(400, 400);
function loop() {
/*
Main loop, train using gradient if there are points
*/
if (X.length) {
for (var i = 0; i < 100; i++) {
theta = gradientDescent(theta, gradient, alpha);
}
}
render(gfx, theta, X, y);
setTimeout(loop, 10);
}
loop();
});
<html>
<body>
<div id="toolbar">
Alpha:
<input id="alpha" type="text" value="0.1" size="10"/>
Lambda:
<input id="lambda" type="text" value="0.0" size="10"/>
Group:
<select id="group">
<option value="0">Blue</option>
<option value="1">Orange</option>
</select>
</div>
<div id="content">
<canvas id="canvas"></canvas>
</div>
</body>
</html>
#toolbar, #content {
width: 400px;
margin: 0;
padding: 0;
text-align: center;
}
body, input, select {
font: bold 11px arial,sans-serif;
}
External resources loaded into this fiddle: