DEV Community

Mrinalini Sugosh (Mrina)
Mrinalini Sugosh (Mrina)

Posted on • Updated on

ML Fundamentals in Javascript

maxresdefault

I recently have been exploring the field of Machine Learning; in all honesty, I have had to relearn almost all of my math fundamentals. It has been a while since college and ML is based on a lot of Linear Algebra. In this blog, I plan to curate the fundamentals alongside my implementation of them in Javascript. I know Python's NumPy library is the industry standard, but I have always been curious about how the basic operations would translate in Javascript. Full disclosure, I am just learning this myself, so if there are any mistakes along the way, please help me out!

Topics include:

  • Basics of Linear Algebra
  • Matrix Operations
  • Types of Matrices
  • Complex Mathematical Expressions

I have also compiled a NodeJS App that contains a working version of all the examples in this blog: ML Fundamentals 1

Let's get started~!

Basics of Linear Algebra

Linear Algebra is a subset of algebra that deals with scalars, vectors, and matrices. In the simplest terms here's what they are:

Matrix

I like to think of a matrix as an array or an array of arrays in programming. Where m is the number of rows and n is the number of columns of a matrix[m][n]. If we were coding, it would look something like this:

const matrix = [
  [0, 1],
  [2, 3],
  [3, 4]
];
Enter fullscreen mode Exit fullscreen mode

Vector

A vector is simply a type of matrix and specifically has only one column. Therefore, it would look something like this:

const vector = [
  [0],
  [1],
  [2],
];
Enter fullscreen mode Exit fullscreen mode

Scalar

Probably, the simplest mathematical object in all of Linear Algebra is a scalar. It is just a number that's often used as a multiplier.

const scalar = 2;
Enter fullscreen mode Exit fullscreen mode

Most matrices and vectors can be expressed with arrays in Javascript or any language. But what about matrices that are 3D or 4D or XD? Typically, most Linear Algebra courses state that a matrix can have x dimensions where x is a number greater than 0. This is where we begin to use the idea of tensors in programming where vectors are essentially ranked to correspond to the various dimensions. In fact, Javascript has a framework called Tensorflow.js that defines and runs computations using tensors. I will dive more into that in a future blog. For now, let's get back to the basics.

Matrix Operations

When you think about Matrix Operations, typically my mind jumps to loops. But using loops to iterate a matrix can start to become real ugly real quick. That's when I discovered the features of the Math.js library that provides JS and Node.js projects with powerful, optimized computations. This means that, instead of defining a matrix as an array of arrays, you could simply define a matrix and it would look something like this with Math.js:

const matrix = math.matrix([[0,1], [2,3], [4,5]])
Enter fullscreen mode Exit fullscreen mode

Some useful methods include size() and valueOf():

math.size(matrix) //returns dimensions of matrix
//[3,2]
matrix.valueOf() //returns string representation of the matrix
//[[0,1], [2,3], [4,5]]
Enter fullscreen mode Exit fullscreen mode

Let's explore examples of four main matrix operations such as addition, subtraction, multiplication, and division:

Matrix Addition

matA = math.matrix([[0, 1], [2, 3], [4, -5]]);
matB = math.matrix([[1, -1], [-2, 4], [-7, 4]]);

const matAdd = math.add(matA, matB);

console.log(matAdd.valueOf());
//[ [ 1, 0 ], [ 0, 7 ], [ -3, -1 ] ]
Enter fullscreen mode Exit fullscreen mode

Matrix Subtraction

matA = math.matrix([[0, 1], [2, 3], [4, -5]]);
matB = math.matrix([[1, -1], [-2, 4], [-7, 4]]);

const matSub = math.subtract(matA, matB);

console.log(matSub.valueOf());
//[ [ -1, 2 ], [ 4, -1 ], [ 11, -9 ] ]
Enter fullscreen mode Exit fullscreen mode

Matrix Multiplication

This is when things start to get interesting. Before I jump to coding examples, its important to understand these two properties of Matrix Multiplication: commutative and associative.

Commutative

Matrix multiplication is not commutative which simply means A x B != B x A. Let's test this out and check with MathJS's built in equal comparator:

matA = math.matrix([[0, 1], [2, 3]]);
matB = math.matrix([[2, 4], [6, 2]]);

const matAxB = math.multiply(matA, matB);
const matBxA = math.multiply(matB, matA);

console.log(math.equal(matAxB.valueOf(), matBxA.valueOf()));
// false
Enter fullscreen mode Exit fullscreen mode

Associative

Matrix multiplication is associative which simply translates to A x (B x C) == (A x B) x C. Let's test this out as well:


const matA = math.matrix([[0, 1], [2, 3], [4, 5]]);
const matB = math.matrix([[2, 4], [6, 2]]);
const matC = math.matrix([[5, 2], [2, -2]]);

const matAxB_C = math.multiply(math.multiply(matA, matB), matC);
const matA_BxC = math.multiply(matA, math.multiply(matB, matC));

console.log(math.equal(matAxB_C.valueOf(), matA_BxC.valueOf()));
// true
Enter fullscreen mode Exit fullscreen mode

Its really important to also note that in the case of math.js, the product of a matrix is not just a new matrix containing the product of the individual matrices - element-wise product (or Hardamard product). In fact, the product we see is a matrix product operation.

An example of a element-wise product is via matrix-scalar multiplication, A x 3, which is performed as such:

matA = math.matrix([[0, 1], [2, 3], [4, -5]]);
const scalar = 3;
const matAx3 = math.multiply(matA, scalar);

console.log(matAx3.valueOf());
//[ [ 0, 3 ], [ 6, 9 ], [ 12, -15 ] ]
Enter fullscreen mode Exit fullscreen mode

Of course, since a vector is just a matrix it is also possible to perform a matrix-vector multiplication:

matA = math.matrix([[0, 1], [2, 3], [4, 5]]);
matB = math.matrix([[2], [1]]);

const matAxvB = math.multiply(matA, matB);

console.log(matAxvB.valueOf());
//[ [ 1 ], [ 7 ], [ 13 ] ]
Enter fullscreen mode Exit fullscreen mode

Matrix Division

Matrix division is also possible to implement in Javascript. Note that in mathematically there is no "Matrix Divsion" as its defined as such:
A/B = AB-1
However to save us from thinking about divisions and inverses we can implement matdix.divide() in js:

matA = math.matrix([[0, 2], [2, 4], [4, 6]]);
matB = math.matrix([[2, 1], [2, 2]]);

const matAB = math.divide(matA, matB);

console.log(matAB.valueOf());
// [ [ -2, 2 ], [ -2, 3 ], [ -2, 4 ] ]
Enter fullscreen mode Exit fullscreen mode

After all, dealing with matrices in math.js isn't that difficult anymore! But you have to know the dimensions of each matrix in your operation, because not every matrix "operates" on another matrix!

Types of Matrices

There are a couple of matrix types in Linear Algebra that is important to understand as well.

Identity Matrix

The Identity (I) Matrix with the dimension i * j is defined as i-dimensional matrix where i == j. They are special and used widely because they are commutative; this means A x I === I x A. Here's an example of an Identity Matrix:

const matrix = [
  [1, 0, 0],
  [0, 1, 0],
  [0, 0, 1],
];
Enter fullscreen mode Exit fullscreen mode

In math.js you can use the eye(i) method to quickly generate an identity matrix with dimension i:

const matI_3 = math.eye(3);
console.log(matA.valueOf());
// [ [ 1, 0, 0 ], [ 0, 1, 0 ], [ 0, 0, 1 ] ]
Enter fullscreen mode Exit fullscreen mode

Transpose Matrix

In a transpose matrix the dimensions are flipped. Simply stated, the rows become columns and the columns become rows Here's an example of taking a vector and transposing it. The transposed matrix is referred to as a row vector:

const matV = math.matrix([[0], [1], [2]]);
const matV_T = math.transpose(matV);

console.log(matV_T.valueOf());
// [ [ 0, 1, 2 ] ]
Enter fullscreen mode Exit fullscreen mode

Inverse Matrix

Of course, its important to discuss the inverse matrix. What's interesting about this is that matrices can have an inverse A-1 but not all matrices (specifically singular or degenerate) have one. The formula to find the inverse of a matrix: A(A-1) = (A-1)A = I.
But Math.js gives us the inverse operation for free as math.inv() check it out:

matA = math.matrix([[0, 1], [2, 3]]);
const matA_1 = math.inv(matA);

console.log(matA_1.valueOf());
// [ [ -1.5, 0.5 ], [ 1, -0 ] ]
Enter fullscreen mode Exit fullscreen mode

Complex Math Expressions

At some point, using built in math.js the proposed way doesn't scale anymore. Let's be honest, ML gets complicated real quick. Especially when you are trying to perform operations with multiple features and use a multivariate linear regression with gradient descent aka a function with multiple inputs. Take the following function of theta as an example:

theta = theta - ALPHA / m * ((X * theta - y)^T * X)^T
Enter fullscreen mode Exit fullscreen mode

If you try to represent this out of the box in Javascript you will get a mess like this:

theta = math.subtract(
  theta,
  math.multiply(
    (ALPHA / m),
    math.transpose(
      math.multiply(
        math.transpose(
          math.subtract(
            math.multiply(
              X,
              theta
            ),
            y
          )
        ),
        X
      )
    )
  )
);
Enter fullscreen mode Exit fullscreen mode

What a mess right? Luckily there is a concise and readable way to evaluate it using the eval function:

theta = math.eval(`theta - ALPHA / m * ((X * theta - y)' * X)'`, {
  theta,
  ALPHA,
  m,
  X,
  y,
});
Enter fullscreen mode Exit fullscreen mode

Shocked that all of this is possible with Javascript? You are not alone! Regardless of the coding language you use today, you will surely find a powerful math library such as MathJS to enable matrix operations and other complex operations to get you started with ML fundamentals! I hope this was helpful.

If you want to experiment with the Math.js library on your own, do checkout the Github repository with all of these examples compiled in a NodeJS app:
https://github.com/mrinasugosh/ml-fundamentals-1

==== Follow me on Social Media(@mrinasugosh) ====
Dev.to: @mrinasugosh
Github: @mrinasugosh
Twitter: @mrinasugosh
LinkedIn: @mrinasugosh

Discussion (9)

Collapse
cjsmocjsmo profile image
Charlie J Smotherman

Have a look at pyodide

The python interpreter compiled to webassembly (python in the web browser), which allows you to use numpy, pandas matplotlib and scipy in the browser for you ML projects

Hope this helps

Collapse
mrinasugosh profile image
Mrinalini Sugosh (Mrina) Author

@cjsmocjsmo With Pyodide, I have been following the webassembly space by Mozilla quite closely now and Pyodide surely looks promising as a on prem environment for ML. Would highly recommend folks reading this article to check it out as well:
pyodide.org/en/stable/
github.com/pyodide/pyodide

Collapse
mrinasugosh profile image
Mrinalini Sugosh (Mrina) Author

Although, I do wish to give folks a heads up that the there is still a lot of work around performance for Pyodide:
github.com/pyodide/pyodide/tree/ma...

Generally, it's 4-8x slower than pure python, and 1-2x slower than Python code that uses lots of c-extensions (e.g. numpy). There is some evidence, Node.js might be somewhat faster than native Python (on the benchmarks they considered) but again its use case dependent.

Thread Thread
cjsmocjsmo profile image
Charlie J Smotherman

Lolol well it seems you have looked into pyodide. I just recently learned about pyodide and it's #63 on my todo list so I haven't had a chance to do a deep dive yet. Thank you for the eval 👍

Collapse
swatirajan7 profile image
Swati Rajan

Great article! I had no idea about math.eval() Can we apply them to matrices too?

Collapse
mrinasugosh profile image
Mrinalini Sugosh (Mrina) Author

@swatirajan7 Glad you found it useful! Yes it can...For instance, it can be used to extract a subset of a Matrix by range indices. For example, this snippet returns the first and second column of Matrix A (indices start with 1) with all their rows as two vectors in a new matrix.

let matAsub = math.eval('matA[:, 1:2]', { matA });
Enter fullscreen mode Exit fullscreen mode
Collapse
mrinasugosh profile image
Mrinalini Sugosh (Mrina) Author

Actually, I would even propose taking it a step further and assign columns in a matrix to a new vector:

math.eval(`matA[:, 1] = vectorB`, { matA, vectorB });
Enter fullscreen mode Exit fullscreen mode
Collapse
inhuofficial profile image
InHuOfficial

Can I give this more than 1 ❤️ And 1 🦄??

I don’t think you could have explained everything any more clearly!

Collapse
mrinasugosh profile image
Mrinalini Sugosh (Mrina) Author

Thank you @inhuofficial ! I am really glad you found it useful :)