基于Python3.6的推荐模型:曼哈顿距离结果异常及欧氏/皮尔逊对比问题
问题分析与解决方案
我来帮你理一理这个问题——在基于用户的协同过滤里用曼哈顿距离踩坑太常见了,尤其是刚上手推荐系统的时候。咱们一步步拆解:
1. 曼哈顿距离“不符合逻辑”的核心原因:混淆了「距离」和「相似度」
曼哈顿距离(Manhattan Distance)的定义是两个用户对同一物品评分的绝对差之和:d(u, v) = Σ|r_u,i - r_v,i|
这里的距离越小,代表两个用户的偏好越相似,但如果你直接把这个距离值当成“相似度”来用,就会出现逻辑反转——比如距离大的反而被当成相似度高,结果自然不对。
正确的做法是把距离转换为相似度,常用的转换方式有两种:
- 倒数转换:
similarity = 1 / (1 + d(u, v)),这样相似度范围是(0,1],值越大越相似 - 归一化转换:如果知道最大可能的距离值
max_d,可以用similarity = 1 - (d(u, v)/max_d),范围[0,1]
2. 欧氏/曼哈顿距离的“单位问题”:未做评分归一化
你提到的“单位问题”,本质是不同用户的评分尺度不一致导致的。比如:
- 用户A习惯给所有物品打3-5分(满分5)
- 用户B习惯给所有物品打1-3分(满分5)
这时候计算出来的距离数值会被用户的评分习惯放大,看起来“单位不对”。另外,Pearson相关系数是无量纲的(范围-1到1),它会自动消除用户评分尺度的影响(因为计算的是评分的相对变化),而曼哈顿/欧氏距离是基于绝对评分差的,所以两者的数值范围完全不同,不能直接对比数值大小。
解决方法是先对每个用户的评分做归一化:
# 以用户为单位,将评分归一化到0-1区间 def normalize_ratings(user_ratings): min_r = min(user_ratings.values()) max_r = max(user_ratings.values()) if max_r == min_r: return {item: 0.5 for item in user_ratings} # 避免除以0 return {item: (r - min_r)/(max_r - min_r) for item, r in user_ratings.items()}
归一化后,所有用户的评分都在同一尺度下,欧氏和曼哈顿距离的数值就会更合理,也能和Pearson的结果在逻辑上对齐(比如相似用户的距离小,Pearson相关系数高)。
3. 代码示例:正确计算曼哈顿相似度的完整流程
这里给你一个Python3.6的示例,基于用户对物品X的评分找相似用户,再推荐物品:
from collections import defaultdict # 模拟用户评分数据:{用户ID: {物品ID: 评分}} user_ratings = { "user1": {"X": 4, "Y": 5, "Z": 3}, "user2": {"X": 3, "Y": 4, "W": 2}, "user3": {"X": 5, "Z": 4, "W": 5}, "user4": {"X": 2, "Y": 2, "Z": 1} } # 步骤1:归一化每个用户的评分 normalized_ratings = {user: normalize_ratings(ratings) for user, ratings in user_ratings.items()} # 步骤2:计算目标用户与其他用户的曼哈顿相似度 target_user = "user1" target_ratings = normalized_ratings[target_user] similarities = defaultdict(float) for user, ratings in normalized_ratings.items(): if user == target_user: continue # 只计算共同评分的物品(这里以物品X为核心,也可以扩展到所有共同物品) common_items = set(target_ratings.keys()) & set(ratings.keys()) if not common_items: continue # 计算曼哈顿距离 manhattan_dist = sum(abs(target_ratings[item] - ratings[item]) for item in common_items) # 转换为相似度 similarities[user] = 1 / (1 + manhattan_dist) # 步骤3:按相似度排序,取最相似的用户 sorted_similar_users = sorted(similarities.items(), key=lambda x: x[1], reverse=True) # 步骤4:推荐相似用户偏好的物品(排除目标用户已评分的) recommended_items = set() for user, sim in sorted_similar_users[:2]: # 取前2个最相似用户 for item in normalized_ratings[user]: if item not in target_ratings: recommended_items.add(item) print("推荐物品:", recommended_items) print("用户相似度:", sorted_similar_users)
4. 额外注意点
- 如果你的场景只基于物品X的评分计算相似度,那共同物品只有X,这时候曼哈顿距离就是两个用户对X评分的绝对差,转换后的相似度是合理的,但推荐的多样性会比较差,建议扩展到多个共同评分的物品。
- Pearson相关系数适合处理用户评分尺度差异的情况,但它对数据稀疏性比较敏感;曼哈顿/欧氏距离在数据密集、评分尺度统一时表现更好,根据你的场景选择合适的度量方式。
内容的提问来源于stack exchange,提问作者user1940212
相关产品推荐
相关产品推荐

