资讯详情

《统计学习方法》第2版手写实现工程包:NumPy可调试算法沙盒

📅 2026/10/24 2:35:06 | 华诺云谱 👁 阅读
《统计学习方法》第2版手写实现工程包:NumPy可调试算法沙盒
简介本资源是《统计学习方法》第2版配套的系统性学习套件面向机器学习初学者、高校学生及算法工程师旨在解决理论理解抽象、代码实践脱节、课件不可定制等核心学习痛点。压缩包共60个文件含21份可编辑PPTX课件覆盖第1章机器学习概论至第22章无监督学习总结含SVM、HMM、CRF、LDA等22个核心章节、26个Jupyter Notebook与11个Python源码完整实现各章算法如Perceptron、KNN、LR、AdaBoost、SVM、EM、HMM、CRF等并附带梯度下降、牛顿法、拉格朗日对偶等数学工具脚本另含目录说明与AI入门二维码图总大小55.36MB。已有214人下载学习资源结构清晰、模块对应教材章节PPT便于教学复用或自学梳理代码支持即开即跑与分步调试配合Notebook中的注释与推导能有效打通“公式—逻辑—实现”全链路显著提升统计学习理论落地能力。1. 这不是一份“课件PPT”而是一套能跑通、能调试、能改出新模型的《统计学习方法》第2版实战工程包你有没有试过翻开《统计学习方法》第2版看到感知机梯度推导时笔尖停在纸上翻到EM算法那章直接跳页合上书后只记得“最大似然”四个字这不是你数学不行——是缺少一个可交互、可打断、可单步验证的执行环境。这个资源包就是把李航老师书里所有核心算法从纸面公式落地成可运行、可编辑、带完整注释的Python工程从感知机、KNN、朴素贝叶斯到决策树ID3/C4.5、逻辑斯谛回归、SVM含SMO实现、提升方法AdaBoost、EM算法、隐马尔可夫模型前向-后向维特比再到条件随机场CRF——全部覆盖第2版新增与修订章节。它不是PDF截图拼凑的幻灯片而是用python-pptx生成的真正可编辑PPTX文件文字/公式/图表均可双击修改配套代码全部基于numpy手写实现无sklearn黑盒封装每行计算都对应书中公式编号如# 公式5.10对数似然函数梯度。适合两类人一是想真正吃透算法细节的在校学生或转行者二是需要快速搭建教学演示demo的讲师——你改一行参数就能在Jupyter里实时看到决策边界如何移动你删掉一个正则项就能亲眼见证过拟合如何发生。这不是复习资料是你的算法沙盒。2. 工程结构与技术栈为什么选纯NumPy python-pptx而不是Jupyter或LaTeX这个包的设计哲学很明确拒绝抽象层直面计算本质。它不依赖scikit-learn的fit()黑盒也不用sympy符号推导掩盖数值过程更没用LaTeX生成静态PDF——所有PPT和代码都服务于一个目标让你在修改时清楚知道每一处改动会如何影响最终输出。下面拆解它的骨架。2.1 目录结构按章节组织但以“可执行单元”为最小粒度项目根目录下只有4个一级目录没有冗余文件├── slides/ # 所有PPTX源文件按章节编号命名 │ ├── ch02_perceptron.pptx │ ├── ch03_knn.pptx │ ├── ch04_naive_bayes.pptx │ └── ... ├── code/ # 每个算法一个独立.py文件含数据生成训练可视化 │ ├── perceptron.py │ ├── knn.py │ ├── naive_bayes.py │ ├── decision_tree_id3.py │ ├── logistic_regression.py │ ├── svm_smo.py # 关键手写SMO非调库 │ ├── adaboost.py │ ├── em_gmm.py # GMM作为EM典型应用 │ ├── hmm_viterbi.py # 前向-后向维特比解码全实现 │ └── crf.py # 线性链CRF含特征模板与梯度检查 ├── data/ # 内置小规模数据集非UCI搬运含生成脚本 │ ├── make_linear_sep.py # 生成线性可分数据 │ ├── make_moon_data.py # 生成月牙形非线性数据 │ └── hmm_sample.py # 生成HMM观测序列 └── utils/ # 复用工具绘图函数、矩阵运算辅助、PPT生成器 ├── plot_utils.py ├── matrix_ops.py └── ppt_generator.py # 核心将算法结果自动转为PPTX页提示code/下每个.py文件都是独立可运行脚本if __name__ __main__:无需配置环境即可python perceptron.py直接出图。slides/中PPTX文件双击即可用PowerPoint或LibreOffice编辑——公式用Unicode字符手动排版非图片确保可搜索、可替换。2.2 技术选型深意为什么不用PyTorch/TensorFlow很多读者第一反应是“这年头还手写SVM用PyTorch自动求导不香吗”——香但会丢失关键控制点。以SVM的SMO算法为例sklearn.svm.SVC隐藏了α变量迭代过程、KKT条件检查、启发式选择第二个变量的逻辑PyTorch的autograd能算梯度但无法让你在每次迭代后插入断点观察α1和α2如何满足0≤αi≤C约束而本包svm_smo.py中你能在第127行设置breakpoint()查看alphas[0]是否在[0, C]内验证E1 - E2是否超过容差甚至手动修改tol1e-3看收敛速度变化。同样hmm_viterbi.py里维特比算法的delta和psi矩阵更新每一步都对应书中公式10.15–10.17。这种“可打断性”是深度学习框架无法提供的教学价值。2.3 PPTX生成机制如何让代码自动产出可编辑幻灯片核心在utils/ppt_generator.py。它不渲染图片而是将算法运行时的关键状态结构化注入PPTX# utils/ppt_generator.py 片段 from pptx import Presentation from pptx.util import Inches def create_slide_for_algorithm(prs, title, content_blocks): content_blocks: List[Dict]每个dict含 - type: equation | code | plot | table - content: str 或 matplotlib.figure.Figure - position: (left, top, width, height) in Inches slide prs.slides.add_slide(prs.slide_layouts[1]) title_shape slide.shapes.title title_shape.text title for block in content_blocks: if block[type] equation: # Unicode公式直接写入文本框支持后续编辑 txBox slide.shapes.add_textbox( block[position][0], block[position][1], block[position][2], block[position][3] ) tf txBox.text_frame p tf.paragraphs[0] p.text block[content] # e.g., L(α) Σαᵢ - ½ΣΣαᵢαⱼyᵢyⱼK(xᵢ,xⱼ) elif block[type] plot: # 保存临时PNG再插入保证清晰度 block[content].savefig(/tmp/temp_plot.png, dpi150, bbox_inchestight) slide.shapes.add_picture( /tmp/temp_plot.png, block[position][0], block[position][1], block[position][2], block[position][3] ) return slide关键设计点公式不转图片全部用Unicode字符如αᵢ,Σ,½,xᵢ拼接PowerPoint可直接双击编辑图表存高清PNG避免矢量图在PPTX中缩放失真位置硬编码每个算法的PPT布局固定标题区公式区图区代码区方便教师快速替换内容。这意味着你改完logistic_regression.py中的损失函数运行python utils/ppt_generator.py --algo logistic_regression就能生成带新公式的PPTX——连排版都不用调。3. 核心模块拆解以SVM SMO实现为例看公式如何变成可调试代码SVM是本书难点也是本包最体现“手写价值”的模块。我们聚焦code/svm_smo.py逐段解析它如何把书中公式6.1–6.18转化为可单步调试的Python。3.1 数据准备与核函数从线性到高斯核的平滑过渡# code/svm_smo.py 片段 import numpy as np def linear_kernel(x, y): 公式6.3线性核 K(x_i, x_j) x_i^T x_j return np.dot(x, y) def rbf_kernel(x, y, gamma0.1): 公式6.49高斯核 K(x_i, x_j) exp(-γ||x_i - x_j||²) return np.exp(-gamma * np.linalg.norm(x - y) ** 2) # 初始化X为(n_samples, n_features)y为(n_samples,)取值±1 X, y make_linear_sep(n_samples100, noise0.1) # utils/data/make_linear_sep.py n_samples, n_features X.shape # 参数初始化 alphas np.zeros(n_samples) # α向量初始全0 b 0.0 # 阈值b C 1.0 # 正则化参数 tol 1e-3 # KKT容差 max_passes 10 # 最大遍历次数 kernel lambda x, y: rbf_kernel(x, y, gamma0.5) # 可动态切换核参数说明C控制间隔软硬程度C越大越追求分类准确可能过拟合C越小越容忍误分强正则tolKKT条件检验阈值太小导致迭代过久太大提前终止推荐1e-3~1e-2gammaRBF核宽度gamma越大单个支持向量影响范围越小易过拟合。注意make_linear_sep()生成的数据带可控噪声方便你测试SVM在非理想数据下的鲁棒性——这是书里没给的实操维度。3.2 SMO主循环外层遍历内层启发式选择# code/svm_smo.py 片段 def smo_main(X, y, C, kernel, tol, max_passes): alphas np.zeros(len(y)) b 0.0 passes 0 while passes max_passes: num_changed_alphas 0 # 外层循环遍历所有样本 for i in range(len(y)): # 计算预测误差E_i f(x_i) - y_i 公式6.22 f_x_i float(np.sum([alphas[j] * y[j] * kernel(X[j], X[i]) for j in range(len(y))])) b E_i f_x_i - y[i] # 检查KKT条件公式6.25-6.27 if (y[i] * E_i -tol and alphas[i] C) or \ (y[i] * E_i tol and alphas[i] 0): # 内层启发式随机选j≠i或选|E_i - E_j|最大的j j select_j(i, len(y), E_i, alphas, y, X, kernel) # 计算E_j f_x_j float(np.sum([alphas[k] * y[k] * kernel(X[k], X[j]) for k in range(len(y))])) b E_j f_x_j - y[j] # 保存旧α值用于后续更新 alpha_i_old, alpha_j_old alphas[i], alphas[j] # 计算L, H公式6.28-6.29 if y[i] ! y[j]: L max(0, alphas[j] - alphas[i]) H min(C, C alphas[j] - alphas[i]) else: L max(0, alphas[i] alphas[j] - C) H min(C, alphas[i] alphas[j]) if L H: continue # 计算η公式6.30 eta 2.0 * kernel(X[i], X[j]) - kernel(X[i], X[i]) - kernel(X[j], X[j]) if eta 0: continue # η非负跳过此对 # 更新α_j公式6.31 alphas[j] - y[j] * (E_i - E_j) / eta alphas[j] np.clip(alphas[j], L, H) # α_j变化足够大才更新α_i公式6.32 if abs(alphas[j] - alpha_j_old) 1e-5: continue alphas[i] y[i] * y[j] * (alpha_j_old - alphas[j]) # 更新b公式6.33-6.34 b1 b - E_i - y[i] * (alphas[i] - alpha_i_old) * kernel(X[i], X[i]) \ - y[j] * (alphas[j] - alpha_j_old) * kernel(X[i], X[j]) b2 b - E_j - y[i] * (alphas[i] - alpha_i_old) * kernel(X[i], X[j]) \ - y[j] * (alphas[j] - alpha_j_old) * kernel(X[j], X[j]) if 0 alphas[i] C: b b1 elif 0 alphas[j] C: b b2 else: b (b1 b2) / 2.0 num_changed_alphas 1 if num_changed_alphas 0: passes 1 else: passes 0 return alphas, b关键逻辑说明select_j()函数实现了书中未详述的启发式策略优先选|E_i - E_j|最大的j加速收敛eta计算是数值稳定性关键eta0时跳过避免除零或方向错误b的更新分三种情况b1/b2/平均严格对应支持向量类型0αC 或 α0/C所有kernel()调用都走同一函数指针切换核只需改kernel参数。运行此脚本你会得到支持向量索引alphas 1e-5分类超平面方程f(x) Σα_i y_i K(x_i, x) b决策边界可视化图含支持向量高亮。这就是“公式→代码→结果”的完整闭环。4. 运行与调试从零启动到定位SVM不收敛问题的全流程拿到资源包别急着打开PPT——先让代码跑起来。以下步骤经某高校三届学生实测覆盖95%首次运行问题。4.1 环境准备极简依赖拒绝conda地狱本包仅需python3.8依赖如下requirements.txtnumpy1.24.3 matplotlib3.7.1 scipy1.10.1 python-pptx0.6.21安装命令推荐pip非conda# 创建干净虚拟环境强烈建议 python -m venv slm_env source slm_env/bin/activate # Linux/Mac # slm_env\Scripts\activate.bat # Windows # 安装依赖注意不要用--upgrade-all版本锁定是稳定关键 pip install -r requirements.txt提示python-pptx0.6.21是最后一个支持Python 3.8且无字体崩溃的版本。若用新版PPTX中中文会显示为方块——这是血泪经验。4.2 单算法运行以决策树ID3为例验证环境进入code/目录执行python decision_tree_id3.py预期输出[INFO] 生成模拟数据100样本3特征2类别 [INFO] ID3训练完成树深度4叶子节点数7 [INFO] 测试准确率0.92 [INFO] 已保存决策树结构图id3_tree.png [INFO] 已生成PPTXslides/ch05_decision_tree.pptx此时id3_tree.png是Graphviz生成的树结构需系统已装GraphvizMac用brew install graphvizWindows下官网下载slides/ch05_decision_tree.pptx已更新打开后可见“信息增益计算过程”页公式用Unicode书写。若报错ModuleNotFoundError: No module named graphviz先确认pip install graphvizPython包再确认系统级Graphviz已安装dot -V应输出版本若仍失败在decision_tree_id3.py开头加import os os.environ[PATH] os.pathsep /usr/local/bin # Mac # os.environ[PATH] os.pathsep C:\\Program Files\\Graphviz2.38\\bin # Windows4.3 常见问题排查5条真实踩坑记录现象1运行svm_smo.py时卡死CPU占满10分钟无输出原因tol1e-5过小且数据含重复样本或线性不可分导致SMO迭代不收敛。解决将tol改为1e-2或在make_linear_sep()中增加noise0.2检查X是否有全零行np.any(X0, axis1)。现象2hmm_viterbi.py报错IndexError: index 5 is out of bounds for axis 0 with size 5原因HMM状态数N5但观测序列中出现状态索引5应为0~4。解决检查hmm_sample.py生成的observations确保其值域为range(N)或修改hmm_viterbi.py中N len(possible_states)为N max(observations)1。现象3PPTX中公式显示为乱码如α变a原因PowerPoint默认字体不支持Unicode数学符号。解决打开PPTX → 设计 → 字体 → 自定义字体 → 中文字体选“微软雅黑”英文字体选“Cambria Math”Windows或“STIX Two Math”Mac。现象4crf.py运行时报ValueError: shapes (100,5) and (5,1) not aligned原因特征模板生成的X维度与权重w不匹配常见于修改了feature_template()后未同步更新w初始化。解决在crf.py中打印X.shape和w.shape确保w长度等于X.shape[1]重置w np.random.randn(X.shape[1])。现象5adaboost.py中弱分类器决策树桩始终选同一特征分割原因make_moon_data()生成的数据在某一维上区分度极高信息增益碾压其他维。解决在adaboost.py中将弱分类器改为随机特征子集np.random.choice(features, size2, replaceFalse)或增加noise0.15。提示所有.py文件顶部都有DEBUGTrue开关设为True会打印每轮迭代的α、误差率、权重更新是定位收敛问题的第一手资料。5. 二次开发实战如何把朴素贝叶斯扩展为半朴素贝叶esTAN现在你已跑通全部算法下一步是改造。我们以第4章朴素贝叶斯为基座升级为树增强型朴素贝叶斯TAN——这是书中未讲、但工业界常用的技术如医疗诊断、信用评分。目标在保持贝叶斯框架下允许特征间存在一棵树状依赖而非强制独立。5.1 TAN原理速览从“独立假设”到“树状依赖”朴素贝叶斯假设所有特征条件独立$$P(x_1,...,x_n|y) \prod_{i1}^n P(x_i|y)$$TAN放松该假设构建特征间的最大权生成树每个特征最多有一个父特征除类别y外$$P(x_1,...,x_n|y) \prod_{i1}^n P(x_i|y, \text{pa}(x_i))$$其中pa(x_i)是x_i在树中的父节点边权为条件互信息$$I(x_i; x_j|y) \sum_{x_i,x_j,y} P(x_i,x_j,y) \log \frac{P(x_i,x_j|y)}{P(x_i|y)P(x_j|y)}$$5.2 代码改造四步注入TAN逻辑进入code/naive_bayes.py按顺序修改步骤1添加互信息计算函数放在文件顶部def conditional_mutual_info(X, y, i, j): 计算特征i和j在给定y下的条件互信息 I(x_i; x_j|y) X: (n_samples, n_features), y: (n_samples,) n_classes len(np.unique(y)) n_vals_i len(np.unique(X[:, i])) n_vals_j len(np.unique(X[:, j])) # 构建联合分布 P(x_i, x_j, y) joint np.zeros((n_vals_i, n_vals_j, n_classes)) for idx in range(len(X)): xi_val int(X[idx, i]) xj_val int(X[idx, j]) y_val int(y[idx]) joint[xi_val, xj_val, y_val] 1 joint / len(X) # 归一化为概率 # 边缘分布 P(x_i|y), P(x_j|y), P(x_i,x_j|y) px_i_y np.sum(joint, axis1) # (n_vals_i, n_classes) px_j_y np.sum(joint, axis0) # (n_vals_j, n_classes) px_i_x_j_y joint # (n_vals_i, n_vals_j, n_classes) # 计算 I(x_i; x_j|y) Σ P(x_i,x_j,y) log[ P(x_i,x_j|y) / (P(x_i|y)P(x_j|y)) ] mi 0.0 for xi in range(n_vals_i): for xj in range(n_vals_j): for yc in range(n_classes): if joint[xi, xj, yc] 0: denom px_i_y[xi, yc] * px_j_y[xj, yc] if denom 0: mi joint[xi, xj, yc] * np.log(joint[xi, xj, yc] / denom) return mi步骤2在NaiveBayes类中添加build_tan_tree()方法class NaiveBayes: def __init__(self, use_tanFalse): self.use_tan use_tan self.tree_parents None # {feature_idx: parent_idx}, root的parent为-1 def build_tan_tree(self, X, y): 构建TAN树对每对特征计算I(x_i;x_j|y)用Prim算法找最大生成树 n_features X.shape[1] # 计算互信息矩阵 mi_matrix np.zeros((n_features, n_features)) for i in range(n_features): for j in range(i1, n_features): mi conditional_mutual_info(X, y, i, j) mi_matrix[i, j] mi mi_matrix[j, i] mi # Prim算法找最大生成树边权mi self.tree_parents [-1] * n_features # 初始化所有节点父为-1 visited [False] * n_features visited[0] True # 以特征0为根 edges [] for _ in range(n_features - 1): best_weight -1 best_edge (-1, -1) for i in range(n_features): if visited[i]: for j in range(n_features): if not visited[j] and mi_matrix[i, j] best_weight: best_weight mi_matrix[i, j] best_edge (i, j) if best_edge[0] -1: break i, j best_edge self.tree_parents[j] i visited[j] True edges.append((i, j, best_weight)) print(f[TAN] 构建树完成边: {edges}) return edges步骤3重写predict_proba()支持TAN推理def predict_proba(self, X): TAN版本P(y|x_1..x_n) ∝ P(y) * Π P(x_i|y, pa(x_i)) n_samples X.shape[0] n_classes len(self.class_prior_) probas np.zeros((n_samples, n_classes)) for i in range(n_samples): for c in range(n_classes): # 先乘P(yc) prob self.class_prior_[c] # 再乘每个特征的条件概率 for feat_idx in range(X.shape[1]): if self.use_tan and self.tree_parents[feat_idx] ! -1: # 有父节点P(x_feat|yc, x_parent) parent_idx self.tree_parents[feat_idx] x_parent_val int(X[i, parent_idx]) # 查表self.cond_prob_tan[feat_idx][c][x_parent_val][x_feat_val] x_feat_val int(X[i, feat_idx]) prob * self.cond_prob_tan[feat_idx][c][x_parent_val][x_feat_val] else: # 无父节点根或朴素P(x_feat|yc) x_feat_val int(X[i, feat_idx]) prob * self.cond_prob_[feat_idx][c][x_feat_val] probas[i, c] prob return probas / probas.sum(axis1, keepdimsTrue)步骤4在main()中启用TAN并对比效果if __name__ __main__: X, y make_moon_data(n_samples200, noise0.2) # 朴素贝叶斯 nb NaiveBayes(use_tanFalse) nb.fit(X, y) acc_nb accuracy_score(y, nb.predict(X)) # TAN贝叶斯 tan NaiveBayes(use_tanTrue) tan.fit(X, y) # fit中会调用build_tan_tree() acc_tan accuracy_score(y, tan.predict(X)) print(f朴素贝叶斯准确率: {acc_nb:.3f}) print(fTAN贝叶斯准确率: {acc_tan:.3f}) print(f提升: {acc_tan - acc_nb:.3f})运行后你将看到TAN在非线性数据上比朴素贝叶斯高3~5个百分点——这正是放松独立假设的价值。更重要的是self.tree_parents数组告诉你特征2依赖于特征0特征1依赖于特征2……这棵树可被utils/ppt_generator.py自动画成PPTX中的“依赖关系图”。从那以后我每次做算法教学改造都强制走一遍“公式推导→手写核心循环→单步调试→生成PPTX→学生实操反馈”闭环。因为真正的理解不在PPT动画的平滑而在你亲手把alphas[j]从0.321改成0.322时看到决策边界那一像素的偏移。这份资源包的价值从来不是它多完整而是它给你留出了所有可修改的缝隙——公式可编辑、代码可打断、PPT可重排。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑