面对用户-物品评分矩阵,我们有一种推荐思路,叫做基于领域的推荐。
什么是itemCF和userCF?可以这样理解,
- 我喜欢这个商品,那么和这个商品非常类似的其他商品,可能也是我喜欢的,这个是itemCF的思路,推荐和用户喜欢的商品类似的其他商品
- 我喜欢这个商品,别人也喜欢这个商品,那么我们可能兴趣很相似,那么那人喜欢的其他商品,可能也是我喜欢的,这个是userCF的思路,推荐和用户相似的其他用户喜欢的商品
那么,什么是领域?
- 对于itemCF而言,领域就是和该商品类似的其他商品,这种度量是商品相似度
- 对于userCF而言,领域就是和该用户类似的其他用户,这种度量是用户相似度
基于用户-物品评分矩阵 R m × n R_{m\times n} Rm×n,相似度 s i m i l a r i t y similarity similarity如何定义?
这在userCF和itemCF里面定义形式相似但有不同,我们分别来说。
相似度
1. userCF里的相似度
有两种常用的以及一个改进的。我们假设 N ( u ) N(u) N(u)为用户 u u u评分过的物品集合。
1.1 Jaccard相似度
用户
u
u
u和
v
v
v的Jaccard相似度为
w
u
v
=
∣
N
(
u
)
∩
N
(
v
)
∣
∣
N
(
u
)
∪
N
(
v
)
∣
w_{uv}=\frac{|N(u)\cap N(v)|}{|N(u)\cup N(v)|}
wuv=∣N(u)∪N(v)∣∣N(u)∩N(v)∣
这里的意思是,两个用户购买的物品越重合,说明两个用户越相似。
1.2 Cosine相似度
用户
u
u
u和用户
v
v
v的余弦相似度为
w
u
v
=
∣
N
(
u
)
∩
N
(
v
)
∣
∣
N
(
u
)
∣
∣
N
(
v
)
∣
w_{uv}=\frac{|N(u)\cap N(v)|}{\sqrt{|N(u)||N(v)|}}
wuv=∣N(u)∣∣N(v)∣∣N(u)∩N(v)∣
当然,也可以直接用评分数据来做,如下
w
u
v
=
∑
i
∈
N
(
u
)
∩
N
(
v
)
r
u
i
r
v
i
∑
i
∈
N
(
u
)
r
u
i
2
⋅
∑
i
∈
N
(
v
)
r
v
i
2
w_{uv}=\frac{\sum_{i\in N(u)\cap N(v)}r_{ui}r_{vi}}{\sqrt{\sum_{i\in N(u)}r_{ui}^2\cdot\sum_{i\in N(v)}r_{vi}^2}}
wuv=∑i∈N(u)rui2⋅∑i∈N(v)rvi2∑i∈N(u)∩N(v)ruirvi
其实就是把评分矩阵的第 u u u行的向量提出来,把第 v v v行的向量提出来,求两个向量的夹角的余弦值。
1.3 改进的相似度
对于热门商品,大家都会买,所以并不能体现两个用户有多相似,由于
N
(
u
)
∩
N
(
v
)
N(u)\cap N(v)
N(u)∩N(v)中可能有一大部分为热门商品,我们期望能降低热门商品的影响,可以重写为
N
(
u
)
∩
N
(
v
)
→
∑
i
∈
N
(
u
)
∩
N
(
v
)
1
l
o
g
(
1
+
N
(
i
)
)
N(u)\cap N(v)\rightarrow \sum_{i\in N(u)\cap N(v)}\frac{1}{log(1+N(i))}
N(u)∩N(v)→i∈N(u)∩N(v)∑log(1+N(i))1
其中, N ( i ) N(i) N(i)为购买过物品 i i i的用户人数。显然,热门商品的购买人数会很大,所以 1 l o g ( 1 + N ( i ) ) \frac{1}{log(1+N(i))} log(1+N(i))1就会小,形成对热门商品的一个惩罚。
改进后的相似度为
w
u
v
=
∑
i
∈
N
(
u
)
∩
N
(
v
)
1
l
o
g
(
1
+
N
(
i
)
)
∣
N
(
u
)
∣
∣
N
(
v
)
∣
w_{uv}=\frac{\sum_{i\in N(u)\cap N(v)}\frac{1}{log(1+N(i))}}{\sqrt{|N(u)||N(v)|}}
wuv=∣N(u)∣∣N(v)∣∑i∈N(u)∩N(v)log(1+N(i))1
1.4 MSD
均方差误差也可以作为相似度,只不过此时值越小,越相似
w
u
v
=
∑
i
∈
N
(
u
)
∩
N
(
v
)
(
r
u
i
−
r
v
i
)
2
∣
N
(
u
)
∩
N
(
v
)
∣
w_{uv}=\frac{\sum_{i\in N(u)\cap N(v)}(r_{ui}-r_{vi})^2}{|N(u)\cap N(v)|}
wuv=∣N(u)∩N(v)∣∑i∈N(u)∩N(v)(rui−rvi)2
1.5 Pearson相似度
我们定义 μ u \mu_u μu为用户 u u u的平均打分。
w u v = ∑ i ∈ N ( u ) ∩ N ( v ) ( r u i − μ u ) ( r v i − μ v ) ∑ i ∈ N ( u ) ( r u i − μ u ) 2 ⋅ ∑ i ∈ N ( v ) ( r v i − μ v ) 2 w_{uv}=\frac{\sum_{i\in N(u)\cap N(v)}(r_{ui}-\mu_u)(r_{vi}-\mu_v)}{\sqrt{\sum_{i\in N(u)}(r_{ui}-\mu_u)^2\cdot \sum_{i\in N(v)}(r_{vi}-\mu_v)^2}} wuv=∑i∈N(u)(rui−μu)2⋅∑i∈N(v)(rvi−μv)2∑i∈N(u)∩N(v)(rui−μu)(rvi−μv)
从表达式可以看出来,pearson相似度其实是中心化之后的consine相似度。
2. itemCF里的相似度
定义 N ( i ) N(i) N(i)为购买过物品 i i i的用户集合。类似的,我们有两个物品之间的Jaccard相似度和余弦相似度。
2.1 Jaccard相似度
物品
i
i
i和物品
j
j
j之间的Jaccard相似度为
w
i
j
=
∣
N
(
i
)
∩
N
(
j
)
∣
∣
N
(
i
)
∪
N
(
j
)
∣
w_{ij}=\frac{|N(i)\cap N(j)|}{|N(i)\cup N(j)|}
wij=∣N(i)∪N(j)∣∣N(i)∩N(j)∣
意思为,购买两个物品的人里面,同时购买两个物品的比例越高,越能说明两个物品相似。
2.2 余弦相似度
物品
i
i
i和物品
j
j
j之间的余弦相似度为
w
i
j
=
∣
N
(
i
)
∩
N
(
j
)
∣
∣
N
(
i
)
∣
∣
N
(
j
)
∣
w_{ij}=\frac{|N(i)\cap N(j)|}{\sqrt{|N(i)||N(j)|}}
wij=∣N(i)∣∣N(j)∣∣N(i)∩N(j)∣
当然,也能利用用户评分数据,如下
w
i
j
=
∑
u
∈
N
(
i
)
∩
N
(
j
)
r
u
i
⋅
r
u
j
∑
u
∈
N
(
i
)
r
u
i
2
⋅
∑
u
∈
N
(
j
)
r
u
j
2
w_{ij}=\frac{\sum_{u\in N(i)\cap N(j)}r_{ui}\cdot r_{uj}}{\sqrt{\sum_{u\in N(i)}r_{ui}^2\cdot\sum_{u\in N(j)}r_{uj}^2}}
wij=∑u∈N(i)rui2⋅∑u∈N(j)ruj2∑u∈N(i)∩N(j)rui⋅ruj
有了相似度定义,我们就可以进一步定义用户 u u u对物品 i i i的打分 p ( u , i ) p(u, i) p(u,i)。
2.3 MSD
均方差误差也可以作为相似度,只不过此时值越小,越相似
w
i
j
=
∑
u
∈
N
(
i
)
∩
N
(
j
)
(
r
u
i
−
r
u
j
)
2
∣
N
(
i
)
∩
N
(
j
)
∣
w_{ij}=\frac{\sum_{u\in N(i)\cap N(j)}(r_{ui}-r_{uj})^2}{|N(i)\cap N(j)|}
wij=∣N(i)∩N(j)∣∑u∈N(i)∩N(j)(rui−ruj)2
2.4 Pearson相似度
我们定义 μ i \mu_i μi为物品 i i i的平均得分。
w i j = ∑ u ∈ N ( i ) ∩ N ( j ) ( r u i − μ i ) ( r u j − μ j ) ∑ u ∈ N ( i ) ( r u i − μ i ) 2 ⋅ ∑ u ∈ N ( j ) ( r u j − μ j ) 2 w_{ij}=\frac{\sum_{u\in N(i)\cap N(j)}(r_{ui}-\mu_i)(r_{uj}-\mu_j)}{\sqrt{\sum_{u\in N(i)}(r_{ui}-\mu_i)^2\cdot \sum_{u\in N(j)}(r_{uj}-\mu_j)^2}} wij=∑u∈N(i)(rui−μi)2⋅∑u∈N(j)(ruj−μj)2∑u∈N(i)∩N(j)(rui−μi)(ruj−μj)
打分函数 p ( u , i ) p(u, i) p(u,i)
由于userCF和itemCF的打分函数并不一样,所以我们依然分开来说。
1. userCF
这里,用户 u u u对物品 i i i评分,需要
- 根据用户相似度,找出用户 u u u最相似的其他 k k k个用户,我们将这些用户集合记为 S ( u , k ) S(u, k) S(u,k)
- 从集合 S ( u , k ) S(u, k) S(u,k)中,找出购买过物品 i i i的用户 v v v,也就是 v ∈ S ( u , k ) ∩ N ( i ) v\in S(u, k)\cap N(i) v∈S(u,k)∩N(i)
- 计算如下打分函数 p ( u , i ) = ∑ v ∈ S ( u , k ) ∩ N ( i ) w u v ⋅ r u i ∑ v ∈ S ( u , k ) ∩ N ( i ) w u v p(u, i)=\frac{\sum_{v\in S(u, k)\cap N(i)} w_{uv}\cdot r_{ui}}{\sum_{v\in S(u, k)\cap N(i)} w_{uv}} p(u,i)=∑v∈S(u,k)∩N(i)wuv∑v∈S(u,k)∩N(i)wuv⋅rui
2. itemCF
这里,用户 u u u对物品 i i i评分,需要
- 根据物品相似度,计算物品 i i i最相似的 k k k个物品,将这些物品的集合记为 S ( i , k ) S(i, k) S(i,k)
- 在物品集合 S ( i , k ) S(i, k) S(i,k)中,找到用户 u u u也使用过的物品 j j j,这里, j ∈ S ( i , k ) ∩ N ( u ) j\in S(i, k)\cap N(u) j∈S(i,k)∩N(u)
- 计算如下打分函数 p ( u , i ) = ∑ j ∈ S ( i , k ) ∩ N ( u ) w i j ⋅ r u j ∑ j ∈ S ( i , k ) ∩ N ( u ) w i j p(u, i)=\frac{\sum_{j\in S(i, k)\cap N(u)}w_{ij}\cdot r_{uj}}{\sum_{j\in S(i, k)\cap N(u)}w_{ij}} p(u,i)=∑j∈S(i,k)∩N(u)wij∑j∈S(i,k)∩N(u)wij⋅ruj
有了用户 u u u对物品 i i i的评分,我们就可以根据评分,生成对用户 u u u的推荐。
简单实战
下面,我们利用surprise库,对数据集movielens进行电影推荐。
movielens数据集已经上传,可以免费下载。
# 第三方库
import pandas as pd
import numpy as np
from surprise import Dataset, Reader
from surprise import KNNBasic
# 载入数据
data = pd.read_csv(r'D:\myfile\开课吧\推荐系统\第八节\movielens\ratings.csv')
data.head()
# 将timestamp列去掉
data.drop('timestamp', axis=1, inplace=True)
data.head()
# 将数据载入surprise
# 定义阅读器
reader = Reader(line_format='user item rating')
# 载入数据
raw_data = Dataset.load_from_df(data, reader=reader)
# 将数据转化为可操作数据
my_data = raw_data.build_full_trainset()
# userCF
# 领域内有40个用户
# 相似度为余弦相似度
algo = KNNBasic(k=40, sim_options={'user_based': True, 'name': 'cosine'})
algo.fit(my_data)
# userCF
# items,记录所有产品
items = data['movieId'].unique().tolist()
# 字典user_items,记录用户购买过的产品
user_items = {}
for user, group in data.groupby('userId'):
user_items[user] = set(group['movieId'].tolist())
# 给用户u推荐
def topN(u, N=4):
scores = {}
for i in items:
if i not in user_items[u]:
scores[i] = algo.predict(u, i).est
return sorted(scores.items(), key=lambda x: x[1], reverse=True)[: N]
# userCF
# 测试
topN(1)
# userCF
# 测试结果
[(60482, 5), (107230, 5), (31123, 5), (134, 5)]
# itemCF
# 训练模型
algo = KNNBasic(k=40, sim_options={'user_based': False, 'name': 'cosine'})
algo.fit(my_data)
# itemCF
# items,记录所有产品
items = data['movieId'].unique().tolist()
# 字典user_items,记录用户购买过的产品
user_items = {}
for user, group in data.groupby('userId'):
user_items[user] = set(group['movieId'].tolist())
# 给用户u推荐
def topN(u, N=4):
scores = {}
for i in items:
if i not in user_items[u]:
scores[i] = algo.predict(u, i).est
return sorted(scores.items(), key=lambda x: x[1], reverse=True)[: N]
# itemCF
# 测试
topN(1)
# itemCF
# 测试结果
[(93320, 5), (26368, 5), (26520, 5), (26928, 5)]