一元函数微分学与机器学习:从导数到梯度下降实战
参加过开源社区组队学习的人应该都有体会一帮人按任务打卡、互相答疑、一起啃同一个主题比自己闷头看书容易坚持得多。我最近刚跟完“机器学习数学基础”系列里的一元函数微分学这个Task 03趁着热乎劲儿把这一路的思考、推导和踩坑记录整理出来希望能帮到正在补数学基础的朋友。这篇文章不打算写成一章教材而是把一个一个知识点拉回“机器学习到底哪里用得上”这条主线上去讲适合数学底子薄、但想认真搞懂模型原理的初学者参考。1. 先摆正坐标一元函数微分学在机器学习里到底解决什么问题1.1 机器学习为什么会用到导数很多人学机器学习一上来就看模型代码结果看到“梯度下降”“反向传播”这些词就发懵。其实剥开外壳大部分机器学习工作的核心就一件事调整参数让损失变小。而“怎么调整才合理”这个问题落到数学上恰好就是微分学要回答的。拿最简单的线性回归举例。假设我们想拟合数据模型是 y wx b损失函数用均方误差 L(w, b) (1/n) Σ (wx_i b - y_i)²。训练的目标是找到一组 (w, b)让 L 尽可能小。这个函数是一元还是二元它其实是关于 w 和 b 的二元函数但如果固定其中一个变量单独看 w 或单独看 b就回到了“一元函数求极值”的问题。一元函数微分学的价值就在这里它能告诉我们函数在某一点的变化趋势。导数大于0说明函数在增加导数小于0说明函数在减小导数等于0的地方就是可能的极值点。机器学习里的“学习”说白了就是把导数当成指南针反复朝着让损失函数下降的方向移动参数。1.2 从“刻画变化”到“指引方向”我特别喜欢用“下山”来比喻梯度下降。想象你站在山坡上周围一片漆黑要想走到山谷最低处最朴素的办法就是踩一踩脚下的地面感觉哪个方向是往下走的就往那边迈一步。这里“脚下的坡度”就是导数或者说是一元情境下损失函数的斜率。在多元情况下你会听到“梯度”这个词它本质上是各个方向偏导数组成的向量。但很多人忽略了一点多元梯度的许多直觉都来自一元导数。你如果连 f(w) (w - 1)² 在 w 3 处导数为正、函数在增大都算不清楚那后面理解多维参数的更新公式只会更难。所以我在学习这个Task时一直提醒自己别看一元函数“简单”它是整个优化理论的地基。地基不牢后面无论看支持向量机的对偶问题还是神经网络的链式求导都会觉得像看天书。2. 导数的定义别糊弄过去它决定了你会不会在细节上踩坑2.1 极限定义拆开看教科书上导数的定义长这样f(x₀) lim(Δx→0) [f(x₀ Δx) - f(x₀)] / Δx很多初学者看到这个式子第一反应是这不就是把 Δx 取得特别小算两点间的斜率吗对物理直觉就是这么回事。但为什么必须用极限而不能直接取 Δx 0因为直接把 Δx 代零分母就是0计算没有意义。极限告诉我们的是“趋势”而不是“代入”。我在组队学习群里看到有同学问既然极限那么抽象我写代码算导数的时候是不是只要把 Δx 设成一个很小的数比如 1e-8就算是导数了这正好引出一个实操中的经典问题——数值微分。用程序实现导数近似通常写成def numerical_derivative(f, x, eps1e-6): return (f(x eps) - f(x - eps)) / (2 * eps)注意这里用的是中心差分 (f(xε) - f(x-ε)) / (2ε)而不是单侧差分 (f(xε) - f(x)) / ε。理由很简单中心差分的误差是 O(ε²)单侧差分只有 O(ε)。同样取 ε 1e-6中心差分结果会精确得多。但 ε 也不是越小越好。如果 ε 取到 1e-16已经接近浮点数精度极限分子在做减法时会产生灾难性抵消算出来的导数反而全是噪声。我在实际计算中一般取 ε 1e-6 或 1e-7这是精度和数值稳定性之间一个比较务实的平衡点。2.2 几何意义与可导性导数的几何意义是曲线在某点处切线的斜率。这句话很重要但要结合图像去理解函数曲线在某个点附近如果变得越来越陡说明导数的绝对值在变大如果曲线趋于平滑导数绝对值就在变小。当曲线出现尖点的时候左导数和右导数不相等该点就不可导。最经典的例子是 f(x) |x|也就是绝对值函数在 x 0 处左边切线斜率是 -1右边是 1左右导数对不上所以不可导。这个例子和机器学习有什么关系关系很大。ReLU 激活函数在原点处就存在同样的问题它的导数在 x 0 时为 1在 x 0 时为 0精确地说在 x 0 处没有定义。工程实践中大家通常约定 ReLU(0) 0 或 1甚至直接不关注这一点反正大部分框架的实现都默认了某个取值。还有一条重要结论可导一定连续连续不一定可导。也就是说可导是比连续更强的一个条件。判断一个函数能不能放心求导先看它是否连续再看它的左右导数是否一致。3. 求导法则吃透机器学习公式才不会推错3.1 四则运算与线性组合求导运算规则本身不复杂加法法则就是“和的导数等于导数之和”(f(x) g(x)) f(x) g(x)常数倍法则(c · f(x)) c · f(x)但掌握这两条规则在机器学习里已经能处理一大类问题了。比如前面说的均方误差损失假设只有一个样本L (wx b - y)²关于 w 求导时先把 wx b - y 看成一个整体 u外层是 u² 这里面其实同时用到了复合函数求导和线性组合求导一步步拆开dL/dw 2(wx b - y) · x这个式子里的 2 来自外层平方求导x 来自内层对 w 的导数。成型之后你会发现这个结果其实特别直观梯度的方向由误差 (wx b - y) 乘以特征 x 决定。误差越大对 w 的调整步子就应该越大。乘法和除法法则也应该熟悉虽然它们在基础机器学习公式里出现频率没有加法高但在推导某些概率模型的似然函数时经常会用到(f · g) fg fg(f / g) (fg - fg) / g²3.2 链式法则反向传播的数学底座链式法则是整个Task里含金量最高的部分也是最需要花时间“想明白”的地方。它的公式很简单(f(g(x))) f(g(x)) · g(x)但这个简单的式子扩展出去就是神经网络反向传播的全部数学原理。神经网络为什么叫“深度”因为它是一层套一层的复合函数。前一层的输出是后一层的输入数据从输入到输出就像俄罗斯套娃一样。我们看一个三层复合的例子y f(g(h(x)))按链式法则dy/dx f(g(h(x))) · g(h(x)) · h(x)这个结构如果画成计算图从最外层一路乘到最内层。反向传播其实就是链式法则的反向应用先算输出层对损失的影响再一层一层往输入方向回传梯度。我在做这个Task时曾经花了一整个晚上拿一个两层网络手工推梯度。网络结构是z w₂ · σ(w₁ · x b₁) b₂其中 σ 是 sigmoid 激活函数。目标是推导损失 L (z - y)² 对 w₁ 的偏导。拆开就是∂L/∂w₁ ∂L/∂z · ∂z/∂a · ∂a/∂z₁ · ∂z₁/∂w₁这里 a σ(z₁)z₁ w₁x b₁。一步一步算下来你会发现链式法则就像流水线一样把误差信号从末端传回前端。只要一个环节的导数算错整个梯度就偏了。3.3 一个实操案例sigmoid 函数的导数为什么那么漂亮sigmoid 函数定义为σ(x) 1 / (1 e⁻ˣ)它在机器学习里非常常见尤其在老式神经网络和逻辑回归中。这个函数的导数特别有名结果是σ(x) σ(x) · (1 - σ(x))为什么实操推一遍最有说服力。先把 σ(x) 看成 (1 e⁻ˣ)⁻¹用链式法则σ(x) -(1 e⁻ˣ)⁻² · (-e⁻ˣ) e⁻ˣ / (1 e⁻ˣ)²然后做个变形σ(x)(1 - σ(x)) (1/(1e⁻ˣ)) · (1 - 1/(1e⁻ˣ)) (1/(1e⁻ˣ)) · (e⁻ˣ/(1e⁻ˣ)) e⁻ˣ/(1e⁻ˣ)²两边结果完全一样。这个推导过程如果自己动手写一遍会对“复合函数代数恒等变形”有非常具体的感受。更重要的是σ(x) σ(x)(1 - σ(x)) 这个形式在反向传播里极其好用只要知道 sigmoid 的输出值就能直接算出它的梯度连 x 都不用再往前找。4. 常见函数求导速查与记忆技巧4.1 一张表解决基本功组队学习的好处在于群里的讨论会把琐碎的知识点体系化。我把Task 03里出现频率最高的求导公式整理成了表格随时翻出来看函数导数备注C常数0不随变量变化xⁿn·xⁿ⁻¹幂函数eˣeˣ自己等于自己指数函数里最特殊aˣaˣ·ln(a)通则ln(x)1/xx 0logₐ(x)1/(x·ln(a))以 a 为底的对数sin(x)cos(x)三角函数cos(x)-sin(x)注意负号tan(x)sec²(x)较少直接用这些公式不能死记关键是理解它们之间的联系。比如 aˣ e^{x·ln(a)}所以 (aˣ) e^{x·ln(a)} · ln(a) aˣ · ln(a)。只要记住了 eˣ 的导数其他指数函数的导数都能推出来。4.2 机器学习里高频出现的几个特殊函数除了基本初等函数机器学习里还有几个高频函数它们的导数形态值得单独拉出来说说。第一个是tanh 函数tanh(x) (eˣ - e⁻ˣ) / (eˣ e⁻ˣ)。它的导数是 1 - tanh²(x)。可以看出这个导数的计算同样只需要输出值不需要原始输入所以在中间层做激活函数时效率很高。tanh 输出范围在 (-1, 1)梯度范围也在 [-1, 1]和 sigmoid 相比中心对称性更好实际使用中收敛往往也更平滑。第二个是ReLU 函数ReLU(x) max(0, x)。它的导数是一个分段函数x 0 时导数为 1x 0 时导数为 0x 0 时导数没有统一定义实践中一般取 0ReLU 的导数不连续但它在深度网络里反而比 sigmoid 更常用主要原因是在正区间梯度恒为 1不像 sigmoid 在两端饱和梯度趋近于 0不容易发生梯度消失。第三个是softmax 函数。它主要用于多分类输出的概率化严格来说它的求导涉及雅可比矩阵比一元函数复杂不少。我一开始看到 softmax 求导就头大后来才意识到如果能把一元函数的链式法则啃透再看 softmax 的导数也能顺着“分情况处理 i 和 j 是否相同”的思路推出来。Task 03 不要求细究 softmax但它会作为后面章节的引子所以把求导基本功打牢很重要。5. 梯度下降手推一遍从导数到参数更新5.1 完整的推导流程理论知识扯再多都不如手推一遍来得实在。我挑一个最简单的例子最小化函数f(w) (w - 1)² 2这个函数在 w 1 时取到最小值 2。我们来看梯度下降怎么一步步“找到”这个点。第一步求导f(w) 2(w - 1)第二步假设初始点 w₀ 3学习率 η 0.1。参数更新公式是w_new w_old - η · f(w_old)代入算几次第1轮w₀ 3f(3) 2(3-1) 4w₁ 3 - 0.1 × 4 2.6第2轮w₁ 2.6f(2.6) 2(2.6-1) 3.2w₂ 2.6 - 0.1 × 3.2 2.28第3轮w₂ 2.28f(2.28) 2.56w₃ 2.28 - 0.1 × 2.56 2.024第4轮w₃ 2.024f(2.024) 2.048w₄ 2.024 - 0.1 × 2.048 1.8192可以清楚看到参数在逐渐往 w 1 靠近。每走一步的幅度也在变小因为越接近极值点导数的绝对值越小“坡度”越平缓。这里有一个重要的工程心得真正在跑的机器学习模型损失函数比这个复杂得多但参数更新的核心机制一模一样。看懂这个循环梯度下降就不再是黑盒了。5.2 学习率怎么定大则震荡小则磨蹭同样是上面的例子如果把学习率改到 η 1.1会发生什么第1轮w₁ 3 - 1.1 × 4 -1.4第2轮f(-1.4) 2(-1.4-1) -4.8w₂ -1.4 - 1.1 × (-4.8) 3.88w 不仅没靠近 1反而在 3 和 -1 之间来回蹦这就是“发散”。如果把学习率改成 η 0.001第1轮w₁ 3 - 0.001 × 4 2.996第10轮也只是到了 2.93 左右理论上最终还是会收敛到 1但这个速度在真实训练里是不能接受的。所以我一直觉得学习率是梯度下降里面最需要“手感”的超参数而手感从哪来就是多做类似上面这样的手工计算亲自感受一下同样一个函数在不同学习率下的行为差异。5.3 用数值微分验证解析梯度在组队学习任务里有个练习要求自己算某个复合函数的解析梯度再和数值微分的结果对比。这个做法非常值得推荐因为它能最快暴露你推导中的错误。比如要验证 f(w) (1 e⁻ʷ)⁻¹ 这个 sigmoid 在 w 2 处的导数。解析导数是f(2) σ(2)(1 - σ(2))你可以顺手算一下1 e⁻² ≈ 1.1353σ(2) ≈ 0.88081 - σ(2) ≈ 0.1192所以 f(2) ≈ 0.1050再用数值微分验证import numpy as np def sigmoid(x): return 1 / (1 np.exp(-x)) def numerical_derivative(f, x, eps1e-6): return (f(x eps) - f(x - eps)) / (2 * eps) print(numerical_derivative(sigmoid, 2.0)) # 输出约 0.10499两个结果几乎完全一致这就说明解析推导没推错。以后不管接触多复杂的模型都可以用这个“双通道检查法”给自己兜底。6. 我在这个Task 03踩过的坑与避坑指南6.1 别把 d、∂、Δ 混着用组队学习群里有个很普遍的乱象微分符号到处乱写。严格来说d 表示一元函数的微分∂ 表示多元函数对某个变量的偏导Δ 表示变化量。虽然很多时候大家口头混着说但自己写推导时一定要分清。尤其是在从“一元函数微分学”迈向“多元函数微分学”的节点上符号的准确性不是吹毛求疵而是直接关系到你对“对谁求导”这个问题的理解。比如损失函数 L(w, b) 关于 w 的偏导写作 ∂L/∂w你如果写成 dL/dw变量就不清楚了。6.2 导数常见错误速查表错误类型错误示例正确做法复合函数漏链对 (x²1)³ 求导写成 3(x²1)²再乘内层导数 2x结果是 6x(x²1)²搞反幂函数和指数函数把 eˣ 的导数当成 x·eˣ⁻¹eˣ 导数还是 eˣ只有 xⁿ 用幂函数规则忽略分段点对 ReLU 在 x0 处直接求导分段讨论工程上取约定值认为导数为0就是极小值f(w) w³ 在 w0 处导数为0但不是极值需要看二阶导数或两边符号变化数值微分 ε 取太小ε1e-16结果全是噪声取 1e-6 或 1e-76.3 一个更隐蔽的坑极值点与拐点混淆导数为0的点叫驻点驻点可能是极大值可能是极小值也可能什么都不是。f(w) w³ 在 w 0 处导数为0但它既不是极大也不是极小而是一个拐点。怎么判断看二阶导数的符号大于0是极小值小于0是极大值等于0要再讨论。这个知识点在机器学习里的意义在于梯度下降只能保证找到局部极值点而且如果初始点选在某个特殊位置比如高维空间的鞍点附近训练就可能卡住。虽然现在有很多优化器如Adam通过动量机制缓解了这类问题但理解“导数为0 ≠ 极值”依然是优化理论的基石。6.4 学习建议先手推再编码跟完这个Task之后我最大的感触是数学基础类的学习一定不能只看不动手。单纯看视频、看文档当时觉得都懂了合上电脑两天后再看同一个公式又好像不认识。所以我给刚开始接触机器学习数学基础的人一个实操建议每学完一个求导法则立刻给自己出三道题。第一道是纯计算题比如对某个复合函数求导第二道是应用场景题比如写一段小程序用数值微分验证手推结果第三道是推导题比如自己把 sigmoid 的导数从头推一遍。三道题都过这个知识点才算真正粘住了。组队学习这种形式最大的优势是有一群人陪你一起做题、互相检查推导过程。哪怕只是把自己卡住的地方发到群里听别人怎么绕过去的也比一个人死磕有效率得多。希望这篇笔记也能帮你减少一点摸索过程中的挫败感。