Last active
June 6, 2025 13:59
-
-
Save simplymathematics/d658396f68c2e4e282e7b9003e93c8f6 to your computer and use it in GitHub Desktop.
An example of building models in javascript and plotting them with plotly.
This file contains hidden or 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> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Model Visualization</title> | |
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script> | |
</head> | |
<body> | |
<h2>Linear SVM Model</h2> | |
<div id="plot" style="width: 700px; height: 600px;"></div> | |
<script type="module"> | |
import { SVM } from './svm.js'; | |
import { plotDecisionBoundary } from './plot.js'; | |
const X = [ | |
[1, 2], | |
[2, 3], | |
[3, 3], | |
[2, 1], | |
[3, 2] | |
]; | |
const y = [1, 1, 1, -1, -1]; | |
const svm = new SVM(); | |
svm.fit(X, y); | |
plotDecisionBoundary(X, y, svm, 'Linear SVM Decision Boundary'); | |
</script> | |
</body> | |
</html> |
This file contains hidden or 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
// plot.js | |
export function plotDecisionBoundary(X, y, model, title = 'SVM Decision Boundary') { | |
const x0 = X.map(row => row[0]); | |
const x1 = X.map(row => row[1]); | |
const scatter = { | |
x: x0, | |
y: x1, | |
mode: 'markers', | |
type: 'scatter', | |
marker: { | |
color: y, | |
colorscale: [['0', 'blue'], ['1', 'red']], | |
cmin: -1, | |
cmax: 1, | |
size: 10 | |
}, | |
name: 'Data Points' | |
}; | |
const xMin = Math.min(...x0) - 1; | |
const xMax = Math.max(...x0) + 1; | |
const yMin = Math.min(...x1) - 1; | |
const yMax = Math.max(...x1) + 1; | |
const resolution = 100; | |
const xVals = Array.from({ length: resolution }, (_, i) => | |
xMin + (i / (resolution - 1)) * (xMax - xMin) | |
); | |
const yVals = Array.from({ length: resolution }, (_, i) => | |
yMin + (i / (resolution - 1)) * (yMax - yMin) | |
); | |
const zVals = yVals.map(yVal => | |
xVals.map(xVal => { | |
const x_point = [xVal, yVal]; | |
return model.dotProduct(x_point, model.w) - model.b; | |
}) | |
); | |
const contour = { | |
z: zVals, | |
x: xVals, | |
y: yVals, | |
type: 'contour', | |
colorscale: 'BuRd', | |
contours: { | |
showlines: true, | |
start: -1, | |
end: 1, | |
size: 1 | |
}, | |
opacity: 0.6, | |
name: 'Decision Boundary' | |
}; | |
const layout = { | |
title: title, | |
xaxis: { title: 'Feature 1' }, | |
yaxis: { title: 'Feature 2' }, | |
showlegend: false | |
}; | |
Plotly.newPlot('plot', [contour, scatter], layout); | |
} |
This file contains hidden or 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
// svm.js | |
export class SVM { | |
constructor(learningRate = 0.001, lambdaParam = 0.01, nIters = 1000) { | |
this.lr = learningRate; | |
this.lambdaParam = lambdaParam; | |
this.nIters = nIters; | |
this.w = null; | |
this.b = 0; | |
} | |
dotProduct(a, b) { | |
return a.reduce((sum, val, i) => sum + val * b[i], 0); | |
} | |
subtractArrays(a, b) { | |
return a.map((val, i) => val - b[i]); | |
} | |
multiplyArrayScalar(arr, scalar) { | |
return arr.map(val => val * scalar); | |
} | |
fit(X, y) { | |
const nSamples = X.length; | |
const nFeatures = X[0].length; | |
const y_ = y.map(val => val <= 0 ? -1 : 1); | |
this.w = Array(nFeatures).fill(0); | |
this.b = 0; | |
for (let i = 0; i < this.nIters; i++) { | |
for (let idx = 0; idx < nSamples; idx++) { | |
const x_i = X[idx]; | |
const condition = y_[idx] * (this.dotProduct(x_i, this.w) - this.b) >= 1; | |
if (condition) { | |
const grad = this.multiplyArrayScalar(this.w, 2 * this.lambdaParam); | |
this.w = this.subtractArrays(this.w, this.multiplyArrayScalar(grad, this.lr)); | |
} else { | |
const grad = this.subtractArrays( | |
this.multiplyArrayScalar(this.w, 2 * this.lambdaParam), | |
this.multiplyArrayScalar(x_i, y_[idx]) | |
); | |
this.w = this.subtractArrays(this.w, this.multiplyArrayScalar(grad, this.lr)); | |
this.b -= this.lr * y_[idx]; | |
} | |
} | |
} | |
} | |
predict(X) { | |
return X.map(x => { | |
const approx = this.dotProduct(x, this.w) - this.b; | |
return Math.sign(approx); | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment