image-nodes
Version:
A library for visual programming of image-processing algorithms on dicom images
260 lines (222 loc) • 6.29 kB
JavaScript
import { Matrix, inverse } from 'ml-matrix';
var Inverse = inverse;
class linearRegression {
constructor() { }
linearLeastSquares(x, y) {
var beta1, beta2;
var xBar = 0;
var yBar = 0;
var temp1 = 0;
var temp2 = 0;
var count = x.length;
// Calculate mean: x-bar and y-bar
for (var i = 0; i < count; i++) {
xBar = xBar + x[i];
yBar = yBar + y[i];
}
xBar = xBar / count;
yBar = yBar / count;
// Calculate beta2
for (var i = 0; i < count; i++) {
temp1 = temp1 + (x[i] - xBar) * (y[i] - yBar);
temp2 = temp2 + (x[i] - xBar) * (x[i] - xBar);
}
beta2 = temp1 / temp2;
// Calculate beta1
beta1 = yBar - beta2 * xBar;
// Return coefficents of regression
return [beta1, beta2];
}
}
var Model = {};
Model.Exponential = {
name:'exponential',
b: [ 1, 1 ],
f: function(x, b) { return b[0] * Math.exp(b[1] * x); },
df:
[
function(x, b) { return Math.exp(b[1] * x); },
function(x, b) { return (b[0] * x) * Math.exp(b[1] * x); }
]
}
Model.BiExponential = {
name:'biexponential',
b: [ 1, 1, 1, 1 ],
f: function(x, b) { return b[0] * Math.exp(b[1] * x) + b[2] * Math.exp(b[3] * x); },
df:
[
function(x, b) { return Math.exp(b[1] * x); },
function(x, b) { return (b[0] * x) * Math.exp(b[1] * x); },
function(x, b) { return Math.exp(b[3] * x); },
function(x, b) { return (b[2] * x) * Math.exp(b[3] * x); }
]
}
class NonLinearRegression {
constructor(model) {
this.model = model;
this.b = this.model.b.map((a) => {return a}); // Copy default beta
this.f = this.model.f;
this.df = this.model.df;
this.damping = 1;
this.errorTolerance = 0.0001;
this.gradientErrorTolerance = 1e-10;
this.gradientMinDifference = 1e30;
this.maxInterations = 100;
}
fit(x, y) {;
if(this.isValidData(y))
this.LevenbergMarquardtAlgorithm(x,y);
return this.b;
}
isValidData(y) {
//var checksum = y.reduce( (a, b) => { return a + b;} );
//var test = (checksum > 10) ? true : false;
//var test2 = !y.reduce(function(a, b){ return (a === b) ? a : NaN; });
//return test && test2;
return true;
}
Residuals(x, y, f, b) {
var R = Matrix.zeros(x.length, 1);
for(var i = 0; i < x.length; i++)
R[i][0] = y[i] - this.f(x[i], b);
return R;
}
Jacobian(x) {
var Nr = x.length;
var Nc = this.b.length;
var J = Matrix.zeros(Nr, Nc);
for (var row = 0; row < Nr; row++)
for (var col = 0; col < Nc; col++)
J[row][col] = this.df[col](x[row], this.b);
return J;
}
GaussNewtonAlgorithm(x, y) {
// Declare varibles
var J, Jt, R, dB;
// Init convergence varibles
var convergence = 1;
var S = [];
R = this.Residuals(x, y, this.f, this.b);
S.push(R.norm());
// Interate
while(convergence > this.errorTolerance) {
// Calculate parameter delta
J = this.Jacobian(x);
Jt = J.transpose();
dB = Inverse(Jt.mmul(J));
dB = dB.mmul(Jt).mmul(R);
// Update parameters
for (var i = 0; i < this.b.length; i++)
this.b[i] = this.b[i] + dB[i][0];
// Calculate convergence
R = this.Residuals(x, y, this.f, this.b);
S.push(R.norm());
var k = S.length - 1;
convergence = Math.abs((S[k] - S[k-1]) / S[k-1]);
}
}
LevenbergMarquardtAlgorithm(x,y) {
// Declare varibles
var J, Jt, JtJ, JtJdiag, R, R0, R1, R2, dB, dB0, dB1, dB2, b0, b1, b2;
var lambda, lambda0, v;
var SE, SE0, SE1, SE2;
// Init convergence variables
var usedGradientDescent = false;
var stop = false;
var interation = 1;
var convergence = 1;
var S = [];
R = this.Residuals(x, y, this.f, this.b);
S.push(R.norm());
// Init damping parameters
lambda = this.damping;
v = 10;
while( convergence > this.errorTolerance ||
(usedGradientDescent && convergence > this.gradientErrorTolerance) &&
!stop &&
interation < this.maxInterations )
{
// Calculate parameter delta
J = this.Jacobian(x);
Jt = J.transpose();
JtJ = Jt.mmul(J);
JtJdiag = Matrix.mul(JtJ, Matrix.eye(JtJ.rows, JtJ.cols));
dB0 = Matrix.add(JtJ, Matrix.mul(JtJdiag, lambda));
dB0 = Inverse(dB0).mmul(Jt).mmul(R);
dB1 = Matrix.add(JtJ, Matrix.mul(JtJdiag, lambda / v));
dB1 = Inverse(dB1).mmul(Jt).mmul(R);
// Calculate MSE of Residuals
b0 = Array(this.b.length);
b1 = Array(this.b.length);
for (var i = 0; i < this.b.length; i++) {
b0[i] = this.b[i] + dB0[i][0];
b1[i] = this.b[i] + dB1[i][0];
}
R0 = this.Residuals(x, y, this.f, b0);
R1 = this.Residuals(x, y, this.f, b1);
SE0 = R0.norm();
SE1 = R1.norm();
// Select dB if it improves the Squared Error
if(SE0 < SE1 && SE0 < S[S.length - 1]) { // Default lambda is best
usedGradientDescent = false;
dB = dB0;
R = R0;
SE = SE0;
}
else if(SE1 < SE0 && SE1 < S[S.length - 1]) { // Decreased lambda is best
usedGradientDescent = false;
dB = dB1;
R = R1;
SE = SE1;
lambda = lambda / v;
}
else { // Search in steepest descent direction
// Increase damping factor by a factor of v until a better MSE is found
dB2 = dB1;
R2 = R1;
SE2 = SE1;
while(SE2 > S[S.length - 1] && lambda < this.gradientMinDifference) {
lambda = lambda * v;
dB2 = Matrix.add(JtJ, Matrix.mul(JtJdiag, lambda));
dB2 = Inverse(dB2).mmul(Jt).mmul(R);
b2 = Array(this.b.length);
for (var i = 0; i < this.b.length; i++)
b2[i] = this.b[i] + dB2[i][0];
R2 = this.Residuals(x, y, this.f, b2);
SE2 = R2.norm();
}
// Couldn't decrease the MSE
if (SE2 >= S[S.length - 1])
stop = true;
usedGradientDescent = true;
dB = dB2;
R = R2;
SE = SE2;
lambda = this.damping; // Reset step-size lambda
}
// Update parameters
for (var i = 0; i < this.b.length; i++)
this.b[i] = this.b[i] + dB[i][0];
// Calculate convergence
S.push(R.norm());
var k = S.length - 1;
convergence = Math.abs((S[k] - S[k-1]) / S[k-1]);
// Increase Iteration
interation++;
}
}
}
class Regression {
constructor() {
this.model = Model.Exponential;
}
linearLeastSquares(x, y) {
var regression = new linearRegression()
return regression.linearLeastSquares(x, y);
}
nonLinearLeastSquares(x, y) {
var regression = new NonLinearRegression(this.model)
return regression.fit(x, y)
}
}
export default new Regression();