JavaScript 中的机器学习基础知识
我最近一直在探索机器学习领域;说实话,我不得不重新学习几乎所有的数学基础知识。大学毕业已经有一段时间了,机器学习很大程度上依赖于线性代数。在这篇博客中,我计划整理一下这些基础知识,并附上我用 JavaScript 实现它们的方法。我知道 Python 的NumPy库是行业标准,但我一直很好奇这些基本操作在 JavaScript 中是如何实现的。坦白说,我目前还在自学,所以如果过程中有任何错误,请指正!
主题包括:
- 线性代数基础
- 矩阵运算
- 矩阵的类型
- 复杂的数学表达式
我还编译了一个 NodeJS 应用程序,其中包含本博客中所有示例的工作版本:ML Fundamentals 1
开始吧〜!
线性代数基础
线性代数是代数的一个子集,它处理标量、向量和矩阵。简单来说,它们的含义如下:
矩阵
在编程中,我喜欢把矩阵想象成一个数组或一个数组的数组。其中m
,是行数,n
是列数matrix[m][n]
。如果我们进行编程,它看起来应该是这样的:
const matrix = [
[0, 1],
[2, 3],
[3, 4]
];
向量
向量只是矩阵的一种,并且只有一列。因此,它看起来像这样:
const vector = [
[0],
[1],
[2],
];
标量
线性代数中最简单的数学对象可能就是标量。它只是一个经常用作乘数的数字。
const scalar = 2;
在 JavaScript 或任何语言中,大多数矩阵和向量都可以用数组表示。但是,3D、4D 或 XD 矩阵呢?通常,大多数线性代数课程都指出,矩阵的x
维度可以x
是大于 0 的数。这时,我们开始在编程中使用张量的概念,向量本质上是按维度排序的。事实上,JavaScript 有一个名为Tensorflow.js的框架,它使用张量定义和运行计算。我将在以后的博客中深入探讨这一点。现在,让我们回到基础知识。
矩阵运算
说到矩阵运算,我通常会想到循环。但用循环来迭代矩阵很快就会变得非常难看。这时,我发现了Math.js库的功能,它为 JS 和 Node.js 项目提供了强大且优化的计算能力。这意味着,你不必将矩阵定义为数组的数组,而是可以直接定义一个数组matrix
,在 Math.js 中它看起来就像这样:
const matrix = math.matrix([[0,1], [2,3], [4,5]])
一些有用的方法包括size()
和valueOf()
:
math.size(matrix) //returns dimensions of matrix
//[3,2]
matrix.valueOf() //returns string representation of the matrix
//[[0,1], [2,3], [4,5]]
让我们探索四种主要矩阵运算的示例,例如加法、减法、乘法和除法:
矩阵加法
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 ] ]
矩阵减法
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 ] ]
矩阵乘法
事情开始变得有趣了。在开始写代码示例之前,理解矩阵乘法的两个性质很重要:交换律和结合律。
交换律
矩阵乘法不满足交换律,这意味着 A x B != B x A。让我们测试一下,并使用 MathJS 的内置equal
比较器进行检查:
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
联想
矩阵乘法是结合的,简单地转换为 A x (B x C) == (A x B) x C。让我们也测试一下:
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
需要特别注意的是,在 math.js 中,矩阵的乘积不仅仅是一个包含各个矩阵乘积的新矩阵——逐元素乘积(或哈达玛乘积)。实际上,我们看到的乘积是一个矩阵乘积运算。
元素级乘积的一个例子是通过矩阵标量乘法 A x 3,其执行方式如下:
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 ] ]
当然,由于向量只是一个矩阵,因此也可以执行矩阵向量乘法:
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 ] ]
矩阵除法
矩阵除法也可以用 JavaScript 实现。需要注意的是,数学上并不存在“矩阵除法”,其定义如下:
A/B = AB -1。
不过,为了省去除法和逆运算的麻烦,我们可以在 JavaScript 中实现 matdix.divide():
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 ] ]
毕竟,在 math.js 中处理矩阵已经不再那么难了!但是你必须知道操作中每个矩阵的维度,因为并非每个矩阵都会“作用”于另一个矩阵!
矩阵的类型
线性代数中还有几种矩阵类型也很重要。
单位矩阵
维度为 i * j 的单位矩阵 (I) 定义为 i 维矩阵,其中 i == j。它们很特殊,并且由于满足交换律而被广泛使用;这意味着 A x I === I x A。以下是单位矩阵的示例:
const matrix = [
[1, 0, 0],
[0, 1, 0],
[0, 0, 1],
];
在 math.js 中可以使用该eye(i)
方法快速生成维度为 i 的单位矩阵:
const matI_3 = math.eye(3);
console.log(matA.valueOf());
// [ [ 1, 0, 0 ], [ 0, 1, 0 ], [ 0, 0, 1 ] ]
转置矩阵
在转置矩阵中,维度被翻转。简单地说,行变成了列,列变成了行。以下是一个对向量进行转置的示例。转置后的矩阵称为行向量:
const matV = math.matrix([[0], [1], [2]]);
const matV_T = math.transpose(matV);
console.log(matV_T.valueOf());
// [ [ 0, 1, 2 ] ]
逆矩阵
当然,讨论逆矩阵非常重要。有趣的是,矩阵可以有逆 A -1,但并非所有矩阵(特别是奇异矩阵或退化矩阵)都有。求矩阵逆的公式为:A(A -1 ) = (A -1 )A = I。
不过 Math.js 为我们提供了逆运算,您可以自行math.inv()
查看:
matA = math.matrix([[0, 1], [2, 3]]);
const matA_1 = math.inv(matA);
console.log(matA_1.valueOf());
// [ [ -1.5, 0.5 ], [ 1, -0 ] ]
复杂的数学表达式
到了一定时候,使用内置的 math.js 提出的方案就不再具有可扩展性了。说实话,机器学习很快就会变得复杂起来。尤其是当你尝试执行包含多个特征的运算,并使用带有梯度下降的多元线性回归(也就是包含多个输入的函数)时。以下面的 theta 函数为例:
theta = theta - ALPHA / m * ((X * theta - y)^T * X)^T
如果你尝试在 Javascript 中将其表示出来,你将会得到如下的混乱:
theta = math.subtract(
theta,
math.multiply(
(ALPHA / m),
math.transpose(
math.multiply(
math.transpose(
math.subtract(
math.multiply(
X,
theta
),
y
)
),
X
)
)
)
);
真是一团糟,对吧?幸运的是,有一个简洁易读的方法可以使用以下eval
函数来求值:
theta = math.eval(`theta - ALPHA / m * ((X * theta - y)' * X)'`, {
theta,
ALPHA,
m,
X,
y,
});
惊讶于这一切竟然可以用 JavaScript 实现?你并不孤单!无论你现在使用哪种编程语言,你一定能找到一个强大的数学库,例如 MathJS,它能够支持矩阵运算和其他复杂的运算,帮助你掌握机器学习的基础知识!希望这篇文章对你有所帮助。
如果您想自己尝试 Math.js 库,请查看 Github 存储库,其中包含在 NodeJS 应用程序中编译的所有这些示例:
https://github.com/mrinasugosh/ml-fundamentals-1
==== 在社交媒体上关注我( @mrinasugosh
) ====
Dev.to: @mrinasugosh
Github: @mrinasugosh
Twitter: @mrinasugosh
LinkedIn: @mrinasugosh