#!/usr/bin/env python3 """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 = [] # Title md(cells, f"# {m['title']}\n\n{m['desc']}") # Background md(cells, f"""## 背景 {m['desc']} 本 notebook 演示 {m['title']} 的完整实现,模型代码见 `ml/basics/{m['name']}.py`。 """) # Math md(cells, f"""## 数学原理 {m['formula']} """) # Import + run code(cells, m['import_code']) code(cells, m['run_code']) # Questions 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}")