▤书架 ← 返回索引
▶目录

阶段 04 讲义

数学 11h:高数 · 求导法则(重点是链式法则)
编程 9h:函数(一)


第一部分 · 数学

1 四则运算求导

$$(u \pm v)' = u' \pm v'$$
$$(uv)' = u'v + uv' \quad \text{(乘法法则)}$$
$$\left(\frac{u}{v}\right)' = \frac{u'v - uv'}{v^2} \quad \text{(除法法则,}v\ne0\text{)}$$

乘法法则记忆:"前导后不导,加前不导后导"。

例子:求 $y = x^2 \sin x$ 的导数

$$y' = (x^2)'\sin x + x^2(\sin x)' = 2x\sin x + x^2\cos x$$

例子:求 $y = \frac{\ln x}{x}$

$$y' = \frac{(\ln x)'\cdot x - \ln x \cdot (x)'}{x^2} = \frac{\frac1x\cdot x - \ln x}{x^2} = \frac{1-\ln x}{x^2}$$

⚠️ 常见错误:$(uv)' \ne u'v'$!
举反例:$u=x$,$v=x$,则 $(uv)'=(x^2)'=2x$,但 $u'v'=1\cdot1=1$。不相等。

2 链式法则 ⭐⭐ 全书最重要的法则

$$\frac{dy}{dx} = \frac{dy}{du}\cdot\frac{du}{dx}$$

人话:复合函数求导 = 外层导数 × 内层导数。

2.1 为什么叫"链式"

如果 $y = f(g(h(x)))$,那就一层层往下乘:

$$\frac{dy}{dx} = f'(g(h(x))) \cdot g'(h(x)) \cdot h'(x)$$

像链条一样环环相扣。

2.2 核心技巧:把复合函数拆开

例 1:求 $y = \sin(3x)$

拆:令 $u = 3x$,则 $y = \sin u$

$$\frac{dy}{dx} = \cos u \cdot 3 = 3\cos(3x)$$

例 2:求 $y = e^{x^2}$

令 $u = x^2$,$y = e^u$

$$\frac{dy}{dx} = e^u \cdot 2x = 2xe^{x^2}$$

例 3:求 $y = \ln(\sin x)$

令 $u = \sin x$,$y = \ln u$

$$\frac{dy}{dx} = \frac{1}{u}\cdot \cos x = \frac{\cos x}{\sin x} = \cot x$$

例 4(三层):求 $y = \sin^2(3x)$

注意 $\sin^2(3x) = (\sin(3x))^2$

令 $v = \sin(3x)$,$y = v^2$

$$\frac{dy}{dx} = 2v \cdot \underbrace{\cos(3x)\cdot 3}_{\text{再对 }\sin(3x)\text{ 用一次链式}} = 6\sin(3x)\cos(3x)$$

💡 判断有没有"内层":看能不能写成 $f(\text{某个含 x 的式子})$。
- $\sin(3x)$:内层是 $3x$ ✓
- $\sin x \cdot 3$:没有内层,直接乘

2.3 这是最重要的法则,因为

反向传播 = 链式法则的系统化应用。
你编程笔记里的 loss.backward(),算的就是这个。
神经网络的梯度是一层一层用链式法则乘出来的。

3 反函数求导

$$[f^{-1}(x)]' = \frac{1}{f'(f^{-1}(x))}$$

例子:$y = \arcsin x$ 的导数

因为 $\sin y = x$,两边对 $x$ 求导(用链式法则):

$$\cos y \cdot y' = 1 \implies y' = \frac{1}{\cos y}$$

由 $\sin y = x$ 得 $\cos y = \sqrt{1-x^2}$(取正,因为 $y\in[-\frac\pi2,\frac\pi2]$)

$$(\arcsin x)' = \frac{1}{\sqrt{1-x^2}}$$

这条公式表里的结果,就是这样推出来的。

4 隐函数求导

适用于:$y$ 和 $x$ 混在一起,不容易解出 $y = f(x)$。

方法:两边同时对 $x$ 求导,把 $y$ 当作 $x$ 的函数(所以对 $y$ 求导时要乘 $y'$)。

例:求圆 $x^2 + y^2 = 25$ 在点 $(3,4)$ 处的切线斜率。

两边对 $x$ 求导:

$$2x + 2y\cdot y' = 0 \implies y' = -\frac{x}{y}$$

代入 $(3,4)$:$y' = -\frac{3}{4}$

⚠️ 关键理解:对 $y^2$ 求导用链式法则,得到 $2y \cdot y'$。
别漏掉那个 $y'$——这是最常见的错误。

5 高阶导数

$f''(x)$ = 导数的导数,记 $y''$,$\frac{d^2y}{dx^2}$。

物理意义:位置 → 速度(一阶)→ 加速度(二阶)。

例:$y = e^{-x^2}$,求 $y'$ 和 $y''$

$$y' = -2xe^{-x^2}$$

用乘法法则 + 链式法则:

$$y'' = -2e^{-x^2} + (-2x)\cdot(-2xe^{-x^2}) = (4x^2-2)e^{-x^2}$$

💡 这是高斯函数。
它的二阶导数会在统计和信号处理里反复出现。


6 🧠 这跟认知科学有什么关系:链式法则 = 反向传播

代码部分我实跑验证过;理论部分的表述请对照文献。

前面说过"反向传播 = 链式法则的系统化应用"。现在把它真的算一遍给你看。

6.1 一个最小的"神经网络"

设一个神经元:$z = wx+b$,$a=\sigma(z)$($\sigma$ 是 sigmoid),损失 $L=(a-t)^2$。

前向(算出预测和损失):

$$x \longrightarrow z=wx+b \longrightarrow a=\sigma(z) \longrightarrow L=(a-t)^2$$

反向(求 $\frac{\partial L}{\partial w}$),就是链式法则一层层往回乘:

$$\frac{\partial L}{\partial w} = \frac{\partial L}{\partial a}\cdot\frac{\partial a}{\partial z}\cdot\frac{\partial z}{\partial w}$$

其中每一项都是局部的、容易算的:

这一项 值 怎么来的
$\frac{\partial L}{\partial a}$ $2(a-t)$ 对 $(a-t)^2$ 求导
$\frac{\partial a}{\partial z}$ $a(1-a)$ sigmoid 的导数性质
$\frac{\partial z}{\partial w}$ $x$ $z=wx+b$ 对 $w$ 求导

6.2 跑起来看

import math

w, x, b, t = 0.8, 1.5, -0.3, 1.0
sig = lambda z: 1/(1+math.exp(-z))

# 前向
z = w*x + b
a = sig(z)
L = (a - t)**2
print(f"前向: z={z:.10f}  a={a:.10f}  L={L:.10f}")

# 反向:链式法则逐层相乘
dL_da = 2*(a - t)
da_dz = a*(1 - a)
dz_dw = x
dL_dw = dL_da * da_dz * dz_dw
print(f"dL/dw = {dL_dw:.10f}")

# 用数值梯度独立核对
h = 1e-7
loss = lambda w_, b_: (sig(w_*x + b_) - t)**2
num = (loss(w+h, b) - loss(w-h, b)) / (2*h)
print(f"数值梯度 = {num:.10f}   差 = {abs(num - dL_dw):.2e}")

输出(我实跑的结果):

前向: z=0.9000000000  a=0.7109495026  L=0.0835501900
dL/dw = -0.1781998981
数值梯度 = -0.1781998980   差 = 1.81e-10

💡 两条路算出同一个数(差 $10^{-10}$,只是浮点误差):
- 一条是手动链式法则逐层相乘
- 一条是数值差分(阶段 3 学过)

这验证了:反向传播不是新数学,就是链式法则。
你以后写的 loss.backward(),内部干的就是上面那三行乘法,
只不过 PyTorch 把它自动化了:每个运算都记住自己的局部导数,
反向时自动串起来乘。

6.3 为什么这件事对认知科学重要

梯度下降 = 学习。有了 $\frac{\partial L}{\partial w}$,就能更新权重:

$$w \leftarrow w - \eta\frac{\partial L}{\partial w}$$

这条式子读作"朝着让损失变小的方向,挪一小步"。$\eta$ 是步长(学习率)。

这就是"学习"在计算层面的定义之一:调整参数以减小误差。
认知科学里用这类模型解释知觉学习、类别学习、强化学习,
大脑被建模成在做某种梯度下降。这个类比的边界在哪,是当前研究的活跃问题。

⚠️ 诚实说明:
· 数学和代码部分我实跑验证过,链式法则与数值梯度一致。
· "大脑在做梯度下降"是一个有影响力的建模假设,不是已证实的结论。
反例和争论很多(比如大脑能否做精确的反向传播)。请以文献为准。


7 数学作业 S4

第 10 题参考代码:

import sympy as sp

x = sp.Symbol('x')

# 定义函数
y = sp.sin(3 * x)                # 改成你想验证的表达式
dy = sp.diff(y, x)               # 求导
print("导数:", dy)

# 验证几个
print()
print("sin(3x) 的导数:", sp.diff(sp.sin(3*x), x))
print("ln(x²+1) 的导数:", sp.diff(sp.log(x**2+1), x))
print("e^(-x²) 的导数:", sp.diff(sp.exp(-x**2), x))
print("e^(-x²) 的二阶导:", sp.simplify(sp.diff(sp.exp(-x**2), x, 2)))

💡 用法:手算完,用 sp.diff 对答案。
这就是"用 Python 验证每个定理"的具体做法。它替代不了手算,只用来检查。

sympy 是什么(第一次见,简单说):
sympy 是"符号计算"库。它不算数值,只推公式。
- sp.Symbol('x'):声明"x 是一个符号"(不是具体的数)
- sp.diff(表达式, x):对 x 求导,返回公式而不是数字
- sp.simplify(...):化简

和 numpy 的区别:numpy 算 $\sin(1.5)$ 的数值;sympy 算 $\frac{d}{dx}\sin x$ 的公式。
一个用来算数,一个用来对答案。两个库都已装好,直接用 import sympy as sp 即可。


第二部分 · 编程

8 为什么需要函数

没有函数时:

# 计算三个数的平均值
a, b, c = 1, 2, 3
avg1 = (a + b + c) / 3
print(avg1)

d, e, f = 4, 5, 6
avg2 = (d + e + f) / 3          # 同样的逻辑又写一遍
print(avg2)

有函数后:

def mean(a, b, c):
    return (a + b + c) / 3

print(mean(1, 2, 3))
print(mean(4, 5, 6))

💡 核心价值:写一次,用多次。改的时候只改一处。

9 定义和调用

def greet(name):            # ← 定义
    return f"你好,{name}"

message = greet("张三")      # ← 调用
print(message)              # 你好,张三

结构分解:

部分 说明
def 关键字,表示"定义函数"
greet 函数名
(name) 参数列表
: 冒号
缩进块 函数体
return 返回值

调用时:用函数名 + 括号 + 参数。

10 return 和 print 的区别 ⭐ 极其重要

这是初学者最大的概念混淆。

def add_print(a, b):
    print(a + b)            # 打印,但没有返回值

def add_return(a, b):
    return a + b            # 返回一个值

# 区别在这里:
x = add_print(1, 2)         # 屏幕上显示 3
print(x)                    # None  ← 没有返回值!

y = add_return(1, 2)        # 屏幕上什么都不显示
print(y)                    # 3     ← 拿到了值

关键区别:

print return
作用 显示给人看 把值交回去
能继续用吗 ❌ 不能 ✅ 能
结果 None 那个值

为什么 return 重要:

def area(w, h):
    return w * h

# 可以继续参与计算
total = area(3, 4) + area(5, 6)
print(total)

如果 area 用 print 而不是 return,上面这行就没法写。

⚠️ 规则:函数应该 return 结果,把"是否显示"留给调用者决定。

这是"能写科研代码"和"只会写脚本"的分界之一。

11 参数与返回值

多个参数:

def rectangle(w, h):
    area = w * h
    perimeter = 2 * (w + h)
    return area, perimeter       # 返回多个值

a, p = rectangle(3, 4)           # 解包接收
print(f"面积{a},周长{p}")

return area, perimeter 实际上返回的是一个元组 (area, perimeter)。
这和你阶段 3 学的 a, b = b, a+b 是同一个机制。

没有返回值:

def show(name):
    print(f"姓名:{name}")
    # 没写 return,实际返回 None

提前返回(常用于"先检查"):

def safe_divide(a, b):
    if b == 0:
        return None          # 提前返回
    return a / b

12 文档字符串

def mean(values):
    """计算一组数的平均值。

    参数:
        values: 数字列表
    返回:
        平均值(浮点数)
    """
    return sum(values) / len(values)

help(mean)          # 会显示上面的文档
print(mean.__doc__)

💡 好习惯:每个函数都写一行说明。
三个月后你会感谢自己。

13 函数的作用域

x = 10          # 全局变量

def f():
    print(x)    # 能读到外层的 x → 10

def g():
    x = 20      # 这是新建的【局部变量】,不影响外层
    print(x)    # 20

f()             # 10
g()             # 20
print(x)        # 10  ← 没变

规则:函数内部赋值会创建局部变量,除非用 global 声明。

def h():
    global x
    x = 30

⚠️ 经验:尽量别用 global。它让代码难以理解。
正确做法是参数传入、返回值传出:

def h(x):
    return x + 20

14 🔧 技能卡 4:函数出错了怎么查 + assert

技能卡 1 教你读报错,2 教你查文档,3 教你查循环。
这张卡解决函数特有的那一类 bug。

14.1 头号 bug:函数默默返回 None

这是初学者最常见、最难发现的错误,因为它不报错。

def add(a, b):
    a + b               # ❌ 算了但没 return

print(add(1, 2))        # None

三种"返回 None"的成因:

# ① 忘了写 return
def f1(x):
    x * 2               # 结果算出来了,但扔掉了

# ② return 写在了 if 里面,某条路径没覆盖
def f2(x):
    if x > 0:
        return "正"
    # x <= 0 时没有 return → 返回 None

# ③ print 当成 return 用了
def f3(x):
    print(x * 2)        # 打印了,但没返回 → None

💡 自查口诀:每个分支的末尾都要有 return。
写完一个函数,先问自己:"所有可能的路径,都 return 了吗?"

14.2 assert:把"我以为"变成代码检查

你以为传进来的 b 不是 0,那就让程序自己检查:

def safe_divide(a, b):
    assert b != 0, f"除数不能为 0(收到 b={b})"
    return a / b

print(safe_divide(6, 2))    # 3.0
print(safe_divide(6, 0))    # AssertionError: 除数不能为 0(收到 b=0)

用法:assert 条件, "出错时显示的话"

💡 为什么要用它:程序出错时,越早发现越好。
一个错的数传进函数,如果不检查,可能算到最后才崩,
那时候你根本不知道是哪一步引入的错误。
assert 让它在入口处就报警。

⚠️ 一个重要的区别:
assert 是给你自己查 bug 用的,不要用它来处理用户的错误输入。
用户输入错了应该用 if ... : return None 这种正常逻辑。

14.3 函数调试三件套

症状 先查什么
返回 None 每条分支都有 return 吗?是不是写成了 print?
返回值不对 在 return 前一行 print 出来看
参数不对 在函数第一行 print 进来的是什么(类型 + 值)
改了外面变量没生效 那是局部变量,见上面"作用域",要 return 出去

15 🧠 这跟认知科学有什么关系(编程侧)

概念性内容,无法用代码验证,请对照文献。

return vs print ↔ 认知科学里的"表征" vs "输出"。

print 是给别人看的,return 是交给下一环节用的。

认知科学里有个类似的重要区分:内部表征和外显反应。
一个认知过程内部"算出了"某个结果(表征),和它是否说出来 / 按键反应(输出),
是两件不同的事,这是反应时实验的核心假设之一:我们只能测到输出,然后反推表征。

print 就像"外显反应":看得见,但没法被下一步计算用。
return 才是把结果交回系统内部继续加工。

⚠️ 这个类比是启发式的,帮你记住 return 的本质是"内部传递"。
它不是认知科学的严格陈述。


16 编程作业 S4

第 7 题的意图:

# ❌ 这样写,函数没法复用
def is_prime_bad(n):
    print(n > 1 and all(n % d for d in range(2, n)))

# ✅ 这样写,可以继续参与逻辑
def is_prime(n):
    if n < 2:
        return False
    for d in range(2, int(n ** 0.5) + 1):
        if n % d == 0:
            return False
    return True

# 因为返回了值,所以能这样用:
primes = [n for n in range(2, 50) if is_prime(n)]
print(primes)

第 2 题提示(阶乘):

def factorial(n):
    result = 1
    for i in range(1, n + 1):
        result *= i
    return result

第 3 题提示:

def count_vowels(s):
    count = 0
    for ch in s.lower():         # .lower() 统一小写,免得分大小写
        if ch in "aeiou":
            count += 1
    return count

💡 注意 ch in "aeiou",这是阶段 2 学的 in 运算符用在字符串上。

第 5 题框架:

def find_max(numbers):
    if not numbers:              # 空列表的处理(边界情况!)
        return None
    best = numbers[0]
    for n in numbers:
        if n > best:
            best = n
    return best

⚠️ 注意空列表的处理——这是"边界情况",不写的话空列表会崩。
好程序员会想边界情况。


本阶段自评

  1. 数学:链式法则的公式是什么?$e^{x^2}$ 的导数怎么算?
  2. 数学:为什么 $(uv)' \ne u'v'$?举个数反例。
  3. 数学:对 $y^2$ 求导(隐函数)得到什么?为什么会有 $y'$?
  4. 数学:反向传播里 $\frac{\partial L}{\partial w}$ 是哪三项相乘?为什么这就叫"链式"?
  5. 编程:return 和 print 的核心区别是什么?为什么 return 更好?
  6. 编程:函数里的 x = 20 会影响外层的 x 吗?为什么?
  7. 技能:一个函数返回了 None,三种可能原因是什么?
  8. 技能:assert 和 if + return 分别该在什么场合用?

全部答得上 → 进阶段 5。


17 📌 阶段 1–4 完成后的检查

如果这四个阶段都走完了,你现在应该:

能力 状态
数学 理解极限的严格定义(ε-N / ε-δ);会求导;掌握链式法则
编程 Python 语法基础全通(类型/条件/循环/函数)|会写返回值正确的函数
工具 能用 Jupyter;会用 sympy 验算;会画图

接下来阶段 5–8 会进入:中值定理、洛必达、函数分析 → 列表与字典 → 行列式与矩阵。

最后更新:2026-09-26