周志华《机器学习》习题Python实现:从KNN到AdaBoost的西瓜书代码实战

发布时间:2026/10/12 12:51:52

周志华《机器学习》习题Python实现:从KNN到AdaBoost的西瓜书代码实战 简介这份资源是周志华《机器学习》西瓜书课程习题的代码实现合集面向正在学习机器学习课程、需要完成课后作业或希望动手复现经典算法的高校学生与自学者。内容按章节组织覆盖线性模型、决策树、神经网络、支持向量机、贝叶斯分类、集成学习、聚类与降维等核心主题每章配有习题说明与对应实现脚本便于对照理论逐题练习。压缩包共90个文件以22个Python脚本和10个Markdown笔记为主另含34张图片、若干数据文件与配置项整体约11.74MB结构清晰、便于按模块检索。目前已有934人学习下载适合希望借助可运行代码理解算法细节、巩固课程知识点的读者参考使用。1. 从一份西瓜书作业代码包说起周志华《机器学习》习题的 Python 落地如果你正在啃周志华老师的《机器学习》大概率经历过这种场景公式推导看懂了手推一遍也能跟上但真让你从零写一个 KMeans 或者 AdaBoost脑子里立刻一片空白。这份MachineLearning_Zhouzhihua_ProblemSets-master就是冲着这个痛点来的——它把西瓜书第 2 到第 10 章的核心习题用 Python 逐个实现了一遍覆盖模型评估、线性模型、决策树、神经网络、支持向量机、贝叶斯分类、集成学习、聚类、降维与度量学习。每个章节目录下都有对应的习题说明 Markdown 和可运行的.py脚本数据文件用的是西瓜数据集 2.0、3.0 系列。适合谁正在做机器学习课程设计、准备期末复习、或者想拿西瓜书当主线系统刷一遍算法实现的人。不适合谁想直接调 sklearn 一行出结果的人——这里很多地方是手写实现目的就是让你看清每一步在算什么。2. 环境搭建与数据准备把西瓜数据集跑起来2.1 目录结构与依赖清单拿到压缩包解压后先别急着跑代码。花两分钟把目录结构看清楚能省掉后面一半的报错排查时间。整个仓库按章节组织每一章是一个独立文件夹命名格式是chX--章节名比如ch3--线性模型、ch8--集成学习。每个章节文件夹里通常包含三类东西习题说明文档chX习题.md、Python 实现脚本如3.3.py、8.3-AdaBoost.py、以及image文件夹存放习题相关的插图。数据文件集中在ch4--决策树目录下包括watermelon3_0_Ch.csv、watermelon2_0_Ch.txt、watermelon3_0a_Ch.txt、watermelon4_0_Ch.txt等。这些就是西瓜书里反复出现的那几个经典数据集分别对应不同的属性数量和样本分布。ch10--降维与度量学习下还有一个ex7faces.mat是用于 PCA 降维实验的人脸数据。依赖方面这份代码主要用到numpy、pandas、matplotlib部分章节可能涉及scipy。没有requirements.txt所以需要手动确认环境。常见做法是建一个干净的虚拟环境然后按需安装。# 创建虚拟环境以 conda 为例 conda create -n watermelon python3.9 conda activate watermelon # 安装核心依赖 pip install numpy pandas matplotlib scipy这里 Python 版本建议 3.8 到 3.10 之间。太新的版本比如 3.12可能在部分老代码上遇到兼容性问题主要是某些 numpy 旧 API 的弃用警告会变成报错。如果你不想折腾版本直接用 3.9 是最稳的选择。2.2 数据加载的两种方式与编码坑西瓜数据集有两种格式.csv和.txt。CSV 文件用 pandas 读很直接但要注意编码。原始文件大概率是 UTF-8但如果你在 Windows 上解压后直接用默认编码读可能会遇到UnicodeDecodeError。import pandas as pd # 读取 CSV 格式的西瓜数据集 # encodingutf-8 显式指定避免 Windows 默认 GBK 导致乱码 df pd.read_csv(ch4--决策树/watermelon3_0_Ch.csv, encodingutf-8) # 查看前几行确认列名和数据类型 print(df.head()) print(df.dtypes)逻辑说明read_csv的encoding参数是关键。很多人在 Windows 上跑报编码错误就是因为系统默认用 GBK 去解码 UTF-8 文件。显式写utf-8能规避这个问题。如果仍然报错可以尝试utf-8-sig它能处理带 BOM 头的文件。对于.txt格式的数据通常是制表符或空格分隔读取时需要指定sep参数# 读取 TXT 格式sep\t 表示制表符分隔 # 如果实际是空格分隔改成 sep\s 用正则匹配 df_txt pd.read_csv(ch4--决策树/watermelon2_0_Ch.txt, sep\t, encodingutf-8) print(df_txt.head())参数说明sep的值取决于文件实际的分隔符。不确定的时候先用open()读前几行看看原始内容比盲目试参数快得多。watermelon2_0_Ch.txt和watermelon3_0_Ch.csv的区别在于属性数量——2.0 版本只有两个特征适合可视化决策边界3.0 版本特征更多适合完整的决策树构建。提示如果数据文件路径包含中文在部分 IDE 里可能出问题。建议把整个项目放在纯英文路径下比如D:/ml_projects/watermelon/能避免很多玄学报错。3. 核心算法实现拆解从 KNN 到 AdaBoost 的代码逻辑3.1 KNN 与 PCA降维与分类的配合ch10--降维与度量学习下的10.1-KNN.py和10.6-PCA.py是两个很适合入门的脚本。KNN 的核心逻辑是计算样本间距离然后取最近的 k 个邻居投票。这份代码里大概率是手写距离计算而不是直接调 sklearn。import numpy as np def euclidean_distance(x1, x2): 计算两个样本之间的欧氏距离 return np.sqrt(np.sum((x1 - x2) ** 2)) def knn_predict(X_train, y_train, x_test, k3): 手写 KNN 预测 X_train: 训练集特征shape (n_samples, n_features) y_train: 训练集标签 x_test: 单个测试样本 k: 邻居数量 # 计算测试样本到所有训练样本的距离 distances [euclidean_distance(x_test, x) for x in X_train] # 按距离升序排序取前 k 个的索引 k_indices np.argsort(distances)[:k] # 获取这 k 个邻居的标签 k_labels [y_train[i] for i in k_indices] # 投票返回出现次数最多的标签 from collections import Counter return Counter(k_labels).most_common(1)[0][0]逻辑说明euclidean_distance是纯 numpy 实现没有用scipy.spatial.distance目的是让你看清距离公式怎么落到代码上。np.argsort返回的是索引数组取前 k 个就得到了最近的邻居。Counter投票是最直观的多分类决策方式。参数说明k的取值直接影响结果。k 太小比如 1模型对噪声敏感容易过拟合k 太大决策边界会过于平滑可能欠拟合。西瓜书里提到k 通常取奇数以避免平票实际项目中常用交叉验证来选。这份代码默认 k3你可以手动改成 5 或 7 对比效果。PCA 脚本10.6-PCA.py配合ex7faces.mat使用逻辑是先对数据去中心化然后计算协方差矩阵的特征值和特征向量取前 d 个最大特征值对应的向量作为投影矩阵。def pca(X, n_components): 手写 PCA 降维 X: 输入数据shape (n_samples, n_features) n_components: 保留的主成分数量 # 去中心化每个特征减去该特征的均值 X_centered X - np.mean(X, axis0) # 计算协方差矩阵 cov_matrix np.cov(X_centered, rowvarFalse) # 特征值分解 eigenvalues, eigenvectors np.linalg.eigh(cov_matrix) # 按特征值从大到小排序取前 n_components 个 sorted_idx np.argsort(eigenvalues)[::-1] top_idx sorted_idx[:n_components] # 投影矩阵 projection_matrix eigenvectors[:, top_idx] # 降维后的数据 return X_centered projection_matrix逻辑说明np.cov计算协方差矩阵时rowvarFalse表示每一列是一个特征每一行是一个样本。np.linalg.eigh用于对称矩阵的特征分解比eig更稳定。排序后取最大的几个特征值对应的特征向量就是主成分方向。参数说明n_components决定了降维后的维度。选多少常见做法是看累计方差贡献率一般保留到 85% 到 95% 之间。这份代码里没有自动选维度的逻辑需要你手动指定。跑ex7faces.mat的时候可以先设n_components50看看效果再调整。3.2 集成学习AdaBoost 与 Bagging 的手写实现ch8--集成学习下的8.3-AdaBoost.py和8.5-StumpBagging.py是这份代码包里比较有分量的部分。AdaBoost 的核心是串行训练弱分类器每一轮根据上一轮的误差调整样本权重。def adaboost_train(X, y, n_estimators10): AdaBoost 训练过程 X: 特征矩阵 y: 标签取值为 1 或 -1 n_estimators: 弱分类器数量 n_samples X.shape[0] # 初始化样本权重均匀分布 weights np.ones(n_samples) / n_samples classifiers [] alphas [] for _ in range(n_estimators): # 训练一个弱分类器这里用决策树桩 stump DecisionStump() stump.fit(X, y, weights) # 计算加权误差 predictions stump.predict(X) error np.sum(weights * (predictions ! y)) / np.sum(weights) # 计算该分类器的权重 alpha 0.5 * np.log((1 - error) / (error 1e-10)) # 更新样本权重分错的样本权重增大 weights weights * np.exp(-alpha * y * predictions) weights weights / np.sum(weights) # 归一化 classifiers.append(stump) alphas.append(alpha) return classifiers, alphas逻辑说明AdaBoost 的关键在于样本权重的迭代更新。每一轮训练后被分错的样本权重会增大下一轮的弱分类器会更关注这些难分样本。alpha是弱分类器的投票权重误差越小权重越大。1e-10是为了防止除零。参数说明n_estimators控制弱分类器的数量。太少会欠拟合太多可能过拟合而且计算量线性增长。西瓜书里的习题通常用 10 到 50 个基学习器就够了。DecisionStump是决策树桩只分裂一次是最简单的弱分类器。Bagging 脚本8.5-StumpBagging.py的逻辑不同它是并行训练多个基学习器每个学习器用自助采样bootstrap得到的子集训练最后投票。def bagging_train(X, y, n_estimators10, sample_ratio0.8): Bagging 训练过程 sample_ratio: 每个基学习器使用的样本比例 n_samples X.shape[0] classifiers [] for _ in range(n_estimators): # 自助采样有放回地抽取样本 indices np.random.choice(n_samples, sizeint(n_samples * sample_ratio), replaceTrue) X_bootstrap X[indices] y_bootstrap y[indices] # 训练基学习器 stump DecisionStump() stump.fit(X_bootstrap, y_bootstrap) classifiers.append(stump) return classifiers逻辑说明np.random.choice的replaceTrue实现了有放回采样这是 Bagging 的核心。每个基学习器看到的训练集不同增加了多样性。预测时所有基学习器投票多数胜出。参数说明sample_ratio控制每个基学习器的训练集大小。常见做法是取 0.6 到 0.8。取 1.0 的话每个学习器看到的样本数和原始一样但因为有放回采样仍然有约 36.8% 的样本没被选中这部分就是“袋外样本”可以用来做验证。3.3 支持向量机mySVM 模块的结构ch6--支持向量机下有一个mySVM文件夹说明这部分是模块化实现的而不是单文件脚本。SVM 的手写实现通常包含核函数、对偶问题求解、KKT 条件判断等。这份代码大概率是用简化的 SMO 算法来求解对偶问题。def kernel_transform(X, kernel_typelinear, gamma1.0): 核函数计算 kernel_type: linear 或 rbf gamma: RBF 核的参数 if kernel_type linear: return X X.T elif kernel_type rbf: # RBF 核exp(-gamma * ||x_i - x_j||^2) sq_dists np.sum(X**2, axis1).reshape(-1, 1) \ np.sum(X**2, axis1) - 2 * X X.T return np.exp(-gamma * sq_dists)逻辑说明线性核直接算内积RBF 核算的是高斯距离。sq_dists的计算用了广播机制避免显式循环效率更高。gamma控制 RBF 核的“宽度”值越大单个样本的影响范围越小容易过拟合。参数说明gamma的取值对 RBF 核影响很大。常见做法是设为1/n_features或者用网格搜索。这份代码里默认gamma1.0在小数据集上可能偏大你可以试着调小到 0.1 或 0.5 对比决策边界的变化。注意手写 SVM 的收敛速度通常比 sklearn 慢很多尤其是在样本量大的时候。这份代码的目的是教学不是生产。如果你要跑大规模数据还是用sklearn.svm.SVC。4. 避坑与排查跑这份代码时最容易翻车的五个地方4.1 路径问题导致文件找不到现象运行脚本时报FileNotFoundError提示找不到watermelon3_0_Ch.csv或ex7faces.mat。原因代码里用的多半是相对路径比如../ch4--决策树/watermelon3_0_Ch.csv。如果你不在脚本所在目录下运行或者项目移动过位置相对路径就失效了。解决要么cd到脚本所在目录再运行要么把路径改成绝对路径。更稳妥的做法是用os.path.dirname(__file__)动态获取脚本所在目录然后拼接数据路径。import os # 获取当前脚本所在目录 script_dir os.path.dirname(os.path.abspath(__file__)) # 拼接数据文件路径 data_path os.path.join(script_dir, .., ch4--决策树, watermelon3_0_Ch.csv) df pd.read_csv(data_path, encodingutf-8)4.2 numpy 版本不兼容导致 API 报错现象运行时报AttributeError: module numpy has no attribute float或类似错误。原因numpy 1.24 之后弃用了np.float、np.int等别名而这份代码可能是在更早的版本下写的。解决要么降级 numpy 到 1.23 以下要么把代码里的np.float改成floatnp.int改成int。全局搜索替换就行。# 降级 numpy不推荐可能影响其他包 pip install numpy1.23.5 # 或者手动改代码把 np.float 替换为 float4.3 matplotlib 中文显示乱码现象画图时标题、坐标轴标签的中文变成方框或乱码。原因matplotlib 默认字体不支持中文。解决在画图前设置中文字体。Windows 上用SimHeiMac 上用Arial Unicode MS。import matplotlib.pyplot as plt # Windows 系统 plt.rcParams[font.sans-serif] [SimHei] # Mac 系统 # plt.rcParams[font.sans-serif] [Arial Unicode MS] plt.rcParams[axes.unicode_minus] False # 解决负号显示问题4.4 数据标签格式不一致导致算法报错现象AdaBoost 或 SVM 脚本运行时报标签类型错误或者结果完全不对。原因这些算法要求标签是 1 和 -1但西瓜数据集里的标签可能是 0 和 1或者是字符串“是”和“否”。解决在训练前做标签映射。先确认原始标签的取值再统一转换。# 查看标签的唯一值 print(df[好瓜].unique()) # 映射为 1 和 -1 label_map {是: 1, 否: -1} y df[好瓜].map(label_map).values4.5 随机种子未固定导致结果不可复现现象每次运行 Bagging 或 KNN 脚本结果都不一样没法对比。原因np.random.choice或np.random.seed没有固定。解决在脚本开头设置随机种子。np.random.seed(42) # 42 是习惯用法任意整数都行提示固定随机种子后每次运行的结果就一致了。但要注意如果你改了采样比例或基学习器数量结果仍然会变这是正常的。5. 进阶用法用这份代码做课程设计的三个具体技巧5.1 把单文件脚本改造成可配置的实验框架这份代码里的脚本大多是“写死”的——数据路径、参数、输出都在代码里。如果你要做课程设计需要跑多组对比实验手动改参数太慢。一个实用的技巧是把关键参数抽到配置文件里用argparse或yaml管理。import argparse parser argparse.ArgumentParser() parser.add_argument(--k, typeint, default3, helpKNN 的邻居数量) parser.add_argument(--n_estimators, typeint, default10, help集成学习的基学习器数量) parser.add_argument(--dataset, typestr, defaultwatermelon3_0_Ch.csv, help数据集文件名) args parser.parse_args() # 用 args.k 替代硬编码的 k这样你可以在命令行里快速切换参数跑完一组实验后直接改参数再跑不用动代码。对于课程设计里的“对比不同 k 值对 KNN 准确率的影响”这类题目效率提升很明显。5.2 用交叉验证替代单次划分很多脚本里用的是简单的训练集/测试集划分但西瓜书第 2 章专门讲了交叉验证。你可以把ch2--模型评估与选择里的思路用起来给 KNN 或决策树加上 k 折交叉验证。from sklearn.model_selection import KFold def cross_validate(X, y, model_fn, n_splits5): k 折交叉验证 model_fn: 返回一个训练好的模型的函数 kf KFold(n_splitsn_splits, shuffleTrue, random_state42) scores [] for train_idx, test_idx in kf.split(X): X_train, X_test X[train_idx], X[test_idx] y_train, y_test y[train_idx], y[test_idx] model model_fn(X_train, y_train) score evaluate(model, X_test, y_test) scores.append(score) return np.mean(scores), np.std(scores)逻辑说明KFold把数据分成 k 份每次用其中一份做测试其余做训练。最后取平均准确率和标准差。标准差能反映模型在不同数据划分下的稳定性。参数说明n_splits通常取 5 或 10。数据量小的时候取 5数据量大的时候取 10。shuffleTrue保证每次划分前打乱数据避免原始顺序的影响。5.3 可视化决策边界让结果更直观课程设计里如果能画出决策边界展示效果会好很多。对于二维特征的数据集比如watermelon3_0a_Ch.txt可以用 matplotlib 的contourf画出分类区域。import numpy as np import matplotlib.pyplot as plt def plot_decision_boundary(model, X, y): 绘制决策边界 x_min, x_max X[:, 0].min() - 0.5, X[:, 0].max() 0.5 y_min, y_max X[:, 1].min() - 0.5, X[:, 1].max() 0.5 xx, yy np.meshgrid(np.arange(x_min, x_max, 0.02), np.arange(y_min, y_max, 0.02)) # 对网格上的每个点预测 Z np.array([model.predict(np.array([x, y])) for x, y in zip(xx.ravel(), yy.ravel())]) Z Z.reshape(xx.shape) # 画等高线填充 plt.contourf(xx, yy, Z, alpha0.3, cmapplt.cm.coolwarm) # 画散点 plt.scatter(X[:, 0], X[:, 1], cy, cmapplt.cm.coolwarm, edgecolorsk) plt.show()逻辑说明np.meshgrid生成网格点然后对每个点预测类别最后用contourf填充颜色。alpha0.3让背景半透明散点更清晰。参数说明网格步长0.02控制边界的精细度。步长越小边界越平滑但计算量越大。对于小数据集0.02 足够了如果数据范围很大可以适当放大到 0.1。注意这个可视化只适用于二维特征。如果你的数据集超过两个特征需要先用 PCA 降到二维再画但降维后的边界和原始空间不完全等价解释时要谨慎。从那以后我每次拿到一份别人的代码包都强制先跑通一个最小脚本确认环境和数据没问题再逐个章节推进。这份西瓜书作业代码包的价值不在于代码写得多优雅而在于它把书里的公式和实际能跑的 Python 代码之间的那道墙给拆了。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/12 13:42:00

REDox 64位token统一表示:多格式互转与内存降70%

1. 从"数据格式地狱"说起:REDox 到底在解决什么问题做数据工程或者后端开发的人,大概率都经历过这样的场景:上游给过来一批 JSON,下游要转成 YAML 做配置,再往后还要塞进 TOML 给某个工具链用,中…

2026/10/12 13:42:00

RAG落地实战:LangChain、向量检索与重排优化指南

简介:面向大模型与人工智能开发者的实战型PDF文档,系统演示如何基于LangChain从零实现一个RAG问答应用。文档选取百度百科中的藜麦数据模拟个人或企业私域数据,完整覆盖项目策划、环境搭建(CUDA 11.7、Python 3.10、PyTorch 1.13.…

2026/10/12 13:42:00

Python协同过滤电影推荐系统毕设:ItemCF源码、评估与论文避坑指南

简介:这是一套面向计算机相关专业毕业设计场景的完整项目资料,以Python实现基于协同过滤算法的电影推荐系统,适合正在准备毕设、课程设计或期末大作业的学生,以及需要推荐算法实战练习的学习者。资源包共约2000个文件,…

2026/10/12 13:42:00

浏览器里重造经典回合制RPG:TypeScript与可视化编辑器实战

1. 为什么要在浏览器里重造一款二十多年前的回合制角色扮演游戏把一款经典回合制角色扮演游戏从零搬到浏览器里,这件事听起来像是情怀驱动,但真正动手之后你会发现,它其实是一次对前端工程能力的全面体检。我最初起这个念头的场景很具体&…

2026/10/12 13:42:00

TypeScript重写仙剑奇侠传:游戏引擎与可视化编辑器全流程

1. 项目缘起与整体架构设计1.1 为什么选择重写经典 RPG 而不是做个新游戏第一次冒出重写《仙剑奇侠传》的念头,其实源于一次很偶然的尝试。当时我想在浏览器里重温一下这款老游戏,发现要么需要装各种模拟器,要么找到的网页版操作手感极差&…

2026/10/11 0:02:13

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/12 12:01:26

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/12 12:54:07

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/12 0:04:22

绝缘子缺陷检测数据集清洗与工业级训练实战指南

简介:本资源是面向电力AI研发人员、工业视觉工程师及智能巡检系统开发者的绝缘子缺陷检测专用YOLO格式数据集,解决无人机航拍场景下绝缘子破损、污闪、积雪等9类典型缺陷的精准识别与定位难题。数据集共2139张真实巡检图像(含训练/验证/测试集…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑