面对用户-物品评分矩阵,我们有一种推荐思路,叫做基于领域的推荐。
什么是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里面定义形式相似但有不同,我们分别来说。
有两种常用的以及一个改进的。我们假设 N ( u ) N(u) N(u)为用户 u u u评分过的物品集合。
用户 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)∣
这里的意思是,两个用户购买的物品越重合,说明两个用户越相似。
用户 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行的向量提出来,求两个向量的夹角的余弦值。
对于热门商品,大家都会买,所以并不能体现两个用户有多相似,由于 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
均方差误差也可以作为相似度,只不过此时值越小,越相似
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
我们定义 μ 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相似度。
定义 N ( i ) N(i) N(i)为购买过物品 i i i的用户集合。类似的,我们有两个物品之间的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)∣
意思为,购买两个物品的人里面,同时购买两个物品的比例越高,越能说明两个物品相似。
物品 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)。
均方差误差也可以作为相似度,只不过此时值越小,越相似
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
我们定义 μ 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)
由于userCF和itemCF的打分函数并不一样,所以我们依然分开来说。
这里,用户 u u u对物品 i i i评分,需要
这里,用户 u u u对物品 i i i评分,需要
有了用户 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)]