斜率和切线的温和介绍
原文:
machinelearningmastery.com/a-gentle-introduction-to-slopes-and-tangents/
直线的斜率及其与曲线切线的关系是微积分中的一个基本概念。它对于函数导数的一般理解非常重要。
在本教程中,你将了解什么是直线的斜率以及什么是曲线的切线。
完成本教程后,你将了解:
-
直线的斜率
-
关于 x 的 f(x)在区间上的平均变化率
-
曲线的斜率
-
曲线在某一点的切线
让我们开始吧。
斜率和切线的温和介绍 艺术家:Jonathan Borofsky,摄影:Mehreen Saeed,部分版权保留。
教程概述
本教程分为两部分;它们是:
-
直线和曲线的斜率
-
曲线的切线
直线的斜率
让我们从复习直线的斜率开始。在微积分中,直线的斜率定义了其陡峭度,该数字是通过将垂直方向的变化除以在水平方向上的变化来计算的。图示显示了如何从直线上的两个不同点 A 和 B 计算斜率。
从直线上的两个点计算的直线斜率
一条直线可以由该直线上的两个点唯一确定。直线的斜率在直线上的每一点都是相同的;因此,任何直线也可以由斜率和直线上的一个点唯一确定。从已知点我们可以根据直线斜率定义的比例移动到直线上的任何其他点。
曲线的平均变化率
我们可以将直线的斜率的概念扩展到曲线的斜率。考虑下图左侧的图。如果我们想测量这条曲线的‘陡峭度’,它将在曲线上的不同点变化。从点 A 到点 B 的平均变化率是负的,因为当 x 增加时函数值在减少。从点 B 到点 A 的情况也是如此。因此,我们可以在区间[x0,x1]上定义它为:
(y1-y0)/(x1-x0)
我们可以看到,上图也是包含点 A 和 B 的割线斜率的表达式。为了刷新你的记忆,割线在曲线上交于两点。
同样,点 C 和点 D 之间的平均变化率是正的,它由包含这两个点的割线的斜率给出。
曲线在区间内的变化率与在某一点的变化率
定义曲线的斜率
现在让我们看看上述图形的右侧图。当我们将点 B 移向点 A 时会发生什么?我们称新的点为 B’。当点 B’ 无限接近 A 时,割线将变成只接触曲线一次的直线。这里 B’ 的 x 坐标是 (x0+h),其中 h 是一个无限小的值。点 B’ 的 y 坐标的对应值是该函数在 (x0+h) 处的值,即 f(x0+h)。
区间 [x0,x0+h] 上的平均变化率表示在长度为 h 的非常小的区间上的变化率,其中 h 接近零。这被称为曲线在点 x0 处的斜率。因此,在任何点 A(x0,f(x0)),曲线的斜率定义为:
点 A 处的曲线斜率的表达式等同于 f(x) 在点 x0 处的导数。因此,我们可以使用导数来找到曲线的斜率。你可以在这个 教程 中回顾导数的概念。
曲线斜率的例子
这里有几个曲线斜率的例子。
-
f(x) = 1/x 在任何点 k (k≠0) 处的斜率由 (-1/k²) 给出。作为例子:
-
f(x) = 1/x 在 (x=2) 处的斜率是 -1/4
-
f(x) = 1/x 在 (x=-1) 处的斜率是 -1
-
-
f(x) = x² 在任何点 k 处的斜率由 (2k) 给出。例如:
-
f(x) = x² 在 (x=0) 处的斜率是 0
-
f(x) = x² 在 (x=1) 处的斜率是 2
-
-
f(x) = 2x+1 的斜率是一个等于 2 的常数值。我们可以看到 f(x) 定义了一条直线。
-
f(x) = k(其中 k 是常数)的斜率为零,因为该函数在任何地方都不发生变化。因此,它在任何点的平均变化率为零。
切线
之前提到过,任何直线可以通过其斜率和一个经过它的点唯一确定。我们也刚刚定义了曲线在点 A 处的斜率。利用这两个事实,我们将曲线 f(x) 在点 A(x0,f(x0)) 处的切线定义为满足以下两个条件之一的直线:
-
该直线通过 A 点
-
直线的斜率等于曲线在点 A 处的斜率
利用上述两个事实,我们可以轻松确定切线在点 (x0,f(x0)) 处的方程。接下来展示了几个例子。
切线的例子
1. f(x) = 1/x
图中显示了 f(x) 及其在 x=1 和 x=-1 处的切线。下面是确定 x=1 处切线的步骤。
f(x) = 1/x
-
具有斜率 m 和 y 截距 c 的直线方程为: y=mx+c
-
任意点的直线斜率由函数 f’(x) = -1/x² 给出
-
曲线在 x=1 处的切线斜率为-1,我们得到 y=-x+c
-
切线经过点 (1,1),因此代入上述方程我们得到:
- 1 = -(1)+c ⟹ c = 2
-
切线的最终方程是 y = -x+2
2. f(x) = x²
下面显示了曲线以及在点 x=2、x=-2 和 x=0 处的切线。在 x=0 处,切线与 x 轴平行,因为 f(x) 在 x=0 处的斜率为零。
这就是我们计算 x=2 处切线方程的方法:
f(x) = x²
-
具有斜率 m 和 y 截距 c 的直线方程为: y=mx+c
-
任意点的切线斜率由函数 f’(x) = 2x 给出
-
曲线在 x=2 处的切线斜率为 4,我们得到 y=4x+c
-
切线经过点 (2,4),因此代入上述方程我们得到:
- 4 = 4(2)+c ⟹ c = -4
-
切线的最终方程是 y = 4x-4
3. f(x) = x³+2x+1
下面展示了这个函数以及其在 x=0、x=2 和 x=-2 处的切线。以下是推导 x=0 处切线方程的步骤。
f(x) = x³+2x+1
-
具有斜率 m 和 y 截距 c 的直线方程为: y=mx+c
-
任意点的直线斜率由函数 f’(x) = 3x²+2 给出
-
曲线在 x=0 处的切线斜率为 2,我们得到 y=2x+c
-
切线经过点 (0,1),因此代入上述方程我们得到:
- 1 = 2(0)+c ⟹ c = 1
-
切线的最终方程是 y = 2x+1
注意,曲线在 x=2 和 x=-2 处的斜率相同,因此这两条切线是平行的。对于任意 x=k 和 x=-k,这种情况也是成立的,因为 f’(x) = f’(-x) = 3x²+2
扩展
本节列出了一些可能扩展教程的想法,你可以考虑探索。
-
速度与加速度
-
函数的积分
如果你探索这些扩展内容,我很想知道。请在下面的评论中分享你的发现。
进一步阅读
本节提供了更多资源,如果你希望深入了解这个话题。
教程
资源
- 关于机器学习的微积分书籍的额外资源
书籍
-
托马斯微积分,第 14 版,2017 年。(基于乔治·B·托马斯的原著,由乔尔·哈斯、克里斯托弗·海尔、莫里斯·韦尔修订)
-
微积分,第 3 版,2017 年。(吉尔伯特·斯特朗)
-
微积分,第 8 版,2015 年。(詹姆斯·斯图尔特)
总结
在本教程中,你了解了曲线在某一点的斜率和曲线在某一点的切线的概念。
具体而言,你学到了:
-
直线的斜率是什么?
-
曲线在某一间隔内相对于 x 的平均变化率是什么?
-
曲线在某一点的斜率
-
在某一点上的曲线的切线
你有什么问题吗?
在下方评论中提出你的问题,我将尽力回答。
泰勒级数的温和介绍
原文:
machinelearningmastery.com/a-gentle-introduction-to-taylor-series/
泰勒级数的温和介绍
泰勒级数展开是一个了不起的概念,不仅在数学领域,而且在优化理论、函数逼近和机器学习中都非常重要。当需要在不同点估计函数值时,它在数值计算中得到了广泛应用。
在本教程中,你将发现泰勒级数以及如何使用其泰勒级数展开在不同点附近逼近函数值。
完成本教程后,你将知道:
-
函数的泰勒级数展开
-
如何使用泰勒级数展开逼近函数
让我们开始吧。
泰勒级数的温和介绍。图片由穆罕默德·库拜布·萨尔法兹提供,部分权利保留。
教程概述
本教程分为 3 部分;它们是:
-
幂级数和泰勒级数
-
泰勒多项式
-
使用泰勒多项式进行函数逼近
什么是幂级数?
以下是关于中心 x=a 和常数系数 c_0, c_1 等的幂级数。
什么是泰勒级数?
令人惊讶的是,具有无限次可微性的函数可以生成一种称为泰勒级数的幂级数。假设我们有一个函数 f(x),并且 f(x)在给定区间上具有所有阶的导数,那么在 x=a 处由 f(x)生成的泰勒级数为:
上述表达式的第二行给出了第 k 个系数的值。
如果我们设定 a=0,那么我们得到一个称为 f(x)的麦克劳林级数展开。
想要开始学习机器学习中的微积分?
立即获取我的免费 7 天邮件速成课程(包含示例代码)。
点击注册并免费获得课程的 PDF 电子书版本。
泰勒级数展开的示例
通过对 f(x) = 1/x 进行微分,可以找到泰勒级数,首先需要对函数进行微分,并找到第 k 阶导数的一般表达式。
现在可以找到关于各个点的泰勒级数。例如:
泰勒多项式
由 f(x) 在 x=a 生成的阶数为 k 的泰勒多项式表示为:
对于 f(x)=1/x 的例子,阶数为 2 的泰勒多项式表示为:
通过泰勒多项式进行近似
我们可以使用泰勒多项式来近似 x=a 处的函数值。多项式的阶数越高,多项式中的项数越多,近似值就越接近该点的实际函数值。
在下图中,函数 1/x 在点 x=1(左侧)和 x=3(右侧)附近绘制。绿色线是实际函数 f(x)= 1/x。粉色线表示通过阶数为 2 的多项式进行的近似。
实际函数(绿色)及其近似值(粉色)
泰勒级数的更多示例
我们来看函数 g(x) = e^x。注意到 g(x) 的 kth 阶导数也是 g(x),g(x) 关于 x=a 的展开表示为:
因此,在 x=0 附近,g(x) 的级数展开表示为(通过设置 a=0 得到):
对于函数 e^x 在点 x=0 附近生成的阶数为 k 的多项式表示为:
下图显示了不同阶数的多项式在 x=0 附近对 e^x 值的估计。我们可以看到,随着距离零点的远离,我们需要更多的项来更准确地近似 e^x。绿色线代表实际函数,隐藏在阶数为 7 的蓝色近似多项式后面。
近似 e^x 的不同阶数的多项式
泰勒级数在机器学习中的应用
在机器学习中,一个常用的方法是牛顿法。牛顿法使用二阶多项式来近似函数在某一点的值。这些使用二阶导数的方法称为二阶优化算法。
扩展
本节列出了一些扩展教程的想法,您可能希望探索。
-
牛顿法
-
二阶优化算法
如果你探索了这些扩展内容,我很想知道。请在下面的评论中分享你的发现。
进一步阅读
本节提供了更多关于该主题的资源,适合希望深入了解的读者。
教程
资源
- Jason Brownlee 关于 机器学习中的微积分书籍 的优秀资源
书籍
-
模式识别与机器学习 由 Christopher M. Bishop 编著。
-
深度学习 由 Ian Goodfellow, Joshua Begio, Aaron Courville 编著。
-
托马斯微积分,第 14 版,2017 年。(基于 George B. Thomas 的原著,由 Joel Hass、Christopher Heil 和 Maurice Weir 修订)
-
微积分,第 3 版,2017 年。(Gilbert Strang)
-
微积分,第 8 版,2015 年。(James Stewart)
总结
在本教程中,你了解了函数在某一点的泰勒级数展开。具体来说,你学到了:
-
幂级数和泰勒级数
-
泰勒多项式
-
如何使用泰勒多项式近似某个值附近的函数
你有任何问题吗?
在下方评论中提问,我会尽力回答
雅可比的温和介绍
原文:
machinelearningmastery.com/a-gentle-introduction-to-the-jacobian/
在文献中,Jacobian一词通常交替用于指代雅可比矩阵或其行列式。
矩阵和行列式都有有用且重要的应用:在机器学习中,雅可比矩阵汇集了反向传播所需的偏导数;行列式在变量转换过程中很有用。
在本教程中,你将回顾雅可比的温和介绍。
完成本教程后,你将了解:
-
雅可比矩阵收集了多变量函数的所有一阶偏导数,可用于反向传播。
-
雅可比行列式在变量转换中很有用,它作为一个坐标空间与另一个坐标空间之间的缩放因子。
让我们开始吧。
雅可比的温和介绍
照片由Simon Berger拍摄,版权所有。
教程概述
本教程分为三个部分;它们是:
-
机器学习中的偏导数
-
雅可比矩阵
-
雅可比的其他用途
机器学习中的偏导数
迄今为止,我们提到了梯度和偏导数对优化算法的重要性,例如,更新神经网络的模型权重以达到最优权重集。使用偏导数可以让每个权重独立更新,通过计算误差曲线相对于每个权重的梯度。
我们在机器学习中通常使用的许多函数是多变量的,向量值函数,这意味着它们将多个实数输入n映射到多个实数输出m:
例如,考虑一个将灰度图像分类到多个类别的神经网络。这样的分类器所实现的函数会将每个单通道输入图像的n像素值映射到m输出概率,这些概率表示图像属于不同类别的可能性。
在训练神经网络时,反向传播算法负责将输出层计算出的误差回传到神经网络中各个隐藏层的神经元,直到达到输入层。
反向传播算法调整网络中权重的基本原则是,网络中的每个权重应根据网络整体误差对该权重变化的敏感性进行更新。
– 第 222 页,《深度学习》,2019 年。
网络整体误差对某个特定权重变化的敏感性以变化率来衡量,这个变化率是通过对误差相对于相同权重的偏导数计算得到的。
为了简单起见,假设某个特定网络的一个隐藏层仅由一个神经元k组成。我们可以用一个简单的计算图来表示这个情况:
一个具有单一输入和单一输出的神经元
为了简单起见,假设一个权重,w*[k],被施加到这个神经元的一个输入上,以根据该神经元实现的函数(包括非线性)生成一个输出,z*[k]。然后,这个神经元的权重可以通过以下方式与网络输出的误差相连接(以下公式在形式上被称为微积分的链式法则,但更多内容将在后续的单独教程中讲解):
在这里,导数,dz*[k]* / dw*[k],首先将权重,w*[k],与输出,z*[k],连接起来,而导数,derror / dz*[k],随后将输出,z*[k]*,与网络误差连接起来。
通常情况下,我们会有许多相互连接的神经元组成网络,每个神经元都被赋予不同的权重。由于我们对这种情况更感兴趣,因此我们可以将讨论从标量情况推广到多个输入和多个输出:
这些项的和可以更紧凑地表示如下:
或者,用向量表示法等效地使用增量算子∇来表示误差对权重,w*[k],或输出,z[k]*的梯度:
反向传播算法包括对图中的每个操作执行这种雅可比-梯度乘积。
– 第 207 页, 深度学习,2017。
这意味着反向传播算法可以通过与雅可比矩阵的乘法,将网络误差的敏感度与权重的变化联系起来,公式为 (∂z*[k]* / ∂w*[k]*)^T。
因此,这个雅可比矩阵包含了什么?
雅可比矩阵
雅可比矩阵收集了多变量函数的所有一阶偏导数。
具体来说,首先考虑一个将 u 个实数输入映射到一个实数输出的函数:
对于长度为 u 的输入向量 x,大小为 1 × u 的雅可比向量可以定义如下:
现在,考虑另一个将 u 个实数输入映射到 v 个实数输出的函数:
对于同一个输入向量,x,长度为 u,雅可比矩阵现在是一个 v × u 的矩阵,J ∈ ℝ*(v×)**u*,定义如下:
将雅可比矩阵重新框架到之前考虑的机器学习问题中,同时保持 u 个实数输入和 v 个实数输出,我们发现这个矩阵包含以下偏导数:
想要开始学习机器学习中的微积分?
立即领取我的 7 天免费电子邮件速成课程(附带示例代码)。
点击注册,并获取课程的免费 PDF 电子书版本。
雅可比矩阵的其他用途
在处理积分时,一个重要的技巧是 变量变换(也称为 积分替换 或 u-替换),即将一个积分简化为另一个更易计算的积分。
在单变量情况下,将某个变量 x 替换为另一个变量 u,可以将原始函数转化为一个更简单的函数,从而更容易找到其不定积分。在双变量情况下,另一个原因可能是我们希望将积分区域的形状转换为不同的形状。
在单变量情况下,通常只有一个改变变量的原因:使函数“更好”,以便我们可以找到其不定积分。在双变量情况下,还有第二个潜在原因:我们需要积分的二维区域在某种程度上不太方便,我们希望用 u 和 v 表示的区域更好——例如,成为一个矩形。
– 第 412 页,单变量与多变量微积分,2020 年。
当在两个(或可能更多)变量之间进行替换时,过程开始于定义要进行替换的变量。例如,x = f(u, v) 和 y = g(u, v)。接着,根据函数 f 和 g 如何将 u–v 平面转换为 x–y 平面,转换积分限。最后,计算并包含 雅可比行列式 的绝对值,以作为一个坐标空间与另一个坐标空间之间的缩放因子。
进一步阅读
本节提供了更多相关资源,如果你想深入了解的话。
书籍
-
深度学习,2017 年。
-
机器学习的数学,2020 年。
-
单变量与多变量微积分,2020 年。
-
深度学习,2019 年。
文章
总结
在本教程中,你了解了关于雅可比矩阵的温和介绍。
具体来说,你学到了:
-
雅可比矩阵收集了多变量函数的所有一阶偏导数,可用于反向传播。
-
雅可比行列式在变量变换中很有用,它作为一个缩放因子在一个坐标空间与另一个坐标空间之间起作用。
你有任何问题吗?
在下面的评论中提出你的问题,我会尽力回答。
拉普拉斯算子的简要介绍
原文:
machinelearningmastery.com/a-gentle-introduction-to-the-laplacian/
拉普拉斯算子首次应用于天体力学研究,即外太空物体的运动,由皮埃尔-西蒙·拉普拉斯提出,因此以他的名字命名。
自那时以来,拉普拉斯算子被用来描述许多不同的现象,从电位,到热和流体流动的扩散方程,以及量子力学。它也被转化为离散空间,在与图像处理和谱聚类相关的应用中得到了应用。
在本教程中,你将发现对拉普拉斯算子的简要介绍。
完成本教程后,你将知道:
-
拉普拉斯算子的定义以及它与散度的关系。
-
拉普拉斯算子与海森矩阵的关系。
-
连续拉普拉斯算子如何被转化为离散空间,并应用于图像处理和谱聚类。
让我们开始吧。
拉普拉斯算子的简要介绍
图片由 Aziz Acharki 提供,部分版权保留。
教程概述
本教程分为两部分;它们是:
-
拉普拉斯算子
-
散度的概念
-
连续拉普拉斯算子
-
-
离散拉普拉斯算子
先决条件
对于本教程,我们假设你已经知道以下内容:
你可以通过点击上述链接来复习这些概念。
拉普拉斯算子
拉普拉斯算子(或称为拉普拉斯算子)是一个函数梯度的散度。
为了更好地理解前述陈述,我们最好从理解散度的概念开始。
散度的概念
散度是一个对向量场进行操作的向量算子。后者可以被看作表示液体或气体的流动,其中向量场中的每个向量代表移动流体的速度向量。
粗略地说,散度测量了流体在一点上聚集或分散的趋势……
– 第 432 页,单变量与多变量微积分,2020 年。
(sin y,cos x)的矢量场的一部分
使用 nabla(或 del)算子 ∇,散度用 ∇ ^. 表示,并在应用于矢量场时产生一个标量值,测量每一点的流量。在笛卡尔坐标系中,矢量场 F = ⟨f,g,h⟩ 的散度由下式给出:
尽管散度计算涉及到散度算子(而不是乘法操作)的应用,但其符号中的点让人联想到点积,这涉及到两个等长序列(在这种情况下为 ∇ 和 F)的组件的乘法以及结果项的求和。
连续拉普拉斯算子
让我们回到拉普拉斯算子的定义。
回顾,二维函数 f 的梯度由下式给出:
然后,f 的拉普拉斯算子(即梯度的散度)可以通过未混合的二阶偏导数的和来定义:
它可以等效地被视为函数的Hessian 矩阵的迹(tr),H(f)。迹定义了一个* n× n* 矩阵主对角线上的元素之和,在这里是 Hessian 矩阵,同时也是它的特征值之和。回顾一下,Hessian 矩阵在对角线上包含本身(或未混合)的二阶偏导数:
矩阵迹的一个重要性质是其对基底变化的不变性。我们已经在笛卡尔坐标系中定义了拉普拉斯算子。在极坐标系中,我们将其定义如下:
迹对基底变化的不变性意味着拉普拉斯算子可以在不同的坐标空间中定义,但它在笛卡尔坐标空间中的某一点(x,y)和在极坐标空间中的同一点(r,θ)给出的值是相同的。
回想一下,我们还提到过二阶导数可以为我们提供有关函数曲率的信息。因此,直观地说,我们可以认为拉普拉斯算子也通过这些二阶导数的总和为我们提供有关函数局部曲率的信息。
连续拉普拉斯算子已被用来描述许多物理现象,如电势和热传导方程。
想要开始机器学习的微积分吗?
现在立即参加我的免费 7 天电子邮件速成课程(附带示例代码)。
点击注册并获得课程的免费 PDF 电子书版本。
离散拉普拉斯算子
类似于连续的拉普拉斯算子,离散版本是为了应用于图像中的离散网格,比如像素值或者图中的节点。
让我们来看看拉普拉斯算子如何在两种应用中重新构造。
在图像处理中,拉普拉斯算子以数字滤波器的形式实现,当应用于图像时,可用于边缘检测。从某种意义上说,我们可以认为在图像处理中使用的拉普拉斯算子也能提供关于函数在某个特定点 (x, y) 曲线(或bends)的信息。
在这种情况下,离散拉普拉斯算子(或滤波器)通过将两个一维二阶导数滤波器组合成一个二维滤波器来构建:
在机器学习中,从图中派生的离散拉普拉斯算子提供的信息可用于数据聚类的目的。
考虑一个图,G = (V, E),有限个V个顶点和E条边。它的拉普拉斯矩阵L可以用度矩阵D来定义,包含每个顶点连接信息,以及邻接矩阵A,指示图中相邻顶点对:
L = D - A
通过在拉普拉斯矩阵的特征向量上应用一些标准聚类方法(如k-means),可以执行谱聚类,从而将图的节点(或数据点)分割成子集。
这样做可能会引发一个与大型数据集的可扩展性问题有关的问题,其中拉普拉斯矩阵的特征分解可能是禁止的。已经提出使用深度学习来解决这个问题,其中训练深度神经网络使其输出近似于图拉普拉斯的特征向量。在这种情况下,神经网络通过约束优化方法进行训练,以强制其输出的正交性。
进一步阅读
如果您希望深入了解此主题,本节提供了更多资源。
书籍
-
单变量与多变量微积分,2020 年。
-
图像与视频处理手册,2005 年。
文章
论文
- SpectralNet: 使用深度神经网络的谱聚类,2018 年。
总结
在本教程中,你发现了对拉普拉斯算子的温和介绍。
具体而言,你学习了:
-
拉普拉斯算子的定义以及它与散度的关系。
-
拉普拉斯算子如何与海森矩阵相关。
-
连续拉普拉斯算子如何被转换为离散空间,并应用于图像处理和谱聚类。
你有任何问题吗?
在下方评论中提问,我会尽力回答。
向量值函数的温和介绍
原文:
machinelearningmastery.com/a-gentle-introduction-to-vector-valued-functions/
向量值函数经常在机器学习、计算机图形学和计算机视觉算法中遇到。它们特别适用于定义空间曲线的参数方程。理解向量值函数的基本概念对于掌握更复杂的概念非常重要。
在本教程中,您将了解什么是向量值函数,如何定义它们以及一些示例。
完成本教程后,您将了解:
-
向量值函数的定义
-
向量值函数的导数
让我们开始吧。
对向量值函数的温和介绍。照片由 Noreen Saeed 拍摄,部分权利保留。
教程概述
本教程分为两个部分;它们是:
-
向量值函数的定义和示例
-
向量值函数的微分
向量值函数的定义
向量值函数也称为向量函数。它是具有以下两个属性的函数:
-
定域是一组实数
-
范围是一组向量
因此,向量函数简单地是标量函数的扩展,其中定义域和值域都是实数集。
在本教程中,我们将考虑其值域是二维或三维向量集的向量函数。因此,这些函数可以用来定义空间中的一组点。
给定与 x 轴、y 轴、z 轴平行的单位向量 i,j,k,我们可以将三维向量值函数写成:
r(t) = x(t)i + y(t)j + z(t)k
它也可以写成:
r(t) = <x(t), y(t), z(t)>
上述两种符号是等价的,并且在各种教科书中经常使用。
空间曲线和参数方程
在前面的部分我们定义了一个向量函数 r(t)。对于不同的 t 值,我们得到相应的 (x,y,z) 坐标,由函数 x(t), y(t) 和 z(t) 定义。因此生成的点集 (x,y,z) 定义了一个称为空间曲线 C 的曲线。因此,x(t), y(t) 和 z(t) 的方程也称为曲线 C 的参数方程。
向量函数的例子
本节展示了一些定义空间曲线的向量值函数的例子。所有的例子也都在例子后面的图中绘制出来。
1.1 一个圆
让我们从一个简单的二维空间中的向量函数的例子开始:
r_1(t) = cos(t)i + sin(t)j
这里的参数方程是:
x(t) = cos(t)
y(t) = sin(t)
参数方程定义的空间曲线是二维空间中的圆,如图所示。如果我们将 t 从 -??? 变化到 ???,我们将生成所有落在圆上的点。
1.2 螺旋线
我们可以扩展示例 1.1 中的 r_1(t) 函数,以便在三维空间中轻松生成螺旋线。我们只需要添加沿 z 轴随 t 变化的值。因此,我们有以下函数:
r_2(t) = cos(t)i + sin(t)j + tk
1.3 扭曲的立方体
我们还可以定义一种具有有趣形状的曲线,称为扭曲的立方体,如下所示:
r_3(t) = ti + t²j + t³k
参数曲线
想要开始学习机器学习中的微积分吗?
立即领取我的免费 7 天邮件速成课程(附样例代码)。
点击注册并获得免费的 PDF 电子书版本课程。
向量函数的导数
我们可以很容易地将标量函数的导数的概念扩展到向量函数的导数。由于向量函数的值范围是一组向量,因此其导数也是一个向量。
如果
r(t) = x(t)i + y(t)j + z(t)k
那么 r(t) 的导数为 r’(t),计算公式如下:
r’(t) = x’(t)i + y’(t)i + z’(t)k
向量函数的导数示例
我们可以找到前一个示例中定义的函数的导数,如下所示:
2.1 圆
2D 中圆的参数方程为:
r_1(t) = cos(t)i + sin(t)j
因此,其导数是通过计算 x(t) 和 y(t) 的相应导数得到的,如下所示:
x’(t) = -sin(t)
y’(t) = cos(t)
这给我们:
r_1′(t) = x’(t)i + y’(t)j
r_1′(t) = -sin(t)i + cos(t)j
由参数方程定义的空间曲线在 2D 空间中是一个圆,如图所示。如果我们将 t 从 -??? 变为 π,我们将生成所有位于圆上的点。
2.2 螺旋线
类似于之前的例子,我们可以计算 r_2(t) 的导数,如下所示:
r_2(t) = cos(t)i + sin(t)j + tk
r_2′(t) = -sin(t)i + cos(t)j + k
2.3 扭曲的立方体
r_3(t) 的导数为:
r_3(t) = ti + t²j + t³k
r_3′(t) = i + 2tj + 3t²k
所有上述示例都显示在图中,其中导数以红色绘制。注意,圆的导数也在空间中定义了一个圆。
参数函数及其导数
更复杂的示例
一旦你对这些函数有了基本了解,你可以通过定义各种形状和曲线在空间中获得很多乐趣。数学界使用的其他流行示例如下所定义,并在图中进行了说明。
环形螺旋:
r_4(t) = (4 + sin(20t))cos(t)i + (4 + sin(20t))sin(t)j + cos(20t)k
三叶结:
r_5(t) = (2 + cos(1.5t))cos (t)i + (2 + cos(1.5t))sin(t)j + sin(1.5t)k
心形曲线:
r_6(t) = cos(t)(1-cos(t))i + sin(t)(1-cos(t))j
更复杂的曲线
向量值函数在机器学习中的重要性
向量值函数在机器学习算法中扮演着重要角色。作为标量值函数的扩展,您会在多类分类和多标签问题等任务中遇到它们。核方法,作为机器学习中的一个重要领域,可能涉及计算向量值函数,这些函数可以在多任务学习或迁移学习中使用。
扩展
本节列出了一些扩展教程的想法,您可能希望探索这些内容。
-
向量函数的积分
-
抛体运动
-
空间中的弧长
如果您探索了这些扩展,我很想知道。请在下方评论中发布您的发现。
进一步阅读
本节提供了更多资源,如果您想深入了解这个主题。
教程
资源
- 关于机器学习的微积分书籍的额外资源
书籍
-
托马斯微积分,第 14 版,2017 年。(基于乔治·B·托马斯的原著,由乔尔·哈斯、克里斯托弗·海尔、莫里斯·韦尔修订)
-
微积分,第 3 版,2017 年。(吉尔伯特·斯特朗)
-
微积分,第 8 版,2015 年。(詹姆斯·斯图尔特)
总结
在本教程中,您了解了什么是向量函数以及如何对其进行微分。
具体来说,您学到了:
-
向量函数的定义
-
参数曲线
-
向量函数的微分
您有任何问题吗?
在下方评论中提出您的问题,我会尽力回答。
神经网络中的微分应用
原文:
machinelearningmastery.com/application-of-differentiations-in-neural-networks/
微分学是机器学习算法中的一个重要工具。特别是在神经网络中,梯度下降算法依赖于梯度,这是通过微分计算得到的量。
在本教程中,我们将探讨反向传播技术如何用于计算神经网络中的梯度。
完成本教程后,你将了解
-
什么是全微分和总导数
-
如何计算神经网络中的总导数
-
反向传播如何帮助计算总导数
让我们开始吧
神经网络中的微分应用
照片由Freeman Zhou提供,部分权利保留。
教程概览
本教程分为 5 部分,它们是:
-
总微分和总导数
-
多层感知器模型的代数表示
-
通过反向传播找出梯度
-
梯度方程的矩阵形式
-
实现反向传播
总微分和总导数
对于函数如 f ( x ) f(x) f(x),我们将其导数表示为 f ′ ( x ) f'(x) f′(x)或 d f d x \frac{df}{dx} dxdf。但对于多变量函数,如 f ( u , v ) f(u,v) f(u,v),我们有相对于 u u u的偏导数 ∂ f ∂ u \frac{\partial f}{\partial u} ∂u∂f,有时写作 f u f_u fu。偏导数是通过对 f f f进行相对于 u u u的微分得到的,同时假设另一个变量 v v v为常数。因此,我们用 ∂ \partial ∂代替 d d d作为微分符号,以表示不同。
不过,如果 f ( u , v ) f(u,v) f(u,v)中的 u u u和 v v v都依赖于 x x x呢?换句话说,我们可以写成 u ( x ) u(x) u(x)和 v ( x ) v(x) v(x)以及 f ( u ( x ) , v ( x ) ) f(u(x), v(x)) f(u(x),v(x))。所以 x x x决定了 u u u和 v v v的值,从而决定了 f ( u , v ) f(u,v) f(u,v)。在这种情况下,问 d f d x \frac{df}{dx} dxdf是完全合理的,因为 f f f最终由 x x x决定。
这就是总导数的概念。事实上,对于多变量函数 f ( t , u , v ) = f ( t ( x ) , u ( x ) , v ( x ) ) f(t,u,v)=f(t(x),u(x),v(x)) f(t,u,v)=f(t(x),u(x),v(x)),我们总是有
$$
\frac{df}{dx} = \frac{\partial f}{\partial t}\frac{dt}{dx} + \frac{\partial f}{\partial u}\frac{du}{dx} + \frac{\partial f}{\partial v}\frac{dv}{dx}
$$
上述符号被称为总导数,因为它是偏导数的和。本质上,它是应用链式法则来求导。
如果我们去掉上述方程中的 d x dx dx部分,得到的是 f f f相对于 x x x的近似变化,即,
$$
df = \frac{\partial f}{\partial t}dt + \frac{\partial f}{\partial u}du + \frac{\partial f}{\partial v}dv
$$
我们称这种符号为总微分。
多层感知器模型的代数表示
考虑网络:
神经网络示例。来源: commons.wikimedia.org/wiki/File:Multilayer_Neural_Network.png
这是一个简单的全连接 4 层神经网络。我们将输入层称为第 0 层,两个隐藏层称为第 1 层和第 2 层,输出层称为第 3 层。在这个图中,我们可以看到有 n 0 = 3 n_0=3 n0=3个输入单元,第一个隐藏层有 n 1 = 4 n_1=4 n1=4个单元,第二个隐藏层有 n 2 = 2 n_2=2 n2=2个单元。输出层有 n 3 = 2 n_3=2 n3=2个单元。
如果我们将网络的输入记作 x i x_i xi,其中 i = 1 , ⋯ , n 0 i=1,\cdots,n_0 i=1,⋯,n0,网络的输出记作 y ^ i \hat{y}_i y^i,其中 i = 1 , ⋯ , n 3 i=1,\cdots,n_3 i=1,⋯,n3。那么我们可以写成
$$
\begin{aligned}
h_{1i} &= f_1(\sum_{j=1}^{n_0} w^{(1)}_{ij} x_j + b^{(1)}_i) & \text{for } i &= 1,\cdots,n_1\
h_{2i} &= f_2(\sum_{j=1}^{n_1} w^{(2)}{ij} h{1j} + b^{(2)}_i) & i &= 1,\cdots,n_2\
\hat{y}i &= f_3(\sum{j=1}^{n_2} w^{(3)}{ij} h{2j} + b^{(3)}_i) & i &= 1,\cdots,n_3
\end{aligned}
$$
这里,第 i i i层的激活函数记作 f i f_i fi。第一隐含层的输出记作 h 1 i h_{1i} h1i,第二隐含层的输出记作 h 2 i h_{2i} h2i。第 i i i单元在第 k k k层的权重和偏置分别记作 w i j ( k ) w^{(k)}_{ij} wij(k) 和 b i ( k ) b^{(k)}_i bi(k)。
在上图中,我们可以看到第 k − 1 k-1 k−1层的输出将输入到第 k k k层。因此,虽然 y ^ i \hat{y}_i y^i 被表示为 h 2 j h_{2j} h2j 的函数,但 h 2 i h_{2i} h2i 也依赖于 h 1 j h_{1j} h1j,而 h 1 j h_{1j} h1j 又依赖于 x j x_j xj。
上述内容描述了神经网络在代数方程中的构建。训练神经网络时,需要指定一个损失函数,以便我们可以在训练过程中最小化它。根据应用的不同,我们通常使用交叉熵处理分类问题,或均方误差处理回归问题。目标变量为 y i y_i yi,均方误差损失函数被指定为
$$
L = \sum_{i=1}^{n_3} (y_i-\hat{y}_i)²
$$
想要开始学习机器学习的微积分吗?
现在就参加我的免费 7 天电子邮件速成课程吧(包含示例代码)。
点击注册,还可以获得免费的课程 PDF 电子书版本。
通过反向传播寻找梯度
在上述构造中, x i x_i xi 和 y i y_i yi 来自数据集。神经网络的参数是 w w w 和 b b b。而激活函数 f i f_i fi 是通过设计得出的输出,每一层的 h 1 i h_{1i} h1i、 h 2 i h_{2i} h2i 和 y ^ i \hat{y}_i y^i 是依赖变量。在训练神经网络时,我们的目标是通过梯度下降更新 w w w 和 b b b,即通过梯度下降更新规则:
$$
\begin{aligned}
w^{(k)}{ij} &= w^{(k)}{ij} – \eta \frac{\partial L}{\partial w^{(k)}_{ij}} \
b^{(k)}{i} &= b^{(k)}{i} – \eta \frac{\partial L}{\partial b^{(k)}_{i}}
\end{aligned}
$$
其中 η \eta η 是梯度下降的学习率参数。
从 L L L的方程我们知道 L L L不依赖于 w i j ( k ) w^{(k)}_{ij} wij(k)或 b i ( k ) b^{(k)}_i bi(k),而是依赖于 y ^ i \hat{y}_i y^i。然而, y ^ i \hat{y}_i y^i最终可以表示为 w i j ( k ) w^{(k)}_{ij} wij(k)或 b i ( k ) b^{(k)}_i bi(k)的函数。让我们逐一看看第 k k k层的权重和偏置是如何与输出层的 y ^ i \hat{y}_i y^i相关联的。
我们从损失指标开始。如果考虑单个数据点的损失,我们有
$$
\begin{aligned}
L &= \sum_{i=1}^{n_3} (y_i-\hat{y}_i)²\
\frac{\partial L}{\partial \hat{y}_i} &= 2(y_i – \hat{y}_i) & \text{for } i &= 1,\cdots,n_3
\end{aligned}
$$
在这里我们看到损失函数依赖于所有输出 y ^ i \hat{y}_i y^i,因此我们可以找到偏导数 ∂ L ∂ y ^ i \frac{\partial L}{\partial \hat{y}_i} ∂y^i∂L。
现在我们来看输出层:
$$
\begin{aligned}
\hat{y}i &= f_3(\sum{j=1}^{n_2} w^{(3)}{ij} h{2j} + b^{(3)}_i) & \text{for }i &= 1,\cdots,n_3 \
\frac{\partial L}{\partial w^{(3)}_{ij}} &= \frac{\partial L}{\partial \hat{y}_i}\frac{\partial \hat{y}i}{\partial w^{(3)}{ij}} & i &= 1,\cdots,n_3;\ j=1,\cdots,n_2 \
&= \frac{\partial L}{\partial \hat{y}i} f’3(\sum{j=1}^{n_2} w^{(3)}{ij} h_{2j} + b^{(3)}i)h{2j} \
\frac{\partial L}{\partial b^{(3)}_i} &= \frac{\partial L}{\partial \hat{y}_i}\frac{\partial \hat{y}_i}{\partial b^{(3)}_i} & i &= 1,\cdots,n_3 \
&= \frac{\partial L}{\partial \hat{y}i}f’3(\sum{j=1}^{n_2} w^{(3)}{ij} h_{2j} + b^{(3)}_i)
\end{aligned}
$$
因为第 3 层的权重 w i j ( 3 ) w^{(3)}_{ij} wij(3)作用于输入 h 2 j h_{2j} h2j并仅影响输出 y ^ i \hat{y}_i y^i。因此,我们可以将偏导数 ∂ L ∂ w i j ( 3 ) \frac{\partial L}{\partial w^{(3)}_{ij}} ∂wij(3)∂L写作两个偏导数的乘积 ∂ L ∂ y ^ i ∂ y ^ i ∂ w i j ( 3 ) \frac{\partial L}{\partial \hat{y}_i}\frac{\partial \hat{y}_i}{\partial w^{(3)}_{ij}} ∂y^i∂L∂wij(3)∂y^i。偏置 b i ( 3 ) b^{(3)}_i bi(3)也是类似的情况。在上述过程中,我们使用了之前已经推导出的 ∂ L ∂ y ^ i \frac{\partial L}{\partial \hat{y}_i} ∂y^i∂L。
但实际上,我们也可以写出 L L L对第二层输出 h 2 j h_{2j} h2j的偏导数。它不会用于第 3 层权重和偏置的更新,但我们稍后会看到它的重要性:
$$
\begin{aligned}
\frac{\partial L}{\partial h_{2j}} &= \sum_{i=1}^{n_3}\frac{\partial L}{\partial \hat{y}_i}\frac{\partial \hat{y}i}{\partial h{2j}} & \text{for }j &= 1,\cdots,n_2 \
&= \sum_{i=1}^{n_3}\frac{\partial L}{\partial \hat{y}i}f’3(\sum{j=1}^{n_2} w^{(3)}{ij} h_{2j} + b{(3)}_i)w{(3)}_{ij}
\end{aligned}
$$
这个导数是有趣的,并且与之前的部分导数不同。注意到 h 2 j h_{2j} h2j 是第 2 层的输出。第 2 层的每一个输出都会影响第 3 层的输出 y ^ i \hat{y}_i y^i。因此,为了找到 ∂ L ∂ h 2 j \frac{\partial L}{\partial h_{2j}} ∂h2j∂L,我们需要将第 3 层的每个输出加起来。因此,上述方程中使用了求和符号。我们可以将 ∂ L ∂ h 2 j \frac{\partial L}{\partial h_{2j}} ∂h2j∂L 视为总导数,其中我们对每个输出 i i i 应用了链式法则 ∂ L ∂ y ^ i ∂ y ^ i ∂ h 2 j \frac{\partial L}{\partial \hat{y}_i}\frac{\partial \hat{y}_i}{\partial h_{2j}} ∂y^i∂L∂h2j∂y^i,然后将它们加起来。
如果我们回到第 2 层,我们可以类似地推导导数:
$$
\begin{aligned}
h_{2i} &= f_2(\sum_{j=1}^{n_1} w^{(2)}{ij} h{1j} + b^{(2)}_i) & \text{对于 }i &= 1,\cdots,n_2\
\frac{\partial L}{\partial w^{(2)}{ij}} &= \frac{\partial L}{\partial h{2i}}\frac{\partial h_{2i}}{\partial w^{(2)}_{ij}} & i&=1,\cdots,n_2;\ j=1,\cdots,n_1 \
&= \frac{\partial L}{\partial h_{2i}}f’2(\sum{j=1}^{n_1} w^{(2)}{ij} h{1j} + b^{(2)}i)h{1j} \
\frac{\partial L}{\partial b^{(2)}i} &= \frac{\partial L}{\partial h{2i}}\frac{\partial h_{2i}}{\partial b^{(2)}_i} & i &= 1,\cdots,n_2 \
&= \frac{\partial L}{\partial h_{2i}}f’2(\sum{j=1}^{n_1} w^{(2)}{ij} h{1j} + b^{(2)}_i) \
\frac{\partial L}{\partial h_{1j}} &= \sum_{i=1}^{n_2}\frac{\partial L}{\partial h_{2i}}\frac{\partial h_{2i}}{\partial h_{1j}} & j&= 1,\cdots,n_1 \
&= \sum_{i=1}^{n_2}\frac{\partial L}{\partial h_{2i}}f’2(\sum{j=1}^{n_1} w^{(2)}{ij} h{1j} + b^{(2)}i) w^{(2)}{ij}
\end{aligned}
$$
在上述方程中,我们重新使用了之前推导的 ∂ L ∂ h 2 i \frac{\partial L}{\partial h_{2i}} ∂h2i∂L。同样,这个导数是作为链式法则中几个乘积的总和来计算的。与之前类似,我们也推导了 ∂ L ∂ h 1 j \frac{\partial L}{\partial h_{1j}} ∂h1j∂L。它不会用于训练 w i j ( 2 ) w^{(2)}_{ij} wij(2) 或 b i ( 2 ) b^{(2)}_i bi(2),但会用于前一层。因此,对于第 1 层,我们有
$$
\begin{aligned}
h_{1i} &= f_1(\sum_{j=1}^{n_0} w^{(1)}_{ij} x_j + b^{(1)}_i) & \text{对于 } i &= 1,\cdots,n_1\
\frac{\partial L}{\partial w^{(1)}{ij}} &= \frac{\partial L}{\partial h{1i}}\frac{\partial h_{1i}}{\partial w^{(1)}_{ij}} & i&=1,\cdots,n_1;\ j=1,\cdots,n_0 \
&= \frac{\partial L}{\partial h_{1i}}f’1(\sum{j=1}^{n_0} w^{(1)}_{ij} x_j + b^{(1)}_i)x_j \
\frac{\partial L}{\partial b^{(1)}i} &= \frac{\partial L}{\partial h{1i}}\frac{\partial h_{1i}}{\partial b^{(1)}_i} & i&=1,\cdots,n_1 \
&= \frac{\partial L}{\partial h_{1i}}f’1(\sum{j=1}^{n_0} w^{(1)}_{ij} x_j + b^{(1)}_i)
\end{aligned}
$$
这完成了使用梯度下降算法进行神经网络训练所需的所有导数。
回顾我们如何推导上述内容:我们首先从损失函数 L L L 开始,按照层的反向顺序逐一求导。我们写下第 k k k 层的导数,并将其用于第 k − 1 k-1 k−1 层的导数。计算从输入 x i x_i xi 到输出 y ^ i \hat{y}_i y^i 是从第 0 层向前进行的,而计算梯度则是按反向顺序进行的。因此称之为“反向传播”。
梯度方程的矩阵形式
虽然我们在上面没有使用它,但以向量和矩阵的形式书写方程会更简洁。我们可以将层和输出重写为:
$$
\mathbf{a}_k = f_k(\mathbf{z}_k) = f_k(\mathbf{W}k\mathbf{a}{k-1}+\mathbf{b}_k)
$$
其中 a k \mathbf{a}_k ak 是第 k k k 层的输出向量,假设 a 0 = x \mathbf{a}_0=\mathbf{x} a0=x 是输入向量, a 3 = y ^ \mathbf{a}_3=\hat{\mathbf{y}} a3=y^ 是输出向量。为了便于记号表示,也表示 z k = W k a k − 1 + b k \mathbf{z}_k = \mathbf{W}_k\mathbf{a}_{k-1}+\mathbf{b}_k zk=Wkak−1+bk。
在这样的记号下,我们可以将 ∂ L ∂ a k \frac{\partial L}{\partial\mathbf{a}_k} ∂ak∂L 表示为一个向量(同样适用于 z k \mathbf{z}_k zk 和 b k \mathbf{b}_k bk),而将 ∂ L ∂ W k \frac{\partial L}{\partial\mathbf{W}_k} ∂Wk∂L 表示为一个矩阵。然后,如果已知 ∂ L ∂ a k \frac{\partial L}{\partial\mathbf{a}_k} ∂ak∂L,我们有
$$
\begin{aligned
\frac{\partial L}{\partial\mathbf{z}_k} &= \frac{\partial L}{\partial\mathbf{a}_k}\odot f_k’(\mathbf{z}_k) \
\frac{\partial L}{\partial\mathbf{W}_k} &= \left(\frac{\partial L}{\partial\mathbf{z}_k}\right)^\top \cdot \mathbf{a}_k \
\frac{\partial L}{\partial\mathbf{b}_k} &= \frac{\partial L}{\partial\mathbf{z}_k} \
\frac{\partial L}{\partial\mathbf{a}_{k-1}} &= \left(\frac{\partial\mathbf{z}k}{\partial\mathbf{a}{k-1}}\right)^\top\cdot\frac{\partial L}{\partial\mathbf{z}_k} = \mathbf{W}_k^\top\cdot\frac{\partial L}{\partial\mathbf{z}_k}
\end{aligned}
$$
其中 ∂ z k ∂ a k − 1 \frac{\partial\mathbf{z}_k}{\partial\mathbf{a}_{k-1}} ∂ak−1∂zk 是雅可比矩阵,因为 z k \mathbf{z}_k zk 和 a k − 1 \mathbf{a}_{k-1} ak−1 都是向量,这个雅可比矩阵恰好是 W k \mathbf{W}_k Wk。
实现反向传播
我们需要矩阵形式的方程,因为这将使我们的代码更简洁,避免了很多循环。让我们看看如何将这些方程转换为代码,并从零开始使用 numpy 构建一个用于分类的多层感知机模型。
首先,我们需要实现激活函数和损失函数。两者都需要是可微分的函数,否则我们的梯度下降过程将无法进行。现在,隐藏层中常用 ReLU 激活函数,输出层中常用 sigmoid 激活函数。我们将它们定义为一个函数(假设输入为 numpy 数组)以及它们的导数:
import numpy as np
# Find a small float to avoid division by zero
epsilon = np.finfo(float).eps
# Sigmoid function and its differentiation
def sigmoid(z):
return 1/(1+np.exp(-z.clip(-500, 500)))
def dsigmoid(z):
s = sigmoid(z)
return 2 * s * (1-s)
# ReLU function and its differentiation
def relu(z):
return np.maximum(0, z)
def drelu(z):
return (z > 0).astype(float)
我们故意将 sigmoid 函数的输入限制在 -500 到 +500 之间以避免溢出。否则,这些函数都是微不足道的。然后,对于分类问题,我们关注准确性,但准确性函数是不可微分的。因此,我们使用交叉熵函数作为训练的损失函数:
# Loss function L(y, yhat) and its differentiation
def cross_entropy(y, yhat):
"""Binary cross entropy function
L = - y log yhat - (1-y) log (1-yhat)
Args:
y, yhat (np.array): 1xn matrices which n are the number of data instances
Returns:
average cross entropy value of shape 1x1, averaging over the n instances
"""
return -(y.T @ np.log(yhat.clip(epsilon)) + (1-y.T) @ np.log((1-yhat).clip(epsilon))) / y.shape[1]
def d_cross_entropy(y, yhat):
""" dL/dyhat """
return - np.divide(y, yhat.clip(epsilon)) + np.divide(1-y, (1-yhat).clip(epsilon))
上述内容中,我们假设输出和目标变量是 numpy 中的行矩阵。因此,我们使用点积运算符@
来计算总和,并除以输出中的元素个数。请注意,这种设计是为了计算一个批次样本的平均交叉熵。
然后我们可以实现我们的多层感知器模型。为了使其更易读,我们希望通过提供每一层的神经元数量以及层中的激活函数来创建模型。同时,我们还需要激活函数的导数以及训练所需的损失函数的导数。然而,损失函数本身不是必需的,但对于我们跟踪进度非常有用。我们创建了一个类来封装整个模型,并根据公式定义每一层 k k k:
$$
\mathbf{a}_k = f_k(\mathbf{z}k) = f_k(\mathbf{a}{k-1}\mathbf{W}_k+\mathbf{b}_k)
$
class mlp:
'''Multilayer perceptron using numpy
'''
def __init__(self, layersizes, activations, derivatives, lossderiv):
"""remember config, then initialize array to hold NN parameters without init"""
# hold NN config
self.layersizes = layersizes
self.activations = activations
self.derivatives = derivatives
self.lossderiv = lossderiv
# parameters, each is a 2D numpy array
L = len(self.layersizes)
self.z = [None] * L
self.W = [None] * L
self.b = [None] * L
self.a = [None] * L
self.dz = [None] * L
self.dW = [None] * L
self.db = [None] * L
self.da = [None] * L
def initialize(self, seed=42):
np.random.seed(seed)
sigma = 0.1
for l, (insize, outsize) in enumerate(zip(self.layersizes, self.layersizes[1:]), 1):
self.W[l] = np.random.randn(insize, outsize) * sigma
self.b[l] = np.random.randn(1, outsize) * sigma
def forward(self, x):
self.a[0] = x
for l, func in enumerate(self.activations, 1):
# z = W a + b, with `a` as output from previous layer
# `W` is of size rxs and `a` the size sxn with n the number of data instances, `z` the size rxn
# `b` is rx1 and broadcast to each column of `z`
self.z[l] = (self.a[l-1] @ self.W[l]) + self.b[l]
# a = g(z), with `a` as output of this layer, of size rxn
self.a[l] = func(self.z[l])
return self.a[-1]
在这个类中的变量z
、W
、b
和a
用于前向传递,而变量dz
、dW
、db
和da
是它们各自的梯度,这些梯度将在反向传播中计算。所有这些变量都以 numpy 数组的形式呈现。
正如我们稍后将看到的,我们将使用 scikit-learn 生成的数据来测试我们的模型。因此,我们会看到数据是形状为“(样本数量,特征数量)”的 numpy 数组。因此,每个样本作为矩阵的一行呈现,在函数forward()
中,权重矩阵会右乘到每个输入a
上。虽然每层的激活函数和维度可能不同,但过程是一样的。因此,我们通过forward()
函数中的循环将神经网络的输入x
转换为其输出。网络的输出只是最后一层的输出。
为了训练网络,我们需要在每次前向传递后运行反向传播。反向传播是计算每一层的权重和偏置的梯度,从输出层开始到输入层。根据我们上面推导的方程,反向传播函数实现如下:
class mlp:
...
def backward(self, y, yhat):
# first `da`, at the output
self.da[-1] = self.lossderiv(y, yhat)
for l, func in reversed(list(enumerate(self.derivatives, 1))):
# compute the differentials at this layer
self.dz[l] = self.da[l] * func(self.z[l])
self.dW[l] = self.a[l-1].T @ self.dz[l]
self.db[l] = np.mean(self.dz[l], axis=0, keepdims=True)
self.da[l-1] = self.dz[l] @ self.W[l].T
def update(self, eta):
for l in range(1, len(self.W)):
self.W[l] -= eta * self.dW[l]
self.b[l] -= eta * self.db[l]
唯一的区别在于,我们计算db
时不是针对一个训练样本,而是针对整个批次。由于损失函数是跨批次的平均交叉熵,因此我们也通过跨样本平均来计算db
。
到这里,我们完成了我们的模型。update()
函数简单地将反向传播找到的梯度应用到参数W
和b
上,使用梯度下降更新规则。
为了测试我们的模型,我们利用 scikit-learn 生成一个分类数据集:
from sklearn.datasets import make_circles
from sklearn.metrics import accuracy_score
# Make data: Two circles on x-y plane as a classification problem
X, y = make_circles(n_samples=1000, factor=0.5, noise=0.1)
y = y.reshape(-1,1) # our model expects a 2D array of (n_sample, n_dim)
然后我们构建了我们的模型:输入是二维的,输出是一维的(逻辑回归)。我们设立了两个隐藏层,分别有 4 个和 3 个神经元:
# Build a model
model = mlp(layersizes=[2, 4, 3, 1],
activations=[relu, relu, sigmoid],
derivatives=[drelu, drelu, dsigmoid],
lossderiv=d_cross_entropy)
model.initialize()
yhat = model.forward(X)
loss = cross_entropy(y, yhat)
print("Before training - loss value {} accuracy {}".format(loss, accuracy_score(y, (yhat > 0.5))))
我们看到,在随机权重下,准确率是 50%。
Before training - loss value [[693.62972747]] accuracy 0.5
现在我们训练我们的网络。为了简化,我们执行全批次梯度下降,并使用固定的学习率:
# train for each epoch
n_epochs = 150
learning_rate = 0.005
for n in range(n_epochs):
model.forward(X)
yhat = model.a[-1]
model.backward(y, yhat)
model.update(learning_rate)
loss = cross_entropy(y, yhat)
print("Iteration {} - loss value {} accuracy {}".format(n, loss, accuracy_score(y, (yhat > 0.5))))
输出为:
Iteration 0 - loss value [[693.62972747]] accuracy 0.5
Iteration 1 - loss value [[693.62166655]] accuracy 0.5
Iteration 2 - loss value [[693.61534159]] accuracy 0.5
Iteration 3 - loss value [[693.60994018]] accuracy 0.5
...
Iteration 145 - loss value [[664.60120828]] accuracy 0.818
Iteration 146 - loss value [[697.97739669]] accuracy 0.58
Iteration 147 - loss value [[681.08653776]] accuracy 0.642
Iteration 148 - loss value [[665.06165774]] accuracy 0.71
Iteration 149 - loss value [[683.6170298]] accuracy 0.614
尽管不完美,我们通过训练看到了改进。至少在上述示例中,我们可以看到在第 145 次迭代时准确率超过了 80%,但随后我们发现模型发散。这可以通过减少学习率来改善,而我们在上述实现中并未做此调整。不过,这展示了我们如何通过反向传播和链式法则计算梯度。
完整代码如下:
from sklearn.datasets import make_circles
from sklearn.metrics import accuracy_score
import numpy as np
np.random.seed(0)
# Find a small float to avoid division by zero
epsilon = np.finfo(float).eps
# Sigmoid function and its differentiation
def sigmoid(z):
return 1/(1+np.exp(-z.clip(-500, 500)))
def dsigmoid(z):
s = sigmoid(z)
return 2 * s * (1-s)
# ReLU function and its differentiation
def relu(z):
return np.maximum(0, z)
def drelu(z):
return (z > 0).astype(float)
# Loss function L(y, yhat) and its differentiation
def cross_entropy(y, yhat):
"""Binary cross entropy function
L = - y log yhat - (1-y) log (1-yhat)
Args:
y, yhat (np.array): nx1 matrices which n are the number of data instances
Returns:
average cross entropy value of shape 1x1, averaging over the n instances
"""
return -(y.T @ np.log(yhat.clip(epsilon)) + (1-y.T) @ np.log((1-yhat).clip(epsilon))) / y.shape[1]
def d_cross_entropy(y, yhat):
""" dL/dyhat """
return - np.divide(y, yhat.clip(epsilon)) + np.divide(1-y, (1-yhat).clip(epsilon))
class mlp:
'''Multilayer perceptron using numpy
'''
def __init__(self, layersizes, activations, derivatives, lossderiv):
"""remember config, then initialize array to hold NN parameters without init"""
# hold NN config
self.layersizes = tuple(layersizes)
self.activations = tuple(activations)
self.derivatives = tuple(derivatives)
self.lossderiv = lossderiv
assert len(self.layersizes)-1 == len(self.activations), \
"number of layers and the number of activation functions does not match"
assert len(self.activations) == len(self.derivatives), \
"number of activation functions and number of derivatives does not match"
assert all(isinstance(n, int) and n >= 1 for n in layersizes), \
"Only positive integral number of perceptons is allowed in each layer"
# parameters, each is a 2D numpy array
L = len(self.layersizes)
self.z = [None] * L
self.W = [None] * L
self.b = [None] * L
self.a = [None] * L
self.dz = [None] * L
self.dW = [None] * L
self.db = [None] * L
self.da = [None] * L
def initialize(self, seed=42):
"""initialize the value of weight matrices and bias vectors with small random numbers."""
np.random.seed(seed)
sigma = 0.1
for l, (insize, outsize) in enumerate(zip(self.layersizes, self.layersizes[1:]), 1):
self.W[l] = np.random.randn(insize, outsize) * sigma
self.b[l] = np.random.randn(1, outsize) * sigma
def forward(self, x):
"""Feed forward using existing `W` and `b`, and overwrite the result variables `a` and `z`
Args:
x (numpy.ndarray): Input data to feed forward
"""
self.a[0] = x
for l, func in enumerate(self.activations, 1):
# z = W a + b, with `a` as output from previous layer
# `W` is of size rxs and `a` the size sxn with n the number of data instances, `z` the size rxn
# `b` is rx1 and broadcast to each column of `z`
self.z[l] = (self.a[l-1] @ self.W[l]) + self.b[l]
# a = g(z), with `a` as output of this layer, of size rxn
self.a[l] = func(self.z[l])
return self.a[-1]
def backward(self, y, yhat):
"""back propagation using NN output yhat and the reference output y, generates dW, dz, db,
da
"""
assert y.shape[1] == self.layersizes[-1], "Output size doesn't match network output size"
assert y.shape == yhat.shape, "Output size doesn't match reference"
# first `da`, at the output
self.da[-1] = self.lossderiv(y, yhat)
for l, func in reversed(list(enumerate(self.derivatives, 1))):
# compute the differentials at this layer
self.dz[l] = self.da[l] * func(self.z[l])
self.dW[l] = self.a[l-1].T @ self.dz[l]
self.db[l] = np.mean(self.dz[l], axis=0, keepdims=True)
self.da[l-1] = self.dz[l] @ self.W[l].T
assert self.z[l].shape == self.dz[l].shape
assert self.W[l].shape == self.dW[l].shape
assert self.b[l].shape == self.db[l].shape
assert self.a[l].shape == self.da[l].shape
def update(self, eta):
"""Updates W and b
Args:
eta (float): Learning rate
"""
for l in range(1, len(self.W)):
self.W[l] -= eta * self.dW[l]
self.b[l] -= eta * self.db[l]
# Make data: Two circles on x-y plane as a classification problem
X, y = make_circles(n_samples=1000, factor=0.5, noise=0.1)
y = y.reshape(-1,1) # our model expects a 2D array of (n_sample, n_dim)
print(X.shape)
print(y.shape)
# Build a model
model = mlp(layersizes=[2, 4, 3, 1],
activations=[relu, relu, sigmoid],
derivatives=[drelu, drelu, dsigmoid],
lossderiv=d_cross_entropy)
model.initialize()
yhat = model.forward(X)
loss = cross_entropy(y, yhat)
print("Before training - loss value {} accuracy {}".format(loss, accuracy_score(y, (yhat > 0.5))))
# train for each epoch
n_epochs = 150
learning_rate = 0.005
for n in range(n_epochs):
model.forward(X)
yhat = model.a[-1]
model.backward(y, yhat)
model.update(learning_rate)
loss = cross_entropy(y, yhat)
print("Iteration {} - loss value {} accuracy {}".format(n, loss, accuracy_score(y, (yhat > 0.5))))
进一步阅读
反向传播算法是所有神经网络训练的核心,无论你使用了什么变种的梯度下降算法。这本教科书涵盖了这一点:
-
《深度学习》,作者:Ian Goodfellow, Yoshua Bengio 和 Aaron Courville,2016 年。
之前也实现了神经网络的基础版本,但没有讨论数学部分,它详细解释了这些步骤:
总结
在本教程中,你学习了如何将微分应用于神经网络的训练。
具体来说,你学习了:
-
什么是全微分以及它如何表示为偏微分的总和
-
如何将神经网络表示为方程式,并通过微分推导梯度
-
反向传播如何帮助我们表达神经网络中每一层的梯度
-
如何将梯度转换为代码以构建神经网络模型
导数的应用
导数定义了一个变量相对于另一个变量的变化率。
这是一个非常重要的概念,在许多应用中极为有用:在日常生活中,导数可以告诉你你的行驶速度,或帮助你预测股市的波动;在机器学习中,导数对于函数优化至关重要。
本教程将探索导数的不同应用,从较为熟悉的开始,然后再到机器学习。我们将深入研究导数告诉我们关于我们所研究的不同函数的内容。
在本教程中,你将发现导数的不同应用。
完成本教程后,你将知道:
-
导数的使用可以应用于我们周围的实际问题。
-
导数在机器学习中对于函数优化至关重要。
让我们开始吧。
导数的应用
图片由Devon Janse van Rensburg提供,版权所有。
教程概述
本教程分为两个部分;它们是:
-
导数在实际生活中的应用
-
导数在优化算法中的应用
导数在实际生活中的应用
我们已经看到,导数模型用于描述变化率。
导数回答诸如“多快?”“多陡?”和“多敏感?”这样的问题。这些都是关于变化率的各种形式的问题。
– 第 141 页,无限的力量,2019 年。
这种变化率表示为,???y / ???x,从而定义了因变量???y相对于自变量???x的变化。
让我们从我们周围最熟悉的导数应用之一开始。
每次你上车时,你都在目睹微分。
– 第 178 页,傻瓜微积分,2016 年。
当我们说一辆车以每小时 100 公里的速度行驶时,我们刚刚陈述了它的变化率。我们常用的术语是速度或速率,虽然最好先区分这两者。
在日常生活中,我们通常将速度和速率互换使用,以描述移动物体的变化速率。然而,这在数学上是不正确的,因为速度始终是正值,而速度引入了方向的概念,因此可以展现正值和负值。因此,在接下来的解释中,我们将考虑速度作为更技术性的概念,其定义为:
速度 = ???y / ???t
这意味着速度在时间间隔???t内给出了汽车位置的变化???y。换句话说,速度是位置对时间的一阶导数。
汽车的速度可以保持不变,例如如果汽车持续以每小时 100 公里行驶,或者它也可以随时间变化。后者意味着速度函数本身随时间变化,或者更简单地说,汽车可以被认为是在加速。加速度定义为速度的第一导数 v 和位置 y 对时间的第二导数:
加速度 = ???v / ???t = ???²**y / ???t**²
我们可以绘制位置、速度和加速度曲线以更好地可视化它们。假设汽车的位置随时间的函数是y(t) = t³ – 8t² + 40t:
汽车位置随时间变化的折线图
图表显示汽车在旅程开始时位置变化缓慢,直到大约 t = 2.7s 时略微减速,此后其变化速率加快并继续增加,直到旅程结束。这由汽车速度的图表描绘:
汽车速度随时间变化的折线图
注意到汽车在整个旅程中保持正速度,这是因为它从未改变方向。因此,如果我们设想自己坐在这辆移动的汽车里,车速表会显示我们刚刚在速度图上绘制的值(由于速度始终为正,否则我们需要找出速度的绝对值来计算速度)。如果我们对y(t)应用幂法则以找到其导数,我们会发现速度由以下函数定义:
v(t) = y’(t) = 3t² – 16t + 40
我们还可以绘制加速度图:
汽车加速度随时间变化的折线图
我们发现在时间间隔 t = [0, 2.7) 秒内,图表现出负加速度特征。这是因为加速度是速度的导数,在这个时间段内,汽车的速度在减小。如果我们必须再次对 v(t) 应用幂规则来找到其导数,我们会发现加速度由以下函数定义:
a(t) = v’(t) = 6t – 16
把所有函数放在一起,我们得到以下结果:
y(t) = t³ – 8t² + 40t
v(t) = y’(t) = 3t² – 16t + 40
a(t) = v’(t) = 6t – 16
如果我们代入 t = 10s,我们可以使用这三个函数来找出,在旅程结束时,汽车行驶了 600 米,其速度为 180 m/s,并且加速度为 44 m/s²。我们可以验证所有这些数值与我们刚刚绘制的图表相符。
我们在找出汽车速度和加速度的背景下讨论了这个特定示例。但是,有许多现实生活现象随时间(或其他变量)变化,可以通过应用导数的概念来研究,就像我们刚刚为这个特定示例所做的那样。例如:
-
人口(无论是人类集合还是细菌群落)随时间的增长率,可用于预测近期人口规模的变化。
-
温度随位置变化的变化,可用于天气预报。
-
随着时间的推移股市的波动,可以用来预测未来的股市行为。
导数还提供了解决优化问题的重要信息,接下来我们将看到。
导数在优化算法中的应用
我们已经看到,优化算法(如梯度下降)通过应用导数来寻找误差(或成本)函数的全局最小值。
让我们更详细地看一看导数对误差函数的影响,通过进行与汽车示例相同的练习。
为此,让我们考虑以下用于函数优化的一维测试函数:
f(x) = –x sin(x)
我们可以应用乘积法则来求 f(x) 的一阶导数,记为 f’(x),然后再次应用乘积法则来求 f’(x) 的二阶导数,记为 f’’(x):
f’(x) = -sin(x) – x cos(x)
f’’(x) = x sin(x) – 2 cos(x)
我们可以对不同的 x 值绘制这三个函数的图像:
函数 f(x)、它的一阶导数 f‘(x) 和二阶导数 f”(x) 的线图
与我们之前在汽车示例中观察到的类似,一阶导数的图示表示了f(x)的变化情况及其变化量。例如,正的导数表示f(x)是一个递增函数,而负的导数则表示f(x)现在在递减。因此,如果优化算法在寻找函数最小值时,根据其学习率ε对输入进行小幅度的变化:
x_new = x – ε f’(x)
然后,算法可以通过移动到导数的相反方向(即改变符号)来减少f(x)。
我们可能还对寻找函数的二阶导数感兴趣。
我们可以将二阶导数视为测量曲率。
– 第 86 页,深度学习,2017 年。
例如,如果算法到达一个临界点,此时一阶导数为零,仅凭f’(x)无法区分该点是局部最大值、局部最小值、鞍点还是平坦区域。然而,当二阶导数介入时,算法可以判断,如果二阶导数大于零,则该临界点是局部最小值。如果是局部最大值,二阶导数则小于零。因此,二阶导数可以告知优化算法应向哪个方向移动。不幸的是,这个测试对于鞍点和平坦区域仍然不确定,因为这两种情况下的二阶导数均为零。
基于梯度下降的优化算法不使用二阶导数,因此被称为一阶优化算法。利用二阶导数的优化算法,如牛顿法,通常被称为二阶优化算法。
进一步阅读
本节提供了更多的资源,如果你想深入了解该主题。
书籍
总结
在本教程中,你了解了导数的不同应用。
具体来说,你学到了:
-
导数的应用可以解决我们周围实际问题。
-
导数的使用在机器学习中至关重要,特别是在函数优化方面。
你有任何问题吗?
在下方评论区提出你的问题,我会尽力回答。
机器学习的微积分书籍
原文:
machinelearningmastery.com/calculus-books-for-machine-learning/
知识微积分对于在机器学习或深度学习中获得结果和解决问题不是必须的。
然而,了解一些微积分将对你有许多帮助,比如在阅读书籍和论文中的数学符号时,理解描述拟合模型的术语如“梯度”,以及理解通过优化拟合模型(如神经网络)的学习动态。
微积分作为大学水平教授的主题是具有挑战性的,但你不需要了解所有的微积分,只需要掌握与数值函数优化相关的几个术语和方法,这对于像神经网络这样的拟合算法至关重要。掌握微积分的最佳方式是通过书籍。
在本教程中,你将发现关于机器学习的微积分书籍。
完成本教程后,你将会了解:
-
哪些关于机器学习的书籍对相关的微积分主题提供了温和的介绍。
-
你可以用来学习微积分的直觉、历史和技术的书籍。
-
你可以用来参考或深入学习微积分技术及其证明的教科书。
让我们开始吧。
机器学习的微积分书籍
照片由Roanish提供,部分权利保留。
教程概述
本教程分为三个部分;它们是:
-
机器学习书中的微积分
-
入门微积分书籍
-
微积分教科书
机器学习书中的微积分
从涵盖微积分基础的机器学习书籍开始。
如果你很久以前在学校学过微积分,需要复习,或者需要快速了解术语和方法,这将非常合适。
许多顶级的机器学习和深度学习教科书会涵盖基础内容,这对于大多数情况来说通常是足够的,例如当你专注于通过机器学习算法获得结果时。
如果你已经拥有一本涵盖一些微积分内容的机器学习教科书,那将会很有帮助,因为你不需要再买另一本书。
两本很好的教科书涵盖了一些微积分内容,包括:
“深度学习”教科书中的微积分内容比较简略。
深度学习
微积分是在优化的背景下引入的,首先是线性回归,然后更一般地用于多变量优化——在拟合神经网络时会看到。
包括以下主题:
-
导数
-
偏导数
-
二阶导数
-
Hessian 矩阵
-
梯度
-
梯度下降
-
临界点
-
静态点
-
局部最大值
-
全局最小值
-
鞍点
-
雅可比矩阵
以及更多内容。
微积分术语。
取自《深度学习》第 xiii 页,2016 年。
书籍“模式识别与机器学习”提供了更深入的覆盖。
具体而言:
-
第十章:近似推断
-
附录 D:变分法
模式识别与机器学习
附录 D 介绍了“变分法”这一主题,第十章使用了这种技术。该主题在深度学习书籍中也有涵盖。
如果一个典型的微积分问题涉及寻找一个优化函数的变量值,那么变分法是关于寻找一个优化另一个函数的函数。我们可以看到这在机器学习中,特别是在神经网络中是相关的,因为神经网络模型(电路)在损失函数下学习任意函数。
在常规微积分中,一个常见的问题是找到一个使函数 y(x)最大化(或最小化)的 x 值。同样,在变分法中,我们寻求一个使泛函 F[y]最大化(或最小化)的函数 y(x)。也就是说,在所有可能的函数 y(x)中,我们希望找到使泛函 F[y]达到最大(或最小)的特定函数。
— 第 703 页,模式识别与机器学习,2006 年。
虽然变分法并不是拟合神经网络所必需的,但它提供了一个有用的工具,以更好地理解我们在拟合神经网络时所解决的问题以及实际中可能遇到的学习动态。
最终,我们开始看到专门讲解机器学习底层数学理解的书籍。
一个例子是“机器学习中的数学”。
机器学习中的数学
这本书涵盖了机器学习所需的大量微积分,并提供了展示其在模型优化(训练/学习)中的适用性。
微积分及其与机器学习的联系。
取自《机器学习中的数学》,第 140 页。
微积分的讨论限于第五章:向量微积分,该章涵盖以下主题:
-
第 5.1 节 单变量函数的微分
-
第 5.2 节 偏导数和梯度
-
第 5.3 节 向量值函数的梯度
-
第 5.4 节 矩阵的梯度
-
第 5.5 节 用于计算梯度的有用公式
-
第 5.6 节 反向传播和自动微分
-
第 5.7 节 高阶导数
-
第 5.8 节 线性化和多变量泰勒级数
这本书是填补或刷新你对机器学习微积分知识的绝佳起点。
入门微积分书籍
了解术语的名称是一回事,但如果你想更全面地了解一些方法呢?
为此,我推荐一本好的初学者书籍,例如:
这些书不是教科书;相反,它们假设几乎没有背景知识(例如,微积分前知识),并将引导你理解直觉、技术及其在简单练习中的应用。
直觉是关键!我们不是在做数学学位,我们是在解决机器学习问题。
一本教科书会教你方法和证明,但很少告诉你这个方法最初是为了解决什么问题,以及一些历史背景。我认为背景知识至关重要。
《傻瓜微积分》
我拥有这两本书。我喜欢《傻瓜微积分》,如果你能不介意名字和风格,我会推荐它。
很多人认为微积分是所有智力历史上的一项伟大成就。因此,它值得付出努力。阅读这本没有术语的书,掌握微积分,加入那些可以自豪地说“微积分?哦,当然,我知道微积分。这没什么大不了的”的快乐少数。
— 第 1 页,傻瓜微积分,2016 年。
目录如下:
-
引言
-
第一部分:微积分概述
-
第一章:什么是微积分?
-
第二章:微积分的两个重要概念:微分与积分——加上无限级数
-
第三章:微积分为何有效
-
-
第二部分:微积分前提知识的热身
-
第四章:初代代数与代数复习
-
第五章:奇特函数及其有趣的图形
-
第六章:三角函数探戈
-
-
第三部分:极限
-
第七章:极限与连续性
-
第八章:评估极限
-
-
第四部分:微分
-
第九章:微分方向
-
第十章:微分规则——是的,伙计,它确实很棒
-
第十一章:微分与曲线形状
-
第十二章:你的问题解决了:微分来救援!
-
第十三章:更多微分问题:偏离正题
-
-
第五部分:积分与无限级数
-
第十四章:积分导论与面积逼近
-
第十五章:积分:它是倒向微分
-
第十六章:专家的积分技术
-
第十七章:忘记菲尔博士:用积分解决问题
-
第十八章:用不适定积分驯服无限
-
第十九章:无限级数
-
-
第六部分:十件事部分
-
第二十章:十个要记住的事项
-
第二十一章:十个要忘记的事项
-
第二十二章:你不能逃避的十件事
-
微积分指南
我发现“微积分的指南”不错,但简洁明了。它直击每种方法的要点。
这本书最棒的地方在于它专注于让你进行计算。通过计算来学习。这就是我学习的方式。
… 微积分需要理解一整套全新的概念,这些概念非常有趣且相当美妙,但确实有点难以掌握。然而,尽管涉及了所有这些新想法,微积分仍然是一种计算方法,因此在你的微积分课程中,你将进行大量的计算,无数的计算,看似无尽的计算!
— 页码 1-2, 微积分的指南, 2019 年。
我强烈推荐的另一本书是一本科普读物:
- 无限的力量:微积分如何揭示宇宙的秘密, 2020 年。
无限的力量
这本书将让你对微积分充满兴趣。
它涵盖了历史,并将让你了解微积分工具为何被发明,以及它们为何如此强大。
没有微积分,我们不会有手机、计算机或微波炉。我们不会有广播、电视、孕妇超声波或迷路者的 GPS。我们不会分裂原子、解开人类基因组,或者把宇航员送上月球。我们甚至可能没有《独立宣言》。
— 页码 vii, 无限的力量, 2020 年。
历史很重要。你需要有人阐述几何和代数无法解决的难题、尝试过的破解方法,以及那些效果良好的新方法。
微积分起源于几何学。大约公元前 250 年,在古希腊,它是一个致力于曲线奥秘的小数学新兴领域。
— 页码 3, 无限的力量, 2020 年。
它强调了微积分不是魔法和咒语,而是解决问题的工具。并且,如果你愿意,这一切都是可以学习的。
微积分教科书
也许你想更深入地了解。
你想查看和理解每种方法的证明,完成本科水平的练习,深入探索。
这不是在机器学习中必需的,但有时我们想全身心投入。我理解。在这种情况下,我推荐一本教科书,比如用于本科课程的教科书。
使用高级/简化材料时,备一本教科书以便随时深入了解具体术语和方法也是个好主意。例如,深入研究海森矩阵。
关于微积分的教科书数量庞大,而且似乎每隔几年就有新版本出现。
然而,一些大学水平的顶级教科书包括以下几本:
-
微积分, 第 3 版, 2017 年。(吉尔伯特·斯特朗)
-
微积分,第 8 版,2015 年。(詹姆斯·斯图尔特)
-
微积分,第 4 版,2008 年。(迈克尔·斯皮瓦克)
-
微积分,第 11 版,2017 年。(罗恩·拉尔森,布鲁斯·爱德华兹)
-
托马斯微积分,第 14 版,2017 年。(乔尔·哈斯,克里斯托弗·海尔,莫里斯·威尔)
我喜欢斯图尔特的书,但它们都差不多。都需要大量的工作。
微积分
你将需要做大量的习题,没有办法绕开这一点。
也许可以浏览几个,并选择一个适合你学习风格的。
总结
在本教程中,你发现了用于机器学习的微积分书籍。
你读过这些书中的任何一本,还是打算买一本?
请在下面的评论中告诉我。
你知道其他优秀的微积分书籍吗?
请在评论中告诉我。
机器学习微积分(7 天迷你课程)
原文:
machinelearningmastery.com/calculus-for-machine-learning-7-day-mini-course/
机器学习微积分速成课程。
在 7 天内熟悉机器学习中的微积分技术。
微积分是许多机器学习算法背后的重要数学技巧。你不一定需要知道它才能使用这些算法。当你深入了解时,你会发现它在每一个有关机器学习模型理论的讨论中都是无处不在的。
作为从业者,我们很可能不会遇到非常复杂的微积分问题。如果需要解决,我们可以使用计算机代数系统等工具来帮助,或至少验证我们的解决方案。然而,更重要的是理解微积分背后的思想,并将微积分术语与我们机器学习算法中的应用联系起来。
在此速成课程中,你将发现机器学习中使用的一些常见微积分思想。你将通过 Python 中的练习在七天内进行学习。
这是一个重要且重大的帖子。你可能想要收藏它。
让我们开始吧。
机器学习微积分(7 天迷你课程)
图片由ArnoldReinhold提供,保留部分权利。
这个速成课程适合谁?
在我们开始之前,让我们确保你处于正确的地方。
本课程适合那些可能了解一些应用机器学习的开发者。也许你知道如何完整解决一个预测建模问题,或者至少了解大部分主要步骤,并使用流行工具。
本课程中的课程假设你具备以下几项条件:
-
你对基本 Python 编程有一定了解。
-
你可能了解一些基本的线性代数。
-
你可能了解一些基本的机器学习模型。
你不需要是:
-
数学天才!
-
机器学习专家!
此速成课程将把你从一个了解一点机器学习的开发者,提升为一个能够有效讨论机器学习算法中微积分概念的开发者。
注意:此速成课程假设你已经拥有一个正常运行的 Python 3.7 环境,并安装了一些库,如 SciPy 和 SymPy。如果你需要帮助设置环境,你可以按照此处的逐步教程:
速成课程概述
此速成课程分为七节课。
你可以每天完成一节课(推荐)或一天内完成所有课程(硬核)。这真的取决于你有多少时间和你的热情程度。
以下是七节课程的列表,这些课程将帮助你入门并高效进行 Python 数据准备:
-
课程 01: 微分学
-
课程 02: 积分
-
课程 03: 向量函数的梯度
-
课程 04: 雅可比矩阵
-
课程 05: 反向传播
-
课程 06: 优化
-
课程 07: 支持向量机
每节课可能花费你 5 分钟到 1 小时。慢慢来,根据自己的节奏完成课程。提出问题,甚至在下面的评论中发布结果。
这些课程可能会期望你去查找如何做的相关信息。我会给你提示,但每节课的部分目的就是迫使你学习如何寻找有关算法和 Python 中最佳工具的帮助。(提示: 我在这个博客上有所有的答案;使用搜索框。)
在评论中发布你的结果;我会为你加油!
坚持住;不要放弃。
课程 01: 微分学
在这节课中,你将发现什么是微分学或微分。
微分是将一个数学函数转化为另一个称为导数的函数的操作。导数表示原始函数的斜率或变化率。
例如,如果我们有一个函数 f ( x ) = x 2 f(x)=x² f(x)=x2,它的导数是一个告诉我们在 x x x 处该函数变化率的函数。变化率定义为: f ′ ( x ) = f ( x + δ x ) − f ( x ) δ x f'(x) = \frac{f(x+\delta x)-f(x)}{\delta x} f′(x)=δxf(x+δx)−f(x) 对于一个小量 δ x \delta x δx。
通常我们将以上定义为极限形式,即,
f ′ ( x ) = lim δ x → 0 f ( x + δ x ) − f ( x ) δ x f'(x) = \lim_{\delta x\to 0} \frac{f(x+\delta x)-f(x)}{\delta x} f′(x)=δx→0limδxf(x+δx)−f(x)
意味着 δ x \delta x δx 应尽可能接近零。
有几种微分规则可以帮助我们更容易地找到导数。适用于上述例子的规则是 d d x x n = n x n − 1 \frac{d}{dx} x^n = nx^{n-1} dxdxn=nxn−1。因此对于 f ( x ) = x 2 f(x)=x² f(x)=x2,我们有导数 f ′ ( x ) = 2 x f'(x)=2x f′(x)=2x。
我们可以通过绘制根据变化率计算的函数 f ′ ( x ) f'(x) f′(x) 与根据微分规则计算的函数来确认这一点。以下使用 Python 的 NumPy 和 matplotlib:
import numpy as np
import matplotlib.pyplot as plt
# Define function f(x)
def f(x):
return x**2
# compute f(x) = x² for x=-10 to x=10
x = np.linspace(-10,10,500)
y = f(x)
# Plot f(x) on left half of the figure
fig = plt.figure(figsize=(12,5))
ax = fig.add_subplot(121)
ax.plot(x, y)
ax.set_title("y=f(x)")
# f'(x) using the rate of change
delta_x = 0.0001
y1 = (f(x+delta_x) - f(x))/delta_x
# f'(x) using the rule
y2 = 2 * x
# Plot f'(x) on right half of the figure
ax = fig.add_subplot(122)
ax.plot(x, y1, c="r", alpha=0.5, label="rate")
ax.plot(x, y2, c="b", alpha=0.5, label="rule")
ax.set_title("y=f'(x)")
ax.legend()
plt.show()
在上面的图中,我们可以看到使用变化率找到的导数函数和使用微分规则找到的导数函数完全一致。
你的任务
我们也可以对其他函数进行类似的微分。例如, f ( x ) = x 3 – 2 x 2 + 1 f(x)=x³ – 2x² + 1 f(x)=x3–2x2+1。使用微分规则找到这个函数的导数,并将你的结果与使用极限率找到的结果进行比较。用上面的图验证你的结果。如果你做得正确,你应该看到以下图形:
在下一节课中,你将发现积分是微分的逆操作。
课程 02: 积分
在这节课中,你将发现积分是微分的逆操作。
如果我们考虑一个函数 f ( x ) = 2 x f(x)=2x f(x)=2x 并且在 δ x \delta x δx 的间隔内每一步(例如, δ x = 0.1 \delta x = 0.1 δx=0.1),我们可以计算,从 x = − 10 x=-10 x=−10 到 x = 10 x=10 x=10 如下:
$$
f(-10), f(-9.9), f(-9.8), \cdots, f(9.8), f(9.9), f(10)
$$
显然,如果我们有一个更小的步长 δ x \delta x δx,上面会有更多的项。
如果我们将上述每一个乘以步长,然后将它们加起来,即,
$$
f(-10)\times 0.1 + f(-9.9)\times 0.1 + \cdots + f(9.8)\times 0.1 + f(9.9)\times 0.1
$$
这个总和被称为 f ( x ) f(x) f(x)的积分。实质上,这个总和是 f ( x ) f(x) f(x)的曲线下的面积,从 x = − 10 x=-10 x=−10到 x = 10 x=10 x=10。微积分中的一个定理表明,如果我们将曲线下的面积作为一个函数,它的导数是 f ( x ) f(x) f(x)。因此,我们可以将积分视为微分的反操作。
正如我们在课程 01 中看到的, f ( x ) = x 2 f(x)=x² f(x)=x2的微分是 f ′ ( x ) = 2 x f'(x)=2x f′(x)=2x。这意味着对于 f ( x ) = 2 x f(x)=2x f(x)=2x,我们可以写作 ∫ f ( x ) d x = x 2 \int f(x) dx = x² ∫f(x)dx=x2,或者我们可以说 f ( x ) = x f(x)=x f(x)=x的反导数是 x 2 x² x2。我们可以通过直接计算面积在 Python 中确认这一点。
import numpy as np
import matplotlib.pyplot as plt
def f(x):
return 2*x
# Set up x from -10 to 10 with small steps
delta_x = 0.1
x = np.arange(-10, 10, delta_x)
# Find f(x) * delta_x
fx = f(x) * delta_x
# Compute the running sum
y = fx.cumsum()
# Plot
plt.plot(x, y)
plt.show()
这个图与课程 01 中的 f ( x ) f(x) f(x)具有相同的形状。事实上,所有通过常数(例如 f ( x ) f(x) f(x)和 f ( x ) + 5 f(x)+5 f(x)+5)不同的函数具有相同的导数。因此,计算得到的反导数的图形将是原始函数在垂直方向上移动。
你的任务
考虑 f ( x ) = 3 x 2 − 4 x f(x)=3x²-4x f(x)=3x2−4x,找出该函数的反导数并绘制它。此外,尝试将上述 Python 代码替换为此函数。如果你将两者一起绘制,你应该会看到以下内容:
在下面的评论中发布你的答案。我很想看看你会得到什么结果。
这两节课讲的是具有一个变量的函数。在下一节课中,你将发现如何将微分应用于多个变量的函数。
课程 03:向量函数的梯度
在本课程中,你将学习多变量函数的梯度概念。
如果我们有一个不仅仅是一个变量而是两个或更多变量的函数,微分自然地扩展为对每个变量的微分。例如,如果我们有函数 f ( x , y ) = x 2 + y 3 f(x,y) = x² + y³ f(x,y)=x2+y3,我们可以将每个变量的微分写作:
$$
\begin{aligned}
\frac{\partial f}{\partial x} &= 2x \
\frac{\partial f}{\partial y} &= 3y²
\end{aligned}
$$
在这里,我们引入了偏导数的符号,表示在假设其他变量为常数的情况下,对一个变量的函数进行微分。因此,在上述计算 ∂ f ∂ x \frac{\partial f}{\partial x} ∂x∂f时,我们忽略了函数 f ( x , y ) f(x,y) f(x,y)中的 y 3 y³ y3部分。
一个具有两个变量的函数可以被视为平面上的一个表面。上述函数 f ( x , y ) f(x,y) f(x,y)可以使用 matplotlib 进行可视化:
import numpy as np
import matplotlib.pyplot as plt
# Define the range for x and y
x = np.linspace(-10,10,1000)
xv, yv = np.meshgrid(x, x, indexing='ij')
# Compute f(x,y) = x² + y³
zv = xv**2 + yv**3
# Plot the surface
fig = plt.figure(figsize=(6,6))
ax = fig.add_subplot(projection='3d')
ax.plot_surface(xv, yv, zv, cmap="viridis")
plt.show()
这个函数的梯度表示为:
∇ f ( x , y ) = ( ∂ f ∂ x , ∂ f ∂ y ) = ( 2 x , 3 y 2 ) \nabla f(x,y) = \Big(\frac{\partial f}{\partial x},\; \frac{\partial f}{\partial y}\Big) = (2x,\;3y²) ∇f(x,y)=(∂x∂f,∂y∂f)=(2x,3y2)
因此,在每个坐标 ( x , y ) (x,y) (x,y)处,梯度 ∇ f ( x , y ) \nabla f(x,y) ∇f(x,y)是一个向量。这个向量告诉我们两件事:
-
向量的方向指向函数 f ( x , y ) f(x,y) f(x,y) 增长最快的地方
-
向量的大小是函数 f ( x , y ) f(x,y) f(x,y) 在这个方向上的变化率
可视化梯度的一种方式是将其视为矢量场:
import numpy as np
import matplotlib.pyplot as plt
# Define the range for x and y
x = np.linspace(-10,10,20)
xv, yv = np.meshgrid(x, x, indexing='ij')
# Compute the gradient of f(x,y)
fx = 2*xv
fy = 2*yv
# Convert the vector (fx,fy) into size and direction
size = np.sqrt(fx**2 + fy**2)
dir_x = fx/size
dir_y = fy/size
# Plot the surface
plt.figure(figsize=(6,6))
plt.quiver(xv, yv, dir_x, dir_y, size, cmap="viridis")
plt.show()
matplotlib 中的 viridis 色图将用黄色显示较大的值,用紫色显示较小的值。因此,我们在上图中看到梯度在边缘“更陡”而不是中心。
如果我们考虑坐标(2,3),可以使用以下方式检查 f ( x , y ) f(x,y) f(x,y) 将在哪个方向上最快增加:
import numpy as np
def f(x, y):
return x**2 + y**3
# 0 to 360 degrees at 0.1-degree steps
angles = np.arange(0, 360, 0.1)
# coordinate to check
x, y = 2, 3
# step size for differentiation
step = 0.0001
# To keep the size and direction of maximum rate of change
maxdf, maxangle = -np.inf, 0
for angle in angles:
# convert degree to radian
rad = angle * np.pi / 180
# delta x and delta y for a fixed step size
dx, dy = np.sin(rad)*step, np.cos(rad)*step
# rate of change at a small step
df = (f(x+dx, y+dy) - f(x,y))/step
# keep the maximum rate of change
if df > maxdf:
maxdf, maxangle = df, angle
# Report the result
dx, dy = np.sin(maxangle*np.pi/180), np.cos(maxangle*np.pi/180)
gradx, grady = dx*maxdf, dy*maxdf
print(f"Max rate of change at {maxangle} degrees")
print(f"Gradient vector at ({x},{y}) is ({dx*maxdf},{dy*maxdf})")
它的输出是:
Max rate of change at 8.4 degrees
Gradient vector at (2,3) is (3.987419245872443,27.002750276227097)
根据公式,梯度向量为(4,27),数值结果足够接近。
你的任务
考虑函数 f ( x , y ) = x 2 + y 2 f(x,y)=x²+y² f(x,y)=x2+y2,在(1,1)点的梯度向量是什么?如果你通过偏导数得到答案,能否修改上述 Python 代码,通过检查不同方向上的变化率来确认?
在下面的评论中发布你的答案。我很想看看你得到的结果。
在下一课中,你将发现一个以向量输入并产生向量输出的函数的微分。
课程 04:雅可比矩阵
在本课中,你将学习雅可比矩阵。
函数 f ( x , y ) = ( p ( x , y ) , q ( x , y ) ) = ( 2 x y , x 2 y ) f(x,y)=(p(x,y), q(x,y))=(2xy, x²y) f(x,y)=(p(x,y),q(x,y))=(2xy,x2y) 是一个有两个输入和两个输出的函数。有时我们称这个函数为接收向量参数并返回向量值的函数。这个函数的微分是一个叫做雅可比矩阵的矩阵。上述函数的雅可比矩阵是:
$$
\mathbf{J} =
\begin{bmatrix}
\frac{\partial p}{\partial x} & \frac{\partial p}{\partial y} \
\frac{\partial q}{\partial x} & \frac{\partial q}{\partial y}
\end{bmatrix}
=
\begin{bmatrix}
2y & 2x \
2xy & x²
\end{bmatrix}
$$
在雅可比矩阵中,每一行包含输出向量中每个元素的偏导数,而每一列包含对输入向量中每个元素的偏导数。
我们稍后将看到雅可比矩阵的应用。由于计算雅可比矩阵涉及大量的偏导数,如果我们能让计算机检查我们的数学计算将会很好。在 Python 中,我们可以使用 SymPy 验证上述结果:
from sympy.abc import x, y
from sympy import Matrix, pprint
f = Matrix([2*x*y, x**2*y])
variables = Matrix([x,y])
pprint(f.jacobian(variables))
它的输出是:
⎡ 2⋅y 2⋅x⎤
⎢ ⎥
⎢ 2 ⎥
⎣2⋅x⋅y x ⎦
我们要求 SymPy 定义符号x
和y
,然后定义了向量函数f
。之后,可以通过调用jacobian()
函数来找到雅可比矩阵。
你的任务
考虑函数
$$
f(x,y) = \begin{bmatrix}
\frac{1}{1+e^{-(px+qy)}} & \frac{1}{1+e^{-(rx+sy)}} & \frac{1}{1+e^{-(tx+uy)}}
\end{bmatrix}
$$
其中 p , q , r , s , t , u p,q,r,s,t,u p,q,r,s,t,u 是常数。 f ( x , y ) f(x,y) f(x,y) 的雅可比矩阵是什么?你能用 SymPy 验证吗?
在下一课中,你将发现雅可比矩阵在神经网络反向传播算法中的应用。
课程 05:反向传播
在本课中,你将了解反向传播算法如何使用雅可比矩阵。
如果我们考虑一个具有一个隐藏层的神经网络,我们可以将其表示为一个函数:
$$
y = g\Big(\sum_{k=1}^M u_k f_k\big(\sum_{i=1}^N w_{ik}x_i\big)\Big)
$$
神经网络的输入是一个向量 x = ( x 1 , x 2 , ⋯ , x N ) \mathbf{x}=(x_1, x_2, \cdots, x_N) x=(x1,x2,⋯,xN),每个 x i x_i xi 会与权重 w i k w_{ik} wik 相乘并输入到隐藏层中。隐藏层中神经元 k k k 的输出将与权重 u k u_k uk 相乘并输入到输出层中。隐藏层和输出层的激活函数分别是 f f f 和 g g g。
如果我们考虑
z k = f k ( ∑ i = 1 N w i k x i ) z_k = f_k\big(\sum_{i=1}^N w_{ik}x_i\big) zk=fk(i=1∑Nwikxi)
然后
$$
\frac{\partial y}{\partial x_i} = \sum_{k=1}^M \frac{\partial y}{\partial z_k}\frac{\partial z_k}{\partial x_i}
$$
如果我们一次考虑整个层,我们有 z = ( z 1 , z 2 , ⋯ , z M ) \mathbf{z}=(z_1, z_2, \cdots, z_M) z=(z1,z2,⋯,zM) 然后
$$
\frac{\partial y}{\partial \mathbf{x}} = \mathbf{W}^\top\frac{\partial y}{\partial \mathbf{z}}
$$
其中 W \mathbf{W} W 是 M × N M\times N M×N 的雅可比矩阵,其中第 k k k 行第 i i i 列的元素是 ∂ z k ∂ x i \frac{\partial z_k}{\partial x_i} ∂xi∂zk。
这就是反向传播算法在训练神经网络中的工作原理!对于具有多个隐藏层的网络,我们需要计算每一层的雅可比矩阵。
你的任务
下面的代码实现了一个神经网络模型,你可以自己尝试。它有两个隐藏层和一个分类网络,用于将二维点分为两类。尝试查看函数 backward()
并识别哪个是雅可比矩阵。
如果你玩这个代码,mlp
类不应该被修改,但你可以改变模型创建时的参数。
from sklearn.datasets import make_circles
from sklearn.metrics import accuracy_score
import numpy as np
np.random.seed(0)
# Find a small float to avoid division by zero
epsilon = np.finfo(float).eps
# Sigmoid function and its differentiation
def sigmoid(z):
return 1/(1+np.exp(-z.clip(-500, 500)))
def dsigmoid(z):
s = sigmoid(z)
return 2 * s * (1-s)
# ReLU function and its differentiation
def relu(z):
return np.maximum(0, z)
def drelu(z):
return (z > 0).astype(float)
# Loss function L(y, yhat) and its differentiation
def cross_entropy(y, yhat):
"""Binary cross entropy function
L = - y log yhat - (1-y) log (1-yhat)
Args:
y, yhat (np.array): nx1 matrices which n are the number of data instances
Returns:
average cross entropy value of shape 1x1, averaging over the n instances
"""
return ( -(y.T @ np.log(yhat.clip(epsilon)) +
(1-y.T) @ np.log((1-yhat).clip(epsilon))
) / y.shape[1] )
def d_cross_entropy(y, yhat):
""" dL/dyhat """
return ( - np.divide(y, yhat.clip(epsilon))
+ np.divide(1-y, (1-yhat).clip(epsilon)) )
class mlp:
'''Multilayer perceptron using numpy
'''
def __init__(self, layersizes, activations, derivatives, lossderiv):
"""remember config, then initialize array to hold NN parameters
without init"""
# hold NN config
self.layersizes = tuple(layersizes)
self.activations = tuple(activations)
self.derivatives = tuple(derivatives)
self.lossderiv = lossderiv
# parameters, each is a 2D numpy array
L = len(self.layersizes)
self.z = [None] * L
self.W = [None] * L
self.b = [None] * L
self.a = [None] * L
self.dz = [None] * L
self.dW = [None] * L
self.db = [None] * L
self.da = [None] * L
def initialize(self, seed=42):
"""initialize the value of weight matrices and bias vectors with small
random numbers."""
np.random.seed(seed)
sigma = 0.1
for l, (n_in, n_out) in enumerate(zip(self.layersizes, self.layersizes[1:]), 1):
self.W[l] = np.random.randn(n_in, n_out) * sigma
self.b[l] = np.random.randn(1, n_out) * sigma
def forward(self, x):
"""Feed forward using existing `W` and `b`, and overwrite the result
variables `a` and `z`
Args:
x (numpy.ndarray): Input data to feed forward
"""
self.a[0] = x
for l, func in enumerate(self.activations, 1):
# z = W a + b, with `a` as output from previous layer
# `W` is of size rxs and `a` the size sxn with n the number of data
# instances, `z` the size rxn, `b` is rx1 and broadcast to each
# column of `z`
self.z[l] = (self.a[l-1] @ self.W[l]) + self.b[l]
# a = g(z), with `a` as output of this layer, of size rxn
self.a[l] = func(self.z[l])
return self.a[-1]
def backward(self, y, yhat):
"""back propagation using NN output yhat and the reference output y,
generates dW, dz, db, da
"""
# first `da`, at the output
self.da[-1] = self.lossderiv(y, yhat)
for l, func in reversed(list(enumerate(self.derivatives, 1))):
# compute the differentials at this layer
self.dz[l] = self.da[l] * func(self.z[l])
self.dW[l] = self.a[l-1].T @ self.dz[l]
self.db[l] = np.mean(self.dz[l], axis=0, keepdims=True)
self.da[l-1] = self.dz[l] @ self.W[l].T
def update(self, eta):
"""Updates W and b
Args:
eta (float): Learning rate
"""
for l in range(1, len(self.W)):
self.W[l] -= eta * self.dW[l]
self.b[l] -= eta * self.db[l]
# Make data: Two circles on x-y plane as a classification problem
X, y = make_circles(n_samples=1000, factor=0.5, noise=0.1)
y = y.reshape(-1,1) # our model expects a 2D array of (n_sample, n_dim)
# Build a model
model = mlp(layersizes=[2, 4, 3, 1],
activations=[relu, relu, sigmoid],
derivatives=[drelu, drelu, dsigmoid],
lossderiv=d_cross_entropy)
model.initialize()
yhat = model.forward(X)
loss = cross_entropy(y, yhat)
score = accuracy_score(y, (yhat > 0.5))
print(f"Before training - loss value {loss} accuracy {score}")
# train for each epoch
n_epochs = 150
learning_rate = 0.005
for n in range(n_epochs):
model.forward(X)
yhat = model.a[-1]
model.backward(y, yhat)
model.update(learning_rate)
loss = cross_entropy(y, yhat)
score = accuracy_score(y, (yhat > 0.5))
print(f"Iteration {n} - loss value {loss} accuracy {score}")
在下一节课中,你将发现利用微分找到函数的最优值。
第 06 课:优化
在本节课中,你将学习微分的重要应用。
由于函数的微分是变化率,我们可以利用微分来找到函数的最优点。
如果一个函数达到了最大值,我们会期望它从一个较低的点移动到最大值,并且如果我们进一步移动,它会下降到另一个较低的点。因此,在最大点上,函数的变化率为零。最小值的情况则相反。
举个例子,考虑 f ( x ) = x 3 − 2 x 2 + 1 f(x)=x³-2x²+1 f(x)=x3−2x2+1。导数为 f ′ ( x ) = 3 x 2 − 4 x f'(x) = 3x²-4x f′(x)=3x2−4x,在 x = 0 x=0 x=0 和 x = 4 / 3 x=4/3 x=4/3 时 f ′ ( x ) = 0 f'(x)=0 f′(x)=0。因此这些 x x x 的位置是 f ( x ) f(x) f(x) 达到最大值或最小值的位置。我们可以通过绘制 f ( x ) f(x) f(x) 来直观地确认(参见第一部分的图)。
你的任务
考虑函数 f ( x ) = log x f(x)=\log x f(x)=logx 并求其导数。当 f ′ ( x ) = 0 f'(x)=0 f′(x)=0 时 x x x 的值是多少?这告诉你关于对数函数的最大值或最小值的什么信息?尝试绘制 log x \log x logx 的函数图像以直观确认你的答案。
在下一节课中,你将发现这一技术在寻找支持向量中的应用。
第 07 课:支持向量机
在本节课中,你将学习如何将支持向量机转换为优化问题。
在二维平面中,任何直线都可以通过以下方程表示:
a x + b y + c = 0 ax+by+c=0 ax+by+c=0
在 x y xy xy坐标系中。从坐标几何学的研究结果表明,对于任意点 ( x 0 , y 0 ) (x_0,y_0) (x0,y0),它到直线 a x + b y + c = 0 ax+by+c=0 ax+by+c=0的距离是:
$$
\frac{\vert ax_0+by_0+c \vert}{\sqrt{a²+b²}}
$$
在 x y xy xy平面中考虑点(0,0),(1,2)和(2,1),其中第一个点和后两个点属于不同的类。什么是最好分离这两类的线?这是支持向量机分类器的基础。支持向量是这种情况下最大分离的线。
要找到这样一条线,我们正在寻找:
$$
\begin{aligned}
\text{minimize} && a² + b² \
\text{subject to} && -1(0a+0b+c) &\ge 1 \
&& +1(1a+2b+c) &\ge 1 \
&& +1(2a+1b+c) &\ge 1
\end{aligned}
$$
目标 a 2 + b 2 a²+b² a2+b2是为了最小化,以使每个数据点到直线的距离最大化。条件 − 1 ( 0 a + 0 b + c ) ≥ 1 -1(0a+0b+c)\ge 1 −1(0a+0b+c)≥1意味着点(0,0)属于类 − 1 -1 −1;对于其他两点也是如此,它们属于类 + 1 +1 +1。直线应该将这两类放在平面的不同侧。
这是一个受约束的优化问题,解决它的方法是使用拉格朗日乘数法。使用拉格朗日乘数法的第一步是找到以下拉格朗日函数的偏导数:
$$
L = a²+b² + \lambda_1(-c-1) + \lambda_2 (a+2b+c-1) + \lambda_3 (2a+b+c-1)
$$
并设置偏微分为零,然后解出 a a a, b b b和 c c c。在这里展示将会太冗长,但我们可以使用 SciPy 在数值上找到解决方案:
import numpy as np
from scipy.optimize import minimize
def objective(w):
return w[0]**2 + w[1]**2
def constraint1(w):
"Inequality for point (0,0)"
return -1*w[2] - 1
def constraint2(w):
"Inequality for point (1,2)"
return w[0] + 2*w[1] + w[2] - 1
def constraint3(w):
"Inequality for point (2,1)"
return 2*w[0] + w[1] + w[2] - 1
# initial guess
w0 = np.array([1, 1, 1])
# optimize
bounds = ((-10,10), (-10,10), (-10,10))
constraints = [
{"type":"ineq", "fun":constraint1},
{"type":"ineq", "fun":constraint2},
{"type":"ineq", "fun":constraint3},
]
solution = minimize(objective, w0, method="SLSQP", bounds=bounds, constraints=constraints)
w = solution.x
print("Objective:", objective(w))
print("Solution:", w)
它将打印:
Objective: 0.8888888888888942
Solution: [ 0.66666667 0.66666667 -1\. ]
上述意味着分离这三点的线是 0.67 x + 0.67 y – 1 = 0 0.67x + 0.67y – 1 = 0 0.67x+0.67y–1=0。请注意,如果你提供了 N N N个数据点,就会有 N N N个定义的约束条件。
你的任务
让我们考虑点(-1,-1)和(-3,-1)作为第一类,以及点(0,0)和点(3,3)作为第二类,还有点(1,2)和(2,1)。在这个有六个点的问题中,你能修改上述程序并找到分离这两类的直线吗?看到解决方案保持不变可能会感到惊讶。这是有原因的。你能说出来吗?
在下面的评论中发表你的答案。我很想看看你们的成果。
这是最后的课程。
结束!
(看看你已经走了多远)
你成功了。干得漂亮!
请花一点时间回顾你已经走了多远。
你发现:
-
什么是微分,以及它对一个函数意味着什么
-
什么是积分
-
如何将微分扩展到向量参数函数
-
如何对向量值函数进行微分
-
在神经网络的反向传播算法中雅可比矩阵的作用
-
如何使用微分找到函数的最优点
-
支持向量机是一个受约束的优化问题,需要微分来解决
总结
你对迷你课程的进展如何?
你喜欢这个速成课程吗?
你有任何问题吗?有什么困难吗?
请告诉我。请在下方留言。