Last active
February 1, 2022 13:30
-
-
Save doleron/742028d2dd2c02f476d7623977585b0d to your computer and use it in GitHub Desktop.
Example of polynomial fitting in Javascript
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
/* * | |
* requires numeric js | |
* https://cdnjs.cloudflare.com/ajax/libs/numeric/1.2.6/numeric.min.js | |
*/ | |
const ctx = document.getElementById('synthetic'); | |
const N = 91; | |
const xs = [...Array(N).keys()].map(x => 2*Math.PI*x / (N - 1)); | |
function mulberry32(a) { | |
return function() { | |
var t = a += 0x6D2B79F5; | |
t = Math.imul(t ^ t >>> 15, t | 1); | |
t ^= t + Math.imul(t ^ t >>> 7, t | 61); | |
return ((t ^ t >>> 14) >>> 0) / 4294967296; | |
} | |
} | |
// change seed to an arbitrary number | |
const seed = 1111; | |
jStat.setRandom(mulberry32(1111)); | |
const signal = xs.map(x => Math.sin(x) ); | |
const sampleSize = 20; | |
const sample = [...Array(N).keys()].sort(() => 0.5 - jStat.uniform.sample(0,1)).slice(0, sampleSize).map(index => ({x: index, y: signal[index] + jStat.normal.sample(0.0, 0.4)})); | |
sample.sort(() => 0.5 - jStat.uniform.sample(0,1)); | |
const dataSplit = Math.round(sampleSize * 0.67); | |
const training_data = sample.slice(0, dataSplit); | |
const validation_data = sample.slice(dataSplit, sampleSize); | |
_labels = ["0", "π/5", "2π/5", "3π/5", "4π/5", "π", "6π/5", "7π/5", "8π/5", "9π/5", "2π"]; | |
const x = []; | |
const y = []; | |
for (let i = 0; i < training_data.length; ++i) { | |
const elem = training_data[i]; | |
x.push(xs[elem.x]); | |
y.push(elem.y); | |
} | |
function fit(x, y, order) { | |
const xMatrix = []; | |
const yMatrix = numeric.transpose([y]); | |
let xTemp = []; | |
for (let j=0; j<x.length; ++j) { | |
xTemp = []; | |
for(let i = 0; i <= order; ++i) { | |
xTemp.push(Math.pow(x[j], i)); | |
} | |
xMatrix.push(xTemp); | |
} | |
const xMatrixT = numeric.transpose(xMatrix); | |
const dot1 = numeric.dot(xMatrixT, xMatrix); | |
const dotInv = numeric.inv(dot1); | |
const dot2 = numeric.dot(xMatrixT, yMatrix); | |
const result = numeric.dot(dotInv, dot2); | |
return result.flat(1); | |
} | |
function predict(x, coeffs) { | |
let result = 0; | |
let xx = 1; | |
for (let i = 0; i < coeffs.length; ++i) { | |
result += xx*coeffs[i]; | |
xx *= x; | |
} | |
return result; | |
} | |
function generateModelData(x, y, order) { | |
const model = fit(x, y, order); | |
const result = []; | |
for (let i = 0; i < xs.length; ++i) { | |
result.push(predict(xs[i], model)); | |
} | |
return result; | |
} | |
const myChart = new Chart(ctx, { | |
data: { | |
labels: xs, | |
datasets: [{ | |
type: 'line', | |
label: 'Original Generative Signal', | |
pointRadius: 0, | |
borderColor: 'rgb(0, 125, 255)', | |
pointBackgroundColor: 'rgb(0, 125, 255)', | |
data: signal | |
},{ | |
type: 'line', | |
label: 'Linear approximation', | |
pointRadius: 0, | |
borderColor: 'rgb(88, 24, 69)', | |
borderDash: [5, 3], | |
data: generateModelData(x, y, 1) | |
},{ | |
type: 'line', | |
label: 'Cubic Approximation', | |
pointRadius: 0, | |
borderColor: 'rgb(255, 87, 51)', | |
data: generateModelData(x, y, 3) | |
},{ | |
type: 'line', | |
label: '5th-degree polynomial approximation', | |
pointRadius: 0, | |
borderColor: 'rgb(165, 255, 51)', | |
borderDash: [10, 5], | |
data: generateModelData(x, y, 5) | |
},{ | |
type: 'line', | |
label: '9th-degree polynomial approximation', | |
pointRadius: 0, | |
borderColor: 'rgb(123, 36, 28)', | |
pointBackgroundColor: 'rgb(255, 0, 0)', | |
borderDash: [10, 2], | |
data: generateModelData(x, y, 9) | |
}, | |
{ | |
type: 'scatter', | |
label: 'Training data', | |
pointStyle: 'crossRot', | |
pointRadius: 10, | |
borderColor: 'rgb(255, 128, 0)', | |
data: Array(N).fill().map((element, index) => { | |
const v = training_data.find(elem => elem.x == index); | |
if (v != undefined) { | |
return v.y; | |
} else { | |
return null; | |
} | |
}) | |
}] | |
}, | |
options: { | |
plugins: { | |
legend: { | |
position: 'bottom' | |
}, | |
title: { | |
display: true, | |
text: 'Training/validation sets', | |
font: { | |
size: 48, | |
family: 'Arial' | |
} | |
} | |
}, | |
scales: { | |
x: { | |
ticks: { | |
maxTicksLimit: _labels.length, | |
maxRotation: 0, | |
minRotation: 0, | |
callback: function(value, index, ticks) { | |
const factor = index * (_labels.length - 1); | |
let result = ""; | |
const n = N - 1; | |
if (factor % n == 0){ | |
result = _labels[Math.ceil(factor / n)]; | |
} | |
return result; | |
} | |
} | |
}, | |
y: { | |
min: -2, | |
max: 2, | |
} | |
} | |
} | |
}); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment