
简介一份面向推荐系统隐私保护方向的毕业设计项目资料适合计算机相关专业学生、研究差分隐私与协同过滤的开发者。项目基于Python实现基于用户的协同过滤推荐系统采用两种相似度计算方法并通过差分隐私机制对推荐结果进行加密扰动在MovieLens不同规模数据集上完成实验对比系统分析隐私预算对推荐准确度与隐私保护程度的影响。压缩包共77个文件核心内容包括12个Python源码、60张实验与过程截图、2个Word论文文档以及数据集、License和README说明整体仅3.37MB结构清晰、便于下载归档。已有922人学习内容覆盖推荐系统与差分隐私的研究背景、算法原理、系统设计、实现测试和结果分析适合毕业设计参考、算法复现或隐私保护方向的进一步研究。1. 带差分隐私的协同过滤推荐系统一份能跑通毕设的完整工程包如果你正被“推荐系统 隐私保护”这个毕业设计方向卡住最大的问题往往不是看不懂差分隐私公式而是打开一个代码包时发现只有零散脚本、没有数据集、没有论文文档、跑起来还全是乱码。这份资源包把闭环给齐了基于Python手写的协同过滤算法、余弦和皮尔逊两种相似度计算、拉普拉斯噪声注入模块、MovieLens小规模数据集、实验截图、论文5.5稿和排版模板。它不是把差分隐私当作论文里的一页理论而是真正把噪声加进了推荐结果并通过对照实验找到了隐私预算与推荐精度之间的平衡点。适合准备做推荐系统方向毕设的学生也适合想在项目里快速落地差分隐私的开发者。拿到手就能按步骤复现。2. 协同过滤与差分隐私为什么推荐系统要扛着噪声干活推荐系统越精准需要的用户行为数据就越细隐私泄露的风险就越大。差分隐私解决的核心矛盾是让推荐系统在输出有价值的推荐结果时不泄露单个用户的评分记录。这一章先把协同过滤和差分隐私这两块基石讲清楚后面拆代码时才不会“知其然不知其所以然”。2.1 基于用户的协同过滤核心流程基于用户的协同过滤有三个固定步骤收集用户偏好、找到相似用户、生成加权推荐。第一步收集偏好在MovieLens这类显式反馈数据集里评分就是偏好信号。常见做法是把评分数据整理成user-item矩阵行是用户列是电影单元格值是对应用户的评分未评分的位置一般填0。但要记住这里的0不代表用户给了0分而是表示“不知道用户是否看过这部电影”这个区别在计算相似度时非常关键。第二步寻找相似用户是整个算法的核心。遍历目标用户与其他用户的评分向量计算相似度。余弦相似度把每个用户的所有评分看成一个高维向量通过向量夹角的余弦值衡量相似程度优点是计算简单、天然不受用户评分数量差异影响。皮尔逊相关系数则在余弦之前先对每个用户的评分做中心化处理减掉该用户自己的评分均值这样做能消除用户打分宽严不一致的问题——有人习惯给4分以上有人喜欢给2到3分中心化之后比较的是“相对偏好”而不是“绝对分值”。第三步生成推荐。选出与目标用户相似度最高的k个用户作为邻居邻居们对某部电影的评分用相似度加权平均得到目标用户对这部电影的预测评分。权重就是相似度值相似度越高的邻居对预测分数的影响越大最后取预测分最高的TopN部电影作为推荐列表。k值一般在10到50之间调太小邻居代表性不足太大则会混入大量低相似度用户。这个项目选用基于用户的协同过滤而不是基于物品主要有两个原因一是隐私风险更直接地落在用户身上论文里想讨论的正是“通过推荐结果反推某个用户看过什么”用户维度天然契合二是实验用的数据集规模不大用户数在几百这个量级用户-用户相似度矩阵的计算成本完全可控适合作为毕设主算法。2.2 差分隐私的定义与拉普拉斯机制差分隐私要防御的攻击模型是攻击者可以多次查询推荐系统的输出通过比较“包含某个用户的推荐结果”和“不包含某个用户的推荐结果”逐步推断出该用户的评分记录。如果推荐算法对训练数据中任意一条记录的增删都不敏感攻击者的推断就无法得到可靠结论。形式化的说法是对于只差一条数据记录的两个相邻数据集D和D’某个随机算法K在两个数据集上的输出分布满足比率不超过e^ε就称算法K满足ε-差分隐私。其中ε叫隐私预算ε越小两个分布越接近单个用户数据的影响被掩盖得越好隐私保护强度越高。代价是加进结果里的噪声越大推荐精度下降越明显。最常用的实现机制是拉普拉斯机制对查询函数f的输出结果加上一个服从拉普拉斯分布的随机噪声噪声尺度由Δf/ε决定Δf是f的敏感度即相邻数据集下f输出结果的最大变化范围。在推荐系统里相似度函数的值域是[-1, 1]敏感度可以取2评分预测函数的值域是1到5敏感度取4。敏感度和ε共同决定噪声大小这就是为什么隐私预算是一个需要仔细实验的工程参数而不是一个拍脑袋定的数。差分隐私还有一个很好的性质叫并行组合性如果把数据集按用户分成互不重叠的组每个组单独分配隐私预算总的隐私保护强度不会叠加。在设计实验时可以对每个用户的相似度计算分别分配ε然后复用同一份预算分析多个物品的推荐结果这也是差分隐私在实际系统里优于简单脱敏的关键原因。2.3 差分隐私嵌入推荐系统的三种位置把差分隐私和协同过滤结合有三种常见的嵌入位置。第一种是输入扰动在用户评分进入算法之前就给评分矩阵加噪声。优点是实现最直接缺点也很明显噪声会在后续的相似度计算和加权聚合过程中不断累计小数据集上零点几的噪声就可能让推荐结果面目全非论文实验里一般不建议用这种方式。第二种是目标扰动在协同过滤的优化目标中注入噪声把推荐问题改造成带噪声的目标函数。这种方式保留了更多模型结构理论上看很有趣但实现复杂度高而且需要把相似度矩阵的闭式解改写对毕业设计来说工作量偏大。第三种是输出扰动也是这份资源采用的方式正常完成相似度计算和推荐列表生成之后在最终输出前添加拉普拉斯噪声。具体到这个项目是在计算完用户相似度矩阵后对相似度值施加噪声再用扰动后的相似度去做加权预测。这样做的工程价值很明显差分隐私模块被设计成一个可插拔组件模型主体逻辑不用动跑一组无噪声基线再跑一组有噪声实验就能得到精度对比曲线。从隐私分析的角度看输出扰动面对的是“攻击者能直接看到加了噪声的相似度或推荐列表”的场景只要保证噪声是算法输出前唯一的数据出口并且每个查询都消耗对应的隐私预算这套机制就算成立。这也决定了实验时要特别小心任何中间结果如果没加噪声就写到日志或图片里都会让隐私保证失效。3. 工程结构与运行环境从解压到跑通全流程下载这份资源包后第一步不是急着打开IDE而是先把目录结构看清楚。很多人在毕设里踩的坑都是因为不知道哪个文件夹能跑、哪份文档对应哪部分代码。这一章按“解压-装环境-看数据-跑脚本”的顺序把整个流程走一遍。3.1 解压与目录结构先弄清每个文件的作用资源包解压后核心内容大致如下目录/文件作用code/可运行的Python代码包含数据处理、协同过滤、差分隐私、实验脚本data/ml-latest-small.zip 压缩包内含 ratings.csv、movies.csv、tags.csvimg/实验运行截图与论文中的结果图对应论文 5.5 稿.docx毕业设计论文文稿含算法设计、实验分析、隐私预算讨论格式模版_demo.docx用于排版论文的格式模板README.md环境要求、运行命令、复现步骤说明强烈建议把README.md当作第一份阅读文档。它通常会写明Python版本、依赖库安装命令、数据集来源和入口脚本参数。我拆过不少毕设资源绝大多数“跑不起来”的问题其实README里都已经写了注意事项只是被直接跳过。img目录下的截图文件名看起来是一串哈希但内容对应论文里不同隐私预算下的实验结果图。跑通代码后可以用这些截图来比对自己生成的曲线判断是不是哪里逻辑写歪了。论文5.5稿是接近终稿的版本章节结构完整从研究背景到实验分析都在里面写自己的论文时可以直接对照这个骨架来组织章节。3.2 环境配置Python版本与依赖项选择这个项目的算法部分完全是手写实现不依赖深度学习框架依赖项很克制。我一般会建议使用Python 3.8或3.10搭配虚拟环境管理依赖避免和其他项目的包冲突。下面是建议的requirements.txtnumpy1.21.0 pandas1.3.0 scikit-learn1.0.0 scipy1.7.0 matplotlib3.4.0安装命令python -m venv venv source venv/bin/activate pip install -r requirements.txt逻辑说明numpy负责矩阵计算pandas读取评分CSVscikit-learn用于训练测试集划分和误差指标计算scipy提供稀疏矩阵支持matplotlib画隐私预算-精度曲线。整个推荐算法和差分隐私扰动逻辑都建立在这些基础库上没有需要编译的高版本要求。参数说明版本号取的是保守值。如果你用的是Python 3.11或更高版本部分老版本库可能缺少预编译wheel导致pip安装时自动走源码编译并报错。遇到这种情况降低Python版本到3.10重跑一遍虚拟环境是最快的解法。Windows用户注意成功激活虚拟环境后命令行提示符前面会出现(venv)确保后续pip install都在这个状态下执行。3.3 数据探索与清洗比解压更重要的一步ml-latest-small.zip解压后核心文件ratings.csv的结构是userId、movieId、rating、timestamp四列。运行协同过滤之前有必要先看一眼数据分布import pandas as pd df pd.read_csv(data/ml-latest-small/ratings.csv) print(总评分记录数:, len(df)) print(用户数:, df[userId].nunique()) print(电影数:, df[movieId].nunique()) print(评分均值:, df[rating].mean()) user_count df.groupby(userId)[movieId].count() print(评分10条的用户占比:, (user_count 10).mean())逻辑说明前四条打印用来确认数据加载正确、理解数据规模。最后一行统计每个用户的评分数量计算评分次数不少于10次的用户占比这个比例决定了能不能直接使用全量数据。参数说明如果用户数在600左右、电影数在9000左右说明是标准的MovieLens小数据集。稀疏度大致是1减去非零评分位置占总矩阵位置的比例通常高达95%以上。对于协同过滤来说这么高的稀疏度意味着两个用户找到共同评分的电影并不容易A同学的项目里就是先做了这层探索才决定把共同评分数量低于5的用户直接排除在邻居选择之外。数据清洗还有一个容易忽略的点检查是否有重复评分。某些版本的数据集可能因为采集异常出现同一个用户对同一部电影有多条评分直接删除重复记录比较稳妥df df.drop_duplicates(subset[userId, movieId], keeplast)逻辑说明drop_duplicates保留最后一次评分作为该用户对该物品的最终态度。如果保留全部重复记录后续构建评分矩阵时会因为id重复而报错或者在pivot_table聚合时产生不确定的取值。3.4 运行实验脚本参数怎么传输出怎么读数据准备完成后运行入口脚本。命令形式一般是python code/main.py \ --data data/ml-latest-small/ratings.csv \ --metric cosine \ --epsilon 1.0 \ --k 20 \ --topn 10脚本运行结束后预期输出包含三类信息[info] metriccosine, epsilon1.0, k20, topn10 [result] MAE0.7921, RMSE0.9834 [result] private_top10_overlap0.724逻辑说明--metric控制用余弦相似度还是皮尔逊相关系数--epsilon设置隐私预算--k是邻居数量--topn控制推荐列表长度。运行日志里先打印实验配置再输出误差指标和推荐列表重合度。重合度表示“加噪声之后的推荐列表与不加噪声版本的重叠比例”它是衡量隐私保护代价的关键指标。参数说明第一次跑通建议把epsilon设成一个大数比如10.0此时噪声极小和纯协同过滤几乎没有区别。确认流程通畅后再把epsilon逐步降到0.1、0.5、1.0观察MAE和重合度的变化趋势。直接上小epsilon容易碰到后面章节里描述的全乱套情况。4. 核心代码拆解相似度计算、拉普拉斯扰动与隐私预算这一章直接进入代码层。我会按照协同过滤实验的正常顺序从数据预处理、相似度计算、噪声注入到推荐评估逐步展示关键代码片段并对每个参数做说明。代码风格是常见的手写实现方便你根据自己的数据集调整。4.1 构建评分矩阵与数据划分评分数据是长表结构需要转成矩阵才能参与后续计算。最常见的方式是pandas的pivot_tableimport pandas as pd import numpy as np df pd.read_csv(data/ml-latest-small/ratings.csv) df df.drop_duplicates(subset[userId, movieId], keeplast) pivot df.pivot_table( indexuserId, columnsmovieId, valuesrating ) matrix pivot.fillna(0).values print(评分矩阵尺寸:, matrix.shape)逻辑说明pivot_table以userId为行、movieId为列、rating为值自动整理出用户-电影评分矩阵。fillna(0)把未评分的空缺位补成0这是为了能用矩阵乘法做批量运算。但前面已经强调过这些0只代表未知不代表用户打了0分所以在后续相似度计算时必须用掩码排除0值位置。参数说明matrix.shape输出的是用户数×电影数如果数据量是几万用户这里的内存开销就会快速膨胀。遇到大数据集建议改用scipy.sparse.csr_matrix存储稀疏矩阵from scipy.sparse import csr_matrix user_ids df[userId].astype(category).cat.codes.values item_ids df[movieId].astype(category).cat.codes.values sparse_matrix csr_matrix( (df[rating].values, (user_ids, item_ids)), shape(user_ids.max() 1, item_ids.max() 1) )逻辑说明把原始userId、movieId映射成从0开始的连续整数保存成稀疏矩阵只存储非零评分位置。差分隐私加噪时也只需要对矩阵中的非零元素操作效率和密集矩阵完全不是一个量级。数据划分建议按时间切分而不是随机切分。推荐系统的目标是用历史行为预测未来行为随机划分会引入未来信息使评估结果虚高df df.sort_values(timestamp) train df.groupby(userId).head(-1) test df.groupby(userId).tail(1)逻辑说明head(-1)表示每个用户去掉最后一次评分作为训练集tail(1)取最后一次评分作为测试集。这样训练和测试在时间上严格前后分离更贴近真实推荐场景。参数说明这个划分方式在数据不平衡时可能导致某些用户只有一条记录这种情况下tail(1)取到的就是唯一一条而训练集为空。稳妥的办法是先过滤掉评分数量小于2的用户再做划分。4.2 余弦相似度与皮尔逊相关系数实现相似度计算是整个算法里最容易隐藏bug的地方。直接对fillna(0)矩阵做余弦相似度会得到严重误导def cosine_similarity_native(matrix): norm np.linalg.norm(matrix, axis1, keepdimsTrue) matrix matrix / (norm 1e-10) return matrix matrix.T这个写法看似正确但0值位置会被当作共同评分参与点积计算把“两个用户都没看过的电影”误当成“两个用户都喜欢同一部电影”相似度被拉高。正确的实现要基于共同评分的掩码def cosine_similarity_two_users(a, b, min_common5): 计算两个用户向量的余弦相似度 mask (a 0) (b 0) common_count mask.sum() if common_count min_common: return 0.0 a_masked a[mask] b_masked b[mask] dot np.dot(a_masked, b_masked) norm_a np.linalg.norm(a_masked) norm_b np.linalg.norm(b_masked) if norm_a 0 or norm_b 0: return 0.0 return dot / (norm_a * norm_b)逻辑说明mask选中两个用户都有评分的电影位置后续点积和模长计算都只在mask后的向量上进行。common_count小于min_common时直接返回0避免少数几个共同评分产生的偶然高相似度。参数说明min_common是共同评分数量阈值经验值在5到10之间。MovieLens小数据集上阈值取5会有更多用户对进入候选邻居集合取10则相似度更稳定但邻居候选变少。如果后续发现很多用户找不到邻居可以适当调低。皮尔逊相关系数同样需要掩码并在计算前做中心化def pearson_similarity_two_users(a, b, min_common5): 计算两个用户向量的皮尔逊相关系数 mask (a 0) (b 0) common_count mask.sum() if common_count min_common: return 0.0 a_centered a[mask] - a[mask].mean() b_centered b[mask] - b[mask].mean() denominator np.linalg.norm(a_centered) * np.linalg.norm(b_centered) if denominator 0: return 0.0 return np.dot(a_centered, b_centered) / denominator逻辑说明中心化后计算的是用户评分相对自身评分均值的偏差趋势而不是绝对分数的一致性。这样克服了“一个用户习惯打4分另一个用户习惯打2分”导致的相似度假性偏低问题。参数说明当a_centered或b_centered全为0时说明该用户对所有共同评分电影给出了完全一致的分数此时相关系数无定义返回0是平稳处理。论文实验里会同时跑这两种相似度目的就是看评分中心化对推荐精度的影响。4.3 拉普拉斯扰动模块与隐私预算控制差分隐私模块的核心是拉普拉斯噪声生成器def add_laplace_noise_to_matrix(sim_matrix, epsilon, sensitivity2.0): 对相似度矩阵施加差分隐私扰动 epsilon: 隐私预算越小噪声越大 sensitivity: 敏感度相似度值域[-1,1]所以取2 scale sensitivity / epsilon noise np.random.laplace(0.0, scale, sizesim_matrix.shape) noisy sim_matrix noise # 相似度值域约束在[-1,1] noisy np.clip(noisy, -1.0, 1.0) # 保持矩阵对称 noisy (noisy noisy.T) / 2 np.fill_diagonal(noisy, 1.0) return noisy逻辑说明np.random.laplace生成位置参数为0、尺度为scale的拉普拉斯噪声加到原始相似度矩阵上。加噪后可能出现三个问题值超出[-1,1]、矩阵不对称、对角线不为1所以用clip、对称化和恢复对角线的操作逐一修正。这里的敏感度取2是因为相似度函数的输出范围是从-1到1任改一条用户评分相似度最大变化就是这个范围宽度。参数说明epsilon是最关键的实验参数。epsilon2.0时scale1.0噪声均值较大推荐结果会明显偏离基线epsilon10.0时scale0.2噪声影响很小。从隐私保证角度看ε2.0已经能提供可观保护所以论文实验区间一般落在0.1到2.0之间从大到小扫描找到精度明显下降的那个拐点。如果要在预测评分阶段加噪敏感度需要重新推导。评分值域是1到5函数输出最大变化范围是4所以敏感度取4def perturb_rating(pred, epsilon, sensitivity4.0): 对预测评分加噪声并限制在评分值域内 scale sensitivity / epsilon noisy pred np.random.laplace(0.0, scale) return np.clip(noisy, 1.0, 5.0)逻辑说明这个函数适用于对单个预测评分做扰动比如离线评估时逐条加噪。注意对同一条数据的多次查询会累计消耗隐私预算实验中要避免对同一个用户反复调用来制造假性的隐私保护。4.4 推荐生成与精度评估相似度矩阵计算完成后生成候选推荐列表。基于用户的协同过滤预测通常是按用户逐行计算def predict_for_user(sim_row, rating_matrix, k20): 基于相似度加权平均预测一个用户的缺失评分 n_items rating_matrix.shape[1] preds np.zeros(n_items) # 排除自己取相似度最高的k个邻居 neighbor_idx np.argsort(sim_row)[::-1][1:k1] neighbor_sims sim_row[neighbor_idx] neighbor_ratings rating_matrix[neighbor_idx, :] for item in range(n_items): ratings neighbor_ratings[:, item] valid_mask ratings 0 if valid_mask.sum() 0: continue weights neighbor_sims[valid_mask] preds[item] np.sum(ratings[valid_mask] * weights) / np.sum(weights) return preds逻辑说明argsort默认从小到大排序[::-1]反转后从大到小取前k个邻居去掉索引0也就是用户自己。对每个物品找到邻居中有评分的位置用相似度加权平均预测缺失评分。如果所有邻居都没有看过该物品就跳过该位置。参数说明k是邻居数决定推荐的广度和稳定性。k偏小比如5推荐结果会集中在极少数高度相似的用户偏好里覆盖率低k偏大比如50会把大量低相关用户拉进来预测值趋向平均值。实验里一般取20作为默认值并做k10、20、50三组对比。评估时使用MAE和RMSEfrom sklearn.metrics import mean_absolute_error, mean_squared_error pred_values [] true_values [] for user_row, item, true_rating in test[[userId, movieId, rating]].values: pred predict_for_user(sim_matrix[user_row], rating_matrix, k20)[item] pred_values.append(pred) true_values.append(true_rating) mae mean_absolute_error(true_values, pred_values) rmse np.sqrt(mean_squared_error(true_values, pred_values)) print(fMAE{mae:.4f}, RMSE{rmse:.4f})逻辑说明逐个从测试集中取出真实评分对应的预测值攒成两个列表后交给sklearn计算指标。MAE衡量平均绝对误差RMSE对误差大的样本更敏感两者同时看可以判断预测是否存在极端偏离。参数说明如果某个测试物品在所有邻居中都没有评分记录predict_for_user会返回0导致MAE被严重拉低。遇到这种情况需要在收集pred_values时过滤掉预测为0的位置或者给未预测位置补一个全局评分均值。5. 避坑记录隐私预算、稀疏矩阵与复现不一致这个项目我前后拆过几次每次都会在不同位置翻车。有一些坑是参数调出来的有一些是数据本身带来的还有一类属于“论文和代码对不上”。这一章把典型的踩坑记录按现象、原因、解决三条线整理出来每一条都值得在跑实验前先看一遍。5.1 参数类踩坑隐私预算与随机噪声现象一把epsilon从1.0直接调到0.01再去看推荐列表发现加噪后的结果和未加噪版本几乎没有重叠MAE飙升到接近随机猜测的水平。原因拉普拉斯噪声的尺度是sensitivity/epsilonepsilon0.01意味着scale200而用户之间的相似度值通常集中在-0.3到0.8之间一个尺度为200的噪声加进去相似度排序被彻底打乱推荐结果基本等于随机抽取。这不是实现bug是差分隐私的数学性质决定的必然结果。解决不要一上来就追求小隐私预算。先跑epsilon1.0的基线记录MAE和重叠率然后按0.5、0.2、0.1的顺序逐步缩小每次只调一个参数直到某个epsilon值下MAE明显恶化那个点就是工程上可接受的最小预算。现象二两次运行完全相同参数的实验输出结果差异明显甚至相邻两次MAE相差0.1以上。原因拉普拉斯噪声是随机生成的np.random.rand没有固定随机种子时每次运行都产生不同的噪声矩阵实验结果自然不可复现。差分隐私实验本质上是随机实验单次结果没有太大参考价值。解决在main.py开头固定随机种子比如np.random.seed(42)并把这个值写进日志。实验时对同一参数组合重复运行5次取MAE和重叠率的平均值作为最终指标。论文表格里的每个数值都应该是多次重复实验的均值而不是挑一次好看的结果。现象三加噪后的相似度矩阵出现不对等元素sim_matrix[1][2]和sim_matrix[2][1]不相等导致推荐结果不稳定。原因np.random.laplace在生成噪声矩阵时每个位置独立采样原始矩阵对称但噪声矩阵不对称相加后对称性被破坏。协同过滤里相似度的语义要求双向一致否则两个用户互为邻居的关系会单向成立推荐结果出现随机振荡。解决加噪后执行noisy (noisy noisy.T) / 2强制对称化再用np.fill_diagonal(noisy, 1.0)恢复对角元为1。这个处理不会显著影响隐私保证因为对称化本身只是对噪声结果做了确定性的后处理。5.2 数据类踩坑稀疏矩阵与数据集规模现象四过滤完用户后计算相似度矩阵发现大量用户行全是0邻居数量不足推荐列表只能靠少数几个用户撑起来。原因MovieLens small数据集有几百个用户和近万个电影评分覆盖率很低。两个用户共同评过分的电影可能只有个位数低于min_common阈值导致相似度被置0。这种情况在数据越稀疏时越严重。解决先把min_common从5调到3试试如果仍然大量为0说明用户维度过稀疏考虑切换到基于物品的协同过滤。物品维度比用户维度稠密得多每部电影的评分数量相对用户评分数量更集中相似度计算能获得更多有效共同点。另一个思路是保留那些“看过至少N部不同电影”的用户维度但要权衡数据损失。现象五把ml-latest-small替换成完整版MovieLens数据集后程序刚加载数据就MemoryError或者运行到相似度计算阶段内存占用飙升。原因pivot_table产生的密集矩阵需要用户数×电影数×8字节存储完整版数据集用户数接近14万、电影数接近5.7万乘积是约80亿个数值远超常规笔记本内存。密集矩阵和基于循环的预测函数都承受不住这个规模。解决评分矩阵改用scipy.sparse的csr_matrix存储相似度计算用稀疏矩阵乘法实现噪声注入只对非零元素操作。推荐生成部分要避免逐物品循环整个矩阵可以先构建非零位置索引列表只对存在评分的邻居位置计算。如果还想进一步压缩计算时间可以对用户做采样随机选取一部分用户作为邻居候选集。现象六按照README里的参数跑完实验指标和论文5.5稿里的数值对不上甚至趋势完全相反。原因最常见的原因是数据集版本不一致。ml-latest-small是精简版完整版评分数量差了一个量级不同版本的用户ID和电影ID也不一样。其次是训练测试划分方式不一致随机划分和时间划分的结果差异很大差分隐私噪声又带来额外波动。解决每一次实验都把数据版本、划分方式、随机种子、epsilon、k、topn这些参数完整记录下来。论文里写明的参数和代码里真正跑出来的参数必须一致。如果论文表格里没有写随机种子那就默认最优结果不可复现需要自己用代码里的默认seed重新跑一轮然后更新论文数据。5.3 排查清单实验记录表应该记什么差分隐私实验里最忌讳“跑完就忘”。我后来的习惯是维护一张实验记录表每次运行自动生成一行记录字段示例值说明datasetml-latest-small数据集版本metriccosine相似度方式epsilon1.0隐私预算sensitivity2.0噪声敏感度k20邻居数量random_seed42随机种子train_modetime训练测试划分方式MAE0.7921平均绝对误差overlap0.724加噪前后推荐列表重合度排查结果异常时先检查这张表里的字段是否都记录完整再对照代码里的默认参数十条里有八条的“玄学问题”最后都会落在这些字段上。这也是我强烈建议每个复现这个项目的人都补上的一套习惯。6. 进阶换自己的数据并验证隐私保护与精度的平衡跑通原始资源包只是第一步。如果你想把这个项目变成自己的毕设或者用到实际业务数据里需要知道怎么替换数据、怎么验证差分隐私是否真正生效以及怎么把实验过程做得规范。6.1 把自己的数据转成标准格式协同过滤代码只认列名不认数据内容。把任意“用户ID、物品ID、评分、时间戳”数据整理成四列并重命名即可import pandas as pd raw pd.read_csv(your_data.csv) raw.columns [userId, itemId, rating, timestamp] raw raw[[userId, itemId, rating, timestamp]] raw.to_csv(data/custom_ratings.csv, indexFalse)逻辑说明代码内部会用pandas的category类型把userId和itemId重新映射成连续整数不需要你提前做ID编码。参数说明如果你的数据是隐式反馈比如点击、点赞没有评分值可以把点击次数或一行值1作为评分代入但要注意评分值域变化会直接影响敏感度的设置差分隐私预算也要重新实验。6.2 验证方法用重合度曲线证明隐私保护在起作用只报告“加了噪声之后MAE涨了”不够严谨。差分隐私生效的证据应该是加噪前后推荐列表的重合度随epsilon变化而平滑变动。用Jaccard重叠率计算def jaccard_overlap(list_a, list_b, topn10): set_a set(list_a[:topn]) set_b set(list_b[:topn]) intersect len(set_a set_b) union len(set_a | set_b) return intersect / max(union, 1)逻辑说明对同一个用户取不加噪的TopN推荐列表和加噪后的TopN推荐列表计算两个集合的交并比。重合度接近1说明噪声对推荐结果影响很小隐私保护强度有限重合度接近0说明推荐内容已被噪声完全改变但此时MAE往往也高得没法用。理想结果是一条平滑下降的曲线中间经过一个可接受的平台期。参数说明topn要与前面的推荐评估一致通常取10或20。可以跑一组epsilon[0.1, 0.5, 1.0, 2.0, 5.0, 10.0]把每个预算下的平均重合率画成曲线论文里直接用作隐私保护与推荐精度的平衡证据。6.3 一个值得保留的实验习惯我在这类项目上吃过一次亏当时跑了一整晚实验找到一组看起来很好的epsilon参数隔天回来想补一张截图却完全不记得自己用了什么随机种子、什么划分方式重新跑出来的结果跟昨晚完全不同之前的分析方向一下变得不可信。从那以后我每次做差分隐私实验都强制走一遍固定流程先固定环境随机种子再在main.py里把epsilon、k、数据集路径、时间划分标记全部写进日志文件名最后把每次运行结果追加到实验记录表里。这个流程让“隐私预算与推荐精度之间那个平衡点”变得可复现、可追溯不再像玄学一样飘忽不定。希望帮到你。本文还有配套的精品资源点击获取