▤书架 ← 返回索引
▶目录

阶段 06 讲义

数学 11h:高数 · 导数的应用(一):单调性与极值、优化
编程 9h:作用域 · 参数类型


第一部分 · 数学

1 用导数判断单调性

导数是"变化率"。所以它的符号直接告诉我们函数在上升还是下降。

单调性判据:在区间 $I$ 上
- 若 $f'(x) > 0$ 恒成立 → $f$ 在 $I$ 上单调递增
- 若 $f'(x) < 0$ 恒成立 → $f$ 在 $I$ 上单调递减

直觉:导数就是切线斜率。斜率正的曲线在往上走,斜率负的往下走。

1.1 为什么成立

由拉格朗日中值定理(阶段 5)。任取 $x_1<x_2$:

$$f(x_2)-f(x_1) = f'(\xi)(x_2-x_1), \quad \xi\in(x_1,x_2)$$

$f'(\xi)>0$ 且 $x_2-x_1>0$ ⟹ $f(x_2)-f(x_1)>0$,即 $f(x_2)>f(x_1)$,递增。

💡 中值定理是"用导数推断函数性质"的总开关。
单调性、极值、凹凸性全都建在它上面。这就是为什么阶段 5 要花那么大力气。

1.2 完整例子

求 $f(x)=x^3-3x$ 的单调区间。

第一步:求导、找驻点

$$f'(x) = 3x^2-3 = 3(x^2-1) = 3(x-1)(x+1)$$

令 $f'=0$:$x=-1$ 或 $x=1$。

第二步:用驻点把数轴分段,判断每段符号

区间 取测试点 $f'$ 的符号 单调性
$(-\infty,-1)$ $x=-2$ $3\cdot3=+9>0$ 递增 ↗
$(-1,1)$ $x=0$ $3\cdot(-1)=-3<0$ 递减 ↘
$(1,+\infty)$ $x=2$ $3\cdot3=+9>0$ 递增 ↗

答:在 $(-\infty,-1)$ 和 $(1,+\infty)$ 递增,在 $(-1,1)$ 递减。

⚠️ 注意写法的陷阱:单调区间不能写成并集 $(-\infty,-1)\cup(1,+\infty)$。
因为在这两个区间之间函数是下降的,说"整体递增"是错的。
必须分开写。

2 极值 ⭐

极小值 / 极大值:在某点附近的最小/最大值。(注意:是"局部"的,不是"全局"的。)

2.1 必要条件:费马引理

若 $x_0$ 是可导的极值点,则 $f'(x_0)=0$。

直觉:如果在山顶,那么左右两边都比你低,切线不可能倾斜,只能是水平的。

⚠️ 反过来不成立!$f'(x_0)=0$ 推不出它是极值点。
反例:$f(x)=x^3$,$f'(0)=3\cdot0=0$,但 $x=0$ 处既不是极大也不是极小
($x^3$ 一直在递增,只是在 0 处"平了一下")。

所以 $f'=0$ 只是"嫌疑点",不是"定罪"。这类点叫驻点。

2.2 第一充分条件:看 $f'$ 的符号变化

最通用的方法,就是上面那张表:

$f'$ 在 $x_0$ 两侧 结论
左正右负($+\to-$) 极大值
左负右正($-\to+$) 极小值
两侧同号 不是极值

2.3 第二充分条件:看 $f''$ 的符号

如果 $f'(x_0)=0$ 且 $f''$ 存在:

$f''(x_0)$ 结论
$<0$ 极大值(曲线是"拱起来"的)
$>0$ 极小值(曲线是"凹下去"的)
$=0$ 失效,回到第一充分条件

直觉:$f''<0$ 意味着 $f'$ 在递减,导数从正变负,所以是极大。

2.4 完整例子

求 $f(x)=x^3-3x$ 的极值。

第一步:求驻点

$$f'=3x^2-3=0 \implies x=\pm1$$
$f''=6x$

第二步:用第二充分条件判断

驻点 $f''$ 结论 极值
$x=-1$ $6\cdot(-1)=-6<0$ 极大值 $f(-1)=-1+3=2$
$x=1$ $6\cdot1=6>0$ 极小值 $f(1)=1-3=-2$

答:极大值 $f(-1)=2$,极小值 $f(1)=-2$。

💡 术语上的讲究:
- 极值说的是 $x=-1$(点),还是 $2$(值)?→ 都行,但极值点指 $x$,极值指函数值。写清楚。
- 极值(局部)和最值(全局)是两回事,看下一节。

3 最值:闭区间上怎么找

极值是"局部"的。实际问题问的通常是在整个区间上的最大/最小。

闭区间 $[a,b]$ 上连续函数的最值求法:
把驻点和两个端点都算一遍,谁大就是最大,谁小就是最小。

为什么端点不能漏:函数可能在端点取到最值(比如单调函数)。

3.1 完整例子

求 $f(x)=x^3-3x$ 在 $[-2,3]$ 上的最大值和最小值。

把所有候选点算一遍:

候选 类型 $f$ 的值
$x=-1$ 驻点(极大) $2$
$x=1$ 驻点(极小) $-2$
$x=-2$ 左端点 $-8+6=-2$
$x=3$ 右端点 $27-9=\mathbf{18}$

答:最大值 $18$(在 $x=3$ 取到),最小值 $-2$(在 $x=1$ 和 $x=-2$ 都取到)。

⚠️ 两个容易错的地方:
1. 漏掉端点 → 会得出"最大值是 2",错。
2. 最小值在两端同时取到 → 最小值可以有多个点,这不是 bug。
("最大值 18 只在 $x=3$" 才是唯一的。)

4 优化问题:四步法

这是导数在应用里最重要的用途,也是认知科学/工程里天天要用的东西。

优化四步法:
1. 设变量:把要求的东西(面积、体积、成本…)写成一个变量的函数
2. 定范围:确定自变量的实际取值范围(长度不能为负等)
3. 求极值:求导、找驻点、用第二充分条件判断
4. 回到问题:算出答案,检查它是否在实际范围内,回答原问题

⚠️ 第 1 步是难点:如果有两个变量,必须用约束条件消掉一个,
变成"一个变量的函数",才能求导。

4.1 例 1:固定周长求最大面积

用 20 米栅栏围一个矩形,怎么围面积最大?

第一步:设变量

设长 $x$,宽 $y$。目标:面积 $A = xy$。
(有两个变量,需要消掉一个)

第二步:用约束消元

周长约束:$2(x+y)=20 \implies y = 10-x$

$$A(x) = x(10-x) = 10x - x^2, \quad x\in(0,10)$$

第三步:求极值

$$A'(x) = 10-2x = 0 \implies x=5$$
$$A''(x) = -2 < 0 \implies \text{极大值}$$

第四步:回答

$A(5) = 25$。此时 $y=10-5=5$,是正方形。

数值验证:

$x$ 1 3 5 7 9
$A$ 9 21 25 21 9

在 $x=5$ 处最大 ✓

💡 这就是"周长一定时,正方形面积最大"的证明。(三维版本是立方体。)

4.2 例 2:容积固定求最省材料(易拉罐问题)

做一个容积 1000 cm³ 的圆柱形罐子,怎么设计用料最省?

第一步:设变量

设底面半径 $r$,高 $h$。用料 = 表面积:
$$S = 2\pi r^2 + 2\pi r h$$

第二步:约束消元

体积约束:$\pi r^2 h = 1000 \implies h = \dfrac{1000}{\pi r^2}$

$$S(r) = 2\pi r^2 + 2\pi r \cdot \frac{1000}{\pi r^2} = 2\pi r^2 + \frac{2000}{r}$$

第三步:求导找驻点

$$S'(r) = 4\pi r - \frac{2000}{r^2} = 0 \implies 4\pi r^3 = 2000 \implies r^3 = \frac{500}{\pi}$$

$$r^* = \sqrt[3]{\frac{500}{\pi}} \approx 5.4193 \text{ cm}$$

第四步:回答

代回求高:$h^* = \dfrac{1000}{\pi r^{*2}} \approx 10.8385$ cm。

注意 $h^* \approx 2r^*$,即高等于直径!

数值验证:

$r$ 4 5 5.4193 6 7
$S$ 600.53 557.08 553.58 559.53 593.59

在 $r^*\approx5.4193$ 处最小 ✓

💡 现实对比:实际易拉罐往往不是这个比例(通常更"瘦高")。
因为罐身和罐底的材料成本不同,还要考虑手持的舒适度。
数学给的是"只在你的假设下成立的最优解",这也是建模的第一课。


5 🧠 这跟认知科学有什么关系

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

极值 ↔ 心理学里的「倒 U 型曲线」。

心理学有一个非常著名的规律,耶克斯-多德森定律(Yerkes-Dodson law):

绩效随唤醒水平先升后降,中间有个最优点。

绩效
 │        ╱▔▔╲
 │      ╱      ╲
 │    ╱          ╲
 │  ╱              ╲
 └────────────────────→ 唤醒水平
        最大点

这在数学上就是:存在 $x^*$ 使 $f'(x^*)=0$ 且 $f''(x^*)<0$,一个极大值点。

为什么这个对应有用:当你在文献里看到"倒 U 型""最优唤醒水平",
你脑子里应该立刻浮现出求导、令其为零、看二阶导数这套动作。
"找最优点"就是"求极大值",两者是同一件事的两种说法。

类似的还有:

心理学现象 数学形式
倒 U 型(唤醒-绩效) 极大值
难度-动机的最优水平 极大值
信息量与决策质量(过载) 极大值
学习时间与收益(有疲劳) 极大值

优化 ↔ 认知的「最优性假设」。

认知科学里一整套理论把大脑建模成在做优化:

这些理论的数学骨架都是"在约束下求极值",就是你这一节学的东西。

⚠️ 重要提醒:"最优性假设"是一个建模立场,不是已证实的结论。
大脑是不是真的在优化?优化什么目标?这是当前争论的焦点。
有大量证据表明人是有限理性的(会犯系统性偏差),不总是最优。
请以文献为准,这一条尤其容易被人当成结论来讲。


6 数学作业 S6

第 2 题提示($f''=0$ 的情形):

import sympy as sp
x = sp.Symbol('x')
f = x**4 - 2*x**2
print("f' =", sp.diff(f, x))
print("驻点:", sp.solve(sp.diff(f, x), x))
print("f'' =", sp.diff(f, x, 2))

你会看到 $f'=4x^3-4x=4x(x^2-1)$,驻点是 $x=0,\pm1$。
$f''=12x^2-4$,在 $x=0$ 处 $f''=-4<0$(极大),在 $x=\pm1$ 处 $f''=8>0$(极小)。
这个例子 $f''$ 恰好都不是 0,想练 $f''=0$ 的情形,试试 $f(x)=x^4$ 在 $x=0$。

第 7 题参考代码:

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(-2.5, 3, 400)
f = x**3 - 3*x

plt.plot(x, f, label="$f(x)=x^3-3x$")
# 驻点
for xc, lbl in [(-1, "极大"), (1, "极小")]:
    plt.plot(xc, xc**3 - 3*xc, "o", color="black")
    plt.annotate(lbl, (xc, xc**3 - 3*xc), textcoords="offset points", xytext=(8, 8))
# 端点
plt.plot([-2, 3], [(-2)**3-3*(-2), 3**3-3*3], "s", color="gray")
plt.xlabel("x"); plt.ylabel("f(x)"); plt.legend()
plt.title("$f(x)=x^3-3x$ 的极值与端点")
plt.show()

第二部分 · 编程

7 作用域:LEGB 规则

作用域决定"这个名字到底指的是哪个变量"。Python 按固定的顺序找:

L → E → G → B

Local(当前函数内)→ Enclosing(外层函数)→ Global(模块全局)→ Builtin(内置)

先找到谁,就用谁。找不到就 NameError。

x = "全局"                    # G

def outer():
    x = "外层"                # E
    def inner():
        x = "内层"            # L
        print(x)              # → 内层(最近的优先)
    inner()

outer()
print(x)                      # → 全局

逐层"看不到"的情况:

x = "全局"
def outer():
    def inner():
        print(x)              # 本层没有 → 找外层 → 也没有 → 找全局
    inner()
outer()                       # → 全局

7.1 global 和 nonlocal

赋值会让名字变成"局部的",这是最常见的困惑来源:

count = 0

def bad():
    count += 1        # ❌ UnboundLocalError!

bad()

报错:UnboundLocalError: cannot access local variable 'count' where it is not associated with a value

为什么:Python 看到函数内有 count += 1(赋值),就把 count 判定为局部变量。
但 += 需要先读 count,而局部 count 还没赋值,冲突。

两个声明:

关键字 作用 用在哪
global 声明"我要改全局的" 模块级变量
nonlocal 声明"我要改外层函数的" 闭包
count = 0
def good():
    global count
    count += 1
good(); print(count)          # 1

def make_counter():
    n = 0
    def inc():
        nonlocal n            # 改外层函数的 n
        n += 1
        return n
    return inc
c = make_counter()
print(c(), c())               # 1 2

⚠️ 经验:尽量别用 global。它让"这个变量在哪被改"变得难以追踪。
nonlocal 在写闭包时是必要的(阶段 5 的计数器就用了),但也要克制。
优先用:参数传入、返回值传出。

8 参数类型:位置、关键字、默认

四种参数,一次说清:

def f(a, b, c=3, *args, d, e=5, **kwargs):
    ...
部分 名称 收什么
a, b 位置参数 按位置传,必填
c=3 默认参数 可省略(阶段 5 讲过它的坑)
*args 可变位置参数 收多余的位置参数,装成元组
d 关键字-only 必须用 d=... 传
e=5 关键字-only + 默认值 可省略
**kwargs 可变关键字参数 收多余的关键字参数,装成字典

实跑结果:

f(1, 2, 4, 6, 7, d=8, x=9, y=10)
# a=1  b=2  c=4  args=(6, 7)  d=8  e=5  kwargs={'x': 9, 'y': 10}

💡 看规律:位置参数从左边开始按顺序填;
填满 a,b,c 之后,多出来的 6,7 被 *args 收走;
d=8 因为前面有 * 所以只能用关键字传;
剩下的 x=9, y=10 被 **kwargs 收走。

8.1 *args 和 **kwargs 的用法

def sum_all(*nums):
    return sum(nums)

print(sum_all(1, 2, 3))       # 6
print(sum_all())              # 0     (没有参数也可以)
print(sum_all(*[1, 2, 3, 4])) # 10    (用 * 展开一个列表)

💡 注意 *nums 和 *[1,2,3,4] 里的 * 是两件事:
- 定义时的 *nums:打包(把多个参数收成元组)
- 调用时的 *列表:解包(把列表拆成多个参数)

def show(**info):
    for k, v in info.items():
        print(f"{k} = {v}")

show(姓名="张三", 年龄=18)
# 姓名 = 张三
# 年龄 = 18

💡 **kwargs 在你以后的科研代码里很常见,
比如 plt.plot(x, y, **style),把一堆样式参数打包传下去。

8.2 位置-only 和 关键字-only

/ 和 * 是分隔符(不是参数名):

def g(a, /, b, *, c):
    return a, b, c

g(1, 2, c=3)        # ✅ (1, 2, 3)
g(a=1, b=2, c=3)    # ❌ TypeError:a 是位置-only,不能用 a= 传
g(1, 2, 3)          # ❌ TypeError:c 是关键字-only,必须 c=3
符号 含义
/ 之前 只能按位置传
* 之后 只能按关键字传

为什么要有这个:库作者用它防止别人用错。
比如内置的 sorted(iterable, /, *, key=None, reverse=False),
因为 iterable 前面有 /,所以 sorted(iterable=[3,1]) 会报 TypeError。
你自己写代码一般不需要,但读别人的函数签名时会遇到。

⚠️ 别想当然:并不是所有内置函数都是位置-only。
比如 pow 就可以用关键字传:pow(base=2, exp=3) 完全合法(实测)。
不确定就试一次,报错试出来比记错强。

9 参数到底是怎么传的 ⚠️(下一阶段的引子)

这是最反直觉的一点,也是阶段 7 那个坑的根源。

Python 传的是「对象的引用」,不是「值的拷贝」。

但为什么感觉像"值传递"?因为不可变对象改不动:

def try_modify_int(v):
    v = 999            # 只是让局部名 v 指向新对象 999

n = 5
try_modify_int(n)
print(n)               # 5   ← 没变

而可变对象会被改到:

def try_modify_list(lst):
    lst.append(999)    # 修改的是【同一个对象】的内容

m = [1, 2]
try_modify_list(m)
print(m)               # [1, 2, 999]   ← 变了!

区别在哪:

操作 效果
lst.append(x) 原地修改同一个对象 → 调用者看得见
v = 999 只是重新贴标签 → 调用者看不见

"改对象的内容"会被看到,"换个对象"不会被看到。

为什么 integer 改了却没事:v = 999 不是"把 5 改成 999",
而是"让 v 这个标签贴到新对象 999 上"。原来那个 5 动都没动。

这直接引出阶段 7 的核心:列表是可变的,所以到处都有"意外共享"的坑。


10 🔧 技能卡 6:读懂别人的函数签名

技能卡 2 教你用 help()。但 help() 的输出常常长这样,看着就头大:

pow(base, exp, mod=None) -> number
plot(*args, scalex=True, scaley=True, data=None, **kwargs)

这张卡教你怎么在 10 秒内看懂它。

10.1 三步读法

① 先找"必填"的,没有默认值的就是必填:

def save(data, path, encoding="utf-8"):
#        ↑必填  ↑必填    ↑可省略

→ save(data, path) 最少这么调。

② 再看 *args / **kwargs 在哪:

plot(*args, scalex=True, ...)
#     ↑ 位置参数可以是任意多个

→ plot(x)、plot(x, y)、plot(x, y, "r--") 都合法。

③ 最后看 * 和 / 这些分隔符:

sorted(iterable, /, *, key=None, reverse=False)
#             ↑          ↑
#        位置-only    后面全是关键字-only

→ sorted([3,1,2], key=lambda x: -x, reverse=True) ✓
→ sorted(iterable=[3,1,2]) ✗(位置-only 不能用关键字传)

10.2 遇到 lambda 怎么办

help() 的示例里经常出现 lambda。它就是"匿名的小函数",写法:

lambda x: -x          # 等价于 def f(x): return -x
lambda a, b: a + b    # 等价于 def f(a, b): return a + b
普通函数 lambda
def f(x): return x*2 lambda x: x*2
有名字,可多行 没名字,只能一个表达式
用于复用的逻辑 用于"用完就扔"的小函数

💡 典型用法:sorted(列表, key=lambda x: x[1]),按第 2 个元素排序。
这种"只用一次的小函数",专门定义个 def 太啰嗦,就用 lambda。

你不需要精通 lambda,能读懂就行。(它内部其实就是函数,阶段 4 的规则全适用。)

10.3 一个实用的自查习惯

不确定一个函数怎么调,别猜,直接试最小的例子:

help(sorted)              # 看签名
print(sorted([3,1,2]))    # 试最简单的调用
print(sorted([3,1,2], reverse=True))   # 加一个参数看变化

⚠️ 比猜强一百倍。在 Jupyter 里,试一次只要 2 秒,猜错再看报错要 2 分钟。


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

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

作用域 ↔ 认知的「模块化」假设(modularity)。

认知科学里有一个重要立场,模块化:认知功能由相对独立的模块完成,
模块内部状态不泄漏到外部,模块之间只通过受限的接口通信。

这几乎就是"作用域"的定义:

编程 认知模块化
函数内部的局部变量 模块的内部状态(外部看不见)
参数和返回值 模块的输入输出接口
global 改全局变量 "泄漏",绕过了接口,破坏封装
为什么劝你别用 global 为什么模块化理论强调信息封装

这解释了编程里那条经验的深层理由:global 之所以被劝退,
不只是"风格问题",它破坏了"模块之间只通过接口通信"这个结构,
追踪 bug 时你不知道谁改了它。认知架构(如 ACT-R)的设计也是同样思路:
模块各管各的内部状态,通过缓冲区和接口交换信息。

参数传递 ↔ 表征的"共享 vs 复制"。

你还记得 lst.append() 会改到调用者、而 v = 999 不会吗?
认知科学里也有类似的区分:两个加工过程是共享同一份表征,还是各自持有一份副本?

这不是纯比喻:工作记忆模型的争论里,"多个任务是否共享同一表征"
正是解释双任务干扰的核心问题之一。共享表征会互相干扰,和列表别名是同一个道理。

⚠️ 这两条都是结构性类比,帮你建立直觉。
"模块化"在认知科学内部本身就是有争议的立场(Fodor vs 反对者)。
请以文献为准,别把类比当结论。


12 编程作业 S6

两种方法:① 用 global;② 不用 global(推荐),改成什么?
- [ ] 6. 预测下面每一行打印什么,先写在纸上,再跑验证:

    def modify(v, lst):
        v = 100
        lst.append(100)

    a = 1
    b = []
    modify(a, b)
    print(a)      # ?
    print(b)      # ?

第 1 题参考写法:

def stats(*nums):
    if not nums:              # 边界:一个参数都没有
        return None
    return sum(nums) / len(nums), max(nums), min(nums)

assert stats(1, 2, 3) == (2.0, 3, 1)
assert stats(5) == (5.0, 5, 5)
assert stats() is None
print("全部通过")

第 3 题参考写法:

def chain(*lists):
    result = []
    for lst in lists:
        result.extend(lst)     # extend:把另一个列表的元素逐个加入
    return result

assert chain([1,2], [3], []) == [1,2,3]
assert chain() == []
print("全部通过")

💡 extend vs append 的区别(容易混):
- lst.append([3,4]) → [1,2,[3,4]](把整个列表当成一个元素加进去)
- lst.extend([3,4]) → [1,2,3,4](把元素逐个加进去)

忘了就 help(list.extend)。

第 5 题的两种解法:

# 方法① 用 global(能修好,但不推荐)
total = 100
def add(n):
    global total
    total += n
    return total

# 方法② 不用 global(推荐):参数传入、返回值传出
def add(total, n):
    return total + n

total = add(total, 50)      # 显式地把新值赋回去
print(total)                # 150

💡 为什么推荐方法②:看 add(total, 50) 这行,你一眼就知道它改了 total。
而方法①里,add(50) 默默改了全局变量,读代码的人不查函数体就看不出来。

第 6 题答案(先自己判断再看):

def modify(v, lst):
    v = 100            # 重新贴标签 → 外面看不到
    lst.append(100)    # 原地修改 → 外面看得到

a = 1
b = []
modify(a, b)
print(a)      # 1    ← 整数不可变,没被改
print(b)      # [100] ← 列表被改到了

本阶段自评

  1. 数学:用导数判断单调性的依据是什么?(和哪个定理有关)
  2. 数学:$f'(x_0)=0$ 能推出 $x_0$ 是极值点吗?举反例。
  3. 数学:求闭区间最值时,为什么端点不能漏?
  4. 数学:优化四步法里,最难的是哪一步?为什么?
  5. 数学:易拉罐问题得出 $h=2r$,但现实中不是,为什么?这说明什么?
  6. 编程:LEGB 分别指什么?查找顺序是怎样的?
  7. 编程:为什么会 UnboundLocalError?global 和 nonlocal 分别解决什么?
  8. 编程:*args 和 **kwargs 分别收什么?装成什么类型?
  9. 编程:def f(v, lst) 里改 v 和改 lst 有什么区别?为什么?
  10. 技能:拿到一个陌生函数,怎么快速搞清怎么调?
  11. 连接:"倒 U 型曲线"在数学上是什么问题?"最优性假设"为什么不能当结论?

全部答得上 → 进阶段 7。

最后更新:2026-09-26