☰
武大机器学习与模式识别Python实验源码拆解:从环境配置到模型调参
2026/10/1 13:13:35 网站建设 项目流程

简介:这份资源是武汉大学机器学习与模式识别课程的实验配套源码与文档,面向计算机、人工智能、通信、自动化等专业的在校学生及自学者,可用于课程实验、课程设计、毕业设计或项目立项演示。内容覆盖监督学习、无监督学习、神经网络等典型算法,以Python实现为主,并配有说明文档,适合在已有基础上修改扩展。压缩包共337个文件,约60.22MB,其中145个py源码文件构成各实验核心实现,72个yaml与9个ipynb用于配置和交互式实验,18个md及若干txt、doc、docx提供文档说明,另有png、jpg等图片与hdf5、mat数据文件辅助训练和结果展示。目前已有154人学习下载。读者可据此获得一套经过运行验证的课程实验代码,理解各算法的实现流程与目录组织,并借助文档快速定位模块、复现结果,为课程作业和后续研究提供参考。

1. 武大机器学习与模式识别实验课:一份能直接跑通的 Python 源码拆解

如果你正在搜「武汉大学 机器学习 模式识别 python 实验」,大概率是三种人之一:正在修这门课、被实验报告卡住的学生;想照着课程实验补一遍机器学习动手能力的自学者;或者手里已经拿到一份「源码+文档说明」的压缩包,但打开一看十几个文件夹不知道从哪下手。这份实验材料的价值不在于它出自哪门课,而在于它把监督学习、无监督学习、神经网络这几条主线用 Python 串成了一条可执行的路径——从 KNN、决策树、SVM 到 K-means、PCA,再到 BP 网络和卷积网络,每个实验都对应一个能跑出结果的最小工程。这篇笔记不逐行念代码,而是按「先跑通、再改参数、最后看边界」的顺序,把这份材料里最值得复现的部分拆开讲清楚,顺带把环境配置、数据格式、调参玄学和几个血泪踩坑点一并交代。适合能看懂 Python 基础语法、但还没完整做过一遍机器学习实验的人。

2. 环境与目录:把源码包跑起来的第一公里

2.1 为什么实验课代码最容易死在环境上

机器学习实验和普通 Python 脚本最大的区别是依赖重。一份课程源码包里通常同时出现 numpy、scipy、scikit-learn、matplotlib,神经网络部分还可能带 pytorch 或 tensorflow。这些库之间有版本耦合:老代码用np.float、sklearn.cross_validation这类已经废弃的写法,在新版本 numpy 和 sklearn 上直接报 AttributeError。所以第一步不是读代码,而是先确认这份材料写于哪个年代、依赖哪个大版本。

我一般的做法是先看有没有 requirements.txt 或文档说明里的环境章节,没有的话就按「numpy 1.x + scikit-learn 0.24 左右 + matplotlib 3.x」这套组合去试,因为这是 2019 到 2021 年课程实验最常用的版本区间。Python 本体建议 3.8 或 3.9,太新的 3.12 会让不少老库装不上。如果你用 vscode python 环境配置,记得把解释器选到虚拟环境里,别用系统全局的,否则装包会互相污染。

2.2 建虚拟环境与装依赖的最小命令

# 建一个独立环境,避免污染系统 Python python -m venv ml_env # 激活(Windows) ml_env\Scripts\activate # 激活(Linux / macOS) source ml_env/bin/activate # 先装基础三件套,指定较稳的版本区间 pip install "numpy<1.24" "scipy<1.11" "scikit-learn==0.24.2" "matplotlib<3.8" # 神经网络实验如果用到 pytorch,单独装 CPU 版即可 pip install torch --index-url https://download.pytorch.org/whl/cpu

逻辑说明:先隔离环境,再锁版本。numpy<1.24是为了避开np.float被彻底移除的分界点;scikit-learn==0.24.2保留了cross_validation的兼容层,很多老实验代码能直接跑。参数上,如果你确认代码里没有老式 API,可以把 sklearn 升到 1.x,但升之前先全局搜一遍cross_validation、np.float、np.int这几个关键词,有就说明得留在老版本。

2.3 目录结构怎么读:先找入口脚本

课程源码包常见的组织方式是按实验分文件夹,每个文件夹里一个主脚本加一个数据目录。判断入口脚本的方法很简单:找带if __name__ == '__main__'的那个文件,或者文件名里带main、run、demo的。数据文件一般是.txt、.csv或.mat,.mat是 MATLAB 格式,用scipy.io.loadmat读。

常见目录名对应实验入口脚本特征
knn / classification监督学习分类含 train/test 划分逻辑
clustering / kmeans无监督聚类含迭代次数、簇数参数
pca / dimensionality降维含方差贡献率输出
nn / bp / cnn神经网络含 epoch、学习率参数

提示:先跑通一个最简单的实验(通常是 KNN),确认环境和数据读取没问题,再去碰神经网络部分。一上来就调 CNN 是最容易劝退的路径。

3. 监督学习实验:从 KNN 到 SVM 的复现与调参

3.1 KNN 与决策树:理解「距离」和「划分」两种思路

监督学习实验一般从 KNN 起步,因为它没有训练过程,预测时直接算距离,代码最短、最容易验证环境。核心就三件事:算样本间距离、取最近的 K 个、投票决定类别。这份材料里 KNN 通常用欧氏距离,K 值默认取 3 或 5。

import numpy as np from collections import Counter def knn_predict(X_train, y_train, x_test, k=3): # 计算测试样本到所有训练样本的欧氏距离 dists = np.sqrt(((X_train - x_test) ** 2).sum(axis=1)) # 取距离最小的 k 个样本的下标 idx = dists.argsort()[:k] # 多数投票 labels = [y_train[i] for i in idx] return Counter(labels).most_common(1)[0][0]

逻辑说明:argsort返回的是升序下标,取前 k 个就是最近邻。参数k是唯一要调的,k 太小对噪声敏感,k 太大边界模糊。实操里我会先跑 k=1、3、5、7 看准确率曲线,选拐点。注意特征量纲问题——如果一个特征取值范围是 0 到 1000,另一个是 0 到 1,距离会被大特征主导,所以 KNN 前一般要做归一化,这是新手最容易漏的一步。

决策树换了个思路:不算距离,而是找能最大程度把类别分开的特征和阈值。sklearn 里DecisionTreeClassifier的max_depth和min_samples_split是两个关键参数,前者控制树深防过拟合,后者控制分裂所需最小样本数。实验里通常要求输出树结构或特征重要性,用plot_tree或feature_importances_就能拿到。

3.2 SVM 与模型评估:别只看准确率

SVM 实验的看点是核函数。线性核适合近似线性可分的数据,RBF 核能处理非线性边界,但多了个gamma参数。C控制惩罚力度,C 大对误分类容忍低、容易过拟合,C 小则更宽松。

from sklearn.svm import SVC from sklearn.metrics import classification_report from sklearn.preprocessing import StandardScaler # SVM 对量纲敏感,先标准化 scaler = StandardScaler() X_train_s = scaler.fit_transform(X_train) X_test_s = scaler.transform(X_test) clf = SVC(kernel='rbf', C=1.0, gamma='scale') clf.fit(X_train_s, y_train) pred = clf.predict(X_test_s) # 不要只打印 accuracy,看每一类的 precision/recall print(classification_report(y_test, pred))

逻辑说明:StandardScaler必须用训练集 fit、测试集 transform,反过来就是数据泄漏。gamma='scale'是让 sklearn 自动按特征数算,比手填稳。评估部分,课程实验常只要求准确率,但类别不平衡时准确率会骗人——比如 90% 样本是一类,全预测成那一类也有 90% 准确率。所以我会额外看classification_report里的 recall,尤其是少数类。

参数怎么改:C从 0.1、1、10 各试一遍,gamma试scale和auto,用交叉验证选。cross_val_score一行就能跑 5 折,比手动划分靠谱。

3.3 数据划分与交叉验证的坑

很多课程代码用train_test_split固定random_state=42,这本身没问题,但如果你反复用同一个测试集调参,测试集就变成了验证集,最后报的准确率会虚高。正确做法是训练集内部再切验证集,或者直接用交叉验证。另一个坑是划分前忘了打乱数据——如果原始数据是按类别排好序的,直接切前 80% 做训练会导致某类样本全在测试集里,结果惨不忍睹。train_test_split默认会 shuffle,但自己手写划分逻辑时容易忘。

4. 无监督学习实验:K-means 与 PCA 的落地细节

4.1 K-means:初始点和 K 值是两个命门

K-means 的目标是最小化簇内平方和,算法本身简单,但有两个绕不开的问题:K 值怎么定、初始中心怎么选。课程代码里常见的是随机初始化,跑几次结果都不一样,这就是所谓的玄学来源。

from sklearn.cluster import KMeans import matplotlib.pyplot as plt # 用肘部法找合适的 K inertias = [] for k in range(1, 11): km = KMeans(n_clusters=k, init='k-means++', n_init=10, random_state=42) km.fit(X) inertias.append(km.inertia_) plt.plot(range(1, 11), inertias, marker='o') plt.xlabel('K') plt.ylabel('inertia') plt.show()

逻辑说明:init='k-means++'是比随机初始化更稳的策略,它让初始中心尽量分散,能显著减少陷入局部最优的概率。n_init=10表示跑 10 次取最好的,进一步降低随机性影响。inertia_是簇内平方和,随 K 增大单调下降,所以不能只看它最小,要看下降曲线的拐点(肘部)。参数上,K 选拐点对应的值,如果曲线平滑没有明显拐点,说明数据本身没有清晰的簇结构,这时候硬聚类意义不大。

4.2 PCA:降维不是越多越好

PCA 通过找方差最大的方向来压缩特征。实验里通常要求输出主成分个数和累计方差贡献率。关键参数是n_components,可以填整数(保留几个主成分)或小数(保留多少比例的方差)。

from sklearn.decomposition import PCA pca = PCA(n_components=0.95) # 保留 95% 的方差 X_reduced = pca.fit_transform(X) print("保留主成分数:", pca.n_components_) print("各主成分方差贡献率:", pca.explained_variance_ratio_)

逻辑说明:填 0.95 表示自动选能解释 95% 方差的最少主成分数,比手动指定个数更省心。explained_variance_ratio_是每个主成分的贡献率,累加就是累计贡献率。坑在于 PCA 对量纲同样敏感,降维前要标准化,否则方差大的特征会主导主成分方向。另外 PCA 是无监督的,它保留的是方差大的方向,不一定对分类有用——如果目的是分类,有时 LDA 更合适,因为 LDA 考虑了类别信息。

4.3 聚类效果怎么验证

无监督没有标签,评估比监督难。常用的是轮廓系数(silhouette score),取值 -1 到 1,越接近 1 说明簇内越紧凑、簇间越分离。但轮廓系数也有偏好,它倾向于凸形簇,对密度不均的数据不友好。实操里我会把聚类结果用 PCA 降到二维画散点图,肉眼看一下簇是否分得开,再结合轮廓系数判断。如果散点图上簇重叠严重,轮廓系数再高也要警惕。

5. 神经网络实验:BP 网络与 CNN 的复现要点

5.1 BP 网络:正向反向传播到底在算什么

BP 网络实验的核心是手推一遍正向传播和反向传播。正向传播是输入乘权重加偏置、过激活函数、逐层往前;反向传播是用链式法则从损失往回算每层权重的梯度,再用梯度下降更新。课程代码里可能用 numpy 手写,也可能用框架。

import numpy as np def sigmoid(x): return 1 / (1 + np.exp(-x)) def sigmoid_deriv(x): s = sigmoid(x) return s * (1 - s) # 单隐层前向 def forward(X, W1, b1, W2, b2): z1 = X @ W1 + b1 a1 = sigmoid(z1) z2 = a1 @ W2 + b2 a2 = sigmoid(z2) return z1, a1, z2, a2 # 反向传播算梯度(以均方误差为例) def backward(X, y, z1, a1, z2, a2, W2): dz2 = (a2 - y) * sigmoid_deriv(z2) dW2 = a1.T @ dz2 db2 = dz2.sum(axis=0) dz1 = (dz2 @ W2.T) * sigmoid_deriv(z1) dW1 = X.T @ dz1 db1 = dz1.sum(axis=0) return dW1, db1, dW2, db2

逻辑说明:sigmoid_deriv里先算 sigmoid 再乘 (1-sigmoid),这是导数化简后的形式,比直接对 z 求导省一次计算。反向传播的顺序是从输出层往输入层推,每层梯度依赖后一层的梯度,这就是「反向」的含义。参数上,学习率是关键,太大震荡不收敛,太小收敛慢,一般从 0.01 或 0.1 起试。权重初始化不能全零,否则所有神经元学到一样的东西,要用随机小值。

5.2 CNN 实验:卷积核和池化到底改了什么

CNN 实验通常做手写数字或简单图像分类。卷积层用滑动窗口提取局部特征,池化层降采样。和全连接相比,CNN 的参数共享大幅减少了参数量,这是它在图像上有效的原因。

import torch import torch.nn as nn class SimpleCNN(nn.Module): def __init__(self): super().__init__() # 1 通道输入,8 个 3x3 卷积核 self.conv1 = nn.Conv2d(1, 8, kernel_size=3, padding=1) self.pool = nn.MaxPool2d(2, 2) self.fc = nn.Linear(8 * 14 * 14, 10) def forward(self, x): x = self.pool(torch.relu(self.conv1(x))) # 28x28 -> 14x14 x = x.view(x.size(0), -1) # 展平 return self.fc(x)

逻辑说明:padding=1让 3x3 卷积后尺寸不变,再池化减半,所以 28x28 变成 14x14。view那步是把特征图展平成一维喂给全连接层,x.size(0)是 batch 大小,不能写死。参数上,卷积核数量、kernel_size、池化窗口都是可调的,核越多特征越丰富但计算量越大。训练时epoch一般 5 到 20,学习率 0.001 配 Adam 优化器比较稳。

5.3 训练不收敛时先查这几点

神经网络实验最常见的现象是 loss 不降或变成 nan。排查顺序:先看学习率是不是太大(loss 震荡或爆炸),再看数据有没有归一化(输入范围 0 到 255 直接喂进去容易梯度爆炸),然后看标签编码对不对(分类任务标签要从 0 开始连续)。如果 loss 降但准确率不涨,可能是数据标签错位或者类别极度不平衡。这些坑我在第一次做 BP 实验时全踩过一遍,loss 变 nan 那次就是因为忘了把像素值除以 255。

6. 避坑与排查:这份实验材料最容易翻车的五个地方

现象一:导入 sklearn 就报cannot import name 'cross_validation'。原因:代码写于老版本,cross_validation在 sklearn 0.20 之后改成了model_selection。 解决:要么降 sklearn 版本到 0.24 以下,要么全局替换成from sklearn.model_selection import ...,后者更推荐,改一处就行。

现象二:KNN 准确率异常低,只有 30% 左右。原因:特征没归一化,某个大范围特征主导了距离计算。 解决:在训练前加StandardScaler或MinMaxScaler,注意 fit 只在训练集上做。

现象三:K-means 每次跑出来的簇不一样。原因:随机初始化导致陷入不同局部最优。 解决:用init='k-means++'并设n_init=10,同时固定random_state方便复现。

现象四:神经网络 loss 变成 nan。原因:学习率过大或输入未归一化,梯度爆炸。 解决:先把输入缩放到 0 到 1 或标准化,学习率降到 0.001,加梯度裁剪。

现象五:测试集准确率很高但换一批数据就崩。原因:反复用测试集调参造成过拟合,或者数据划分时没打乱。 解决:训练集内部切验证集调参,测试集只在最后用一次;划分前确认数据已 shuffle。

注意:这五条里前两条几乎人人都会遇到,后三条属于「不踩一次记不住」的类型。建议跑通第一个实验后就把这几条对照检查一遍。

7. 把实验代码改成自己的工具:三个进阶习惯

跑通课程实验只是起点,真正让这份材料产生长期价值的是把它改造成自己能复用的工具。我自己的习惯是先从三个地方下手。

第一,把数据加载和预处理抽成独立函数。课程代码往往把读数据、归一化、划分写在一个大脚本里,改一次数据就要动很多地方。抽成load_data(path)和preprocess(X)之后,换数据集只需要改路径。第二,把评估逻辑统一。不管是分类还是聚类,我都写一个evaluate(y_true, y_pred)函数,分类输出准确率和各类 recall,聚类输出轮廓系数,这样不同实验的结果可以直接对比。第三,给每个实验加一个配置文件或命令行参数,把 K 值、学习率、epoch 这些从代码里挪出来,改参数不用改代码。

import argparse def get_args(): parser = argparse.ArgumentParser() parser.add_argument('--k', type=int, default=3, help='KNN 的 K 值') parser.add_argument('--lr', type=float, default=0.01, help='学习率') parser.add_argument('--epochs', type=int, default=10) return parser.parse_args() if __name__ == '__main__': args = get_args() # 后续用 args.k、args.lr 代替硬编码

逻辑说明:argparse让参数从命令行传入,跑不同配置不用改代码,也方便批量实验。default给一个能跑通的默认值,新手直接python main.py也能出结果。这套习惯看起来多写了几行,但当你需要对比十组参数时,省下的是反复改代码和记结果的时间。

验证方法上,我一般会做两件事:一是用一份自己构造的小数据集(比如几十个样本、两三类)跑一遍,确认逻辑没写反;二是把结果和 sklearn 现成实现对比,如果自己手写的 KNN 和KNeighborsClassifier结果差很多,说明距离计算或投票逻辑有问题。这个对比习惯帮我抓出过好几次维度对不上的 bug。

最后说个我自己的教训:刚开始做这些实验时,我总想一次把所有代码看懂再跑,结果卡在环境配置上两天没进展。后来改成「先让最简单的脚本跑出结果,再逐个实验啃」,效率完全不一样。课程实验的价值不在代码多优雅,而在你亲手跑通、改过参数、见过它翻车又修好的过程。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询