阶段 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
- [ ] 1. 求导:$y = x^3\cos x$
- [ ] 2. 求导:$y = \frac{e^x}{x}$
- [ ] 3. 求导(链式):$y = \cos(5x)$
- [ ] 4. 求导(链式):$y = \ln(x^2+1)$
- [ ] 5. 求导(链式):$y = e^{\sin x}$
- [ ] 6. 求导(三层):$y = \sqrt{1+x^2}$(提示:写成 $(1+x^2)^{1/2}$)
- [ ] 7. 求导(三层):$y = \ln(\cos(2x))$
- [ ] 8. 隐函数:求 $x^2 + xy + y^2 = 3$ 的 $y'$
- [ ] 9. 求 $y = e^{-x^2}$ 的一阶和二阶导数
- [ ] 10.(Python)用
sympy求导,和手算结果对比
第 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 条件, "出错时显示的话"
- 条件为真 → 什么都不发生,继续跑
- 条件为假 → 立刻报
AssertionError,带上你写的话
💡 为什么要用它:程序出错时,越早发现越好。
一个错的数传进函数,如果不检查,可能算到最后才崩,
那时候你根本不知道是哪一步引入的错误。
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
- [ ] 1. 写
is_prime(n)函数,返回布尔值,判断 n 是否为素数 - [ ] 2. 写
factorial(n)函数,返回 n 的阶乘 - [ ] 3. 写
count_vowels(s)统计字符串里的元音(aeiou)个数 - [ ] 4. 写
is_palindrome(s)判断字符串是否回文(正读反读一样) - [ ] 5. 写
find_max(numbers)返回列表最大值(不许用内置max()) - [ ] 6. 写
celsius_to_fahrenheit(c)摄氏度转华氏度,公式 $F = C \times \frac95 + 32$ - [ ] 7.(重点)把上面所有函数改成
return而不是print,然后在main里调用并打印 - [ ] 8.(技能)给上面每个函数都加一条
assert,检查参数是否合理例如
is_prime(n)里加assert n >= 0, "n 应该非负";
find_max(numbers)里加assert isinstance(numbers, list), "要传列表"
(isinstance用来判断类型,忘了就help(isinstance))
第 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
⚠️ 注意空列表的处理——这是"边界情况",不写的话空列表会崩。
好程序员会想边界情况。
本阶段自评
- 数学:链式法则的公式是什么?$e^{x^2}$ 的导数怎么算?
- 数学:为什么 $(uv)' \ne u'v'$?举个数反例。
- 数学:对 $y^2$ 求导(隐函数)得到什么?为什么会有 $y'$?
- 数学:反向传播里 $\frac{\partial L}{\partial w}$ 是哪三项相乘?为什么这就叫"链式"?
- 编程:
return和print的核心区别是什么?为什么return更好? - 编程:函数里的
x = 20会影响外层的x吗?为什么? - 技能:一个函数返回了
None,三种可能原因是什么? - 技能:
assert和if + return分别该在什么场合用?
全部答得上 → 进阶段 5。
17 📌 阶段 1–4 完成后的检查
如果这四个阶段都走完了,你现在应该:
| 能力 | 状态 |
|---|---|
| 数学 | 理解极限的严格定义(ε-N / ε-δ);会求导;掌握链式法则 |
| 编程 | Python 语法基础全通(类型/条件/循环/函数)|会写返回值正确的函数 |
| 工具 | 能用 Jupyter;会用 sympy 验算;会画图 |
接下来阶段 5–8 会进入:中值定理、洛必达、函数分析 → 列表与字典 → 行列式与矩阵。