尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

KMeans聚类在宿舍分配中的实战:特征工程到K值选择

KMeans聚类在宿舍分配中的实战:特征工程到K值选择 简介针对高校宿舍分配场景这份基于KMeans聚类算法的Python源码包提供了从数据预处理、模型训练到结果可视化的完整实现适合需要将无监督学习落地到实际管理问题的数据科学初学者或高校信息管理相关技术人员。压缩包共13个文件主要包含Python源码、CSV聚类结果数据、MP4操作与文档演示视频、README说明及XML配置等整体大小约10.71MB既可直接运行查看代码逻辑也可借助录屏理解整个算法流程。目前已有820人学习下载。资源特别讲解了KMeans的质心初始化、K值选取如肘部法则以及用scikit-learn完成宿舍分组的实践要点同时提供最终聚类结果与占比展示便于验证算法效果并迁移到其他聚类场景。无论用于课程设计、项目参考还是算法原理学习都能获得可复用的代码框架与操作指引。1. 从一纸名单到聚类分组为什么宿舍分配需要KMeans每年开学季高校后勤最头疼的不是床位不够而是“怎么分才不出矛盾”。按学号顺序排、按专业排、按新生报到顺序排看起来公平实际结果往往是打游戏的遇上了要考研的夜猫子和晨型人住进同一间屋子第一周就开始申请换寝。原因很直接人工分寝只考虑了“哪个班、哪个专业”没有考虑学生的生活习惯、作息偏好和兴趣特征而这些恰恰是矛盾的主要来源。KMeans聚类算法解决的就是这类“没有标准答案的划分问题”。它把每个学生抽象成一组特征向量比如作息时间、噪音容忍度、清洁习惯、是否熬夜、是否沉迷游戏然后用无监督学习把这些特征相似的人聚合到同一个簇里最后把同一个簇的学生分到同一间宿舍。相比“按学号取模”的硬编码思路KMeans做的是数据驱动的相似度划分属于一类可复用的源码设计不是一次性脚本。这篇文章从原理讲到代码实现再到带演示视频的完整项目结构路径是特征工程、K值选择、聚类训练、结果导出、可视化核对。适合正在做课设、准备毕业设计、以及在高校信息化部门做数据应用开发的读者。2. 数据侧的准备工作从学生信息表到可聚类的特征矩阵KMeans终究是个距离算法它不关心你的字段叫“是否熬夜”还是“sleep_at_02”它只认数值。这决定了宿舍分配项目的第一步不是调库而是做特征数值化、量纲统一和缺失值处理。2.1 宿舍分配场景下的特征如何定义从项目源码的角度看“宿舍分配”不是一个模糊概念而是要明确哪些特征参与距离计算。你至少需要三类特征第一类是作息规律。比如“通常几点睡觉”“早上几点起床”“会不会午休”这些可以直接映射为小时数22.5、23.0、7.0 这样的浮点数。第二类是行为偏好。比如“是否打游戏”“是否喜欢安静”“是否经常带朋友回寝室”可以用 0/1 编码也可以做成 1 到 5 的等级制。第三类是个人属性。比如年级、专业、是否为烟民这类特征用来约束聚类结果。注意专业和年级如果直接丢进 KMeans数值化后会产生“文科生和理科生天然分开”的硬边界反而可能不是你要的效果。我一般会把特征分成“参与聚类的特征”和“约束条件”两类。参与聚类的只保留对生活冲突有实际影响的变量比如睡眠时间、起床时间、噪音敏感度、清洁频率。约束条件则在聚类之后单独处理比如同一个宿舍尽量同一个年级或者抽烟的同学优先分到通风好的楼层。2.2 用pandas把原始数据转成特征矩阵这个项目里我们面对的是 CSV 数据内容大致是学生学号、睡眠时间、起床时间、是否熬夜、对噪音的敏感程度等。先用 pandas 读进来再做编码和转换。import pandas as pd import numpy as np # 原始学生数据列名按实际文件调整 df pd.read_csv(student_data.csv, encodingutf-8-sig) # 对分类型特征做数值映射这里以“是否熬夜”为例 df[is_night_owl] df[是否熬夜].map({是: 1, 否: 0}) # 时间特征统一转成 24 小时制小数23:30 - 23.5 def time_to_float(x): if pd.isna(x): return np.nan parts str(x).split(:) return int(parts[0]) int(parts[1]) / 60 df[sleep_time] df[就寝时间].apply(time_to_float) df[wake_time] df[起床时间].apply(time_to_float) # 构造参与聚类的特征矩阵 features df[[sleep_time, wake_time, is_night_owl, noise_sensitivity]].copy() print(features.head())这段代码做了两件事一是把“是否熬夜”这种中文文本字段映射成 0/1二是把“23:30”这种字符串时间转成 23.5 的数值。之所以要转成小数是因为 KMeans 的欧氏距离对数值的绝对大小非常敏感字符串类型无法参与距离计算。2.3 标准化不是可选项是必选项看一下特征矩阵的数值范围睡眠时间取值在 20 到 26 之间噪音敏感度取值可能只有 1 到 5。如果直接做 KMeans睡眠时间的差异会在距离计算中占绝对主导地位噪音敏感度基本被忽略。正确做法是做标准化处理让每个特征的平均值为 0标准差为 1。from sklearn.preprocessing import StandardScaler # 先填充缺失值用中位数处理更稳妥 features features.apply(lambda col: col.fillna(col.median())) # 标准化注意要用 fit 后的 scaler 去做后续任何新数据的转换 scaler StandardScaler() features_scaled scaler.fit_transform(features) print(features_scaled.shape)标准化之后每个特征都在同一个尺度上聚类的意义才成立。有一点需要提醒StandardScaler是对整个数据集做的全局变换实际部署到新生数据时要保存这个 scaler 对象用它的transform方法处理新数据而不是重新fit否则分布变了聚类结果就不可比了。3. KMeans实现与K值确定肘部法则与轮廓系数的完整代码特征矩阵准备好之后核心问题变成到底分几类这里的“类”对应到宿舍场景就是你希望宿舍楼里存在几种不同的生活方式。K值太小混住冲突多K值太大每个宿舍的成员特征过于单一管理难度上升。通常的做法是结合肘部法则和轮廓系数一起选K。3.1 sklearn中KMeans的关键参数怎么设置sklearn 的 KMeans 提供了几个直接决定结果质量的参数n_clusters、init、n_init、random_state、max_iter。from sklearn.cluster import KMeans # 常规范式k-means 初始化20次随机启动固定随机种子 model KMeans( n_clusters4, initk-means, n_init20, max_iter500, random_state42 ) model.fit(features_scaled)initk-means是 sklearn 的默认值它通过让初始质心彼此尽量远离来降低陷入局部最优的概率比完全随机初始化稳定得多。n_init表示用不同的质心初始状态跑多少轮选其中惯性最小的结果。random_state必须固定否则每次运行结果不同演示视频里的复现能力就没了。3.2 手写一个K值扫描脚本同时输出肘部图和轮廓系数只算一次 KMeans 不够你需要在 K 从 2 到 10 的范围里循环训练记录每个 K 对应的 SSE簇内平方和和轮廓系数。from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score import matplotlib.pyplot as plt sse [] silhouette_scores [] k_range range(2, 11) for k in k_range: km KMeans(n_clustersk, initk-means, n_init20, random_state42) labels km.fit_predict(features_scaled) sse.append(km.inertia_) sil silhouette_score(features_scaled, labels) silhouette_scores.append(sil) print(fK{k}, SSE{km.inertia_:.2f}, sil{sil:.4f}) # 画出肘部曲线 plt.figure(figsize(12, 4)) plt.subplot(1, 2, 1) plt.plot(k_range, sse, markero) plt.xlabel(K) plt.ylabel(SSE) plt.title(Elbow Method) plt.subplot(1, 2, 2) plt.plot(k_range, silhouette_scores, markers, colorgreen) plt.xlabel(K) plt.ylabel(Silhouette Score) plt.title(Silhouette Analysis) plt.tight_layout() plt.show()km.inertia_就是残差平方和每个样本到所属簇质心的距离平方之和。K 增加时 SSE 必然下降你要找的是“下降趋势出现明显拐点”的位置在宿舍分配这个场景里通常是 3 到 5 之间。轮廓系数范围是 -1 到 1越接近 1 表示样本离自己簇的距离远远小于离其他簇的距离聚类结构越清晰。从这段代码跑出的数据里挑“手肘处且轮廓系数高”的 K 值比拍脑袋定参数靠谱。4. 从聚类标签到宿舍名单结果落地与规则映射聚类本身只生产“标签”不生产“宿舍号”。中间还差一个映射层这是项目源码里最容易被忽略、也是最需要实用技巧的部分。4.1 给每个簇分配具体的宿舍楼和房号假设学校宿舍是6人间KMeans分出了 4 个簇那么做法是把每个簇的学生列表按学号排序每 6 人切成一个房间房间号按宿舍楼和楼层预先配置。输出 CSV 时保留学号、姓名、簇编号、宿舍号。import pandas as pd # features_scaled 对应 df 的行 df[cluster] model.fit_predict(features_scaled) # 预定义房号池比如 1号楼301到3052号楼201到203 room_pool [1-301, 1-302, 1-303, 1-304, 1-305, 2-201, 2-202, 2-203, 2-204, 2-205] def assign_room(group): group group.sort_values(student_id) # 每组最多 6 人 room_list [] for i in range(0, len(group), 6): room room_pool[i // 6 % len(room_pool)] room_list.extend([room] * min(6, len(group) - i)) group[room_id] room_list return group df df.groupby(cluster, group_keysFalse).apply(assign_room) df.to_csv(final_assignments.csv, indexFalse, encodingutf-8-sig)这段代码的逻辑是把同一个簇的学生按学号排序后每六个人依次落进预先定义的房号池。room_pool里既有楼栋号也有楼层房号实际使用时可以把宿舍容量、男女分楼、楼层限电规则都抽出来做成配置。注意group_keysFalse是为了避免 groupby 在结果里多出 cluster 列索引。4.2 宿舍分配里的硬约束与 KMeans 的边界KMeans 追求的是特征距离最小化但它不懂“同专业优先同楼”“不同性别不能同寝”“研修室不能安排大三以下学生”这些规则。所以纯聚类结果只能作为候选方案不能直接作为最终制度。实际操作中你需要先做一次硬约束过滤再做聚类。常见的硬约束有四个性别隔离、同院系优先、身体原因如睡眠障碍需要单间、年级差异控制。这四个条件适合在预处理阶段过滤掉特殊情况后剩余学生才进入 KMeans 计算。如果直接拿全部学生跑聚类性别字段一旦进特征结果会强行走成“男女两簇”其他特征全部失效。另外簇的大小和宿舍容量不一致的情况经常出现。比如某个簇有 25 人六人间只能装 24 人剩下 1 人必须去别的簇。处理办法是把“溢出”的学生按欧氏距离分配到最近的簇距离相等的再按学号递补。这个逻辑在源码里可以用cdist或跟第二近的质心比较来实现属于一个很实用的小技巧。5. 可视化核对与项目排错让聚类结果经得起检查班主任和后勤主任不会只看聚类报告他们要看到“为什么这个人分到了这间宿舍”。可视化就是把抽象的距离变成可解释的图形同时也是你排查特征工程错误最直接的手段。5.1 用散点图和雷达图检查聚类质量KMeans 处理高维特征时直观的方法是先做 PCA 降维到二维再画散点图。宿舍分配场景里特征本身不超过 6 个PCA 后的前两个主成分通常能解释 70% 以上的方差可以放心用。from sklearn.decomposition import PCA pca PCA(n_components2) coords pca.fit_transform(features_scaled) plt.figure(figsize(10, 7)) colors plt.cm.Set1(np.unique(model.labels_) / max(model.labels_)) scatter plt.scatter(coords[:, 0], coords[:, 1], cmodel.labels_, cmapSet1, s30) plt.colorbar(scatter) plt.xlabel(PC1) plt.ylabel(PC2) plt.title(Cluster Visualization after PCA) plt.show()核心是看两件事一是簇与簇之间是否有明显的分离边界如果所有簇都混在一起说明特征选得不够有区分度二是每个簇内是否有离群点离群点往往对应数据录入错误、缺失值没处理干净或者特征严重偏离常模的学生。5.2 常见坑位标准化顺序、随机种子、空簇与数据顺序第一标准化和填充缺失值的顺序不能颠倒。先填充再标准化否则填充值会拉低方差影响后续变换。第二KMeans 对初始化敏感忘了设random_state会让两次运行得到完全不同的宿舍名单这是演示视频和实际部署最容易翻车的地方。第三如果 K 值设得过大可能出现空簇即某个初始质心没有分配到任何样本。此时 sklearn 会把空簇中心的样本重新随机初始化但仍可能影响整体稳定性所以 K 上限不要超过样本量的十分之一。另一个隐藏坑在“数据顺序”。有的源码在读入 CSV 后没做随机打乱导致聚类结果里每个簇的样本几乎按学号连续排列如果学号前缀包含了院系编码聚类行为就会被院系字段偷跑表现成“分簇效果好”其实是字段泄漏。排查方法很简单打印每个簇的学号前缀分布如果出现某一簇集中在某个院系说明特征里有不该进模型的信息。6. 把整套流程封装成可复现脚本对齐演示视频的操作路径拿到带演示视频的源码包后第一步不是看代码而是看如何从零跑通。演示视频里展示的操作本质上是三个台阶环境准备、数据替换、一键出结果。6.1 用 requirements.txt 和脚本入口固定整个实验环境项目里有requirements.txt这是整个实验能够在一台新电脑上复现的关键。在干净环境里执行下面的命令就能装齐依赖pip install -r requirements.txtrequirements.txt 内容至少需要scikit-learn、pandas、numpy、matplotlib、openpyxl因为openpyxl负责把结果写到 xlsx 格式。源码里的宿舍分配.py应该暴露一个核心入口函数推荐用main()接收 CSV 路径和 K 值参数这样既方便 IDE 调试也方便在命令行里批处理多批次数据。6.2 建议把单次运行的完整流程固化成函数链下面的代码结构可以直接对照演示视频里的步骤每一步都是一个函数顺序和命名都能对上。def build_feature_matrix(csv_path): # 读取、映射、标准化 pass def find_best_k(data, k_range): # 肘部法则 轮廓系数 pass def run_clustering(data, n_clusters): # 固定随机种子训练 pass def assign_rooms(df, labels, room_pool): # 聚类标签映射到宿舍号 pass def save_results(df, output_path): # 输出最终 CSV 和聚类中心占比 CSV passrun_clustering 里还需要把聚类中心及占比一并输出也就是项目自带的聚类中心及占比展示.csv。聚类中心就是每个簇的特征均值占比就是每个簇的人数占总人数的百分比。这两个指标能直接告诉公寓管理员你们学校生活习惯相似的人群大致分为几类每一类大概多少人从而提前规划宿舍楼的功能分区。6.3 针对演示视频的录屏级操作建议演示视频一般会当场跑出最终聚类结果展示.csv和聚类中心及占比展示.csv两个文件。实际操作时注意两点一是演示前先删掉旧输出文件避免观众分不清哪些是本次生成的二是跑 K 值扫描时加上print输出进度比如每个 K 值的 SSE 和轮廓系数视频里滚动得比较自然同时也能让技术面试官一眼看到你的算法分析过程。最后补一个自己常用的验证技巧生成最终的宿舍分配表后随机抽三间宿舍人工核对这十几个学生的原始特征是否真的相似。比如抽查的一间宿舍是否都集中在“睡眠时间接近零点以后、对噪音不敏感、不抽烟”的画像上。如果抽查有超过两间宿舍违和优先回去查标准化和硬约束过滤是否调反了顺序而不是去调 K 值。这类人工抽检在数据处理项目里属于交付前的安全网每次跑完批量分配都必须执行一遍。本文还有配套的精品资源点击获取
返回列表