▤书架 ← 返回索引
▶目录

阶段 14 讲义

数学 11h:高数期末复习
编程 9h:综合小项目


第一部分 · 数学

这一阶段不学新东西,只做两件事:把 13 个阶段连成一张网,然后用一套模拟卷把它压实。

复习不是"再看一遍"。这一阶段的认知科学连接会告诉你为什么。

1 全景知识地图

先把骨架立起来。这 13 个阶段的知识点最后归成四条线:

                    ┌─────────────────────────────────┐
                    │   极限(阶段 1–2)              │
                    │   一切的基础:趋于、ε-N、ε-δ    │
                    └───────────────┬─────────────────┘
                                    │
                    ┌───────────────▼─────────────────┐
                    │   导数(阶段 3–7)              │
                    │   变化率 · 链式法则 · 中值定理   │
                    │   凹凸性 · 函数作图             │
                    └───────────────┬─────────────────┘
                                    │
        ┌───────────────────────────┴───────────────────────────┐
        │                                                       │
┌───────▼──────────────────┐                    ┌───────────────▼─────┐
│  积分(阶段 11–12)       │                    │  线性代数           │
│  ——导数的逆运算          │                    │  (阶段 8–10,13)   │
│  换元 · 分部 · 定积分应用 │                    │  行列式→矩阵→方程组 │
└──────────────────────────┘                    │  →向量空间→特征值   │
                                                └─────────────────────┘

两条主线的关系:

主线 研究对象 核心工具
分析(微积分) 连续变化的量 极限 → 导数 → 积分
代数(线性代数) 线性结构 行列式 → 矩阵 → 特征值

它们在大一上是分开学的,但阶段 16 会看到它们如何汇合(SVD 是"矩阵的微积分式分解")。

1.1 逐阶段速查

阶段 核心内容 一句话记住
01 数列极限 ε-N "要多近有多近,从某项之后一直在里面"
02 函数极限 ε-δ 同上,把"某项之后"换成"某点附近"
03 导数定义 $f'(x) = \lim_{\Delta x\to0}\frac{\Delta y}{\Delta x}$
04 求导法则 乘积、商、链式法则
05 中值定理 洛必达、罗尔、拉格朗日
06 导数的应用 单调性、极值
07 凹凸性 $f''$ 决定弯曲方向
08 行列式 解方程组的钥匙
09 矩阵与逆 矩阵里的"除法"
10 向量组与方程组 秩决定解的情况
11 不定积分 求导的逆运算,带 $+C$
12 定积分 一个数,牛顿-莱布尼茨
13 特征值 变换中"方向不变"的那些方向

2 核心公式总表

这张表建议打印出来,考前反复看。

2.1 极限与等价无穷小($x \to 0$)

公式 值
$\lim_{x\to0}\dfrac{\sin x}{x}$ $1$
$\lim_{x\to0}\dfrac{\tan x}{x}$ $1$
$\lim_{x\to0}\dfrac{e^x-1}{x}$ $1$
$\lim_{x\to0}\dfrac{\ln(1+x)}{x}$ $1$
$\lim_{x\to0}\dfrac{1-\cos x}{x^2}$ $\dfrac12$
$\lim_{x\to\infty}\left(1+\dfrac1x\right)^x$ $e$

2.2 导数

公式 结果
$(x^n)'$ $nx^{n-1}$
$(\sin x)'$ $\cos x$
$(\cos x)'$ $-\sin x$
$(e^x)'$ $e^x$
$(\ln x)'$ $\dfrac1x$
$(\arctan x)'$ $\dfrac{1}{1+x^2}$
链式法则 $[f(g(x))]' = f'(g(x))\cdot g'(x)$
乘积法则 $(uv)' = u'v + uv'$
商法则 $\left(\dfrac uv\right)' = \dfrac{u'v-uv'}{v^2}$

2.3 积分

公式 结果
$\int x^n dx$ $\dfrac{x^{n+1}}{n+1}+C$($n\ne-1$)
$\int \dfrac1x dx$ $\ln\lvert x\rvert+C$
$\int e^x dx$ $e^x+C$
$\int \sin x\,dx$ $-\cos x+C$
$\int \cos x\,dx$ $\sin x+C$
$\int \dfrac{1}{1+x^2}dx$ $\arctan x+C$
分部积分 $\int u\,dv = uv-\int v\,du$
牛顿-莱布尼茨 $\int_a^b f = F(b)-F(a)$

2.4 线性代数

公式 结果
二阶行列式 $\begin{vmatrix}a&b\\c&d\end{vmatrix} = ad-bc$
$2\times2$ 逆矩阵 $\dfrac{1}{ad-bc}\begin{pmatrix}d&-b\\-c&a\end{pmatrix}$
逆矩阵的性质 $(AB)^{-1}=B^{-1}A^{-1}$,$(A^T)^{-1}=(A^{-1})^T$
转置 $(AB)^T = B^TA^T$
解的情况 $r_A\ne r_b$ 无解;$r_A=r_b=n$ 唯一解;$r_A=r_b<n$ 无穷多解
齐次有非零解 $\iff \text{rank}(A)<n$
特征方程 $\det(A-\lambda I)=0$
特征值性质 $\sum\lambda=\text{tr}(A)$,$\prod\lambda=\det(A)$

3 高频题型与解法套路

考试考的题型是有限的。下面六类覆盖了绝大部分:

3.1 套路一:求极限

顺序:先试直接代入 → 不行就看能不能用等价无穷小 → 再不行洛必达

$$\lim_{x\to0}\frac{\sin 3x}{x} = \lim_{x\to0}\frac{3x}{x} = 3 \quad (\text{实算确认})$$

⚠️ "$0/0$" 或 "$\infty/\infty$" 才能用洛必达。别上来就求导。

3.2 套路二:求导(重点是链式法则)

判断层次:从外往里剥。

$$y = \sin(x^2) \Longrightarrow y' = \cos(x^2)\cdot 2x = 2x\cos(x^2) \quad (\text{实算确认})$$

$$y = x^2e^x \Longrightarrow y' = 2xe^x + x^2e^x = x(x+2)e^x \quad (\text{实算确认})$$

3.3 套路三:不定积分(选方法)

看到 用什么
复合函数,内层导数的倍数在场 凑微分
两种类型的乘积($xe^x$、$x\cos x$) 分部积分
$\ln x$、$\arctan x$ 单独出现 分部积分(补 $dx$)

3.4 套路四:定积分

先求原函数,再代端点,上减下。

$$\int_0^1(x^2+1)dx = \left[\frac{x^3}{3}+x\right]_0^1 = \frac13+1 = \frac43 \quad (\text{实算确认})$$

3.5 套路五:行列式与逆矩阵

三阶行列式:萨吕斯法则(6 项,别漏)。
求逆:$2\times2$ 用口诀"主对角互换、副对角变号、除以行列式"。

$$\begin{pmatrix}2&1\\5&3\end{pmatrix}^{-1} = \frac{1}{1}\begin{pmatrix}3&-1\\-5&2\end{pmatrix} = \begin{pmatrix}3&-1\\-5&2\end{pmatrix} \quad (\text{实算确认})$$

3.6 套路六:特征值

$$\det(A-\lambda I)=0 \Longrightarrow \text{解 }\lambda \Longrightarrow \text{回代求特征向量}$$

$$A = \begin{pmatrix}3&1\\0&2\end{pmatrix} \Longrightarrow \lambda = 3,\ 2 \quad (\text{三角矩阵,直接看对角线})$$

4 高频错误清单(考前必读)

这张表是按"实际出现频率"排的,不是按重要性。

# 错误 正确做法
1 $\int \sin x\,dx$ 漏负号 是 $-\cos x+C$
2 不定积分忘写 $+C$ 每次都写
3 $(AB)^{-1}$ 写成 $A^{-1}B^{-1}$ 是 $B^{-1}A^{-1}$,顺序反
4 $(AB)^T$ 写成 $A^TB^T$ 是 $B^TA^T$,顺序反
5 换元后忘了换积分限 换了 $u$ 就换上下限
6 $[-\cos x]_0^\pi$ 算成 0 $-\cos\pi = +1$,答案是 2
7 求 $\det(A-\lambda I)$ 时忘了 $\lambda I$ 只减主对角线
8 特征向量漏写(只写特征值) 两者必须配对
9 克拉默法则忘记 $D\ne0$ 的前提 先算 $D$
10 只算 $\text{rank}(A)$ 不算 $\text{rank}([A\mid b])$ 两个都要算
11 洛必达用在非不定式上 先确认是 $0/0$ 或 $\infty/\infty$
12 求面积时判错"谁在上面" 先比较或画图

⭐ 这 12 条里,第 3、4 条最隐蔽,因为它们在"看起来对"的时候错。
记忆法:逆和转置都会"颠倒顺序",就像"穿脱袜子鞋子"。

5 模拟卷(限时 90 分钟)

请先自己做完,再看下一节的答案。

6 模拟卷答案(全部用 sympy 实算核对)

题 答案
1 $3$
2 $1$
3 $x(x+2)e^x$
4 $2x\cos(x^2)$
5 $\dfrac{2x}{x^2+1}$
6 $x\sin x+\cos x+C$
7 $\dfrac43$
8 $1$
9 $5$
10 $\begin{pmatrix}3&-1\\-5&2\end{pmatrix}$
11 $\lambda = 3,\ 2$
12 $\lambda^2-5\lambda+5$
13 $2$
14 $\dfrac16$
15 收敛,值为 $1$
16 $3$
17 $e^{-x^2}$
18 两边都是 $\begin{pmatrix}\frac32&-\frac12\\-2&1\end{pmatrix}$ ✓

第 13 题详解(容易只答一半):

$$A = \begin{pmatrix}1&2&3\\2&4&6\\1&1&1\end{pmatrix}$$

第 2 行 = 第 1 行 × 2(冗余)。行变换后非零行有 2 行 → $\text{rank} = 2$。

第 17 题详解(考的是微积分基本定理,不需要求出原函数):

$$\frac{d}{dx}\int_0^x e^{-t^2}dt = e^{-x^2}$$

注意:$e^{-t^2}$ 没有初等原函数,但这条定理照样成立。
这正是它叫"基本定理"的原因。

第 8 题详解(分部积分 + 定积分):

$$\int_1^e \ln x\,dx = [x\ln x - x]_1^e = (e\cdot1 - e) - (1\cdot0 - 1) = 0-(-1) = 1$$


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

这一节模型是我自己用代码搭的(可复现),但它是玩具模型,不是实证数据。
认知科学的结论请以文献为准。

"复习"这件事本身就是认知科学的研究对象,而研究结论说:大多数人的复习方式是错的。

7.1 两个最稳健的发现

现象 内容 经典文献
间隔效应(spacing effect) 分散复习比集中复习记得久得多 Ebbinghaus (1885);Cepeda et al. (2006) 元分析
提取练习(testing effect / retrieval practice) 主动回忆比反复阅读有效得多 Roediger & Karpicke (2006)

"间隔效应"是心理学史上最早被发现的记忆规律之一(Ebbinghaus 1885 年用自己当被试做的)。

7.2 我用代码模拟给你看(注意:玩具模型)

模型设定:记忆强度 $S$ 每天按 $S \to S\cdot e^{-1/\tau}$ 衰减($\tau = 5$ 天);
每次复习把 $S$ 提升一定比例,且有收益递减(越接近 1 提升越少)。

实跑结果(30 天):

复习方式 第 7 天 第 14 天 第 30 天
不复习 $0.247$ $0.061$ $0.002$
集中复习(连续 4 天) $0.449$ $0.111$ $0.005$
间隔复习(每周 1 次) $1.000$ $1.000$ $\mathbf{0.165}$

第 30 天时,间隔复习的记忆强度是集中复习的约 37 倍。

再把"提取练习"的巩固效果调强一点(复习同样在 7/14/21 天):

方式 第 30 天
重复阅读 $0.094$
提取练习 $\mathbf{0.139}$(高约 47%)

7.3 ⚠️ 这个模拟能说明什么,不能说明什么

必须说清楚:

能说明 不能说明
在"指数衰减 + 收益递减"这个假设下,间隔复习在数值上确实远优于集中复习 ❌ 真实记忆不是简单的指数衰减
这个模型可以复现出文献里报告的方向(间隔 > 集中,提取 > 重读) ❌ 上面那些具体倍数(37 倍、47%)是模型算出来的,不是实验结果
提供了一个"为什么间隔有用"的直觉 ❌ 不能用来估计你该隔几天复习

真实的记忆曲线有平台期、有干扰、有睡眠依赖的巩固、有情绪调制。
玩具模型把这一切都简化掉了。

⭐ 所以正确的用法是:
这个模拟给你直觉,文献给你结论,你的成绩来自行动。

7.4 对你现在(阶段 14)的具体建议

做法 为什么 怎么做
别只看,要"考自己" 提取练习 > 重复阅读 用本讲义每阶段的「自评」先说答案再看书
分散,别突击 间隔效应 每天 1 小时 × 6 天 > 周末突击 6 小时
主动做作业 作业就是提取练习 本讲义反复强调"别跳作业"不是为难你
错题要重做 提取失败的地方正是要再提取 第 4 节的 12 条错误清单
睡前回顾 睡眠参与记忆巩固 睡前 5 分钟过一遍当天要点

⭐ 这解释了一个常见的困惑:
"我明明看懂了,为什么考不出来?"
因为"看懂"是识别(recognition),"会做"是提取(recall)。这是两种不同的能力。
看懂答案时的流畅感,会让你误以为已经掌握了。这叫"流畅性错觉"。
唯一可靠的检测方式是:合上书,自己写一遍。

⚠️ 诚实说明:
- "间隔效应"和"提取练习"是认知心理学中最稳健、可重复性最好的发现之一,
有大量实验和元分析支持。可以放心采用。
- 但"最佳复习间隔是多久"依赖于你想记多久和材料难度,
研究结论是"间隔应该与目标保持时间成比例",而不是一个固定数字。
- 上面所有具体数值都来自我的玩具模型,不是实证结果。
请以 Cepeda et al. (2006, Psychological Bulletin)、
Roediger & Karpicke (2006, Psychological Science) 等文献为准。


8 数学作业 S14

第 7 题参考代码:

"""公式自测器:从题库随机抽题,先自己想,再按回车看答案。"""
import random

# 题库:(题目, 答案)
QUESTIONS = [
    ("∫ sin x dx = ?", "-cos x + C"),
    ("∫ 1/x dx = ?", "ln|x| + C"),
    ("∫ e^x dx = ?", "e^x + C"),
    ("∫ 1/(1+x²) dx = ?", "arctan x + C"),
    ("(AB)⁻¹ = ?", "B⁻¹A⁻¹"),
    ("(AB)ᵀ = ?", "BᵀAᵀ"),
    ("det(A-λI) = ? 求什么", "求特征值的方程"),
    ("Σλ 等于什么?", "tr(A)(迹)"),
    ("∫_a^b f dx = ?", "F(b) - F(a)(牛顿-莱布尼茨)"),
    ("分部积分公式", "∫u dv = uv - ∫v du"),
    ("lim_{x→0} sin x / x = ?", "1"),
    ("lim_{x→0} (e^x-1)/x = ?", "1"),
    ("∫ x^n dx = ? (n≠-1)", "x^(n+1)/(n+1) + C"),
    ("2×2 逆矩阵口诀", "主对角互换,副对角变号,除以行列式"),
]


def quiz(n=5):
    """抽 n 道题自测。"""
    picked = random.sample(QUESTIONS, min(n, len(QUESTIONS)))
    right = 0
    for i, (q, a) in enumerate(picked, 1):
        print(f"\n第 {i} 题:{q}")
        input("  想好了按回车看答案...")
        print(f"  答案:{a}")
        ok = input("  答对了吗?(y/n) ").strip().lower().startswith("y")
        right += ok
    print(f"\n得分:{right}/{len(picked)}")
    return right, len(picked)


if __name__ == "__main__":
    # 固定随机种子可选,保证可复现
    # random.seed(42)
    quiz(5)

💡 这就是"提取练习"的代码实现:先自己回忆,再看答案。
把题库换成你自己的薄弱点,它就是你最好的复习工具。
建议每天抽 5 题,比考前突击 2 小时有效得多。


第二部分 · 编程

9 项目说明

阶段 1–13 你学的都是零件。这一阶段把零件装成一台能跑的机器。

项目:反应时数据分析流程

它已经做好并放在你的电脑上了:

编程/项目-反应时分析/
├── README.md          项目说明
├── .gitignore         哪些文件不该进版本库
├── main.py            主流程
├── io_utils.py        数据进出
├── stats_utils.py     统计工具
├── report.py          呈现
├── 自检.py            测试
└── data/
    ├── raw.csv        原始数据(只读)
    └── clean.csv      清洗后的数据(可重新生成)

先跑一遍看看:

cd ~/Desktop/大一/编程/项目-反应时分析
~/miniconda3/bin/python main.py

10 项目结构:每个文件管一摊

对照技能卡 11(拆分代码),这个项目就是它的示范。

文件 职责 不负责什么
main.py 编排流程:读 → 清洗 → 分析 → 出报告 ❌ 不写具体解析逻辑
io_utils.py 数据进出:解析行、读文件、写文件 ❌ 不做统计
stats_utils.py 统计计算:均值、中位数、标准差、分组 ❌ 不碰文件
report.py 呈现:把数字排成表格 ❌ 不计算,只排版
自检.py 测试:验证每个模块 —

依赖方向是单向的(技能卡 11 讲过):

main.py ──┬──> io_utils.py
          ├──> stats_utils.py
          └──> report.py ──> stats_utils.py

没有循环导入,这是能跑起来的前提。

11 逐模块讲解

11.1 io_utils.py:数据进来

核心函数 parse_line:

def parse_line(line, lineno=0):
    """把一行 CSV 解析成字典。"""
    parts = [p.strip() for p in line.split(",")]
    if len(parts) != len(COLUMNS):
        raise ValueError(f"字段数应为 {len(COLUMNS)},实际 {len(parts)}")

    subject, trial, condition, rt_s, correct_s = parts
    try:
        rt = float(rt_s)
        correct = bool(int(correct_s))
    except ValueError as e:
        raise ValueError(f"数值转换失败:{e}") from e

    return {"subject": subject, "trial": int(trial),
            "condition": condition, "rt": rt, "correct": correct}

这一段用到了你在阶段 9–11 学的:

用到的 来自哪个阶段
split(",") 切字符串 阶段 9
列表推导式 [p.strip() for p in ...] 阶段 11
元组解包 a, b, c, d, e = parts 阶段 9
返回字典(用名字取值) 阶段 8
raise ValueError + from e 保留原因 阶段 10
f-string 格式化报错信息 阶段 9

⚠️ 注意 raise ... from e:它保留原始异常作为"原因",
报错时会显示"这个错误是由那个错误引起的"。
线索不断,这是好习惯。

核心函数 read_raw:

def read_raw(path, skip_bad=True):
    path = Path(path)
    records = []
    bad_lines = []

    with open(path, "r", encoding="utf-8") as f:
        for lineno, line in enumerate(f, start=1):
            line = line.strip()
            if not line or line.startswith("#"):
                continue
            if lineno == 1 and line.lower().startswith("subject"):
                continue
            try:
                records.append(parse_line(line, lineno))
            except ValueError as e:
                if not skip_bad:
                    raise ValueError(f"第 {lineno} 行解析失败:{e}") from e
                bad_lines.append((lineno, line, str(e)))

    return records, bad_lines

设计要点:

做了什么 为什么
返回 (记录, 坏行) 两个东西 坏行被跳过但要报告,不能静默吞掉
enumerate(f, start=1) 行号从 1 开始,和编辑器显示一致
跳过空行和 # 注释行 真实数据里很常见
skip_bad 参数 给调用方选择"宽容"还是"严格"

⭐ "不静默吞掉错误"是这个项目最重要的设计。
阶段 10 的认知科学连接讲过:科研代码里,静默失败比崩溃危险得多。

11.2 stats_utils.py:算

def median_of(nums):
    """中位数。空列表返回 0。"""
    if not nums:
        return 0
    s = sorted(nums)
    mid = len(s) // 2
    if len(s) % 2 == 1:
        return s[mid]
    return (s[mid - 1] + s[mid]) / 2

注意 len(s) // 2(整除)。索引必须是整数,用 / 会得到浮点数而报错。

group_by 用到了阶段 8 的技能卡:

def group_by(records, key):
    groups = {}
    for r in records:
        groups.setdefault(r[key], []).append(r)
    return groups

setdefault(key, []):键不存在时设为 [],存在时返回已有的那个列表。
一行替代了 if key not in groups: 分支,和阶段 8 的 counts.get(w, 0) + 1 是同一个技巧。

11.3 main.py:编排

HERE = Path(__file__).resolve().parent      # 脚本所在目录
RAW = HERE / "data" / "raw.csv"             # 拼出数据路径

RT_MIN, RT_MAX = 0.2, 3.0                   # 清洗阈值,放模块顶部方便改


def main():
    print(f"读取:{RAW}")
    records, bad_lines = read_raw(RAW)
    print(f"  读到 {len(records)} 条记录")

    if bad_lines:
        print(f"  ⚠️ {len(bad_lines)} 行格式有问题,已跳过:")
        for lineno, line, err in bad_lines:
            print(f"     第 {lineno} 行:{line!r}  ({err})")

    kept, dropped = clean(records)
    print(f"清洗:保留 {len(kept)} 条,丢弃 {len(dropped)} 条")

    write_csv(kept, CLEAN)
    text = build_report(kept, conditions, subjects)
    REPORT.write_text(text, encoding="utf-8")
    print(text)


if __name__ == "__main__":
    main()

两个关键设计:

设计 为什么
Path(__file__).resolve().parent 路径自动定位,换电脑换目录都不会崩(技能卡 10)
if __name__ == "__main__": 能被 import 而不自动跑(阶段 11),所以 自检.py 能用它的函数

⚠️ RT_MIN / RT_MAX 放在模块顶部,而不是写在函数里。
参数集中在显眼处,别人(和三个月后的你)一眼就能找到并修改。

11.4 清洗规则

def clean(records):
    """按规则过滤,返回 (保留的, 丢掉的)。"""
    kept, dropped = [], []
    for r in records:
        if r["rt"] < RT_MIN or r["rt"] > RT_MAX:
            dropped.append(r)
        else:
            kept.append(r)
    return kept, dropped

为什么剔除极端反应时:

情况 通常的解释
rt < 0.2s 提前反应 / 误按,人不可能那么快完成认知加工
rt > 3.0s 走神 / 分心,不是在执行任务

⚠️ 这是常见的做法,但不是唯一做法,也不是无争议的。
不同的剔除规则会改变结果,所以论文里必须写明用了什么规则。
这正是"可复现性"的一部分。

12 跑起来

完整流程:

$ cd ~/Desktop/大一/编程/项目-反应时分析
$ ~/miniconda3/bin/python main.py

读取:.../data/raw.csv
  读到 11 条记录
  ⚠️ 1 行格式有问题,已跳过:
     第 11 行:'坏行没有逗号'  (字段数应为 5,实际 1)
清洗:保留 9 条,丢弃 2 条(RT 不在 [0.2, 3.0] 秒)
写出清洗后的数据:.../data/clean.csv(9 行)
写出报告:.../data/report.txt

============================================
反应时数据分析报告
============================================
总试次数:9
被试数  :2

【按条件】
条件                n        均值       中位数        SD
-------------------------------------------------
congruent         5     0.556     0.556     0.063
incongruent       4     0.959     0.950     0.114

【按被试】
被试            n       正确率      均值RT
-----------------------------------
A01           4     75.0%     0.684
A02           5     80.0%     0.776

跑测试:

$ ~/miniconda3/bin/python 自检.py
【io_utils.parse_line】
  ✓ 字段正确
  ✓ 容忍空格
  ✓ 字段数不对应报错
...
✓ 全部通过

结果读出来的意思:congruent(一致条件)比 incongruent(不一致条件)快约 0.4 秒,这正是经典的 Stroop 效应。

13 这个项目示范了什么

把技能卡串起来的六条:

# 做法 来自
1 按职责拆文件 技能卡 11
2 路径自动定位,不写死 技能卡 10
3 原始数据只读,结果写新文件 技能卡 10
4 坏数据要报告,不静默吞 阶段 10
5 每个模块可单独测 技能卡 5(检查用例)
6 if __name__ == "__main__" 让模块能被导入 阶段 11

⭐ 这六条合起来就是一个"可复现的分析流程",任何人拿到你的项目,跑一条命令就能得到同样的结果。
这就是认知科学近年最强调的"可复现研究"在技术上最低限度的样子。


14 🔧 技能卡 14:从零把一个项目跑起来

技能卡 13 教你保住自己的代码;这张卡教你"拿到一个陌生项目,怎么让它跑起来",这是你进实验室第一天要做的事。

14.1 为什么这是独立的一项技能

你 90% 的时间在跑别人的代码,写新代码只占很小一部分:
师兄留下的分析脚本、公开数据集的处理代码、GitHub 上复现论文的仓库。

跑不起来是常态。下面是一套排查顺序。

14.2 五步排查法(按顺序做,别跳)

第 1 步:看 README

cat README.md

90% 的项目会告诉你怎么跑。别急着试命令。

第 2 步:看项目结构,找入口

找什么 文件名常是
入口 main.py、run.py、app.py、__main__.py
依赖清单 requirements.txt、environment.yml、pyproject.toml
配置 config.py、.env、config.yaml
数据放哪 data/、inputs/

第 3 步:装依赖

pip install -r requirements.txt

⚠️ 尽量用虚拟环境(阶段 16 详讲)。
直接往系统环境装包,久了会变成一团乱麻。

第 4 步:跑入口,看第一个报错

python main.py

关键心态:不要想"一次跑通",要想"一次消灭一个错误"。

第 5 步:按报错类型对号入座

报错 通常是 怎么办
ModuleNotFoundError: No module named 'xxx' 依赖没装 pip install xxx
FileNotFoundError 路径不对 / 数据没放 用 describe() 小工具查(技能卡 10)
ImportError: attempted relative import 运行位置不对 从项目根目录运行
SyntaxError 代码是为旧/新 Python 写的 看 Python 版本要求
KeyError / IndexError 数据格式和代码预期不符 打印数据的实际样子(repr)

⭐ 99% 的"跑不起来"是这五类之一。
看到报错先分类,再动手,不要瞎试命令。

14.3 三条铁律

① 报错要读完整,特别是最后一行和倒数第二行

最后一行告诉你错误类型和消息;
倒数第二行(File "xxx.py", line N)告诉你出错的位置。
往往那个位置不是真正的原因,但它是线索的起点。

② 一次只改一个东西

同时改三个地方,跑通了也不知道是哪个起的效。

③ 改不动就给原作者发消息,附上完整报错

把完整报错贴出来(不是"跑不了"三个字)。
别人一看报错就能定位,省双方时间。

💡 把你的排查过程记下来。
同一个项目的坑,第二次遇到时你会庆幸自己记过。


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

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

这个项目 ↔ 认知科学实验的"数据流水线",而流水线的每一步都可能是发表后被质疑的地方。

15.1 原始数据长什么样(真实的脏)

你在项目里看到的是故意造的"温和版"脏数据(1 个坏行、2 个极端值)。真实的更糟:

真实数据的常见问题 本项目里对应哪一行代码在处理
空行、注释行 if not line or line.startswith("#")
字段数不对 len(parts) != len(COLUMNS) 检查
数字里混了 "NA"、"" try/except ValueError
多余空格 p.strip()
编码不是 UTF-8 encoding="utf-8"
时间戳格式不统一 (本项目没涉及,但一定会遇到)
被试中途退出,数据不全 (同上)

15.2 为什么"清洗规则"是研究可复现性的核心

这是认知科学近年最受批评的地方之一。

问题 后果
剔除规则不写明 别人无法复现你的结果
剔除规则改来改去(试到结果显著为止) 这是p-hacking 的一种,严重的方法学问题
不同规则给出不同结论 说明结果不稳健,这本身是重要信息
剔除比例很高但不报告 读者无法判断数据的真实质量

"可复现"在技术上就是:

$$\text{同一个 raw 数据} + \text{同一份代码} \longrightarrow \text{同一份结果}$$

本项目就是一个最小示范:raw.csv 不动,规则写在代码里(RT_MIN/RT_MAX),
任何人跑 python main.py 都得到同样的报告。

15.3 实验数据处理的"流水线"视角

认知科学的分析其实是一条流水线,你的项目实现了它的前几段:

阶段 做什么 本项目
① 采集 被试做任务,软件记录 (E-Prime / PsychoPy 负责)
② 读取 解析软件导出的文件 ✓ io_utils.read_raw
③ 清洗 剔除异常试次、排除被试 ✓ main.clean
④ 汇总 算均值/正确率 ✓ stats_utils
⑤ 统计检验 t 检验 / ANOVA / 混合模型 (大一下 / 大二)
⑥ 报告 表格、图、论文 ✓ report.build_report
⑦ 共享 数据 + 代码一起发布 (GitHub)

⭐ 你现在能把 ②③④⑥ 全部做出来了,这已经超过很多只会点鼠标的人。
⑤ 是统计课的内容;⑦ 是 Git 和开放科学的要求。

⚠️ 诚实说明:
- 上面提到的 p-hacking、"剔除规则不透明导致不可复现"是学界公认的方法学问题,
近年有大量讨论(如 Psychological Science 上的多篇方法学论文、
以及整个 reproducibility/replication 讨论)。
- 但"该怎么剔除反应时"没有唯一正确标准,不同领域、不同任务有不同惯例
(有人用 ±3SD,有人用绝对阈值,有人用分布拟合)。
- 本项目的 [0.2, 3.0] 是为了教学方便选的,不是推荐值。
请以你所在领域的方法论文献和实验室惯例为准。


16 编程作业 S14

先自己把项目跑一遍(第 4 节),再做下面的题。

第 6 题参考写法:

def build_cross_report(records, conditions, subject_ids):
    """按"被试 × 条件"给出均值矩阵。"""
    out = []
    header = f"{'被试':<10}" + "".join(f"{c:>14}" for c in conditions)
    out.append(header)
    out.append("-" * len(header))
    for sid in subject_ids:
        row = f"{sid:<10}"
        for c in conditions:
            rts = [r["rt"] for r in records
                   if r["subject"] == sid and r["condition"] == c]
            cell = f"{mean_of(rts):.3f} (n={len(rts)})" if rts else "-"
            row += f"{cell:>14}"
        out.append(row)
    return "\n".join(out)


# 在 main() 里调用
# print(build_cross_report(kept, conditions, subjects))

用本项目 raw.csv 实跑出来的输出(清洗后 9 条记录):

被试             congruent   incongruent
--------------------------------------
A01          0.495 (n=2)   0.873 (n=2)
A02          0.596 (n=3)   1.045 (n=2)

两个被试的效应方向一致(都是 incongruent 更慢),这比只看总平均更有说服力。

💡 这种"被试 × 条件"的表格在认知科学论文里到处都是,它让你一眼看出效应在被试间是否一致。
如果某个被试的效应方向反了,那可能是重要的信息,不该被平均掉。

第 2 题答案:

# 把 stats_utils.py 里的
mid = len(s) // 2        # 整除,得到整数索引

# 改成
mid = len(s) / 2         # 浮点数!

# 然后跑 自检.py,会看到:
#   ✗ 中位数奇数
#   ...
#   ✗ 失败 N 项:['中位数奇数', '中位数偶数', ...]

为什么测试能发现:因为索引必须是整数,
len(s) / 2 得到 1.5,用 s[1.5] 会直接抛
TypeError: list indices must be integers。

这正是"检查用例"的价值(技能卡 5):
改动之后跑一次测试,30 秒就知道有没有改坏东西,比手工在浏览器/终端里试半天可靠得多。

第 9 题答案:

1. 报错说缺 pandas → 先确认项目依赖清单
   看 requirements.txt 里有没有 pandas

2. 有 → 装它:pip install -r requirements.txt
   没有 → 说明作者漏写了,自己装:pip install pandas

3. 装完再跑,看是否出现下一个错误
   (通常还有:FileNotFoundError 找不到数据)

4. 如果 pip install 也失败
   → 看 Python 版本是否匹配(README 里通常写了要求)

心态:不要想"一次跑通",一次消灭一个错误。

本阶段自评

  1. 数学:你的知识地图有哪四条主线?它们之间的关系是什么?
  2. 数学:默写等价无穷小表的前 4 条(不许翻书)
  3. 数学:$(AB)^{-1}$ 和 $(AB)^T$ 各等于什么?它们的共同规律是什么?
  4. 数学:不定积分表里最容易漏负号的是哪一条?
  5. 数学:求特征向量的完整步骤是什么?
  6. 数学:$\int_1^\infty\frac{1}{x}dx$ 收敛吗?为什么和 $\frac{1}{x^2}$ 不一样?
  7. 数学:微积分基本定理说的是什么?为什么 $e^{-t^2}$ 这种"积不出来"的函数也适用?
  8. 数学:模拟卷你错了几道?错在哪几类(对照 12 条清单)?
  9. 编程:这个项目为什么拆成 4 个模块?各自的职责是什么?
  10. 编程:read_raw 为什么返回两个东西(记录和坏行)?只返回记录有什么问题?
  11. 编程:setdefault(key, []) 和 groups.get(key, 0) 这类写法,省掉了什么?
  12. 编程:main.py 里的路径为什么用 Path(__file__).resolve().parent 而不是写死?
  13. 技能:拿到一个陌生项目,你的五步排查顺序是什么?
  14. 技能:ModuleNotFoundError 和 FileNotFoundError 分别意味着什么?怎么查?
  15. 连接:为什么"看懂答案"不等于"会做"?"流畅性错觉"是什么?
  16. 连接:间隔效应和提取练习,对你的复习方式有什么具体改变?

全部答得上 → 进阶段 15,你离"大一上主线完成"只剩两阶段。
有任何一个答不上 → 回去补,别跳。

最后更新:2026-09-26