阶段 16 讲义
数学 11h:线代复习 · SVD 启蒙
编程 9h:环境管理与阶段总结
第一部分 · 数学
这是大一上最后一个数学阶段。做两件事:把线性代数串成一条线,然后认识 SVD。
SVD(奇异值分解)是现代数据科学的支柱:图像压缩、推荐系统、降维、去噪、伪逆……
全都建立在它上面。这一节只做"启蒙",让你知道它是什么、为什么重要。
1 线性代数全景回顾
阶段 8–10、13、15 学的其实是同一个故事的不同章节。
1.1 一条主线:解 $Ax = b$
行列式(阶段 8) 矩阵与逆(阶段 9)
判断"有没有唯一解" → 把"除法"做出来
│ │
└────────────┬───────────┘
▼
向量组与秩(阶段 10)
回答"到底有几个解"——秩决定一切
│
▼
特征值(阶段 13、15)
"哪些方向不被扭转" + 对角化
│
▼
SVD(阶段 16)
"任何矩阵都能拆成 旋转×拉伸×旋转"
1.2 核心概念对照表
| 概念 | 一句话 | 阶段 |
|---|---|---|
| 行列式 | 一个数,衡量"体积缩放"和"是否可逆" | 08 |
| 逆矩阵 | 矩阵里的除法,$AA^{-1}=I$ | 09 |
| 秩 | 独立的行/列数,决定解的个数 | 10 |
| 线性相关 | 有人是多余的 | 10 |
| 特征值 | 沿某方向只拉伸的倍数 | 13 |
| 对角化 | 换基让矩阵变简单:$A=P\Lambda P^{-1}$ | 15 |
| 正交矩阵 | $Q^TQ=I$,求逆就是转置 | 15 |
| 奇异值 | 任何矩阵的"拉伸倍数"(特征值的推广) | 16 |
| SVD | 任何矩阵都能拆成三个好矩阵的乘积 | 16 |
关键的关系串联:
$$\underbrace{\det A = \prod\lambda_i}_{\text{行列式}} \qquad \underbrace{\text{rank} = \text{非零特征值个数}}_{\text{秩}} \qquad \underbrace{A = Q\Lambda Q^T}_{\text{对称矩阵}} \qquad \underbrace{A = U\Sigma V^T}_{\text{任何矩阵}}$$
最后一个是前面所有的推广。
2 SVD 是什么
任何矩阵(不要求方阵、不要求对称、不要求可逆)都能拆成:
$$\boxed{A = U\Sigma V^T}$$
| 部分 | 形状 | 性质 | 含义 |
|---|---|---|---|
| $U$ | $m\times m$ | 正交($U^TU=I$) | 输出空间的旋转 |
| $\Sigma$ | $m\times n$ | 对角,非负且降序 | 拉伸倍数 = 奇异值 |
| $V^T$ | $n\times n$ | 正交($V^TV=I$) | 输入空间的旋转 |
奇异值 $\sigma_1\ge\sigma_2\ge\cdots\ge0$,是 $\Sigma$ 的对角线元素。
⭐ SVD 说的是:任何一个线性变换,不管多复杂,
都等价于"先旋转 → 再沿坐标轴拉伸 → 再旋转"。这就是它能成为"线性代数终极定理"的原因,它没有任何前提条件。
2.1 完整例子
$$A = \begin{pmatrix}3&1\\1&3\\0&0\end{pmatrix} \quad (3\times2)$$
numpy 实算:
$$U = \begin{pmatrix}-0.707&-0.707&0\\-0.707&0.707&0\\0&0&1\end{pmatrix}, \quad \Sigma = \begin{pmatrix}4&0\\0&2\\0&0\end{pmatrix}, \quad V^T = \begin{pmatrix}-0.707&-0.707\\-0.707&0.707\end{pmatrix}$$
奇异值:$\sigma_1 = 4,\ \sigma_2 = 2$
验证重构:
$$U\Sigma V^T = \begin{pmatrix}3&1\\1&3\\0&0\end{pmatrix} = A \quad ✓ \text{(实算确认)}$$
验证正交性:$U^TU = I_3$ ✓,$VV^T = I_2$ ✓(实算确认)
⚠️ 注意 $U$ 和 $V$ 的维度:$A$ 是 $3\times2$,
所以 $U$ 是 $3\times3$、$V$ 是 $2\times2$,它们不是同一个大小。
重构时要用 $U$ 的前 2 列(对应 2 个非零奇异值)。
3 SVD 和特征值的关系
这是最容易被绕晕的地方,但说起来很简单:
$$\boxed{A^T A \text{ 的特征值} = \text{奇异值的平方}}$$
具体地:
$$A^TA\,v_i = \sigma_i^2\,v_i \quad\Longrightarrow\quad v_i \text{ 是 } V \text{ 的列(右奇异向量)}$$
$$AA^T\,u_i = \sigma_i^2\,u_i \quad\Longrightarrow\quad u_i \text{ 是 } U \text{ 的列(左奇异向量)}$$
验证(实算):
$$A^TA = \begin{pmatrix}10&6\\6&10\end{pmatrix}, \quad \text{特征值} = 4,\ 16$$
$$\sigma^2 = 2^2,\ 4^2 = 4,\ 16 \quad ✓ \text{完全一致}$$
💡 所以 SVD 并没有引入全新的数学。
它可以"用特征值算出来":构造 $A^TA$(这是对称矩阵!),
求它的特征值和特征向量,开根号就得到奇异值。但实际计算不会这么做,因为 $A^TA$ 的条件数是 $A$ 的平方,
数值上会损失一半精度。实际用的是专门的 SVD 算法(Golub-Kahan 等)。
3.1 对称矩阵的特殊情形
如果 $A$ 是对称矩阵,那 SVD 和特征分解就几乎一样了:
$$\sigma_i = |\lambda_i|$$
实算验证:
| 矩阵 | 特征值 | 奇异值 |
|---|---|---|
| $\begin{pmatrix}4&2\\2&3\end{pmatrix}$ | $1.438,\ 5.562$ | $5.562,\ 1.438$ ✓ |
| $\begin{pmatrix}4&2\\2&-3\end{pmatrix}$ | $\mathbf{-3.531},\ 4.531$ | $4.531,\ \mathbf{3.531}$ ✓ |
⚠️ 看第二行:特征值有负数,但奇异值永远是正的(取绝对值)。
这就是 SVD 比特征分解适用范围更广的原因。
特征值可以是复数、可以没有、可以不存在(不可对角化),
但奇异值永远存在、永远是实数、永远非负。
4 应用一:低秩近似(数据压缩的原理)
这是 SVD 最重要的应用。
核心事实(Eckart–Young 定理):只用前 $k$ 个奇异值,就能得到"最好的"近似:
$$A \approx U_k\Sigma_kV_k^T$$
而且这是所有秩为 $k$ 的矩阵里,误差最小的那一个(在 Frobenius 范数下)。
实算给你看:我造了一个 $10\times5$ 的接近低秩的矩阵:
| 奇异值 | $145.533,\ 2.134,\ 1.904,\ 1.313,\ 0.718$ |
|---|---|
注意第一个奇异值远大于其他,说明这个矩阵几乎是秩 1 的。
| 保留前 $k$ 个 | 相对误差 | 需要的数 |
|---|---|---|
| $k=1$ | $0.0222$ | $16$ |
| $k=2$ | $0.0166$ | $32$ |
| $k=3$ | $0.0103$ | $48$ |
| 全部($k=5$) | $0$ | $50$ |
看第一行:只用 16 个数就还原了原矩阵(50 个数),误差只有 2%。
⭐ 这就是图像压缩的原理:
一张图片是个矩阵,它的奇异值衰减很快,
只存前几十个奇异值和对应的向量,就能还原出肉眼看不出差别的图片。
JPEG、推荐系统、潜在语义分析,用的都是这件事。💡 同时这也解释了 PCA:
对中心化的数据矩阵做 SVD,右奇异向量就是主成分方向,
奇异值的平方 ÷(n−1) 就是特征值(= 各主成分的方差)。
PCA 就是"对数据做 SVD 然后截断"。
5 应用二:伪逆(解决"不能求逆"的问题)
回忆阶段 9:只有方阵、且 $\det\ne0$ 才有逆矩阵。
但现实里的矩阵大多不是方阵(比如 $X$ 是 $n\times p$,被试数 $\ne$ 变量数)。
SVD 给出替代品:伪逆(Moore-Penrose 伪逆)。
$$A^+ = V\Sigma^+U^T$$
其中 $\Sigma^+$ 是"把非零奇异值取倒数,再转置"。
它解决的是最小二乘问题 $Ax\approx b$。
$$\hat{x} = A^+b$$
⭐ 这正是线性回归的解法:
$\hat\beta = (X^TX)^{-1}X^Ty$ 在 $X^TX$ 不可逆时会崩,
但用伪逆(np.linalg.pinv底层就是 SVD)总能给出一个答案,
它给的是"最小范数最小二乘解"。阶段 8/9/10 反复遇到的"共线性导致求逆失败",SVD 是最终的工程解法。
6 常见的坑
| 坑 | 说明 |
|---|---|
| 以为 SVD 要求方阵 | 任何 $m\times n$ 矩阵都有 SVD,这正是它的威力 |
| 奇异值当特征值用 | 奇异值 $\ge0$,特征值可以是负的、复的 |
| $U$ 和 $V$ 维度搞混 | $U$ 是 $m\times m$,$V$ 是 $n\times n$,大小不同 |
| 重构时没截断 | $A = U\Sigma V^T$ 里,$\Sigma$ 的形状要和 $U$、$V$ 的列数匹配 |
| 以为奇异值排序无所谓 | numpy 返回时已按降序排好,这个顺序就是重要性顺序 |
用 A.T@A 的特征值算奇异值 |
数学上对,数值上会损失精度,实际用 svd |
| 以为"低秩近似"是随便截断 | Eckart–Young 定理保证它是最优的 |
| 符号约定 | $U$、$V$ 的列符号可以整体翻转(不影响重构),不同库可能不同 |
7 线性代数到此为止
学完这 9 个阶段,你手里有这些能力。
| 能力 | 具体 |
|---|---|
| 判断解的存在性 | 用秩,不用解方程 |
| 解线性方程组 | 消元、逆矩阵、伪逆 |
| 理解线性变换 | 拉伸、旋转、投影 |
| 找"不变的量" | 特征值、行列式、迹、秩 |
| 分解矩阵 | 对角化、正交对角化、SVD |
| 知道什么时候"不能算" | 奇异、病态、共线性 |
⭐ 这些是大二所有量化课程的地基:
多元统计、数值分析、机器学习、心理测量、脑影像分析,
没有一个是能绕开线性代数的。
8 🧠 这跟认知科学有什么关系
这一节的对应关系是我验证过的(用代码实算),但心理学结论请对照文献。
表征相似性分析(RSA)和 MDS = SVD 在认知神经科学里最直接的两次出场。
8.1 RSA 是怎么做的
表征相似性分析(Representational Similarity Analysis) 是近十几年认知神经科学最流行的方法之一。
核心想法:
不要问"这个神经元对哪个刺激反应强",而要问"哪些刺激在这个区域里更相似"。
步骤:
| 步 | 做什么 | 数学 |
|---|---|---|
| 1 | 每种刺激得到一组神经活动模式 | 向量 |
| 2 | 两两算距离,得到 RDM(表征不相似矩阵) | 距离矩阵 |
| 3 | 比较不同脑区/不同模型的 RDM | 相关系数 |
| 4 | (可选)把 RDM 投影到低维看结构 | MDS = 特征分解 |
8.2 实算给你看
我造了 5 个刺激在 3 个"神经元"上的活动模式:
$$P = \begin{pmatrix}1.0&0.5&0.0\\0.9&0.6&0.1\\0.0&0.0&1.0\\0.1&0.1&0.9\\0.5&0.5&0.5\end{pmatrix}$$
算两两之间的欧氏距离,得到 RDM:
$$\text{RDM} = \begin{pmatrix}0&0.173&1.5&1.334&0.707\\0.173&0&1.407&1.237&0.574\\1.5&1.407&0&0.173&0.866\\1.334&1.237&0.173&0&0.693\\0.707&0.574&0.866&0.693&0\end{pmatrix}$$
看第一二行:刺激 1 和刺激 2 的距离是 $0.173$(很近),刺激 3 和 4 也是 $0.173$。
说明它们分别构成了两组。
8.3 MDS:把 RDM 变回坐标(这一步用特征分解)
经典 MDS 的做法:
- 双中心化:$B = -\frac12 J(\text{RDM}^2)J$,其中 $J = I - \frac1n\mathbf{1}\mathbf{1}^T$
- 对 $B$ 做特征分解:$B = V\Lambda V^T$
- 取前 $k$ 个正特征值,坐标 = $V_k\sqrt{\Lambda_k}$
实算结果($B$ 的特征值):
$$-0,\ -0,\ 0,\ 0.039,\ \mathbf{1.893}$$
只有 2 个是正的,说明这 5 个刺激本质上活在 2 维里(虽然原始数据是 3 维)。
MDS 二维坐标:
| 刺激 | 坐标 |
|---|---|
| 1 | $(-0.716,\ 0.113)$ |
| 2 | $(-0.622,\ -0.033)$ |
| 3 | $(0.783,\ 0.055)$ |
| 4 | $(0.614,\ 0.014)$ |
| 5 | $(-0.059,\ -0.149)$ |
1、2 在左边,3、4 在右边,和 RDM 的结构对应。
前两个特征值解释了 100% 的(正)变异(实算确认)。
用这 2 维坐标重构距离,最大误差为 $0.0$,完美还原(实算确认)。
⭐ 所以 MDS 和 PCA 和 SVD 是同一件事的三种说法:
- 对数据矩阵做 SVD → PCA
- 对距离矩阵做特征分解 → 经典 MDS
- $A$ 的右奇异向量 = $A^TA$ 的特征向量 = PCA 的主成分方向
8.4 为什么 RSA 在认知科学里这么重要
| 优势 | 说明 |
|---|---|
| 不受"单位"影响 | 不同脑区、不同测量方式的活动强度不可比,但距离结构可比 |
| 能做模型比较 | 把"行为模型""计算模型"的预测也变成 RDM,看哪个和脑区最像 |
| 能跨模态 | fMRI、EEG、MEG、单细胞记录,全都能算 RDM |
| 能比较物种 | 猴子的 RDM 和人的 RDM 可以直接比 |
典型应用:
| 研究问题 | 怎么做 |
|---|---|
| 视觉皮层怎么表征物体 | 算物体之间的 RDM,看是否符合某个 CNN 层的 RDM |
| 语言理解的脑区分工 | 比较句子/词的 RDM |
| 记忆的神经表征 | 比较"学过的"和"新"刺激的 RDM |
| 个体差异 | 比较不同被试的 RDM 相似度 |
⚠️ 诚实说明:
- 上面的 RDM、双中心化特征值、MDS 坐标、重构误差都是我实算的(可复现),
"经典 MDS = 双中心化 + 特征分解""SVD 与 PCA 的关系"是数学事实。
- 但 RSA 的统计推断怎么做(怎么检验"两个 RDM 真的相关吗")
是一个活跃的方法学领域,早期做法有不少批评,
近年有置换检验、噪声上限校正等更严格的方法。
- MDS 有经典 MDS 和非度量 MDS 等多种变体,本讲义只演示了经典 MDS。
- 请以 Kriegeskorte et al. (2008, Frontiers in Systems Neuroscience)
和 Representational Similarity Analysis 方向的后续文献为准。
我保证数学,不保证统计推断的做法。
9 数学作业 S16
- [ ] 1. 求 $A = \begin{pmatrix}2&0\\0&3\\0&0\end{pmatrix}$ 的 SVD(直接看就能答)
- [ ] 2. 对 $A = \begin{pmatrix}3&1\\1&3\\0&0\end{pmatrix}$,写出 $U$、$\Sigma$、$V^T$ 的形状,并说明为什么 $U$ 和 $V$ 大小不同
- [ ] 3. 验证 $A^TA$ 的特征值 = 奇异值的平方(用作业 2 的 $A$)
- [ ] 4.(重点)说明"任何矩阵都有 SVD"为什么比"只有方阵才能对角化"更强
- [ ] 5. 对称矩阵 $\begin{pmatrix}4&2\\2&-3\end{pmatrix}$ 的特征值和奇异值分别是什么?为什么不一样?
- [ ] 6. 为什么"奇异值永远 $\ge 0$"而"特征值可以是负的"?
- [ ] 7.(重点)用 Eckart–Young 定理说明:为什么"只保留前几个奇异值"是最优的低秩近似?
- [ ] 8. 说明 SVD 和 PCA 的关系(用"奇异值平方 = $A^TA$ 的特征值"这条)
- [ ] 9. 为什么线性回归里"$X^TX$ 不可逆"不是死路?伪逆做了什么?
- [ ] 10.(Python)用 numpy 做一个完整的 SVD demo:分解、验证重构、低秩近似、以及与特征值的关系
第 10 题参考代码:
import numpy as np
np.set_printoptions(precision=4, suppress=True)
def svd_demo(A, name=""):
"""完整演示 SVD 的各个性质。"""
A = np.asarray(A, dtype=float)
m, n = A.shape
U, S, Vt = np.linalg.svd(A)
print(f"=== {name or A.tolist()} ===")
print(f"A 的形状: {m}×{n}")
print(f"U 形状 {U.shape}, 奇异值 {len(S)} 个, V^T 形状 {Vt.shape}")
print("奇异值:", S)
# ① 重构(注意 U 要取前 k 列,k = 奇异值个数)
k = len(S)
recon = U[:, :k] @ np.diag(S) @ Vt
print("重构正确?", np.allclose(A, recon))
# ② 正交性
print("U 正交?", np.allclose(U.T @ U, np.eye(m)))
print("V 正交?", np.allclose(Vt @ Vt.T, np.eye(n)))
# ③ 与特征值的关系:A^T A 的特征值 = 奇异值²
evals = np.linalg.eigvalsh(A.T @ A)
print("A^T A 特征值 :", np.sort(evals))
print("奇异值的平方 :", np.sort(S ** 2))
print("两者一致?", np.allclose(np.sort(evals), np.sort(S ** 2)))
# ④ 低秩近似
if min(m, n) > 1:
print("低秩近似(相对误差):")
for kk in range(1, min(m, n) + 1):
Ak = U[:, :kk] @ np.diag(S[:kk]) @ Vt[:kk]
err = np.linalg.norm(A - Ak) / np.linalg.norm(A)
print(f" 前 {kk} 个奇异值: {err:.6f}")
print()
svd_demo([[3, 1], [1, 3], [0, 0]], "3×2 矩形矩阵")
svd_demo([[2, 1], [1, 2]], "对称矩阵")
svd_demo([[4, 2], [2, -3]], "有负特征值的对称矩阵")
print("=== 伪逆 vs 普通逆 ===")
X = np.array([[1., 2.], [2., 4.], [3., 6.]]) # 列线性相关(第2列=第1列×2)
print("X =\n", X)
print("X^T X 行列式 =", round(np.linalg.det(X.T @ X), 10), " ← 0,不可逆!")
try:
np.linalg.inv(X.T @ X)
print("求逆成功?")
except np.linalg.LinAlgError as e:
print("np.linalg.inv 失败:", e)
pinv = np.linalg.pinv(X)
print("伪逆形状:", pinv.shape)
print("伪逆能算出来 ✓(这就是共线性时的兜底方案)")
第二部分 · 编程
10 为什么需要环境管理
先看一个真实的灾难:
# 三个月前的你
pip install numpy pandas matplotlib
# 现在的你(跑同一个脚本)
ImportError: cannot import name 'foo' from 'numpy'
# 或者更糟:能跑,但结果和之前不一样
问题出在哪:
| 问题 | 后果 |
|---|---|
| 装的包越来越多 | 版本互相冲突,没人知道哪个包依赖哪个 |
| 升级了某个包 | 三个月前的代码跑不动了 |
| 换电脑 / 别人跑 | "在我电脑上能跑",经典的不可复现 |
| 一个项目要 numpy 1.x,另一个要 2.x | 无法共存 |
环境管理解决的就是这件事:给每个项目一套独立的、版本明确的包。
11 三层的"环境"概念
先分清楚三个东西:
| 层级 | 是什么 | 怎么看 |
|---|---|---|
| Python 解释器 | 哪个 Python 在跑 | which python / sys.executable |
| 环境 | 一套独立的包安装目录 | 虚拟环境 / conda 环境 |
| 包 | 具体的库和版本 | pip list / conda list |
你现在的情况(本项目用的):
~/miniconda3/bin/python ← 解释器
~/miniconda3/envs/... ← conda 环境(base 是默认的)
numpy 2.5.3, pandas 3.0.5, matplotlib 3.11.1, scipy 1.18.1, sympy 1.14.0
检查你自己环境的命令:
~/miniconda3/bin/python -c "import sys; print(sys.executable)"
~/miniconda3/bin/python -c "import numpy; print(numpy.__version__)"
~/miniconda3/bin/python -m pip list
12 venv:Python 自带的虚拟环境
venv 是标准库自带的(不用装任何东西),最轻量。
# ① 在项目目录创建虚拟环境(会生成 .venv/ 目录)
python -m venv .venv
# ② 激活它
source .venv/bin/activate # macOS / Linux
# .venv\Scripts\activate # Windows(PowerShell)
# ③ 激活后,python 和 pip 都指向这个环境了
which python # → .../.venv/bin/python
pip install numpy pandas
# ④ 用完退出
deactivate
我实跑验证过:
$ .venv/bin/python -c "import sys; print(sys.executable)"
/private/tmp/envtest/.venv/bin/python ← 指向虚拟环境 ✓
$ ~/miniconda3/bin/python -c "import sys; print(sys.executable)"
/Users/mamingyu/miniconda3/bin/python ← 系统环境,不受影响 ✓
⭐ 核心理解:虚拟环境就是"一套独立的包安装目录"。
激活的本质是改PATH,让python指向那一套。
12.1 venv vs conda
venv |
conda |
|
|---|---|---|
| 来源 | Python 自带 | 需安装 miniconda/anaconda |
| 管什么 | 只管 Python 包 | 还能管非 Python 依赖(如 C 库、R) |
| 速度 | 快、轻量 | 慢一些 |
| 适合 | 纯 Python 项目 | 科学计算、需要底层库 |
你现在用 conda(因为已经装了 miniconda),两个都行。建议:
- 课程作业、小项目 →
venv就够 - 要装 PyTorch / 需要 CUDA 的科学计算 →
conda更省事
12.2 conda 常用命令(你用得上)
conda create -n myenv python=3.11 # 建环境
conda activate myenv # 激活
conda deactivate # 退出
conda env list # 列出所有环境
conda install numpy pandas # 装包
conda env export > environment.yml # 导出环境(复现用)
conda env create -f environment.yml # 从文件重建环境
13 requirements.txt:让别人能复现你的环境
这是项目的"依赖清单"。
# 导出当前环境的所有包
pip freeze > requirements.txt
# 别人(或者三个月后的你)一键装齐
pip install -r requirements.txt
更好的写法:只写直接依赖,不要 pip freeze 的全量列表
numpy>=2.0
pandas>=2.2
matplotlib>=3.8
scipy>=1.11
两种写法的对比:
| 写法 | 优点 | 缺点 |
|---|---|---|
pip freeze 全量 |
完全可复现 | 几百行,含所有间接依赖,难读 |
| 手写直接依赖 | 可读、好维护 | 依赖解析可能给出不同版本 |
💡 实践建议:
开发时用手写清单(好读),要严格复现时用全量pip freeze。
或者用pip-tools/poetry/uv这类工具管理,不过大一上你还不需要。
给阶段 14 的项目加上依赖清单:
cd ~/Desktop/大一/编程/项目-反应时分析
建一个 requirements.txt:
# 这个项目只用了标准库,所以没有第三方依赖。
# 但显式写出来,是为了说明"依赖清单"这件事本身。
#
# 如果以后用到了 numpy:
# numpy>=2.0
⭐ 注意阶段 14 的项目其实只用标准库,
这本身就是个好设计:依赖越少,越容易在别人电脑上跑起来。
"零依赖"是最强的可复现性。
14 换电脑 / 换电脑后的完整复现流程
这是技能卡 16 的主题,这里先给流程。
场景:你从宿舍的电脑换到图书馆的电脑(或者买了新电脑),想把项目跑起来。
| 步 | 做什么 | 命令 |
|---|---|---|
| 1 | 拿到代码 | git clone <url> |
| 2 | 确认 Python 版本 | cat .python-version 或看 README |
| 3 | 建独立环境 | python -m venv .venv |
| 4 | 激活 | source .venv/bin/activate |
| 5 | 装依赖 | pip install -r requirements.txt |
| 6 | 跑测试 | python 自检.py ← 先跑测试,不要先跑主程序 |
| 7 | 跑主程序 | python main.py |
⭐ 第 6 步的顺序很重要:
先跑测试,能立刻知道"是环境问题还是数据问题"。
测试过了说明环境没问题,那主程序出问题就是数据或参数的问题。
15 大一上阶段总结
16 个阶段走完了。盘点一下你拿到了什么。
15.1 数学
| 模块 | 掌握 |
|---|---|
| 极限 | ε-N、ε-δ、等价无穷小、洛必达 |
| 微分 | 定义、链式法则、中值定理、凹凸性、作图 |
| 积分 | 换元、分部、定积分、面积/体积/平均值、反常积分 |
| 线性代数 | 行列式、矩阵运算、逆、秩、方程组、向量空间、特征值、对角化、SVD |
15.2 编程
| 模块 | 掌握 |
|---|---|
| 语言基础 | 类型、可变性、运算符、条件、循环 |
| 数据结构 | 列表、字典、集合、元组、字符串 |
| 函数 | 参数、作用域、返回值、可变默认参数 |
| 面向对象 | 类、实例、继承、魔术方法、property、组合 |
| 文件与异常 | 读写、路径、异常处理 |
| 模块化 | 拆文件、import、__main__ |
| 工具 | Git(提交/分支/合并/远程)、调试器 |
| 环境 | venv/conda、requirements.txt、可复现 |
15.3 张技能卡(这可能是最实用的收获)
| # | 技能 |
|---|---|
| 01 | 读懂报错信息 |
| 02 | 自己找答案(help/dir) |
| 03 | 循环出错怎么查 |
| 04 | 函数调试 + assert |
| 05 | 给函数写检查用例 |
| 06 | 读懂别人的函数签名 |
| 07 | 画内存图,理解"可变" |
| 08 | 数据结构怎么选 |
| 09 | 字符串怎么切、怎么格式化 |
| 10 | 文件读写与路径 |
| 11 | 拆分代码成多个文件 |
| 12 | 类与实例的关系 |
| 13 | Git 基本工作流 |
| 14 | 从零把一个项目跑起来 |
| 15 | 用调试器定位 bug |
| 16 | 换电脑怎么复现环境 |
15.4 你现在的实际能力
能独立完成的:
- ✅ 写一个多文件、有测试、能被别人跑起来的 Python 项目
- ✅ 处理真实实验数据(读文件 → 清洗 → 统计 → 出报告)
- ✅ 用 Git 管理代码、推上 GitHub
- ✅ 用 sympy/numpy 验证数学结论
- ✅ 遇到报错能自己定位(读报错 → 二分法 → 调试器)
这正是《学习路线》里"阶段 16 末"定的目标的一部分:
GPA 稳住 + 1 个 GitHub 项目 + Python 语法全通 + 高数上/线代基础扎实
16 接下来做什么
《学习路线》的规划是:
| 时间 | 数学 | 编程 |
|---|---|---|
| 大一下 | 高数(下):多元微积分、重积分、级数 | 概率论 | numpy/pandas/matplotlib | 第一个数据分析项目 | 英语攻坚(雅思) |
| 大二上 | 数理统计、离散数学、常微分方程 | 工程能力(测试/环境/命令行)| PsychoPy | 进实验室 |
| 大二下 | 多元统计、数值分析 | 面向对象进阶 | PyTorch 入门 |
但有几件事比"进入下一阶段"更紧急:
| 优先级 | 做什么 | 为什么 |
|---|---|---|
| ⭐⭐⭐ | 把阶段 01–16 的作业补完 | 跳过作业的进度是假的 |
| ⭐⭐⭐ | 把阶段 14 的项目推上 GitHub | 这是你简历的第一块砖 |
| ⭐⭐ | 把编程 4 篇笔记 + 2 份速查表过一遍 | 建立你自己的检索体系 |
| ⭐⭐ | 英语按计划推进 | 有截止日的东西不能拖 |
| ⭐ | 复习 16 份讲义的自评问题 | 答不上就回去补 |
⭐ 进度可以慢,但不能跳过作业。
你已经走完了大一上最难的 16 个阶段,这本身就不容易。
接下来是把它变成"真的会",不能停在"看起来完成了"。
17 🔧 技能卡 16:换电脑怎么复现环境
技能卡 15 教你修 bug;这张卡教你"在另一台电脑上,从零把这个项目跑起来"。这是可复现性的最后一环,也是你以后发给导师或合作者的第一件事。
17.1 为什么会"在我电脑上能跑"
你的电脑上有一堆你没意识到的东西:
| 隐含依赖 | 举例 |
|---|---|
| Python 版本 | 3.11 和 3.13 行为可能不同 |
| 第三方包版本 | numpy 1.x 和 2.x 有 breaking change |
| 全局装的包 | 你三个月前 pip install 过,自己忘了 |
| 环境变量 | PYTHONPATH、PATH 被改过 |
| 操作系统差异 | 路径分隔符、换行符 |
"在我电脑上能跑"是软件工程里最经典的借口,也是最该被消除的。
17.2 五个复现要素(缺一不可)
| # | 要素 | 文件/做法 |
|---|---|---|
| 1 | 代码 | Git 仓库 |
| 2 | Python 版本 | README 写明,或 .python-version |
| 3 | 依赖清单 | requirements.txt 或 environment.yml |
| 4 | 数据 | 放进 data/,或用脚本下载(大文件别进 Git) |
| 5 | 操作说明 | README 里写清楚"怎么跑" |
⭐ 判断标准:把仓库发给一个陌生人,他能不能跑出和你一样的结果?
能,就叫"可复现"。
17.3 完整流程(存下来,以后照着做)
在"发送方"(你的电脑):
cd 项目目录
git status # ① 确认要提交的都提交了
cat .gitignore # ② 确认该忽略的忽略了
# ③ 记录 Python 版本
python --version > .python-version
# ④ 生成依赖清单
pip freeze > requirements.txt
# ⑤ 推送
git add . && git commit -m "整理依赖清单和环境说明"
git push
在"接收方"(另一台电脑):
git clone <仓库地址>
cd 项目目录
python -m venv .venv # ① 建独立环境
source .venv/bin/activate # ② 激活
pip install -r requirements.txt # ③ 装依赖
python 自检.py # ④ 先跑测试!
python main.py # ⑤ 再跑主程序
17.4 README 必须写清楚的四件事
① 环境要求,写清 Python 版本和依赖
## 环境要求
- Python 3.11+
- 依赖见 requirements.txt
② 怎么跑,命令逐步列出
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python 自检.py # 先跑测试确认环境
python main.py # 跑主流程
③ 数据从哪来,文件在哪、什么格式
## 数据从哪来
data/raw.csv,格式见下方说明。
④ 已知问题。这一节最容易被省略,但值得写
## 已知问题
- 剔除阈值是我自己定的,不同阈值结果会变
- 还没处理缺失值
⚠️ "已知问题"这一节防止别人(和三个月后的你)误以为"结果就是这样"。
17.5 三条新手铁律
① .venv/ 永远不要提交
写进 .gitignore(阶段 13 讲过)。
它是几百 MB 的本地文件,换电脑后完全没用,别人自己重建就行。
② 依赖清单要么精确,要么宽松,但不要说谎
numpy(不写版本)意味着"任何版本都行",但可能不是真的。
如果代码对版本敏感,必须写 numpy>=2.0,<3。
③ 先跑测试,再跑主程序
自检.py 过了 → 环境没问题 → 主程序出问题一定是数据/参数问题。
这一步能省你大量排查时间。
💡 遇到"别人跑不了你的代码":
让他把完整报错发给你,然后问三个问题:
1.python -V是什么?
2.pip list里有没有你用的包?
3. 是不是从项目根目录运行的?这三个问题能解决 90% 的"跑不了"。
18 🧠 这跟认知科学有什么关系(编程侧)
概念性内容,无法用代码验证,请对照文献。
环境管理 ↔ 认知科学里的"语境依赖性"和"编码特异性"。记忆不是独立于环境的。
18.1 编程侧的事实
"在我电脑上能跑"是一个真实的现象:同一份代码,在不同环境下行为不同。
| 环境因素 | 影响 |
|---|---|
| Python 版本 | 语法/行为差异 |
| 包版本 | API 变更 |
| 全局状态 | 你无意中改过的配置 |
| 工作目录 | 相对路径指向不同地方 |
关键洞察:代码的"行为"不是代码自带的属性。它属于"代码 + 环境"这个整体。
18.2 认知科学里的对应物
编码特异性原则(encoding specificity principle)(Tulving & Thomson, 1973):
提取的效果取决于"提取时的线索"和"编码时的情境"匹配程度。
| 现象 | 说明 |
|---|---|
| 情境依存记忆(context-dependent) | 在同一个房间学的,在同一个房间考得更好 |
| 状态依存记忆(state-dependent) | 在某种情绪/药物状态下学的,同样状态下更容易回忆 |
| 移植效应(transfer-appropriate processing) | 学习时的加工方式要和考试要求匹配 |
| 气味/声音线索 | 特定气味能显著提高回忆率 |
和"环境管理"的对照:
| 编程 | 认知 |
|---|---|
| 代码 + 环境 → 行为 | 记忆 + 情境 → 提取 |
| 换环境 → 行为可能变 | 换情境 → 回忆变差 |
requirements.txt 冻结环境 |
回到编码时的情境能提高提取 |
| 隔离环境消除干扰 | 减少情境变化减少遗忘 |
⭐ 这个对照有实际的用处:
"复习要在和考试相似的情境下",这不是迷信,
是编码特异性原则的直接推论。
如果你考试在教室、复习在宿舍,你可能损失一部分成绩,纯粹因为情境不匹配。
(但要注意:情境依存效应通常不大,不要指望靠这个逆天改命。)
18.3 一个更深的对照:认知的"语境依赖性"
认知科学里"语境"无处不在:
| 现象 | 语境的作用 |
|---|---|
| 词汇识别 | "bank"在"river"和"money"语境下加工不同 |
| 范畴判断 | 企鹅是"鸟"吗?取决于语境 |
| 句子理解 | 同一个词在不同句子里激活不同的意义 |
| 推理 | 沃森选择任务里,语境能大幅改变正确率 |
| 情绪知觉 | 同一个表情在不同情境下判断不同 |
"同一刺激,不同语境,不同反应",
这和"同一代码,不同环境,不同行为"是同一个结构。
⚠️ 诚实说明:
- 编码特异性原则和情境依存记忆是认知心理学的重要发现,
有大量实验支持(Godden & Baddeley 1975 的水下实验是经典),
但效应的强度在不同条件下差异很大,有时很小。
- "情境依存"和"编码特异性"是相关但不同的概念,
本节的概括是简化版。
- 程序环境与认知语境的对照是教学类比,
不是说"大脑是虚拟机"或"记忆是数据库"。请以文献为准。
19 编程作业 S16
- [ ] 1. 用
python -m venv建一个虚拟环境,激活,确认which python变了 - [ ] 2. 在虚拟环境里装一个包,退出后确认系统环境里没有这个包
- [ ] 3. 给阶段 14 的项目写
requirements.txt和.python-version - [ ] 4. 完善项目
README.md:补上"环境要求"和"已知问题"两节 - [ ] 5.(重点)把项目推上 GitHub,然后换个目录 clone 下来,按 README 完整跑一遍
- [ ] 6. 验证
.venv/没有被提交(git status里不该出现) - [ ] 7. 故意改坏
.venv(删掉某个包),看自检.py报什么错,说明测试如何帮你定位环境问题 - [ ] 8. 解释
venv和conda的区别,什么情况下该用哪个 - [ ] 9.(技能)假设朋友 clone 了你的项目,跑
python main.py报ModuleNotFoundError: No module named 'pandas',写出你的三条排查回复 - [ ] 10.(技能)给项目写一份"新人上手"检查清单(照技能卡 16 的流程)
- [ ] 11.(Python)写一个脚本
检查环境.py:输出 Python 版本、关键包版本、当前工作目录,用于排查环境问题
第 11 题参考写法:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""环境体检:把"我这台机器上是什么情况"一次性打印出来。
遇到"在我电脑上能跑"的问题时,先让对方跑这个脚本。
"""
import platform
import sys
from pathlib import Path
# 关心的包(按需增删)
PACKAGES = ["numpy", "pandas", "matplotlib", "scipy", "sympy"]
def main():
print("=" * 46)
print("环境体检报告")
print("=" * 46)
print(f"Python 版本 : {sys.version.split()[0]}")
print(f"解释器路径 : {sys.executable}")
print(f"操作系统 : {platform.system()} {platform.release()}")
print(f"当前工作目录 : {Path.cwd()}")
print(f"脚本所在目录 : {Path(__file__).resolve().parent}")
print()
print("第三方包:")
import importlib.metadata as md
for name in PACKAGES:
try:
print(f" {name:<12} {md.version(name)}")
except md.PackageNotFoundError:
print(f" {name:<12} ⚠️ 未安装")
print()
print("提示:如果某个包装了但版本不对,")
print(" 对照 requirements.txt 重新 pip install -r requirements.txt")
if __name__ == "__main__":
main()
输出大概是这样:
==============================================
环境体检报告
==============================================
Python 版本 : 3.14.0
解释器路径 : /Users/mamingyu/miniconda3/bin/python
操作系统 : Darwin 25.6.0
当前工作目录 : /Users/mamingyu/Desktop/大一
脚本所在目录 : /Users/mamingyu/Desktop/大一
第三方包:
numpy 2.5.3
pandas 3.0.5
matplotlib 3.11.1
scipy 1.18.1
sympy 1.14.0
提示:如果某个包装了但版本不对,
对照 requirements.txt 重新 pip install -r requirements.txt
💡 把
检查环境.py放进项目里,它能一次性回答"是环境问题还是代码问题"。
遇到"跑不了"的问题,先让对方跑它,比来回问十句都快。
第 9 题答案:
回复三条:
1. 你 `python -V` 是什么版本?
(Python 版本不匹配会导致各种诡异问题)
2. 你 `pip list | grep pandas` 有没有输出?
没有 → pip install -r requirements.txt
有但版本不对 → 同上,用清单里的版本
3. 你是从项目根目录运行的吗?
(相对路径依赖工作目录;从别的目录跑会 FileNotFoundError)
如果这三条都没问题,让 TA 跑 检查环境.py,把输出发过来。
本阶段自评
这是最后一份自评,问题多一些,它同时是一次总复习。
- 数学:SVD 的完整形式是什么?$U$、$\Sigma$、$V^T$ 各自的形状和性质?
- 数学:SVD 的几何意义(三个动作)是什么?
- 数学:奇异值和 $A^TA$ 的特征值是什么关系?
- 数学:为什么"任何矩阵都有 SVD"比"可对角化"适用范围广?举一个没有实特征值但有奇异值的矩阵
- 数学:对称矩阵的奇异值和特征值差在哪?为什么?
- 数学:Eckart–Young 定理说了什么?为什么它保证低秩近似是最优的?
- 数学:伪逆解决什么问题?它和线性回归的关系是什么?
- 数学:把"行列式、秩、特征值、奇异值"四个概念串成一句话
- 编程:虚拟环境解决的核心问题是什么?激活的本质是什么?
- 编程:
venv和conda各适合什么场景? - 编程:
pip freeze全量清单和手写清单各自的优缺点? - 编程:
.venv/为什么绝对不能提交? - 技能:复现一个项目的七个步骤是什么?为什么第 6 步是"先跑测试"?
- 技能:判断"可复现"的标准是什么(一句话)?
- 技能:朋友说"跑不了",你问哪三个问题?
- 连接:编码特异性原则是什么?它对你的复习方式有什么实际启示?
- 总复习:16 张技能卡里,你觉得最有用的是哪三张?为什么?
- 总复习:16 个阶段里,哪个概念你到现在还有点虚?(诚实回答,然后回去补)
全部答得上 → 大一上主线完成。
但这不等于结束:
- 把 16 份作业补完(这是最重要的事)
- 把阶段 14 的项目推上 GitHub
- 把 2 份速查表用起来(编程代码 + 数学公式)
- 然后按《学习路线》进入大一下进度可以慢,但不能跳过作业。