| |
| """Batch-generate all basics notebooks.""" |
|
|
| import nbformat as nbf |
|
|
| MODELS = [ |
| { |
| "name": "logistic_regression", |
| "title": "Logistic Regression", |
| "desc": "Single Linear layer + Softmax for MNIST digit classification (92.3%).", |
| "formula": r"""$$P(y=c \mid x) = \frac{e^{w_c \cdot x + b_c}}{\sum_{j=1}^{10} e^{w_j \cdot x + b_j}}$$ |
| |
| $$\mathcal{L} = -\frac{1}{N} \sum_{i=1}^N \log P(y_i \mid x_i)$$""", |
| "import_code": "from ml.basics.logistic_regression import train", |
| "run_code": "train()", |
| "questions": [ |
| "Logistic Regression 和 Linear Regression 的区别是什么?(输出 vs 损失函数)", |
| "为什么用 Softmax + CrossEntropy 而不是 MSE 做分类?", |
| "把学习率从 0.1 改到 0.01,准确率会怎么变?试试。", |
| ], |
| }, |
| { |
| "name": "linear_regression", |
| "title": "Linear Regression", |
| "desc": "Normal Equation + Gradient Descent on California Housing (R²=0.583).", |
| "formula": r"""$$\theta = (X^T X)^{-1} X^T y \quad \text{(Normal Equation)}$$ |
| |
| $$\theta \leftarrow \theta - \alpha \cdot \frac{2}{m} X^T (X\theta - y) \quad \text{(Gradient Descent)}$$""", |
| "import_code": "from ml.basics.linear_regression import train", |
| "run_code": "train()", |
| "questions": [ |
| "Normal Equation 和 Gradient Descent 的优缺点?", |
| "特征标准化为什么对 GD 重要而对 Normal Equation 不重要?", |
| "把学习率从 0.1 改到 1.0,GD 还会收敛吗?", |
| ], |
| }, |
| { |
| "name": "svm", |
| "title": "Support Vector Machine", |
| "desc": "Primal GD + Dual SMO with Linear and RBF kernels (MNIST 93.3%).", |
| "formula": r"""**Primal (hinge loss + L2):** |
| |
| $$\min \frac{1}{n} \sum \max(0, 1 - y_i (w \cdot x_i + b)) + \lambda \|w\|^2$$ |
| |
| **Dual (kernel trick):** |
| |
| $$\max \sum \alpha_i - \frac{1}{2} \sum \sum \alpha_i \alpha_j y_i y_j K(x_i, x_j)$$""", |
| "import_code": "from ml.basics.svm import main", |
| "run_code": "main()", |
| "questions": [ |
| "RBF kernel 为什么能处理非线性可分数据?", |
| "SMO 为什么选择两个 α 同时优化而不是一个?", |
| "参数 C 越大,模型更偏向于什么(大间隔还是少错误)?", |
| ], |
| }, |
| { |
| "name": "perceptron", |
| "title": "Perceptron", |
| "desc": "Single neuron with step activation (Rosenblatt 1958).", |
| "formula": r"""$$y = \text{sign}(w \cdot x + b)$$ |
| |
| **更新规则(误分类时):** |
| |
| $$w \leftarrow w + \eta \cdot y \cdot x$$ |
| |
| $$b \leftarrow b + \eta \cdot y$$""", |
| "import_code": "from ml.basics.perceptron import demo", |
| "run_code": "demo()", |
| "questions": [ |
| "Perceptron 为什么只能解决线性可分问题?", |
| "Perceptron 和 Logistic Regression 的核心区别是什么?", |
| "Perceptron Convergence Theorem 保证什么?", |
| ], |
| }, |
| { |
| "name": "k_means", |
| "title": "K-Means", |
| "desc": "Unsupervised clustering on MNIST, 57.8% cluster purity.", |
| "formula": r"""**E-step(分配):** |
| |
| $$c_i = \arg\min_k \|x_i - \mu_k\|^2$$ |
| |
| **M-step(更新):** |
| |
| $$\mu_k = \frac{1}{|C_k|} \sum_{i \in C_k} x_i$$""", |
| "import_code": "from ml.basics.k_means import train", |
| "run_code": "train()", |
| "questions": [ |
| "K-Means 一定能收敛吗?收敛到全局最优吗?", |
| "k 值怎么选?(提示:肘部法则)", |
| "为什么 K-Means 对初始中心点敏感?K-Means++ 怎么改进?", |
| ], |
| }, |
| { |
| "name": "decision_tree", |
| "title": "Decision Tree", |
| "desc": "ID3/CART on Iris dataset (93.3%, ASCII tree).", |
| "formula": r"""**熵(impurity 度量):** |
| |
| $$H(S) = -\sum p_i \log_2 p_i$$ |
| |
| **信息增益:** |
| |
| $$IG = H(S) - \sum \frac{|S_v|}{|S|} H(S_v)$$""", |
| "import_code": "from ml.basics.decision_tree import demo", |
| "run_code": "demo()", |
| "questions": [ |
| "决策树在 Iris 上只用了哪两个特征?为什么?(提示:print_tree 观察)", |
| "max_depth 太小会欠拟合,太大会过拟合,怎么选?", |
| "信息增益和基尼系数(Gini impurity)有什么区别?", |
| ], |
| }, |
| { |
| "name": "random_forest", |
| "title": "Random Forest", |
| "desc": "Bagging + random feature subsets, multiple decision trees, majority vote (Iris 93.3%).", |
| "formula": r"""**Bootstrap 采样:** 从训练集中有放回地采样 N 个样本 |
| |
| **特征随机子空间:** 每个节点从 $\lfloor \sqrt{m} \rfloor$ 个随机特征中选最优分割 |
| |
| **预测(多数投票):** $\hat{y} = \text{mode}(\{T_1(x), T_2(x), \dots, T_B(x)\})$""", |
| "import_code": "from ml.basics.random_forest import demo", |
| "run_code": "demo()", |
| "questions": [ |
| "为什么 Bagging 能降低方差而不增加偏差?", |
| "特征随机性(max_features='sqrt')的作用是什么?", |
| "增大 n_estimators 会过拟合吗?为什么?", |
| ], |
| }, |
| { |
| "name": "gbdt", |
| "title": "GBDT", |
| "desc": "Gradient Boosting — sequential regression trees fit pseudo-residuals (sin(x) reg + binary cls).", |
| "formula": r"""**初始化:** $F_0(x) = \arg\min_\gamma \sum L(y_i, \gamma)$ |
| |
| **对 m=1..M:** |
| 1. 负梯度(伪残差): $\tilde{y}_i = -\left[\frac{\partial L(y_i, F(x_i))}{\partial F(x_i)}\right]_{F=F_{m-1}}$ |
| 2. 拟合回归树: $h_m(x)$ 到 $\tilde{y}_i$ |
| 3. 更新: $F_m(x) = F_{m-1}(x) + \eta \cdot h_m(x)$""", |
| "import_code": "from ml.basics.gbdt import demo", |
| "run_code": "demo()", |
| "questions": [ |
| "GBDT 用回归树拟合残差,为什么叶子值不是直接取均值?", |
| "学习率 η 和树数量 M 之间的关系是什么?", |
| "LogLoss 的负梯度和 MSE 的负梯度有什么不同?", |
| ], |
| }, |
| { |
| "name": "naive_bayes", |
| "title": "Naive Bayes", |
| "desc": "Gaussian Naive Bayes on MNIST (53.0%) — shows independence assumption gap.", |
| "formula": r"""**贝叶斯定理 + 特征独立假设:** |
| |
| $$P(y \mid x) \propto P(y) \prod_{i=1}^d P(x_i \mid y)$$ |
| |
| **高斯似然:** |
| |
| $$P(x_i \mid y) = \frac{1}{\sqrt{2\pi\sigma_{iy}^2}} \exp\left(-\frac{(x_i - \mu_{iy})^2}{2\sigma_{iy}^2}\right)$$""", |
| "import_code": "from ml.basics.naive_bayes import demo", |
| "run_code": "demo()", |
| "questions": [ |
| "为什么 Logistic Regression(92.3%)远好于 Naive Bayes(53.0%)?", |
| "像素之间真的独立吗?相邻像素的关系是怎样的?", |
| "如果特征满足独立假设,Naive Bayes 是最优分类器吗?", |
| ], |
| }, |
| { |
| "name": "pca", |
| "title": "PCA", |
| "desc": "SVD-based dimensionality reduction, MNIST 2D visualisation.", |
| "formula": r"""**中心化:** $\tilde{X} = X - \bar{x}$ |
| |
| **SVD:** $\tilde{X} = U \Sigma V^T$ |
| |
| **投影到前 k 个主成分:** $X_{\text{proj}} = \tilde{X} \cdot V_{:,:k}$""", |
| "import_code": "from ml.basics.pca import demo", |
| "run_code": "demo()", |
| "questions": [ |
| "PCA 的第一主成分捕获了什么?(在 MNIST 上观察 ASCII 图)", |
| "为什么用 SVD 而不是直接做协方差矩阵的特征分解?", |
| "保留多少主成分能保留 90% 的方差?", |
| ], |
| }, |
| { |
| "name": "knn", |
| "title": "k-NN", |
| "desc": "k-Nearest Neighbors on MNIST — instance-based, no training.", |
| "formula": r"""**欧氏距离:** |
| |
| $$d(x, y) = \sqrt{\sum_{i=1}^d (x_i - y_i)^2}$$ |
| |
| **预测(多数投票):** |
| |
| $$\hat{y} = \text{majority vote of } k \text{ nearest neighbors}$$""", |
| "import_code": "from ml.basics.knn import demo", |
| "run_code": "demo()", |
| "questions": [ |
| "k=1 和 k=10 的区别是什么?(偏差-方差权衡)", |
| "为什么 k-NN 在 784 维空间表现有限?(维度灾难)", |
| 'k-NN 为什么是"懒惰学习"?训练阶段做了什么?', |
| ], |
| }, |
| ] |
|
|
|
|
| def md(cells, s): |
| cells.append(nbf.v4.new_markdown_cell(s)) |
|
|
| def code(cells, s): |
| cells.append(nbf.v4.new_code_cell(s)) |
|
|
|
|
| for m in MODELS: |
| nb = nbf.v4.new_notebook() |
| nb.metadata = { |
| "kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"}, |
| "language_info": {"name": "python", "version": "3.12.0"}, |
| } |
| cells = [] |
|
|
| |
| md(cells, f"# {m['title']}\n\n{m['desc']}") |
|
|
| |
| md(cells, f"""## 背景 |
| |
| {m['desc']} 本 notebook 演示 {m['title']} 的完整实现,模型代码见 `ml/basics/{m['name']}.py`。 |
| """) |
|
|
| |
| md(cells, f"""## 数学原理 |
| |
| {m['formula']} |
| """) |
|
|
| |
| code(cells, m['import_code']) |
| code(cells, m['run_code']) |
|
|
| |
| qs = "\n".join(f"{i+1}. {q}" for i, q in enumerate(m['questions'])) |
| md(cells, f"""## 思考题 |
| |
| {qs} |
| """) |
|
|
| nb.cells = cells |
| path = f"ml/basics/{m['name']}.ipynb" |
| with open(path, "w") as f: |
| nbf.write(nb, f) |
| print(f"Generated {path}") |
|
|