在 JavaScript 中执行指数回归
Performing an exponential regression in JavaScript
重要更新:
经过大量研究和实验后,我学到了一些让我重新设计问题的东西。我不是在尝试寻找“指数回归”,而是在尝试优化具有有界输入和可能无界输出的非线性误差函数。
只要函数是线性的,就有办法直接计算最优参数来最小化平方误差项(通过对函数求导,定位导数为零的点,然后用这个局部最小值作为您的解决方案)。很多时候,当人们说“指数回归”时,他们指的是 a * e^(b*x)
形式的方程。这个原因,如下所述,是通过取两边的自然对数,这完美地映射到一个线性方程,因此可以使用相同的方法在一个步骤中直接计算。
然而,在我的例子中,方程 a * b^x
而不是 映射到线性方程,因此没有直接解决方案。相反,必须反复确定解决方案。
有一些非线性曲线拟合算法。值得注意的是 Levenberg-Marquardt。我发现了该算法的一些实现:
- C++:https://www.gnu.org/software/gsl/doc/html/nls.html
- Python: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.curve_fit.html
- JavaScript: https://github.com/mljs/levenberg-marquardt
不幸的是,我尝试了所有这三种实现,曲线拟合只是 糟糕。我有一些包含 11,000 个点的样本数据,我知道最佳参数是 a = 0.08
和 b = 1.19
,但是这些算法经常返回奇怪的结果,例如 a = 117, b = 0.000001
或 a = 0, b = 3243224
接下来我尝试使用 Excel 来验证我对问题的理解。误差函数可以定义为 sum((y - y')^2)
,其中 y'
是给定参数和输入 x
的估计值。然后问题就落在了最小化这个误差函数上。我打开了我的数据 (CSV),为“计算”值添加了一列,为平方误差项添加了另一列,最后使用规划求解来优化误差项的总和。这很好用!我回来了a = 0.0796, b = 1.1897
。在同一张图(原始数据和估计数据)上绘制两条线显示非常适合。
起初我尝试使用 OpenOffice 做同样的事情,但是 OpenOffice 内置的求解器和我做的 Levenberg-Marquardt 实验一样糟糕,并且反复给出毫无价值的解决方案。即使我设置了初始值,它也会“优化”问题并得出比开始时更糟糕的结果。
在 Excel 中证明了我的概念后,我尝试使用 optimization-js。我尝试了他们的遗传优化和鲍威尔优化(因为我没有梯度函数),在这两种情况下都产生了糟糕的结果。
我确实发现 a question regarding how Excel's Solver works which linked to an ugly PDF. I haven't taken the time to read the PDF yet, but it may provide hints for solving the problem manually. I also found a Python example 据报道实现了广义梯度下降(与 Excel 相同的算法),所以如果我能理解它并重写它以接受通用函数作为输入,那么我也许可以使用它。
新问题(鉴于所有这些):
如何,最好在 JavaScript 中(尽管其他语言也可以接受,只要它们可以在 AWS Lambda 上 运行),我可以优化以下函数的参数以最小化其输出吗?
data = /* ... read from CSV ... */;
let errorFunc = function([a, b]) {
let sumSqErr = 0;
data.forEach(pt => {
const est = a * Math.pow(b, pt.x);
const err = est - pt.y;
sumSqErr += err * err;
});
return sumSqErr * 1e-7;
}
老问题:
给定数千个数据点,我想执行最小二乘回归分析来计算 a
和 b
形式的方程:
y = a * b^x
在我大学时代,我知道如何使用矩阵数学来做到这一点,但恐怕那是 10 年前的事了,我已经不再练习了。我首先阅读了使用 YouTube 系列矩阵(例如 https://www.youtube.com/watch?v=fb1CNQT-3Pg 中的矩阵计算最小二乘参数的方法 - 但是在摸索了一下如何从 linear[ 进行转换之后=121=]回归到指数,我决定看看是否有人已经为我解决了这个问题并且前往 google 搜索“JavaScript 指数回归”
这出现了两个图书馆:
然而,这两个库都求解以下形式的方程:
y = a * e^(b*x)
我想知道是否有人可以帮助我将上述形式转换为我想要的形式(我可以在执行回归之前修改输入的 y
或 x
值,因为实例计算每个的自然对数)或者如果有人可以带我了解在没有第三方库的情况下执行这样的回归的基础知识(除了可能的 math.js 之类的东西以促进矩阵运算)
更新
仔细想想,我明白了为什么每个库都解决了 y = a * e^(b*x)
——取两边的自然对数简化为基本的线性回归:
ln(y) = ln(a * e^(b*x))
ln(y) = ln(a) + ln(e^(b*x))
ln(y) = ln(a) + b*x
用 a'
替换 ln(a)
并在执行回归分析之前取所有输入 y
值的自然对数(我们可以调用新值 y'
) 表明我们此时只是在做线性回归:
y' = a' + bx (solve for a' and b)
然后您可以计算 e^(a')
以恢复 a
不幸的是,我要解决的问题更为复杂,因为指数的底不是常数。要“求解”b
,必须能够在等式的一侧单独得到 b
。我们能做的最好的是:
y = ab^x
(y/a) = b^x
(y/a)^(1/x) = b
这让我们取 y
的“第 x
个根”。我不完全确定这在实践中如何(或是否)起作用?与前面的示例不同,我采用了 y
输入的自然对数(对每个输入执行的常见操作),这让我执行了 different 对每个输入的操作(一个 y
我取平方根,另一个我取立方根,另一个我取四次根,等等)
这个回归是否有可能只能迭代求解(例如通过牛顿法)?如果是这样,我将如何解决它?我需要先计算复数导数吗?
首先也是最重要的:
- 我的方程是可微的,我只是不确定如何微分它。误差函数是一个总和,因此偏导数只是总和内部的导数,它是使用链式法则计算的。这意味着我可以使用任何需要梯度的非线性优化器算法
- 当输入的非常小的变化导致输出的 巨大的 变化时,许多非线性优化器都会遇到麻烦,这可能是指数级的情况职能。调整阻尼参数或收敛参数可以帮助解决这个问题
经过一些工作,我能够得到一个版本的梯度下降来计算与 Excel 相同的答案,但是 运行 需要 15 秒(对比 Excel 运行 求解器在 ~2 秒内)——显然我的实现很糟糕
更重要的是
参见:https://math.stackexchange.com/a/3850781/209313
e^(bx)
和 b^x
之间没有有意义的区别。因为b^x == e^(log(b)*x)
。因此,我们可以使用线性回归模型,然后通过对模型输出的任何内容取 e
次方来计算 b
。
const reg = require("regression");
const data = /* ... get data from CSV ... */;
const results = reg.exponential(data);
console.log("a: " + results.equation[0] + ", b: " + Math.exp(results.equation[1]));
重要更新:
经过大量研究和实验后,我学到了一些让我重新设计问题的东西。我不是在尝试寻找“指数回归”,而是在尝试优化具有有界输入和可能无界输出的非线性误差函数。
只要函数是线性的,就有办法直接计算最优参数来最小化平方误差项(通过对函数求导,定位导数为零的点,然后用这个局部最小值作为您的解决方案)。很多时候,当人们说“指数回归”时,他们指的是 a * e^(b*x)
形式的方程。这个原因,如下所述,是通过取两边的自然对数,这完美地映射到一个线性方程,因此可以使用相同的方法在一个步骤中直接计算。
然而,在我的例子中,方程 a * b^x
而不是 映射到线性方程,因此没有直接解决方案。相反,必须反复确定解决方案。
有一些非线性曲线拟合算法。值得注意的是 Levenberg-Marquardt。我发现了该算法的一些实现:
- C++:https://www.gnu.org/software/gsl/doc/html/nls.html
- Python: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.curve_fit.html
- JavaScript: https://github.com/mljs/levenberg-marquardt
不幸的是,我尝试了所有这三种实现,曲线拟合只是 糟糕。我有一些包含 11,000 个点的样本数据,我知道最佳参数是 a = 0.08
和 b = 1.19
,但是这些算法经常返回奇怪的结果,例如 a = 117, b = 0.000001
或 a = 0, b = 3243224
接下来我尝试使用 Excel 来验证我对问题的理解。误差函数可以定义为 sum((y - y')^2)
,其中 y'
是给定参数和输入 x
的估计值。然后问题就落在了最小化这个误差函数上。我打开了我的数据 (CSV),为“计算”值添加了一列,为平方误差项添加了另一列,最后使用规划求解来优化误差项的总和。这很好用!我回来了a = 0.0796, b = 1.1897
。在同一张图(原始数据和估计数据)上绘制两条线显示非常适合。
起初我尝试使用 OpenOffice 做同样的事情,但是 OpenOffice 内置的求解器和我做的 Levenberg-Marquardt 实验一样糟糕,并且反复给出毫无价值的解决方案。即使我设置了初始值,它也会“优化”问题并得出比开始时更糟糕的结果。
在 Excel 中证明了我的概念后,我尝试使用 optimization-js。我尝试了他们的遗传优化和鲍威尔优化(因为我没有梯度函数),在这两种情况下都产生了糟糕的结果。
我确实发现 a question regarding how Excel's Solver works which linked to an ugly PDF. I haven't taken the time to read the PDF yet, but it may provide hints for solving the problem manually. I also found a Python example 据报道实现了广义梯度下降(与 Excel 相同的算法),所以如果我能理解它并重写它以接受通用函数作为输入,那么我也许可以使用它。
新问题(鉴于所有这些):
如何,最好在 JavaScript 中(尽管其他语言也可以接受,只要它们可以在 AWS Lambda 上 运行),我可以优化以下函数的参数以最小化其输出吗?
data = /* ... read from CSV ... */;
let errorFunc = function([a, b]) {
let sumSqErr = 0;
data.forEach(pt => {
const est = a * Math.pow(b, pt.x);
const err = est - pt.y;
sumSqErr += err * err;
});
return sumSqErr * 1e-7;
}
老问题:
给定数千个数据点,我想执行最小二乘回归分析来计算 a
和 b
形式的方程:
y = a * b^x
在我大学时代,我知道如何使用矩阵数学来做到这一点,但恐怕那是 10 年前的事了,我已经不再练习了。我首先阅读了使用 YouTube 系列矩阵(例如 https://www.youtube.com/watch?v=fb1CNQT-3Pg 中的矩阵计算最小二乘参数的方法 - 但是在摸索了一下如何从 linear[ 进行转换之后=121=]回归到指数,我决定看看是否有人已经为我解决了这个问题并且前往 google 搜索“JavaScript 指数回归”
这出现了两个图书馆:
然而,这两个库都求解以下形式的方程:
y = a * e^(b*x)
我想知道是否有人可以帮助我将上述形式转换为我想要的形式(我可以在执行回归之前修改输入的 y
或 x
值,因为实例计算每个的自然对数)或者如果有人可以带我了解在没有第三方库的情况下执行这样的回归的基础知识(除了可能的 math.js 之类的东西以促进矩阵运算)
更新
仔细想想,我明白了为什么每个库都解决了 y = a * e^(b*x)
——取两边的自然对数简化为基本的线性回归:
ln(y) = ln(a * e^(b*x))
ln(y) = ln(a) + ln(e^(b*x))
ln(y) = ln(a) + b*x
用 a'
替换 ln(a)
并在执行回归分析之前取所有输入 y
值的自然对数(我们可以调用新值 y'
) 表明我们此时只是在做线性回归:
y' = a' + bx (solve for a' and b)
然后您可以计算 e^(a')
以恢复 a
不幸的是,我要解决的问题更为复杂,因为指数的底不是常数。要“求解”b
,必须能够在等式的一侧单独得到 b
。我们能做的最好的是:
y = ab^x
(y/a) = b^x
(y/a)^(1/x) = b
这让我们取 y
的“第 x
个根”。我不完全确定这在实践中如何(或是否)起作用?与前面的示例不同,我采用了 y
输入的自然对数(对每个输入执行的常见操作),这让我执行了 different 对每个输入的操作(一个 y
我取平方根,另一个我取立方根,另一个我取四次根,等等)
这个回归是否有可能只能迭代求解(例如通过牛顿法)?如果是这样,我将如何解决它?我需要先计算复数导数吗?
首先也是最重要的:
- 我的方程是可微的,我只是不确定如何微分它。误差函数是一个总和,因此偏导数只是总和内部的导数,它是使用链式法则计算的。这意味着我可以使用任何需要梯度的非线性优化器算法
- 当输入的非常小的变化导致输出的 巨大的 变化时,许多非线性优化器都会遇到麻烦,这可能是指数级的情况职能。调整阻尼参数或收敛参数可以帮助解决这个问题
经过一些工作,我能够得到一个版本的梯度下降来计算与 Excel 相同的答案,但是 运行 需要 15 秒(对比 Excel 运行 求解器在 ~2 秒内)——显然我的实现很糟糕
更重要的是
参见:https://math.stackexchange.com/a/3850781/209313
e^(bx)
和 b^x
之间没有有意义的区别。因为b^x == e^(log(b)*x)
。因此,我们可以使用线性回归模型,然后通过对模型输出的任何内容取 e
次方来计算 b
。
const reg = require("regression");
const data = /* ... get data from CSV ... */;
const results = reg.exponential(data);
console.log("a: " + results.equation[0] + ", b: " + Math.exp(results.equation[1]));