目前为止都是在讨论 TopNTopNTopN 推荐,即给定一个用户,如何给他生成一个长度为 NNN 的推荐列表,使该推荐列表能够尽量满足用户的兴趣和需求。本书之所以如此重视 TopNTopNTopN 推荐,是因为它非常接近于满足实际系统的需求,实际系统绝大多数情况下就是给用户提供一个包括N个物品的个性化推荐列表。
但是,很多从事推荐系统研究的童鞋最早接触的却是评分预测问题,评分预测问题一直是推荐系统研究的核心。评分预测问题最基本的数据集就是用户评分数据集。该数据集由用户评分记录组成,每一条评分记录是一个三元组 (u,i,r)(u,i, r)(u,i,r),表示用户 uuu 给物品 iii 赋予了评分 rrr,本章用 ruir_{ui}rui 表示用户 uuu 对物品i的评分。因为用户不可能对所有物品都评分,因此评分预测问题就是如何通过已知的用户历史评分记录预测未知的用户评分记录。
- 平均预测算法
最简单的评分预测算法,就是使用平均值的方法,下面会讲讲各种平均值的使用:
- 全局平均值
在平均值里最简单的是全局平均值。它的定义为训练集中所有评分记录的评分平均值:
μ = ∑(u,i)∈Trainrui∑(u,i)∈Train1\mu {\text{ = }}\frac{{\sum\nolimits_{(u,i) \in Train} {{r_{ui}}} }}{{\sum\nolimits_{(u,i) \in Train} 1 }}μ = ∑(u,i)∈Train1∑(u,i)∈Trainrui - 用户评分平均值
用户 uuu 的评分平均值 ru‾\overline {{r_u}}ru 定义为用户 uuu 在训练集中所有评分的平均值:
ru‾=∑i∈N(u)rui∑i∈N(u)1\overline {{r_u}} = \frac{{\sum\nolimits_{i \in N(u)} {{r_{ui}}} }}{{\sum\nolimits_{i \in N(u)} 1 }}ru=∑i∈N(u)1∑i∈N(u)rui - 物品评分平均值
物品 iii 的评分平均值 ri‾\overline {{r_i}}ri 定义为物品 iii 在训练集中所有评分的平均值:
ri‾=∑u∈N(i)rui∑u∈N(i)1\overline {{r_i}} = \frac{{\sum\nolimits_{u \in N(i)} {{r_{ui}}} }}{{\sum\nolimits_{u \in N(i)} 1 }}ri=∑u∈N(i)1∑u∈N(i)rui - 用户分类对物品分类的平均值
假设有两个分类函数,一个是用户分类函数 ϕ\phiϕ,一个是物品分类函数 φ\varphiφ 。ϕ(u)\phi(u)ϕ(u) 定义了用户 uuu 所属的类, φ(i)\varphi(i)φ(i) 定义了物品 iii 所属的类。那么,我们可以利用训练集中同类用户对同类物品评分的平均值预测用户对物品的评分,即:
rui^=∑(v,j)∈Train,ϕ(u)=ϕ(v),φ(i)=φ(j)rvj∑(v,j)∈Train,ϕ(u)=ϕ(v),φ(i)=φ(j)1\widehat {{r_{ui}}} = \frac{{\sum\nolimits_{(v,j) \in Train,\phi (u) = \phi (v),\varphi (i) = \varphi (j)} {{r_{vj}}} }}{{\sum\nolimits_{(v,j) \in Train,\phi (u) = \phi (v),\varphi (i) = \varphi (j)} 1 }}rui=∑(v,j)∈Train,ϕ(u)=ϕ(v),φ(i)=φ(j)1∑(v,j)∈Train,ϕ(u)=ϕ(v),φ(i)=φ(j)rvj
前面提出的全局平均值,用户评分平均值和物品评分平均值都是类类平均值的一种特例。
- 如果定义ϕ(u)=0\phi(u)=0ϕ(u)=0, φ(i)=0\varphi(i)=0φ(i)=0,那么 rui^\widehat {{r_{ui}}}rui 就是全局平均值。
- 如果定义ϕ(u)=μ\phi(u)=\muϕ(u)=μ, φ(i)=0\varphi(i)=0φ(i)=0,那么 rui^\widehat {{r_{ui}}}rui 就是用户评分平均值。
- 如果定义ϕ(u)=0\phi(u)=0ϕ(u)=0, φ(i)=i\varphi(i)=iφ(i)=i,那么 rui^\widehat {{r_{ui}}}rui 就是物品评分平均值。
除了这3种特殊的平均值,在用户评分数据上还可以定义很多不同的分类函数。 - 用户和物品的平均分 对于一个用户,可以计算他的评分平均分。然后将所有用户按照评分平均分从小到大排序,并将用户按照平均分平均分成N类。物品也可以用同样的方式分类。
- 用户活跃度和物品流行度 对于一个用户,将他评分的物品数量定义为他的活跃度。得到用户活跃度之后,可以将用户通过活跃度从小到大排序,然后平均分为N类。物品的流行度定义为给物品评分的用户数目,物品也可以按照流行度均匀分成N类。
- 基于邻域算法
这里主要讲一下基于邻域的方法,其他方法我这里暂时就不考虑了。
基于用户的邻域算法和基于物品的邻域算法都可以应用到评分预测中。基于用户的邻域算法认为预测一个用户对一个物品的评分,需要参考和这个用户兴趣相似的用户对该物品的评分,即:
rui^=ru‾+∑v∈S(u,K)∩N(i)wuv(rvi−rv‾)∑v∈S(u,K)∩N(i)∣wuv∣\widehat {{r_{ui}}} = \overline {{r_u}} + \frac{{\sum\nolimits_{v \in S(u,K) \cap N(i)} {{w_{uv}}({r_{vi}} - \overline {{r_v}} )} }}{{\sum\nolimits_{v \in S(u,K) \cap N(i)} {|{w_{uv}}|} }}rui=ru+∑v∈S(u,K)∩N(i)∣wuv∣∑v∈S(u,K)∩N(i)wuv(rvi−rv)
这里, S(u,K)S(u, K)S(u,K) 是和用户 uuu 兴趣最相似的 KKK 个用户的集合,N(i)N(i)N(i) 是对物品 iii评过分的用户集合, rvir_{vi}rvi 是用户 vvv 对物品 iii 的评分,rvr_{v}rv 是用户 vvv 对他评过分的所有物品评分的平均值。用户之间的相似度 wuvw_{uv}wuv可以通过皮尔逊系数计算,III 为用户 u,vu,vu,v 同时评价过的物品:
wuv=∑i∈I(rui−ru‾)(rvi−rv‾)∑i∈I(rui−ru‾)2∑i∈I(rvi−rv‾)2{{\text{w}}_{uv}} = \frac{{\sum\nolimits_{i \in I} {({r_{ui}} - \overline {{r_u}} )({r_{vi}} - \overline {{r_v}} )} }}{{\sqrt {\sum\nolimits_{i \in I} {{{({r_{ui}} - \overline {{r_u}} )}^2}} \sum\nolimits_{i \in I} {{{({r_{vi}} - \overline {{r_v}} )}^2}} } }}wuv=∑i∈I(rui−ru)2∑i∈I(rvi−rv)2∑i∈I(rui−ru)(rvi−rv)
3. 基于邻域算法的实例分析
| 虎口脱险 | 变形金刚 | 唐山大兄 | 少林足球 | 大话西游 | 黑客帝国 | |
|---|---|---|---|---|---|---|
| A | 1 | 5 | 4 | 5 | ||
| B | 4 | 2 | 3 | 5 | ||
| C | 4 | 3 | 5 | |||
| D | 5 | 5 | 2 | |||
| E | 5 | 4 | 4 |
- 数据处理
在这里用户没有对电影评过分的我们将其评分默认为0来处理
def load_data(filePath):
f = open(filePath, "r", encoding="utf-8")
records = dict()
for line in f:
user, movie, score = line.strip().split("\t")
records.setdefault(user, {})
records[user][movie] = int(score)
return records
{'A': {'虎口脱险': 1, '变形金刚': 0, '唐山大兄': 5, '少林足球': 4, '大话西游': 5, '黑客帝国': 0}, 'B': {'虎口脱险': 4, '变形金刚': 2, '唐山大兄': 0, '少林足球': 3, '大话西游': 0, '黑客帝国': 5}, 'C': {'虎口脱险': 0, '变形金刚': 4, '唐山大兄': 0, '少林足球': 3, '大话西游': 0, '黑客帝国': 5}, 'D': {'虎口脱险': 5, '变形金刚': 0, '唐山大兄': 5, '少林足球': 0, '大话西游': 2, '黑客帝国': 0}, 'E': {'虎口脱险': 0, '变形金刚': 5, '唐山大兄': 0, '少林足球': 0, '大话西游': 4, '黑客帝国': 4}}
- 平均值计算
def item_users_aver(records):
item_users = dict()
ave_vote = dict()
for u, items in records.items():
num = 0; sum_score = 0
for i, score in items.items():
item_users.setdefault(i, {})
item_users[i].setdefault(u, 0)
item_users[i][u] = score
if score != 0:
num += 1
sum_score += score
ave_vote.setdefault(u, 0)
ave_vote[u] = sum_score / num
print("用户平均评分: ", ave_vote)
print("电影-用户倒排列表: ", item_users)
return item_users, ave_vote
用户平均评分: {'A': 3.75, 'B': 3.5, 'C': 4.0, 'D': 4.0, 'E': 4.333333333333333}
电影-用户倒排列表: {'虎口脱险': {'A': 1, 'B': 4, 'C': 0, 'D': 5, 'E': 0}, '变形金刚': {'A': 0, 'B': 2, 'C': 4, 'D': 0, 'E': 5}, '唐山大兄': {'A': 5, 'B': 0, 'C': 0, 'D': 5, 'E': 0}, '少林足球': {'A': 4, 'B': 3, 'C': 3, 'D': 0, 'E': 0}, '大话西游': {'A': 5, 'B': 0, 'C': 0, 'D': 2, 'E': 4}, '黑客帝国': {'A': 0, 'B': 5, 'C': 5, 'D': 0, 'E': 4}}
- 用户相似度
这里我觉得《推荐系统实践》中的代码没有考虑到用户没有评分的情况,所以我修改了一下,有不同意见的童鞋可以提出来,大家相互讨论一下。
def UserSimilarity(records, item_users, ave_vote):
nu = dict()
W = dict()
for i, items in item_users.items():
for u, rui in items.items():
if rui == 0: # 用户没有评分,直接跳过
continue
for v, rvi in items.items():
if u == v or rvi == 0: # 用户没有评分或者相同用户,直接跳过
continue # u,v 必须同时对 i 产生评分
nu.setdefault(u, {})
nu[u].setdefault(v, 0)
nu[u][v] += pow((rui - ave_vote[u]), 2)
W.setdefault(u, {})
W[u].setdefault(v, 0)
W[u][v] += (rui - ave_vote[u])*(rvi - ave_vote[v])
for u in W:
W[u] = {v: y/sqrt(nu[u][v]*nu[v][u]) for v, y in W[u].items()}
print("用户相似度: ", W)
return W
用户相似度: {'A': {'B': -0.7682212795973759, 'D': -0.49951243284357016, 'C': -1.0, 'E': -1.0}, 'B': {'A': -0.7682212795973759, 'D': 1.0, 'C': 0.6488856845230502, 'E': -0.9486832980505137}, 'D': {'A': -0.49951243284357016, 'B': 1.0, 'E': 1.0}, 'C': {'B': 0.6488856845230502, 'E': -0.4472135954999575, 'A': -1.0}, 'E': {'B': -0.9486832980505137, 'C': -0.4472135954999575, 'A': -1.0, 'D': 1.0}}
- 预测
我们这里使用用户 AAA 对电影《唐山大兄》进行预测:
def PredictAll(records, test, ave_vote, W, K):
user_items = dict()
user, movie, score = test.strip().split(" ")
predict = int(score)
norm = 0
for v, wuv in sorted(W[user].items(), key=itemgetter(1), reverse=True)[0:K]: # 和user相似度最高的K个用户
if movie in records[v]:
rvi = records[v][movie]
predict += wuv * (rvi - ave_vote[v])
norm += abs(wuv)
if norm > 0:
predict /= norm
predict += ave_vote[user]
print("《", movie, "》预测分数: ", predict)
return predict
《 唐山大兄 》预测分数: 5.476910016088435
4. 总结
其实实际生活中使用推荐会考虑多种方法,不管是基于邻域还是矩阵分解等等,具体使用看具体情况决定,只要能最高效,获得最好结果就行。

本文探讨了评分预测在推荐系统中的重要性,从平均预测算法如全局平均值、用户评分平均值、物品评分平均值,到基于邻域算法的用户和物品相似度计算。通过对用户和物品的分类,以及用户和物品的平均分和流行度,来预测用户对未评分物品的评分。文中还分析了基于邻域算法的实例,强调实际推荐系统会结合多种方法以实现最佳效果。

1万+

被折叠的 条评论
为什么被折叠?



