
简介本资源是武汉大学《机器学习与模式识别》课程配套的Python实践项目合集面向计算机、人工智能、自动化等专业的本科生及初阶学习者覆盖监督学习、无监督学习、神经网络等核心模块可支撑课程实验、课程设计、毕设开发与自学进阶。压缩包共337个文件含145个Python源码实现算法与模型训练、72个YAML配置文件用于超参管理与环境定义、18个Markdown文档含实验说明、运行指南与原理简析、17张PNG/JPG示意图及9个Jupyter Notebook交互式案例整体大小60.22MB结构清晰、模块分明便于按主题分步学习与二次开发。已有153人下载学习所有代码均经实机测试通过答辩平均分96分附完整README与多场景运行说明支持基础复现、参数调优及功能拓展亦可作为教学参考或项目原型快速启动。1. 这不是一份“课程作业打包”而是一套可直接复用的机器学习教学实验骨架武汉大学《机器学习与模式识别》课程配套的 Python 实验源码常被误认为是仅供交差的课堂练习。但实际翻看其结构会发现它用清晰分层的模块组织supervised/,unsupervised/,neural_networks/统一的数据加载接口datasets/下含 Iris、Wine、MNIST 等标准数据集预处理脚本以及每份实验都附带.md文档说明——从数学推导如 SVM 的拉格朗日对偶问题求解步骤、代码逻辑梯度下降中 learning_rate 与 epoch 的耦合关系、到可视化输出决策边界热力图、聚类轮廓系数曲线全部闭环。它解决的不是“如何跑通一个 sklearn 示例”而是“如何在无框架封装前提下亲手实现并验证算法内核”。适合高校助教快速搭建实验课环境、自学者系统补全手写算法能力、或工程师回溯经典模型底层细节。你不需要重造轮子但必须理解每个for循环里更新的是哪个参数、每次np.dot()背后的几何意义。2. 用原生 NumPy 实现监督学习分类器从感知机到支持向量机监督学习实验的核心价值在于剥离 scikit-learn 的黑盒封装暴露模型训练过程中的关键决策点。该课程实验包中supervised/perceptron.py和supervised/svm.py是典型代表——它们不调用sklearn.svm.SVC而是基于 NumPy 手动实现梯度更新与约束优化。2.1 感知机的权重更新逻辑与收敛性验证感知机作为最基础的线性分类器其实现难点不在公式本身而在样本遍历顺序对收敛速度的影响。课程代码采用随机打乱np.random.shuffle 批量更新策略# supervised/perceptron.py 关键片段 def train(self, X, y, max_iter1000): self.w np.zeros(X.shape[1]) self.b 0.0 for _ in range(max_iter): # 随机打乱索引避免周期性震荡 indices np.random.permutation(len(X)) misclassified 0 for i in indices: if y[i] * (np.dot(self.w, X[i]) self.b) 0: self.w self.lr * y[i] * X[i] self.b self.lr * y[i] misclassified 1 if misclassified 0: break注意self.lr学习率并非固定值。实验文档明确指出当X未归一化时lr0.01可能导致权重爆炸若使用StandardScaler预处理则lr1.0更稳定。这正是课程强调的“数据预处理与超参强耦合”原则——没有脱离数据分布谈学习率的合理性。2.2 SVM 对偶问题的手动求解与核函数注入点SVM 实验的精髓在于将原始优化问题转化为对偶问题并通过 SMO序列最小优化思想简化求解。课程代码supervised/svm.py中solve_dual_problem()函数不依赖cvxopt而是用坐标上升法迭代更新 α# supervised/svm.py 片段SMO 核心循环 def solve_dual_problem(self, X, y): n_samples X.shape[0] alphas np.zeros(n_samples) b 0.0 for _ in range(self.max_iter): alpha_pairs_changed 0 for i in range(n_samples): # 计算预测误差 E_i E_i self._predict(X[i], alphas, y, X, b) - y[i] # 启发式选择 j非 i 的最大 |E_i - E_j| j self._select_j(i, E_i, alphas, y, X, b) # 计算 L, H 边界满足 0≤α≤C 约束 L, H self._compute_bounds(alphas[i], alphas[j], y[i], y[j]) if L H: continue # 计算 η K_ii K_jj - 2*K_ij eta self._kernel(X[i], X[i]) self._kernel(X[j], X[j]) - 2 * self._kernel(X[i], X[j]) if eta 0: continue # 更新 α_j alpha_j_new alphas[j] y[j] * (E_i - E_j) / eta alpha_j_new np.clip(alpha_j_new, L, H) # 更新 α_i保证 ∑α_i y_i 0 alpha_i_new alphas[i] y[i] * y[j] * (alphas[j] - alpha_j_new) # 更新 b根据支持向量条件 b self._update_b(alphas, y, X, b, i, j, alpha_i_new, alpha_j_new, E_i, E_j) alphas[i], alphas[j] alpha_i_new, alpha_j_new if abs(alpha_j_new - alphas[j]) 1e-5: alpha_pairs_changed 1 if alpha_pairs_changed 0: break return alphas, b2.2.1 核函数替换的三个关键位置课程文档特别标注若要将线性 SVM 改为 RBF SVM需同时修改三处self._kernel()方法从lambda x1,x2: np.dot(x1,x2)替换为lambda x1,x2: np.exp(-gamma * np.linalg.norm(x1-x2)**2)gamma参数初始化在__init__()中增加self.gamma gammasolve_dual_problem()内eta计算self._kernel()调用已自动适配新核函数提示RBF 核的gamma值直接影响决策边界曲率。实验建议先用GridSearchCV在小数据集如 Iris上粗筛gamma ∈ [0.001, 10]再代入手写 SVM 验证——这比盲目调参更高效。2.3 多分类策略的实现差异OvR vs OvO课程实验对比了 One-vs-RestOvR和 One-vs-OneOvO两种策略在supervised/logistic_regression.py中的实现策略分类器数量训练数据量决策逻辑适用场景OvRn_classes全量数据每次取一类正样本其余负样本argmax(各分类器输出概率)类别不平衡时更鲁棒OvOn_classes*(n_classes-1)/2每次仅用两类样本子集投票制得票最多者胜出小样本、高维数据收敛更快代码中MultiClassifier类通过strategyovr或ovo切换且fit()方法内部会动态生成子分类器列表。这种设计让学习者直观看到多分类不是“单个模型的简单扩展”而是分类器组合架构的设计问题。3. 无监督学习实验K-Means 初始化陷阱与层次聚类距离度量选择无监督学习实验的价值在于揭示“没有标签”的情况下算法如何依赖数据内在结构做出假设。课程中unsupervised/kmeans.py和unsupervised/hierarchical.py并非简单调用sklearn.cluster而是暴露了 K-Means 的“初始中心敏感性”与层次聚类的“距离度量歧义性”。3.1 K-Means 的 k-means 初始化与收敛判断标准 K-Means 对初始质心敏感易陷入局部最优。课程实验强制实现 k-means 初始化# unsupervised/kmeans.py def _initialize_centers_kmeans_plusplus(self, X): n_samples, n_features X.shape centers np.zeros((self.n_clusters, n_features)) # 第一个中心随机选取 centers[0] X[np.random.randint(n_samples)] # 后续中心按距离平方概率选取 for c in range(1, self.n_clusters): distances np.array([min([np.linalg.norm(x-center)**2 for center in centers[:c]]) for x in X]) probabilities distances / distances.sum() cumulative_probabilities np.cumsum(probabilities) r np.random.rand() for i, p in enumerate(cumulative_probabilities): if r p: centers[c] X[i] break return centers3.1.1 收敛判定的双重标准课程代码不采用简单的“质心移动距离 ε”而是叠加簇内平方和WCSS变化率# 迭代中计算 wcss_old self._calculate_wcss(X, labels, centers) # ... 更新质心与标签 ... wcss_new self._calculate_wcss(X, labels, centers) if abs(wcss_new - wcss_old) / wcss_old 1e-4 and np.all(np.linalg.norm(centers_new - centers_old, axis1) 1e-4): break注意1e-4是经验阈值。当X维度 50 时建议将 WCSS 变化率阈值放宽至1e-3否则可能因浮点精度导致无限循环。3.2 层次聚类的三种距离度量与树状图截断逻辑层次聚类实验unsupervised/hierarchical.py提供euclidean、manhattan、cosine三种距离选项并要求用户手动指定截断高度threshold# 构建距离矩阵 if self.metric euclidean: dist_matrix squareform(pdist(X, metriceuclidean)) elif self.metric manhattan: dist_matrix squareform(pdist(X, metriccityblock)) elif self.metric cosine: dist_matrix squareform(pdist(X, metriccosine)) # 执行凝聚层次聚类单连接 linkage_matrix linkage(dist_matrix, methodsingle) # 截断获取簇标签 labels fcluster(linkage_matrix, tself.threshold, criteriondistance)3.2.1 距离度量选择指南度量类型适用数据特征数学表达课程实验建议场景Euclidean连续型特征、各维度量纲一致√Σ(x_i-y_i)²Iris 花瓣长度/宽度单位统一Manhattan高维稀疏数据、存在异常值Σx_i-y_iCosine方向敏感、忽略向量模长1 - (x·y)/(x实验文档强调同一数据集切换距离度量可能导致树状图分支结构完全改变。例如在 Wine 数据集上Euclidean 距离下前 3 层合并的样本组Cosine 距离下可能分散在不同子树——这正是无监督学习“无唯一正确答案”的体现。4. 神经网络实验从全连接到卷积层的手动反向传播推导神经网络实验是课程难度跃升的关键环节。neural_networks/mlp.py和neural_networks/cnn.py不使用 PyTorch/TensorFlow而是用 NumPy 实现前向传播与反向传播强制学习者理解链式法则在每一层的具象化。4.1 全连接网络的梯度计算与权重更新MLP 实验中backward()方法需逐层计算梯度。以 ReLU 激活的隐藏层为例# neural_networks/mlp.py def backward(self, X, y_true): # 输出层梯度MSE loss d_loss_d_out (self.output - y_true) / len(y_true) # 均值损失 # 输出层到隐藏层权重梯度 d_loss_d_w2 self.hidden.T d_loss_d_out d_loss_d_b2 np.sum(d_loss_d_out, axis0) # 隐藏层激活函数梯度ReLU d_relu (self.hidden 0).astype(float) # 导数x0 时为1否则为0 # 隐藏层输入梯度 d_loss_d_hidden d_loss_d_out self.W2.T * d_relu # 输入层到隐藏层权重梯度 d_loss_d_w1 X.T d_loss_d_hidden d_loss_d_b1 np.sum(d_loss_d_hidden, axis0) # 更新权重带动量 self.v_W1 self.momentum * self.v_W1 - self.lr * d_loss_d_w1 self.W1 self.v_W1 self.v_b1 self.momentum * self.v_b1 - self.lr * d_loss_d_b1 self.b1 self.v_b1 # ... 同理更新 W2, b24.1.1 动量项的物理意义与调试技巧课程文档解释self.momentum0.9并非随意设定而是模拟“惯性”——使权重更新方向更平滑减少震荡。调试时若发现 loss 曲线剧烈波动应检查self.lr是否过大尝试0.001 → 0.0001self.momentum是否过小低于0.8时惯性不足d_loss_d_w1计算是否遗漏X.T矩阵维度错误会导致梯度爆炸4.2 卷积层的 im2col 优化与反向传播映射CNN 实验neural_networks/cnn.py的核心是conv_forward()中的im2col技术# 将卷积操作转为矩阵乘法 def im2col(self, x, filter_h, filter_w, stride1, pad0): n, c, h, w x.shape out_h (h 2*pad - filter_h) // stride 1 out_w (w 2*pad - filter_w) // stride 1 # 补零 col np.pad(x, [(0,0), (0,0), (pad,pad), (pad,pad)], constant) # 按滑动窗口展开 col np.array([ col[:, :, i:ifilter_h, j:jfilter_w].reshape(n, -1) for i in range(0, out_h * stride, stride) for j in range(0, out_w * stride, stride) ]).transpose(1, 0, 2).reshape(n, -1, filter_h * filter_w * c) return col.reshape(n * out_h * out_w, -1) # 前向传播 col self.im2col(x, self.fh, self.fw, self.stride, self.pad) out col self.W.T self.b # 矩阵乘法替代循环卷积 out out.reshape(n, out_h, out_w, -1).transpose(0, 3, 1, 2)4.2.1 反向传播中 col2im 的维度还原conv_backward()必须将d_out形状(n, f, out_h, out_w)还原为d_col再映射回输入梯度d_x# d_out 形状(n, f, out_h, out_w) d_out_reshaped d_out.transpose(0, 2, 3, 1).reshape(-1, self.f) d_col d_out_reshaped self.W # (n*out_h*out_w, c*fh*fw) # col2im将 d_col 按 im2col 逆序还原 d_x np.zeros_like(x) for i in range(out_h): for j in range(out_w): d_x[:, :, i*stride:i*strideself.fh, j*stride:j*strideself.fw] \ d_col[i*out_wj].reshape(c, self.fh, self.fw)提示d_col的 reshape 维度必须与im2col输出严格一致。若d_col.shape ! (n*out_h*out_w, c*fh*fw)说明d_outtranspose 顺序错误——这是 CNN 手写实现中最常见的维度 bug。5. 实验环境配置与常见报错定位从 Python 安装到 NumPy 版本兼容课程实验对运行环境有明确要求但文档未提供一键安装脚本。以下是基于 Ubuntu 22.04 和 macOS 13 的实操配置路径覆盖 Python 安装、依赖管理及典型报错修复。5.1 Python 环境的最小化安装避开系统 Python 冲突课程实验要求Python 3.8但禁止使用系统自带 PythonUbuntu 的/usr/bin/python3或 macOS 的/usr/bin/python3因其包管理受限且升级风险高。推荐方案# Ubuntu 22.04使用 deadsnakes PPA 安装 Python 3.9 sudo apt update sudo apt install -y software-properties-common sudo add-apt-repository ppa:deadsnakes/ppa sudo apt update sudo apt install -y python3.9 python3.9-venv python3.9-dev # macOS使用 pyenv 管理多版本避免 Homebrew Python 权限问题 brew install pyenv pyenv install 3.9.18 pyenv global 3.9.18注意python3.9-venvUbuntu或pyenv virtualenvmacOS是必须安装的组件。课程实验依赖venv创建隔离环境而非conda——后者在 NumPy BLAS 后端兼容性上易出问题。5.2 依赖包的精确版本锁定与冲突解决实验requirements.txt明确指定numpy1.23.5 scipy1.10.1 matplotlib3.7.1 pandas1.5.3但直接pip install -r requirements.txt可能失败原因在于numpy 1.23.5与较新pip的 wheel 兼容性问题。解决方案# 步骤1降级 pip 到兼容版本 python3.9 -m pip install pip22.3.1 # 步骤2强制从 PyPI 源安装避免镜像缓存旧 wheel python3.9 -m pip install --index-url https://pypi.org/simple/ --no-cache-dir numpy1.23.5 # 步骤3逐个安装防止依赖解析冲突 python3.9 -m pip install --no-cache-dir scipy1.10.1 python3.9 -m pip install --no-cache-dir matplotlib3.7.1 python3.9 -m pip install --no-cache-dir pandas1.5.35.2.1 典型报错与修复对照表报错信息根本原因修复命令ImportError: numpy.core.multiarray failed to importNumPy 安装损坏或 ABI 不匹配python3.9 -m pip uninstall numpy -y python3.9 -m pip install --no-cache-dir numpy1.23.5ModuleNotFoundError: No module named sklearn课程实验未依赖 scikit-learn但某些.py文件误导入删除import sklearn行改用from supervised.svm import SVM等本地模块ValueError: operands could not be broadcast together with shapes (100,4) (100,)y_true未 reshape 为列向量在mlp.py的train()中添加y_true y_true.reshape(-1, 1)OSError: [Errno 24] Too many open filesLinux 系统默认文件描述符限制过低影响pandas.read_csvulimit -n 65536临时或echo * soft nofile 65536 /etc/security/limits.conf永久5.3 VS Code 调试配置设置 Python 解释器路径与断点验证课程实验需在 VS Code 中调试main.py关键配置如下打开命令面板CtrlShiftP输入Python: Select Interpreter选择路径为~/.pyenv/versions/3.9.18/bin/pythonmacOS或/usr/bin/python3.9Ubuntu在.vscode/settings.json中添加{ python.defaultInterpreterPath: /home/user/.pyenv/versions/3.9.18/bin/python, python.testing.pytestArgs: [tests/], python.formatting.provider: autopep8 }在mlp.py的forward()函数首行设断点运行调试F5观察self.W1.shape是否匹配X.shape[1]——这是验证数据流完整性的最快方式。提示若断点无法命中检查 VS Code 左下角 Python 解释器路径是否显示为3.9.18。曾有用户因未重启 VS Code 导致仍使用旧解释器造成ModuleNotFoundError。本文还有配套的精品资源点击获取