简介:围绕乳腺癌数据集,面向机器学习初学者与课程设计学生,提供K均值聚类与K近邻分类联合预测方案。该资源从数据读取与缺失值处理入手,使用K均值聚类对特征进行聚类,并将聚类结果作为新增特征,同时结合各项指标的聚类结果与雷达图透视数据分布;随后按八比二比例划分训练集与测试集,通过交叉验证搜索最佳K值,完成K近邻模型训练与测试,并给出分类准确率评估。压缩包内共5个文件,涵盖可执行的脚本、原始数据表、设计说明文档、交互式分析笔记以及可视化报告,整体约1.05MB,便于按需查看代码实现、数据字段和图表结果。已有142人学习。资源完整呈现从数据清洗、特征工程、模型调参到效果评估的课设全流程,可帮助读者掌握聚类特征构造思路与分类模型评价方法,适合作为高校机器学习或数据挖掘课程的参考实现。 这学期帮几个师弟师妹review课设代码,发现“KMeans+KNN实现乳腺癌预测”这个题目几乎是机器学习课的“标配”。好处确实明显:数据集是sklearn自带的,不用为爬虫和数据清洗费劲;两个算法都能直接调包,跑出来分数不难看;可视化部分天然有料,图表一多,报告厚度也上去了。但正因为做的人多,老师看过的同质化作品也多,想拿高分靠的不是“跑通”,而是你能否把两个算法各自在做什么、组合起来有什么意义讲清楚。这篇文章把整个项目从数据处理到可视化再到答辩准备的链路完整拆开,适合正在做课设、或者想拿公开数据集练手机器学习基础流程的同学。
我先把项目整体的逻辑框架理清,再逐个环节说实现和坑。文章里的代码都是Python + scikit-learn + matplotlib这套标准组合,环境用Anaconda基本零成本复现。
1. 课题定位:这个组合其实在考察“监督”和“无监督”的分工
1.1 为什么KMeans和KNN放在一起是有意义的
很多同学拿到这个题目第一反应是“两个算法各跑一遍,多堆几张图”。这样做的结果是:报告看着很厚,但答辩时老师一问“这两个算法有什么关系、为什么要放在一起”,立刻就露馅。
实际上这个组合能自洽地讲出完整故事:KMeans是无监督聚类,它不知道样本的真实标签,只凭特征分布把样本分成K簇,作用在于探索数据本身有没有可分性;KNN是有监督分类,它用带标签的训练样本去预测未知样本,作用在于验证这个数据的可预测性。两者都用“距离”作为核心度量,但使用场景完全不同。
一个加分做法是:先用KMeans对全部样本做聚类,把聚类得到的簇标签和真实标签做对比,用可视化和一致性指标说明“在没有标签的情况下,数据本身的簇结构是否接近真实类别”。然后再切分训练集和测试集,用KNN建模预测。这样形成一条“无监督探索→有监督验证”的完整分析链路。
1.2 数据集的基本情况
选用sklearn内置的乳腺癌数据集,load_breast_cancer即可加载。它包含569个样本,每个样本有30个数值型特征(如肿瘤半径均值、纹理均值、平滑度均值等,由细胞核图像计算得出),标签为二分类:恶性肿瘤(malignant)和良性肿瘤(benign),样本分布为恶性212例、良性357例,存在一定不平衡但不是特别严重。
这个数据集非常适合课设:样本量适中,训练快速,特征有明确医学含义,且不需要额外下载文件。另一个优点是数据质量高,没有缺失值,预处理成本低,学生可以把精力放在算法理解和效果分析上。
建议在报告里把特征分组讲一下:10个均值特征、10个标准差特征、10个最差值特征。这能让评分老师看出你对数据做过功课。
2. 数据预处理:先解决“标准化的时机”这个高频翻车点
2.1 第一步永远是看数据,而不是跑模型
拿到数据集先不要急着fit任何模型。用data.DESCR查看完整特征说明,用df.describe()和df.info()确认分布和缺失情况。print一下标签分布,看看类别是否平衡。
这段代码建议放在任何建模之前:
import pandas as pd from sklearn.datasets import load_breast_cancer data = load_breast_cancer() df = pd.DataFrame(data.data, columns=data.feature_names) df['target'] = data.target print(df.shape) print(df.isnull().sum().sum()) # 确认没有缺失值 print(df['target'].value_counts()) print(df.describe().T)乳腺癌数据没有缺失值,这一步看似多余,但课设报告里写一句“经验证无缺失值、无重复样本,故不需要插补”,能让流程完整性加分不少。
2.2 为什么必须做标准化
KMeans和KNN都是基于距离的算法,特征间的数值尺度会严重干扰距离计算。比如mean radius的取值范围是6到28,而mean texture是9到39,mean concavity只有0到0.4。如果不做标准化,距离几乎完全由数值大的特征主导,小数值特征相当于被淹没。
解决方案是StandardScaler,把每个特征转为均值为0、标准差为1的分布。有人问要不要用MinMaxScaler?两者都可以,但KMeans、KNN这类基于欧氏距离的算法更习惯用StandardScaler,因为它对所有维度做了方差归一,距离计算更合理。
2.3 先切训练集测试集,再标准化
这是整篇文章最值得强调的一个操作顺序问题。
错误做法:先对整个数据集做标准化,再train_test_split。这样做的隐患是:标准化时使用了测试集的均值和方差,属于典型的信息泄漏。测试集本应该模拟“未来数据”,当你用未来数据的统计量去缩放训练数据,评估结果会偏乐观,且模型部署时无法复现这种操作。
正确做法:
from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler X_train, X_test, y_train, y_test = train_test_split( data.data, data.target, test_size=0.2, random_state=42, stratify=data.target ) scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test)注意:scaler只调用fit_transform在训练集上,测试集只调用transform,不能重新fit。这个细节在答辩时被问到的概率极高,务必理解并解释清楚。stratify参数做分层抽样,让训练集和测试集的恶性/良性比例与原数据一致,避免随机切分带来的分布偏移。
3. KMeans聚类探索:肘部法则、轮廓系数和标签对齐
3.1 聚类在这个项目里负责回答什么问题
KMeans在乳腺癌预测里的角色不是预测,而是探索。我们要回答的问题是:这569个样本在特征空间中,天然分成几簇?这些簇是否正好对应良性和恶性?
如果聚类效果接近真实标签,说明这个数据本身类别可分性强,后续分类模型效果好的可能性也高。这就是聚类的“预分析”价值。
3.2 原理简述:迭代收敛与KMeans++
KMeans的核心逻辑是四步,理解它不需要复杂数学:
- 随机选K个点作为初始簇中心。
- 每个样本计算到所有簇中心的距离,归属到最近的簇。
- 对每个簇内所有样本取均值,更新簇中心。
- 重复2、3步,直到簇中心不再明显变化。
初始化的随机性会导致结果不稳定,sklearn默认用KMeans++算法,即让初始簇中心彼此尽量远离,减少陷入局部最优的概率。同时设置random_state固定随机种子,保证结果可复现。
3.3 如何确定K值:用代码和图表说话
K值的选取不能拍脑袋,常见方法有两种:肘部法则和轮廓系数。
肘部法则:横轴是K值,纵轴是簇内误差平方和,也就是inertia_,等于每个样本到其簇中心的距离平方之和。随着K增加,inertia一定会下降,但下降幅度会越来越小,形成“肘部”拐点,这个拐点对应的K就是合理的簇数。
实现如下:
from sklearn.cluster import KMeans import matplotlib.pyplot as plt inertia = [] for k in range(2, 11): km = KMeans(n_clusters=k, init='k-means++', random_state=42) km.fit(X_train_scaled) inertia.append(km.inertia_) plt.plot(range(2, 11), inertia, marker='o') plt.xlabel('K') plt.ylabel('Inertia') plt.title('Elbow Method') plt.show()轮廓系数的思路更直接:某个样本的轮廓系数衡量它与自身簇内其他样本的紧密程度,以及与其他簇样本的分离程度,取值范围[-1, 1],越大说明聚类效果越好。对所有样本求平均得到平均轮廓系数,选择平均轮廓系数最大的K值。
from sklearn.metrics import silhouette_score best_k = 2 best_score = -1 for k in range(2, 11): km = KMeans(n_clusters=k, init='k-means++', random_state=42) labels = km.fit_predict(X_train_scaled) score = silhouette_score(X_train_scaled, labels) if score > best_score: best_score = score best_k = k print(f"best k = {best_k}, silhouette = {best_score:.4f}")在乳腺癌数据上,肘部法则通常会得到一个并不陡峭的曲线,拐点不像教材里那样明显,这时可以把轮廓系数作为更重要的参考。一般K=2时轮廓系数最高,因为数据本质上就是二分类结构,这与真实情况吻合。
我在实际做的时候,遇到过KMeans聚类标签0正好对应良性、1对应恶性,但也遇到过一次正好反过来。这给了一个重要教训:聚类簇的编号是无意义的,分析前必须做标签对齐。
3.4 聚类输出与真实标签的对比分析
聚类完成之后,得到一个簇标签,但簇标签(0、1)与真实标签(0=恶性、1=良性)之间可能有映射关系。直接计算accuracy会得到极低分数,造成“聚类效果很差”的错误结论。
正确的处理办法是构建一个映射:统计每个簇内良性、恶性样本的数量,取多数的那个类别作为该簇的语义标签。这种“多数投票”方式简单有效,也可以用scipy库的Hungarian算法做最优匹配,但课设阶段多数投票足够。
完成对齐后,计算调整兰德指数或者一致性矩阵。ARI取值范围[-1, 1],越靠近1说明聚类与真实类别越一致。乳腺癌数据上这个值通常在0.5到0.7之间,说明无监督结构已经能捕捉到大部分类别信息,这正是后面KNN效果好的前提条件。
4. KNN分类预测:超参数选择、交叉验证和评估指标
4.1 先分清两类算法的边界
进入KNN阶段之前,我把边界说清楚:KMeans是在无标签数据上找簇结构,KNN是有监督算法,需要利用训练集标签预测测试集。两者虽然都依赖距离,但没有“KMeans负责特征工程、KNN负责分类”这种流水线关系。
这个项目里它们是两个独立思考的任务:KMeans回答“数据是否可分”,KNN回答“可分性有多强、预测准确率多高”。答辩时这样描述逻辑,远比“先用KMeans再喂给KNN”这种说法准确。
4.2 KNN原理与关键参数
KNN不做显式训练,它把训练样本直接“记住”,预测新样本时计算它与所有训练样本的距离,选出距离最近的K个邻居,让邻居投票决定类别。因此KNN也叫惰性学习算法,真正的计算发生在预测阶段。
sklearn中的KNeighborsClassifier主要参数有三个:
- n_neighbors:邻居数量,通常取奇数避免平票
- weights:uniform表示所有邻居权重相同,distance表示距离越近权重越大
- p:距离度量,p=2为欧氏距离,p=1为曼哈顿距离
4.3 用交叉验证和网格搜索选出最优参数
小范围K值可以直接用循环加交叉验证。把训练集再切出验证集,或者直接用cross_val_score。乳腺癌数据集样本量不大,5折交叉验证足够:
from sklearn.model_selection import cross_val_score from sklearn.neighbors import KNeighborsClassifier k_range = range(1, 21) scores = [] for k in k_range: knn = KNeighborsClassifier(n_neighbors=k, weights='uniform', p=2) score = cross_val_score(knn, X_train_scaled, y_train, cv=5, scoring='accuracy') scores.append(score.mean()) best_k = k_range[scores.index(max(scores))] print(f"best_k = {best_k}, acc = {max(scores):.4f}")如果你想让报告更丰富,可以用GridSearchCV同时对n_neighbors、weights、p做网格搜索:
from sklearn.model_selection import GridSearchCV param_grid = { 'n_neighbors': range(1, 21), 'weights': ['uniform', 'distance'], 'p': [1, 2] } grid = GridSearchCV(KNeighborsClassifier(), param_grid, cv=5, scoring='accuracy') grid.fit(X_train_scaled, y_train) print(grid.best_params_) print(grid.best_score_)在乳腺癌数据集上,K=5到K=9附近通常表现较好,accuracy基本在95%以上。weights选择distance、p=2时效果通常更好,但要注意distance方式对异常点更敏感。
4.4 混淆矩阵、召回率和医学场景的特殊性
分类准确率只是最浅层的评估指标。在医学诊断场景里,我们格外关注两类错误:
- 假阳性:把良性误判为恶性,后果是额外的检查负担。
- 假阴性:把恶性误判为良性,后果是延误治疗,严重得多。
因此一定要在报告里给出precision、recall、f1-score和混淆矩阵。对二分类问题,用classification_report即可输出全部指标。
from sklearn.metrics import classification_report, confusion_matrix y_pred = grid.predict(X_test_scaled) print(classification_report(y_test, y_pred, target_names=data.target_names))混淆矩阵用heatmap画出来,不仅让报告看起来专业,答辩讲解时也更直观:
import seaborn as sns cm = confusion_matrix(y_test, y_pred) sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=data.target_names, yticklabels=data.target_names) plt.xlabel('Predicted') plt.ylabel('True') plt.show()在医学课题中,模型设计的目标通常是“不漏掉恶性”,因此recall是比较硬性的指标,建议在报告里明确说:“本模型在测试集中对恶性的召回率达到xx%,意味着只有x例恶性样本被漏判。”这句话比单纯写accuracy有力量得多。
5. 可视化设计:图表不是装饰,是在回答问题
5.1 可视化清单与信息逻辑
很多课设的可视化是“想到什么画什么”,堆了十来张图但每张都没有解释为什么画。高分项目里的每张图都对应一个问题,几张图串起来形成完整论证链条。
乳腺癌预测项目我建议按这个顺序组织图表:
| 图表 | 回答的问题 | 展示位置 |
|---|---|---|
| 标签分布柱状图 | 两类样本是否均衡 | 数据探索章节 |
| 特征分布箱线图 | 特征是否包含异常值、量纲差异 | 数据探索章节 |
| 前两个主成分散点图 | 数据在二维空间是否可分 | 探索性分析章节 |
| 肘部法则曲线 | KMeans的K值依据 | 聚类分析章节 |
| 轮廓系数图 | 聚类的质量验证 | 聚类分析章节 |
| 聚类与真实标签对比图 | 无监督结构是否对应真实类别 | 聚类分析章节 |
| 混淆矩阵热力图 | 分类错误的类型和数量 | 分类评估章节 |
| K值与准确率折线图 | KNN调参过程 | 分类评估章节 |
5.2 高价值的PCA降维散点图
30维特征无法直接画散点图,先PCA降维到2维,看数据分布。先fit_transform训练集并同时transform测试集,再把两类样本分别着不同颜色画出来:
from sklearn.decomposition import PCA pca = PCA(n_components=2) X_train_pca = pca.fit_transform(X_train_scaled) X_test_pca = pca.transform(X_test_scaled) plt.figure(figsize=(8, 6)) scatter = plt.scatter(X_train_pca[:, 0], X_train_pca[:, 1], c=y_train, cmap='coolwarm', alpha=0.6) plt.xlabel(f'PC1 ({pca.explained_variance_ratio_[0]:.2%})') plt.ylabel(f'PC2 ({pca.explained_variance_ratio_[1]:.2%})') plt.colorbar(scatter) plt.show()PCA的坐标轴最好标注方差贡献率,比如“PC1 (44.2%)”,这能让评委看出你知道主成分到底保留了多大信息量。乳腺癌数据集前两个主成分通常能保留约60%以上的方差,散点图上良性样本聚在一侧、恶性样本散在另一侧,直观展示可分性。
5.3 颜色、布局和中文问题
matplotlib默认不处理中文,图里出现“预测”“真实”等中文会在某些环境中显示为方框。两种解决办法:
- 用英文标签最省事,适合全英文报告。
- 中文报告必须手动设置字体:
plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False配色方面,不要用太多颜色,一图两色或三色足够。聚类结果与真实标签的对比图可以用上下两个子图,同样颜色代表同一类别,方便对比。整体建议用plt.subplots(1, 2)组合,而不是把一张图拆成多张,减少报告页数冗余。
我用seaborn的PairPlot尝试过选4个关键特征画对角图,效果很华丽,但打印后信息量过于密集。课设图的首要目标是答辩时三秒钟能看懂,简洁比花哨重要。
6. 复盘与三个隐藏扣分点
6.1 特征选择不是必须,但一定要分析
不少课设代码是把30个特征全部塞进模型。这种做法本身没错,乳腺癌数据特征不过量,但报告里写一句“由于各特征与目标的相关性不同,先用SelectKBest筛选出top10特征进行对比实验”会显得你做了更深一层的分析。我试过用卡方检验选中10个特征后,KNN的accuracy基本不掉,反而训练时间和图表的可解释性更好。
当然这属于锦上添花,不是必需。如果时间不够,保底做法是对每个特征计算与标签的相关系数,画一个热力图,证明你考虑过特征与目标的关系。
6.2 聚类与分类的边界:千万不要说“先聚类后分类”
有一类常见表述是:“先用KMeans把样本聚成几类,再用KNN分类,所以这个题目是两个算法的结合应用。”这种逻辑其实是错的。KMeans和KNN在这个项目里是独立运行的,不是串行流水线。
如果想表现两者的结合关系,正确的讲法是:
- KMeans聚类证明数据具有内在簇结构,且簇结构与真实类别较高程度吻合。
- 这个结果从无监督角度验证了数据可分性,是KNN分类能取得高准确率的底层原因之一。
- KNN在有监督条件下量化了这种可分性,并给出了可精确评估的预测模型。
这样表述,两个算法形成“探索”和“验证”的闭环,逻辑上没有硬伤。
6.3 答辩被问最多的四个问题,提前准备好
根据我帮人模拟答辩的经验,下面这几个问题出现的概率极高:
- KMeans的K为什么选2?答:轮廓系数在K=2时最大,且乳腺癌本身是二分类问题,聚类结果与医学常识一致。
- KNN的K为什么选这个数?答:通过5折交叉验证遍历1到20,选择验证准确率最高的K值,同时取奇数避免平票。
- 标准化为什么不能全数据fit?答:测试集要模拟未来数据,不能用它的统计量参与训练阶段的缩放,否则评估结果偏乐观。
- 你对聚类和分类的理解?答:聚类是无监督,目标是把相似样本聚合,没有标签参与;分类是有监督,用已标记样本训练模型,对未知样本预测。
这四个问题如果答流利了,课设答辩基本稳了。
最后分享一个我每次做这类项目都会安利给别人的习惯:所有实验的关键结果都顺手存一份CSV,包括不同K值下的交叉验证分数、不同参数组合的网格搜索结果。这不仅能让你写报告时直接引用数据,更重要的是,老师在答辩现场问“你试过K=15吗”的时候,你能立刻调出实测结果回答,而不是现场重新训练一遍。很多细节就是这样拉开分数差距的。
本文还有配套的精品资源,点击获取