从零到英雄的 Python 数值导数

原文:towardsdatascience.com/hands-on-numerical-derivative-with-python-from-zero-to-hero-79eb5b5ffabf

至少在每所大学的实验室里都能找到一句著名的言论,它是这样的:

理论是你知道一切但什么都不起作用。实践是当一切起作用但没有人知道为什么。在这个实验室中,我们结合了理论和实践:什么都不起作用,没有人知道为什么。

我觉得这句话在数据科学领域非常贴切。我说这个是因为数据科学最初是一个数学问题(理论):你需要最小化损失函数。然而,当你进入**现实生活(实验/实验室)**时,事情开始变得非常混乱,你完美的理论世界假设可能不再适用(它们永远不会适用),而且你不知道为什么。

例如,考虑导数的概念。任何处理数据科学复杂概念的人都知道(或者,更好的是,必须知道)什么是导数。但是,如何将优雅的理论概念应用于现实生活中的导数,在噪声信号上,在没有解析方程的情况下?

在这篇博客文章中,我想展示一个像导数这样简单的概念在处理现实生活时如何揭示出许多复杂性。我们将按以下顺序进行:

  1. 导数概念符号导数数值导数简要介绍

  2. 数值导数概念的实现和限制

  3. 数值导数的改进以克服简单实现的限制。

让我们开始吧!

1. 导数的理论介绍

我相信导数的概念对你们中的许多人来说都很熟悉,我不想浪费你们的时间:我会非常快速地讲解理论思想,然后我们将专注于数值部分。

因此,导数是曲线切线的斜率。例如,对于曲线 y = x²,导数是 x = 2 时橙色线的斜率。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/f8c46c0d2eab888122e4019102725674.png

作者制作的照片

现在,一个大的正导数意味着你在那个点迅速增加:如果你增加 x,那么你也会增加 y。所有这些都总结在这个强大的定义中:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/81a847d5684b40cd361042dc4cda1e0c.png

作者制作的照片

就这样。***这是这篇文章的最后一部分理论,我保证。***现在让我们来做真正的事情。

2. 数值实现的介绍

当我们有一个方程的符号表示时,一切都很简单。例如,我们知道(从理论中):

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/5f449b19c071c3f6bd239be51b078e5c.png

作者制作的照片

但假设我们没有符号方程。我们如何计算导数?

嗯,在现实生活中(对于数值信号)你没有那么奢侈,可以取"h 趋向于 0"。你唯一拥有的就是:

  • 信号:这是一个列表。

  • 时间轴:另一个列表

当然,这两个(无论是信号还是时间轴)都不是连续的。所以你唯一的希望就是做这样的事情:

def dydx(y,x,i):
return (y[x[i]]-y[x[i+1]])/(x[i+1]-x[i]) 

这意味着在离散空间中你实际上不能做"少于 1 步"。所以你可能想:“好吧,那我们就这么做!让我们假设 h 为 1,每次移动 1 步”

问题在于,当信号噪声大时,导数可能会非常大,*并不是因为信号在 x 方向上真的增加而是仅仅因为噪声恰好增加了。

想象一个上下波动的信号是这样的:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/ffbee3ceb09be69ad30c86d1f0cf19ed.png

由作者制作的照片

导数非常不稳定,但信号在任何地方都没有增加或减少。它是在局部变化的,但这只是我们不感兴趣的噪声的随机变化。

那么在这些情况下我们怎么做呢?让我们一起来看看:

2.1 简单实现及其局限性:

让我们探讨一个真实案例,并且用Python来实现它。对于这篇博客,你不需要很多,我使用了sklearnscipynumpyMatplotlib。它们可能都包含在 Anaconda 包中(很难跟踪它们),但如果不是的话,简单的pip install就能解决问题。

无论如何,让我们导入它们:

cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fjovian.com%2Fembed%3Furl%3Dhttps%3A%2F%2Fjovian.ml%2Fpiero-paialunga%2Fderivative-notebook%2Fv%2F1%26cellId%3D0&dntp=1&display_name=Jovian&url=https%3A%2F%2Fjovian.ml%2Fpiero-paialunga%2Fderivative-notebook%2Fv%2F1%26cellId%3D0&key=a19fcc184b9711e1b4764040d3dc5c07&type=text%2Fhtml&scroll=auto&schema=jovian

现在让我们考虑一个非常非常简单的二次信号。

cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fjovian.com%2Fembed%3Furl%3Dhttps%3A%2F%2Fjovian.ml%2Fpiero-paialunga%2Fderivative-notebook%2Fv%2F3%26cellId%3D1&dntp=1&display_name=Jovian&url=https%3A%2F%2Fjovian.ml%2Fpiero-paialunga%2Fderivative-notebook%2Fv%2F3%26cellId%3D1&key=a19fcc184b9711e1b4764040d3dc5c07&type=text%2Fhtml&scroll=auto&schema=jovian

数值导数可以使用 numpy 来完成,并且:

np.diff(y)/step

其中“步长”将是“h”,在这种情况下,是第二个和第一个 x 之间的距离。所以如果我们做“理论”与“数值”的比较,我们得到如下结果:

cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fjovian.com%2Fembed%3Furl%3Dhttps%3A%2F%2Fjovian.ml%2Fpiero-paialunga%2Fderivative-notebook%2Fv%2F7%26cellId%3D2&dntp=1&display_name=Jovian&url=https%3A%2F%2Fjovian.ml%2Fpiero-paialunga%2Fderivative-notebook%2Fv%2F7%26cellId%3D2&key=a19fcc184b9711e1b4764040d3dc5c07&type=text%2Fhtml&scroll=auto&schema=jovian

所以它工作得很好,让我们结束这篇文章(我在开玩笑

问题出现在信号不是童话中的信号,而是来自现实世界的一个信号。当数据来自现实世界时,它们必然会有噪声。例如,让我们考虑同一个之前的信号,但让我们让它稍微有点噪声

这是稍微有点噪声的信号:

cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fjovian.com%2Fembed%3Furl%3Dhttps%3A%2F%2Fjovian.ml%2Fpiero-paialunga%2Fderivative-notebook%2Fv%2F11%26cellId%3D5&dntp=1&display_name=Jovian&url=https%3A%2F%2Fjovian.ml%2Fpiero-paialunga%2Fderivative-notebook%2Fv%2F11%26cellId%3D5&key=a19fcc184b9711e1b4764040d3dc5c07&type=text%2Fhtml&scroll=auto&schema=jovian

它有点噪声,但并不是那么,对吧?然而,这个非常小的噪声完全破坏了导数。

Jovian 上的导数笔记本

我们该如何解决这个问题呢?让我们看看一些解决方案。

3. 导数细化

3.1 清洗信号

我们首先可以做的事情(比较简单)是对原始信号进行平滑处理:如果我们知道我们的信号是嘈杂的,我们应该在执行其他任何操作之前尝试减少噪声。

我最喜欢的降噪方法是Savitzky-Golay 滤波器(我在另一篇文章中提到了它,你可以在这里找到)。让我们平滑信号并看看它的样子:

Jovian 上的导数笔记本

现在,我们可以在平滑后的信号上进行求导。

Jovian 上的导数笔记本

非常好。然而,这并不是真正的导数细化。这更像是将相同的概念应用于平滑信号。有点作弊,但这是一个第一步。让我们再进行一些细化

3.2 自定义步长

从理论角度来看,h 越小越好。然而,如果信号是噪声的,我们可能会将 h 视为一个参数。因此,我们可以调整这个参数,看看导数看起来如何。

例如,如果 x 轴在 -2(例如秒)和 2 秒之间,步长为 0.004 秒,那么我们可能会认为我们可以在 0.4 秒(100 个点)之后而不是在 0.004 秒(1 个点)之后计算导数。

为了做到这一点,我们必须定义我们自己的数值导数函数。就像这样*:

cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fjovian.com%2Fembed%3Furl%3Dhttps%3A%2F%2Fjovian.ml%2Fpiero-paialunga%2Fderivative-notebook%2Fv%2F21%26cellId%3D13&dntp=1&display_name=Jovian&url=https%3A%2F%2Fjovian.ml%2Fpiero-paialunga%2Fderivative-notebook%2Fv%2F21%26cellId%3D13&key=a19fcc184b9711e1b4764040d3dc5c07&type=text%2Fhtml&scroll=auto&schema=jovian

我们需要小心,因为在信号的末尾,我们可能不会有 100 个点,而是大约 20 个或 30 个点。所以我做了一点检查,如果我们得到的点数少于最初的一半,我们就停止计算导数。

让我们再次测试这个方法:

cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fjovian.com%2Fembed%3Furl%3Dhttps%3A%2F%2Fjovian.ml%2Fpiero-paialunga%2Fderivative-notebook%2Fv%2F21%26cellId%3D11&dntp=1&display_name=Jovian&url=https%3A%2F%2Fjovian.ml%2Fpiero-paialunga%2Fderivative-notebook%2Fv%2F21%26cellId%3D11&key=a19fcc184b9711e1b4764040d3dc5c07&type=text%2Fhtml&scroll=auto&schema=jovian

让我们修改导数的定义。

cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fjovian.com%2Fembed%3Furl%3Dhttps%3A%2F%2Fjovian.ml%2Fpiero-paialunga%2Fderivative-notebook%2Fv%2F23%26cellId%3D13&dntp=1&display_name=Jovian&url=https%3A%2F%2Fjovian.ml%2Fpiero-paialunga%2Fderivative-notebook%2Fv%2F23%26cellId%3D13&key=a19fcc184b9711e1b4764040d3dc5c07&type=text%2Fhtml&scroll=auto&schema=jovian

因此,我们窗口化版本的导数比平滑信号上的导数(第一次改进)要好得多。

3.3 自定义步长 + 线性回归

现在,修改步长有助于改进,但可能还不够。让我们考虑这种情况。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/7a094fe0afe030b01bef893dda7fd7da.png

作者制作的照片

如果我们想要计算 f(x)在蓝色交叉点的导数,到目前为止,我们有两种选择:

  1. 绿色线条,我们只信任下一个点并做切线

  2. 你采用我们提出的窗口化方法(黑色线条

如我们所见,窗口化方法与橙色(真实)的基准线相似,但并不完全一样。这是因为,如果噪声保持不变,即使在 k 步之后我们也会有一点点噪声。那么我们如何解决这个问题呢?

好吧,我们不仅考虑蓝色和红色交叉点之间的差异,然后用 x 差值进行归一化,我们还可以在蓝色和红色交叉点之间的所有点之间运行线性回归算法。

通过应用线性回归,我们积极考虑切线(穿过蓝色和红色点的线)但我们也在整合中间的所有其他点。这意味着我们不仅仅盲目地相信红色交叉点,我们还考虑了所有介于两者之间的东西,从而在噪声上进行了平均。当然,一旦我们进行了线性回归,相应的导数就是拟合线的系数(或斜率)。如果你这么想,在极限情况下,如果你只有两个点,拟合线性回归的斜率就是导数的定义。

所以简单来说:我们继续通过“窗口化”来做导数但是我们也应用线性回归方法。

这种(剧透)效果是有效地平滑信号的导数,正如我们所期望的。

我真的很喜欢这种导数的估计,让我们用一个硬核案例来测试它,即一个非常嘈杂的信号:

Jovian

因此,我们将测试两种方法:

  1. 窗口导数应用于滤波信号

  2. 窗口线性回归导数应用于滤波信号

首先让我们实现第 2 点:

Jovian

这些是已实现的方法:

Jovian

cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fjovian.com%2Fembed%3Furl%3Dhttps%3A%2F%2Fjovian.ml%2Fpiero-paialunga%2Fderivative-notebook%2Fv%2F27%26cellId%3D18&dntp=1&display_name=Jovian&url=https%3A%2F%2Fjovian.ml%2Fpiero-paialunga%2Fderivative-notebook%2Fv%2F27%26cellId%3D18&key=a19fcc184b9711e1b4764040d3dc5c07&type=text%2Fhtml&scroll=auto&schema=jovian

如我们所见,导数的橙色红色估计相似,但红色估计更平滑,使得导数更少噪声。因此,我们可以认为这种改进比之前的改进更好。

4. 结论

我们已经到了这里,经过了许多版本的迭代 😊

在这篇博客文章中,我们讨论了导数。特别是,我们做了以下几件事:

  1. 我们描述了导数的理论定义,并在二次信号上给出了一个非常简短的例子。

  2. 我们在简单的二次信号和噪声信号上定义了导数的标准数值定义。我们看到了在噪声信号上的导数估计非常差,因为它试图“模拟噪声”而不是信号的真相。

  3. 我们在三个方面改进了标准的数值导数:通过平滑信号(第一种方法),通过平滑信号并对导数进行“窗口化”(第二种方法),通过平滑信号,对导数进行“窗口化”,并应用线性回归算法(第三种方法)。

  4. 我们展示了每种改进如何提高前一种,最终得到一个即使在噪声信号上也能有效定义的导数。

我真心希望你喜欢这篇博客文章,并且觉得它很有趣。❤

5. 关于我!

再次感谢你花时间。这对我们意义重大。

我的名字是皮埃罗·帕亚尔尼亚,我就是这里这个人:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/f0a229477351070e5b2ec9b085c89e1f.png

作者制作的照片

我是在辛辛那提大学航空航天工程系的博士候选人,同时也是 Gen Nine 的机器学习工程师。我在博客文章和领英上谈论人工智能和机器学习。如果你喜欢这篇文章,并想了解更多关于机器学习的内容以及关注我的研究,你可以:

如果你想向我提问或开始合作,请在这里或**领英**上留言:

点击这里

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值