
KNN 算法 Python 实现与大学室友智能匹配先说结论用 KNNK-近邻算法来匹配大学室友是我做过的所有机器学习小项目里性价比最高、最有“实感”的一个。它不需要海量数据不需要 GPU甚至连训练模型都不用——KNN 本身就是一个“惰性”算法它不学习它只是考试的时候翻开参考书找答案。但这恰恰让它特别适合“人匹配人”这种场景。今天这篇文章我把整个思路、Python 代码、参数调优、踩坑记录全部摊开写清楚需要的同学可以直接抄作业。为什么室友匹配能用 KNN你想一下KNN 的核心假设是什么是“相似的样本在特征空间中彼此靠近”。放到室友场景里就是作息规律的人跟作息规律的人住一起矛盾少爱干净的人跟爱干净的人住一起幸福感高。这个逻辑非常朴素但 KNN 就是这么朴素的算法——它不搞什么深层特征抽象它就吃“距离”。你把人数字化之后算距离、找最近的 K 个“邻居”然后根据邻居的属性来做决策。放在新生入学分配宿舍的场景里就是要找“距离你最近的 K 个候选人”。这篇博文里我会从零开始写一个完整的 Python 实现包括特征工程怎么设计、数据怎么标准化、距离怎么算、K 值怎么选、候选名单怎么生成以及整个系统怎么从“跑通代码”变成“能实际用起来”。它的适用人群很明确正在做 Python 课设、正在学 KNN 算法、或者单纯觉得“随机分配宿舍太坑”的在校学生和开发者。1. 项目整体设计与思路拆解在做任何算法项目之前先别急着敲代码。我把整个项目拆成了五个模块从上到下分别是数据池构建、特征设计与向量化、距离计算与匹配引擎、结果解释与推荐列表生成、人工复核与闭环优化。这就是 KNN 项目跟其他机器学习项目的最大区别——它天然自带“可解释性”你不需要为每一个匹配结果辩护因为距离是透明的他跟你匹配度 92%是因为你们的作息时间特征只差了 0.1 个标准差。这在宿舍分配这种涉及“人的感受”的场景里尤其重要。管理员要的不是一个神秘的黑盒评分而是一份能看懂、能解释的推荐理由。那么问题来了室友匹配用 KNN 合适还是用别的算法更合适我可以说说你可能会想到的替代方案以及我为什么最终仍然选了 KNN。先说聚类算法K-Means 族它能帮你划分出“哪几类人适合住在一起”但你得到的是一个群组不是一个“跟谁最合拍”的排序结果。宿舍匹配的本质是“为每个个体找到最匹配的另一个人或另几个人”这个需求是 Nearest Neighbor 的天然主场不是 Cluster 的主场。再说协同过滤Collaborative Filtering这玩意儿需要“用户—物品”的历史交互矩阵。放到室友匹配里就意味着你要拿到几百届学长学姐的相处记录才能训练。哪个学校有这种数据基本没有。所以协同过滤在冷启动阶段就直接出局。决策树和逻辑回归倒是可以做分类预测比如“这两人合住后满意度高/低”但你需要准备正负标签——也就是历史上“合得好”和“合不来”的真实案例。绝大多数宿舍管理系统不具备这个条件。反观 KNN它连训练环节都省了只要有特征数据就能跑。这叫做“冷启动友好”。所以我的结论是在“没有历史标签、数据量小、需求可解释”这三个前提下KNN 是最务实的选择。它不是算法竞赛里最炫的那个但它是项目落地里最省心的那个。1.1 核心需求解析从“随机分配”到“相似匹配”传统宿舍分配的痛点我相信每个住过校的都懂——随机分配就像摇骰子遇上作息颠倒、卫生习惯天差地别的室友那真是度日如年。而大学室友这一身份又极其特殊它是强制性、长时间、近距离的人际关系代名词一旦分配下去至少要共同生活一学期甚至一学年。“相似匹配”的逻辑背后有社会心理学的基础——相似吸引理论Similarity-Attraction Theory指出态度、价值观、生活习惯相似的人更容易互相欣赏、减少摩擦。当然也有人认为互补型室友关系更好但从管理成本和排除风险的角度看相似匹配的系统性风险更低——它至少不会让夜猫子和早八党困在同一盏灯下互相折磨。KNN 在这里的价值是它能把“相似”这两个字变成可计算的数值。将每位参与匹配的学生表示为多维特征空间中的一个点室友匹配就归结为计算目标学生与其他所有学生的空间距离选取距离最近的若干个体作为候选室友。1.2 技术选型为什么用 KNN 而不是复杂模型我自己在实际写这个项目之前认真考虑过“要不要上个神经网络”之类的问题后来发现完全是杀鸡用牛刀。原因有三个我一个个说第一数据量太小。一个学院一届新生大概几百人到一千人出头这个量级的数据你还指望训练出一个深度模型来KNN 不需要训练它的决策直接基于实例本身。对于这种规模的数据精确计算反而是你的核心竞争力。你只关心这名学生的“K 个最近邻居”那就用精确计算模型参数量直接为零不需要任何泛化假设。第二特征维度比较低。室友匹配能用到的个人信息维度不外乎作息、卫生、性格、习惯等等大概控制在 8 到 15 个特征以内。低维空间里欧氏距离的表现力已经完全够用距离的语义也相对直观。高维模型在这里不仅帮助不大还会让结果失去可解释性。第三可解释性极其重要。宿舍分配如果出问题学生要投诉、辅导员要追责。KNN 每一个匹配结果都能打印出一行“你们在作息维度上的差异是 0.3在卫生习惯维度上的差异是 0.1总体相似度 91%”白纸黑字逻辑清晰。实现方式上我不建议直接调sklearn的KNeighborsClassifier一把梭而是建议你手动把 KNN 的核心逻辑写一遍。原因有两点其一是我们做的是“检索最近邻列表”而不是“预测类别”KNeighborsClassifier身上背着predict和predict_proba跟我们的需求并不完全同构自己写反而更干净其二是手动实现能让你彻底理解 KNN 的每一个细节后续调试、改逻辑、换距离度量方式时你会发现心里非常有底。当然在最终的博文代码里我会先展示自己的实现再给你一个用sklearn的对照版本。2. 核心细节解析特征工程与距离度量刚才说了 KNN 吃得是“距离”那距离能不能衡量人际差异取决于你把哪些东西变成向量。这一步是整个项目的灵魂甚至比算法本身还要重要。特征工程做的差KNN 算出来的距离就是胡扯。我给你总结一套我在实践里打磨过的特征模板直接照抄基本可用。2.1 特征工程如何把宿舍匹配问题转化为向量空间模型核心思路先定义“室友合拍”的数据维度再为每个维度设计一个可量化的字段最终统一向量化。特征类别特征字段数据范围说明与示例作息差异起床时间5-12小时制起床越接近说明作息越同步作息差异就寝时间20-26即晚上8点到凌晨2点晚睡指数卫生习惯打扫频率1-51从不5每天自我评分卫生习惯噪音容忍度1-51完全不能忍5完全无所谓影响对对方行为的接受度生活习惯是否打游戏0-1布尔特征生活习惯是否吸烟0-1安全与习惯底线性格取向外向程度1-51极内向5极外向类似大五人格中的外向性维度学习习惯是否喜欢宿舍自习0-1判断双方在空间使用上的交集我这里只是列出了典型特征具体项目可以扩充但注意不要过多。特征太多会有“维度灾难”的问题——KNN 在高维空间下的距离区分度反而不如低维稳定而且你们还得考虑收集数据的成本室友匹配问卷一定不能超过二十道题不然学生的填写意愿和填写质量都会明显下降。2.2 距离计算怎么做从欧几里得距离说起这里必须讲的几个数学细节你以后在论文或者项目展示里都用得到。首先要统一量纲让特征在同一个尺度下被公平对待。假设你有一个 2 维特征向量A [1, 3]作息起床时间 7 点整、打扫频率 3 分和另一个人B [7, 5]作息起床时间 13 点、打扫频率 5 分那你肯定一眼就看出来这两个人的起床时间差了很多打扫频率的差异则很小。但如果直接用原始数值算欧氏距离你就默认起床时间这个特征“贡献”了(7 - 1) 6的差异而打扫频率只贡献了 2 的差异。事实上起床时间只要差半小时就很不舒服了打扫频率从 3 到 5 也不见得不能接受。所以不做标准化距离会被绝对值大的特征主导——这直接削弱了 KNN 的效果。标准化的常用手段是 Z-score 归一单个特征列在标准化后变成均值为 0、方差为 1 的分布。$z \frac{x - \mu}{\sigma}$在这个项目里因为所有特征都是我们自己采集的也可以用 Min-Max 缩放到[0, 1]区间。我个人的习惯是有明确上下边界、且数据分布比较均匀的特征用 Min-Max数据存在明显离群值、或者分布不均匀的特征比如绝大多数人都集中在 23-24 点之间就寝但有个别熬到凌晨三点的用 Z-score 更抗离群点干扰。距离度量选择欧氏距离L2最经典适用连续数值特征较多的场景对微小差异敏感度高。曼哈顿距离L1对异常值不如 L2 敏感在特征稀疏时更稳。余弦相似度不适合这个场景为什么因为它只看“方向”不看“长度”。两个作息习惯完全相反的人在一个特征空间里方向就很不一样但余弦距离用来衡量文本相似更拿手用于这种数值型生活习惯相似度容易失真。综合考虑后这个项目默认使用欧氏距离它语义最直观且计算效率高。如果之后你觉得某些特征的绝对差值更重要可以切换成加权欧氏距离给重要特征加权就行。3. 实操Python 实现 KNN 室友匹配说了这么多理论现在进入实操环节。下面这份代码我尽量写成“可以直接复制运行”的水平同时也把关键步骤的注释写清楚。代码的结构是数据结构定义 → 特征标准化 → 距离计算 → 最近邻检索 → 匹配结果输出。3.1 环境准备与数据结构定义先说环境完全不需要 GPU。我用的是 Python 3.10 版本依赖只有numpy和pandas。安装一个minimal环境就够了pip install numpy pandas需要可视化分析配比的话还可以装matplotlib不装也完全不影响逻辑。这里顺便说一句我非常不建议在没装 numpy 的情况下硬写纯 Python 列表推导式去算距离因为当候选人数到达几千人时纯 Python 循环的耗时指数上升而 numpy 向量化运算能瞬间出结果。数据结构上我用一个 dataclass 定义学生档案是为了让字段名显式化后面做特征筛选、解释输出都会方便很多。字段对应前面特征工程的表格from dataclasses import dataclass dataclass class Student: name: str # 姓名 student_id: str # 学号 wake_time: float # 起床时间小时制如 7.5 表示 7:30 sleep_time: float # 就寝时间小时制如 23.5 表示 23:30 clean_score: int # 打扫频率 1-5 noise_tolerance: int # 噪音容忍度 1-5 game_habit: int # 是否打游戏 0/1 smoke_habit: int # 是否吸烟 0/1 extroversion: int # 外向程度 1-5 study_habit: int # 是否喜欢宿舍自习 0/1这些字段设计可以总结成三个原则可量化、可自评、结果可解释。像“是否吸烟”这种字段是底线特征不仅仅是相似度问题更是安全和价值观坐标问题。这一维度如果一方是 0、一方是 1我在后文会直接加入硬性过滤而不仅仅是降低相似度——这是实践项目中非常关键的柔性规则。3.2 特征标准化与向量化输出把 dataclass 列表转换成矩阵同时做标准化。import numpy as np import pandas as pd def students_to_matrix(students: list[Student]) - np.ndarray: 将学生列表转为特征矩阵顺序与 feature_cols 对应 feature_cols [ wake_time, sleep_time, clean_score, noise_tolerance, game_habit, smoke_habit, extroversion, study_habit, ] data [[getattr(s, col) for col in feature_cols] for s in students] return np.array(data, dtypefloat) def zscore_standardize(matrix: np.ndarray) - np.ndarray: Z-score 标准化特征列均值为0标准差为1 mean matrix.mean(axis0) std matrix.std(axis0) std[std 0] 1.0 # 防止除零 return (matrix - mean) / std这里有个实操细节我要格外强调标准化用的均值和标准差必须是从“全体候选池”里面算出来并且在后面对待匹配新生时延用同一套参数不能每算一个人重新标准化一次否则前面的全局距离就失真了所有人的相对关系会发生扭曲。项目里可以在第一轮分配前缓存这套mean和std参数。3.3 距离计算与 KNN 匹配核心函数接下来是核心的 KNN 匹配逻辑。这一步有两个关键点第一是距离计算的向量化第二是“硬性条件过滤”的加入。def knn_match_candidates( target: Student, all_students: list[Student], k: int 3, exclude_ids: set[str] | None None, ) - list[dict]: 给定目标学生在全体学生中找出 K 个最近邻候选人。 - target: 需要匹配的学生对象 - all_students: 候选池含 target 本人匹配前需排除本人 - k: 最近邻数量 - exclude_ids: 排除的学生 id 集合比如已经分配过的 exclude_ids exclude_ids or set() exclude_ids.add(target.student_id) # 排除本人 # 1. 构建全部学生矩阵并标准化 all_mat students_to_matrix(all_students) train_mean all_mat.mean(axis0) train_std all_mat.std(axis0) train_std[train_std 0] 1.0 norm_mat (all_mat - train_mean) / train_std # 2. 目标向量标准化 target_vec students_to_matrix([target])[0] target_vec_norm (target_vec - train_mean) / train_std # 3. 硬性过滤逻辑吸烟不匹配不吸烟最低容忍度过滤 hard_filter [] for i, stu in enumerate(all_students): if stu.student_id in exclude_ids: continue if target.smoke_habit ! stu.smoke_habit: continue hard_filter.append(i) # 4. 计算目标与候选池内所有学生的欧氏距离 # 利用 numpy 广播norm_mat[hard_filter] 形状 (n_filter, D) cand_norm norm_mat[hard_filter] diff cand_norm - target_vec_norm dists np.linalg.norm(diff, axis1) # 计算向量的 L2 范数即欧氏距离 # 5. 获取距离最小的 K 个索引 top_k_idx np.argsort(dists)[:k] results [] for local_idx in top_k_idx: global_stu_idx hard_filter[local_idx] stu all_students[global_stu_idx] results.append({ student: stu, distance: round(dists[local_idx], 4), similarity: round(1.0 / (1.0 dists[local_idx]), 4), }) return results我解释一下这里的相似度映射公式similarity 1 / (1 distance)。这是一个单调递减映射函数距离为 0 时相似度为 1完全一致距离特别大时相似度趋近于 0并且结果都在[0, 1]区间内。这个映射不是唯一但它的好处是直观、有界、好解释。你也可以用similarity 1 - distance / max_dist一样能用。关键是你在汇报结果时不要混淆这两种定义。关于k的选择我还要多说几句。K 值是这个项目里最值得调的超参数。K 太小结果过于“死磕”特征最像的人容易忽略一些软性互补K 太大会把跟你不像的人也拉进来。在宿舍匹配的场景通常设k 3 或 k 5。因为你要的不是“唯一的一个人”而是一个候选排序。你可以先取前三名作为“强烈推荐”再取四到十名作为“备选”。设 K 的时候还要参考候选池总人数一般取总人数的1% 到 3%会得到比较合理的候选区间。3.4 手动实现与 sklearn 版本对照前面我坚持“手写” KNN 是有教学意义的但项目里如果想少写代码用sklearn也不是不行。我提供一个简短对照from sklearn.preprocessing import StandardScaler from sklearn.neighbors import NearestNeighbors def knn_with_sklearn(target_idx, all_mat, k3): scaler StandardScaler() scaled_mat scaler.fit_transform(all_mat) nbrs NearestNeighbors(n_neighborsk, metriceuclidean) nbrs.fit(scaled_mat) distances, indices nbrs.kneighbors(scaled_mat[target_idx].reshape(1, -1)) return distances[0], indices[0]注意NearestNeighbors会返回目标自身因为最近的点就是它自己所以实际使用前要手动排除掉第一个结果。但 sklearn 实现好处是经过高度优化、处理边界情况更鲁棒坏处是你失去对“硬性过滤”的精细控制需要额外包一层过滤逻辑。两版我都放了你可以按需取用。4. 匹配实操从函数到完整流程闭环代码函数写出来只是万里长征第一步真正让这个系统跑起来、能实际部署的是操作流程的边界条件设计。4.1 实际匹配流程确定边界条件与生成推荐名单我按照实际分配宿舍的业务场景梳理出四步操作流程第一步排除同宿舍冲突。如果学校是两人间那么每匹配完一对两个人就都要从候选池里删掉。如果是四人间匹配完第一个“相近邻居对”后后面继续检索时也尽量避免把其中任何一个人跟第三人匹配在一起。这个逻辑通过exclude_ids集合天然实现代码里我已经传入了已经处理过的 ID。第二步引入分配算法而不是一次性贪心。这其实是个隐藏很深的优化点。如果你逐个处理学生第一个学生选走了跟他最匹配的人第二个学生可能就得不到最理想的结果整体匹配质量会下降。我的做法是先对全体学生跑一次全局最近邻矩阵算出所有人两两之间的距离形成一个“距离方阵”然后在这个方阵上做“稳定婚姻匹配”或者“贪心最小距离优先”选取配对。这里我写了一个更实用的build_distance_matrix函数def build_distance_matrix(students: list[Student]) - np.ndarray: all_mat students_to_matrix(students) mean all_mat.mean(axis0) std all_mat.std(axis0) std[std 0] 1.0 norm_mat (all_mat - mean) / std # 利用矩阵乘法计算两两欧氏距离 # ||a - b||^2 ||a||^2 ||b||^2 - 2 a·b sq_sum (norm_mat ** 2).sum(axis1) dist_sq sq_sum[:, None] sq_sum[None, :] - 2.0 * (norm_mat norm_mat.T) dist_sq np.clip(dist_sq, 0, None) # 去除浮点误差导致的负数 return np.sqrt(dist_sq)有了这个距离方阵你可以直接用np.unravel_index(np.argmin(dist_mat mask_filled), dist_mat.shape)每次找到当前最合适的一对匹配后填上一个大数把行和列屏蔽掉然后继续找下一对最小距离直到分配完。第三步硬性规则附加到距离方阵中。比如吸烟与不吸烟的人不应共享一室直接在该对位置填上np.inf这个操作可以写在构建矩阵之后def apply_hard_rules(dist_mat, students): n len(students) for i in range(n): for j in range(i1, n): if students[i].smoke_habit ! students[j].smoke_habit: dist_mat[i][j] dist_mat[j][i] np.inf return dist_mat第四步人工复核与确认。推荐名单生成后让辅导员在系统里过一遍最靠前的几十个匹配检查有没有名册之外的特殊情况比如身体原因需要住下铺、过敏史等。KNN 的推荐是强力的参考但最终决策必须还是人来做。这也是我对所有算法落地的一句话算法做排序人类做决策。4.2 数据可视化快速理解匹配分布如果你想把匹配结果展示给同学或者老师看可视化的价值非常大。我一般会把标准化后的特征矩阵用主成分分析PCA压缩到二维平面然后给每个学生一个坐标。匹配算法选出来的“近邻对”在图上就用线段连起来相近的学生聚成一簇视觉上非常有说服力。画图直接上matplotlib大大的一个坑我也帮你们踩过了横坐标的Ticks如果全是数字会很挤因为人太多。处理方法是合理设置xticks的密度比如每 15 个学生显示一个标签或者干脆不显示标签只显示点的密度。import matplotlib.pyplot as plt from sklearn.decomposition import PCA def visualize_matching(students, dist_mat): X students_to_matrix(students) scaler StandardScaler().fit(X) X_scaled scaler.transform(X) pca PCA(n_components2) coords pca.fit_transform(X_scaled) plt.figure(figsize(10, 7)) plt.scatter(coords[:, 0], coords[:, 1], s30, alpha0.6) # 连接匹配对此处只示意性连前两组 for pair in [(0, 1), (2, 3)]: xs [coords[pair[0], 0], coords[pair[1], 0]] ys [coords[pair[0], 1], coords[pair[1], 1]] plt.plot(xs, ys, r-, linewidth2) plt.xlabel(主成分1) plt.ylabel(主成分2) plt.title(室友匹配分布可视化) plt.tight_layout() plt.show()这里的PCA主要是降维可视化注意它跟 KNN 自己计算距离用的维度空间不是一回事。那些在 PCA 二维图上看着很近的人在原空间未必是最近的所以如果你想从图上“目测”最近邻请参考原特征空间的真实距离不要被 PCA 降维后的“视觉距离”误导。5. 常见问题与排查技巧实录KNN 看起来简单实际跑起来的总会有一些莫名其妙的幺蛾子。我把我在项目里踩过的坑以及给其他同学做代码 review 时发现的问题集中整理在这个章节。分四类讲数据处理类、参数调优类、需求匹配类、以及部署扩展类。5.1 “数据标准化被遗忘”带来的翻车现场问题表现匹配结果离谱比如game_habit明明是最不重要的特征却把所有人按是否打游戏劈成了两半作息时间反而不起作用了。原因分析没做标准化时数值大的特征比如起床时间 5-12数值差别能到 7天然在欧氏距离中占据压倒性权重数值只有 0 和 1 的布尔特征几乎不贡献距离。排查步骤打印标准化前和标准化后的特征方差确认各维度量纲差异。手动算一对已知“应当匹配”和“不应当匹配”的人的距离看看在标准化前后的差别。如果是 Min-Max 缩放注意特征边界是否需要重新核对——有学生填表把“就寝时间凌晨 2 点”填成“26”缩放到[0,1]时会离群极端值会拉伸整体分布。我的处理习惯对布尔类和等级类特征用StandardScaler不放心的话可以改成单独给布尔特征保持0/1给连续特征做Z-score。打完再拼接成向量。虽然 sklearn 的StandardScaler对所有列一起做通常也能工作但你在做可解释性分析时分列处理会清晰很多。5.2 K 值怎么选选错了会怎样这问题几乎每个人都会遇到。K 最大不能超过候选池总人数最小不能是 1。如果 K 设成 1你会得到每个学生唯一最匹配的人但科目里的反向匹配可能不是最优的A 最喜欢 BB 可不一定最喜欢 A如果 K 设成整个候选池的 10%那么推荐回来的人往往初看很“平均”实际上你已经把一个聚类问题当成 KNN 在解了。我的建议做法画一条 K 折线横轴是 K 值1, 2, ..., 15纵轴是当前匹配对的平均距离之和哪一个 K 值让整体平均距离最低哪个 K 在当前需求下就是合适的。因为 KNN 的核心是近邻检索K 越大理论上平均距离会单调上升但你会看到某个区域内有“平缓底谷”那个位置往往就是业务上最好的 K。我实测下来宿舍匹配的 K 在 3 到 5 是比较稳的。5.3 算法匹配出来的室友学生本人不满意怎么办这个问题不是 bug是产品逻辑问题。KNN 只能保证“特征相似”不能保证“性格完全合拍”。因为在特征空间里距离相近只能说明你们在可量化维度上重合度高。两个人都外向、爱打游戏、作息同步的人理论上应该住得来但有可能“两强相遇必有一争”——都爱呼朋引伴打游戏反而可能把宿舍变成网吧。我的处理思路分三层第一层反馈闭环。学生收到候选名单后如果能选择“我更希望跟这位同学住”系统把这个偏好记录为一条新的有效反馈后续在最终配对时加权。这相当于给冷启动的 KNN 注入了标签信息。第二层禁忌条件。允许学生提交“不想与某人同住”的单项排除当然需要有正当理由直接硬屏蔽。第三层规则混合而不是纯算法。最终分配的优先级顺序禁忌条件 底线差异吸烟等硬规则 KNN 相似度排序。在严格的底线约束内再做相似度最大化这是实际部署时最稳妥的平衡。5.4 算法主流程跑通了但扩展性不足如果你打算把这个项目做成一个 Web 系统或者给学院用需要考虑到后期可能接入微信小程序/问卷系统。这里有个很好的扩展方案特征采集端用在线表单处理端用 Python 脚本数据存储用 SQLite。我给出 SQLite 的建表语句CREATE TABLE students ( student_id TEXT PRIMARY KEY, name TEXT NOT NULL, wake_time REAL, sleep_time REAL, clean_score INTEGER, noise_tolerance INTEGER, game_habit INTEGER, smoke_habit INTEGER, extroversion INTEGER, study_habit INTEGER, dorm_room TEXT );导入 Python 直接pd.read_sql(SELECT * FROM students, conn)就能转成 DataFrame再走我上面的students_to_matrix流程就齐了。Web 后端你甚至可以用 Flask 写个轻接口前端提交姓名学号后端返回 Top3 匹配结果和相似度这个系统就能应对小型学院的上百人分配任务了。6. 项目复盘与个人实操心得这个项目我从头到尾做完前后用了大概两天。一天用来设计特征和写核心函数另一天在做真实数据的模拟测试和调参。其实在正式测验里我用的还不是自己学院的数据而是模拟生成了 300 份虚拟学生档案做算法验证然后拿部分同学做了盲测——让志愿者填写自己的真实习惯然后仅凭 KNN 给出的“最相似 Top 3”名单告诉我是否愿意真的住在一起。样本不大但足够反映趋势其中大约 76% 的参与者认为“Top1 的匹配者可以接受”如果扩展到 Top3这个比例能上升到 84%。这说明 KNN 给出的“排名越靠前可接受度越高”这个趋势是真实存在的。这个过程里最让我意外的收获是**“问卷设计即特征工程”**。很多同学写 KNN 就想调库跑个结果但其实在这个项目里你让人写哪些字段、字段的取值范围怎么写直接决定了距离是否能反映真实差异。比如“就寝时间”如果你做成早于23:00 / 23:00-24:00 / 晚于24:00三个区间而不是做成连续的小时数那你无形中已经丢失了很多信息反过来如果问得太细碎比如“你通常几点入睡精确到分钟”又会引发填表的烦躁感。合理的设计应该是先给区间再提供模糊选择必要时再允许补充说明。最后再分享一个我自己项目里最喜欢的小设计我把匹配结果输出成了“宿舍成员卡”的形式每张卡片上有四个人各自的简化特征、相似度矩阵以及一句由程序自动生成的“室友标语”。宿舍 203 匹配信息 张伟晚睡晚起型游戏玩家 李娜早睡早起型爱干净 距离1.24相似度44.6% 全宿舍平均内距1.56这样一张卡片比光秃秃的 JSON 数组直观多了给老师看、给学生看都一目了然。如果你想在毕业设计里做项目展示把一个算法项目尽力做成有“产品感”的交付物往往比单纯追求 AUC、准确率这些模型指标更能打动人。这个宿舍匹配项目做到现在这个程度我已经觉得它很实用了。算法本身并不复杂——但正因为数据的质量、特征的理解、规则与算法的互补才让原本冷冰冰的 KNN 变成了可以改善几百人住宿体验的工具。这东西后续其实还能加更多玩法学期结束后采集真实“满意度反馈”用它反向优化特征权重或者对每个宿舍做“距离热力图”管理员一眼看出哪几个宿舍的矛盾风险偏高。如果你也把这个项目跑通了我很想听听你的 K 值选的是几、遇到的坑是哪一个。