通过算法自动发掘用户行为数据,从用户的行为中推测出用户的兴趣,从而给用户推荐满足他们兴趣的物品。
基于用户行为分析的推荐算法是个性化推荐系统的重要算法,学术界一般将这种类型的算法称为协同过滤算法。
用户行为在个性化推荐系统中一般分两种:显性反馈行为(explicit feedback)和隐性反馈行为(implicit feedback)。显性反馈行为包括用户明确表示对物品喜好的行为。隐性反馈行为指的是那些不能明确反应用户喜好的行为。
互联网的很多数据都存在Power Law长尾分布。
基于用户行为分析的推荐算法一般称作协同过滤算法,他们分为:基于领域的方法(neighborhood-based)、隐语义模型(latent factor model)、基于图的随机游走算法(random walk on graph)等。
基于领域的方法主要分为基于用户的协同过滤算法和基于物品的协同过滤算法。
本章采用GroupLens提供的MovieLens数据集介绍和评测算法。将数据集分为M份,其中M-1份作为训练集,1份作为测试集。下面的代码是将数据集随机分为训练集和测试集的过程:
def SplitData(data, M, k, seed):
test = []
train = []
random.seed(seed)
for user, item in data:
if random.randint(0, M) == k:
test.append([user, item])
else:
train.append([user,item])
return train, test
这是为了进行M次实验,防止过拟合。
对用户u推荐N个物品,公式见课本42页,代码如下:
def Recall(train, test, N):
hit = 0
all = 0
for user in train.keys():
tu = test[user]
rank = GetRecommendation(user, N)
for item, pui in rank:
if item in tu:
hit += 1
all += len(tu)
return hit / (all * 1.0)
def Precision(train, test, N):
hit = 0
all = 0
for user in train.keys():
tu = test[user]
rank = GetRecommendation(user, N)
for item, pui in rank:
if item in tu:
hit += 1
all += N
return hit/(all*1.0)
覆盖率表示最终的推荐列表中包含多大比例的物品,如果所有物品都被推荐给至少一个用户,那么覆盖率就是100%:
def Coverage(train, test, N):
recommend_items = set()
all_items = set()
for user in train.keys():
for item in train[user].keys():
all_item.add(items)
rank = GetRecommendation(user, N)
for item, pui in rank:
recommend_items.add(item)
return len(recommend_items)/(len(all_items)*1.0)
最后,我们还需要评测推荐的新颖度,这里用推荐列表中物品的平均流行度度量推荐结果的新颖度:
def Popularity(train, test, N):
item_popularity = dict()
for user, items in train.items():
for item in items.keys():
if item not in item_popularity:
item_popularity[item]=0
net = 0
n = 0
for user in train.keys():
rank = GetRecommendataion(user, N)
for item, pui in rank:
ret += math.log(1+item_popularity[items])
n += 1
ret /= n*1.0
return ret
这里,平均流行度对每个物品的流行度取对数,这是因为物品的流行度分布满足长尾分布,在取对数后,流行度的平均值更加稳定。
基于领域的算法分类两大类,一类是基于用户的协同过滤算法,另一类是基于物品的协同过滤算法。
基于用户的协同过滤算法分为两个步骤:
(1)找到和目标用户兴趣相似的用户集合;
(2)找到这个集合中的用户喜好的,且目标用户没有听说过的物品推荐给用户。
可以使用余弦公式计算相似度,代码如下:
def UserSimilarity(train):
W = dict()
for u in train.keys():
for v in train.keys():
if u == v:
continue
W[u][v] = len(train[u] & train[v])
W[u][v] /= math.sqrt(len(train[u])*len(train[v])*1.0)
return W
但是这种算法时间复杂度是O(|U|*|U|),事实上很多用户相互之间并没有对同样的物品产生过行为。我们可以首先计算出对同样的物品产生过购买行为的进行计算。
可以首先建立物品到用户的倒排表,对于每个物品都保存对其产生过行为的用户列表。使用系数矩阵C[u][v],假设用户u和用户v同时属于倒排表中K个物品对应的用户列表,就有其等于K:
def UserSimilarity(train):
#从物品列表建立倒排表
item_users = dict()
for u, items in train.items():
for i in items.keys():
if i not in item_users:
item_users[i] = set()
item_users[i].add(u)
#计算用户间共同评价过的物品
C = dict()
N = dict()
for i, users in item_users.items():
for u in users():
N[u] += 1
for v in users:
if u == v:
continue
c[u][v] += 1
#计算最终相似度矩阵
W = dict()
for u, related_users in C.items():
for v, cuv in related_users.items():
W[u][v] = cuv/math.sqrt(N[u]*N[v])
return W
UserCF算法公式见课本47页,代码如下:
def Recommend(user, train, W):
rank = dict()
interacted_items = train[user]
for v, wuv in sorted(W[u],items, key=itemgetter(1), reverse=True)[0:K]:
for i, rvi in train[v].items():
if i in interacted_items:
#我们应该在继续之前过滤相关联的物品和用户
rank[i] += wuv * rvi
return rank
我们尝试从moives的ml-1m数据中读取数据:
import pandas as pd
unames = ['user_id', 'gender', 'age', 'occupation', 'zip']
users = pd.read_table('ml-1m/users.dat', sep='::', header=None, names=unames)#p数167
rnames = ['user_id', 'movie_id', 'rating', 'timestamp']
ratings = pd.read_table('ml-1m/ratings.dat', sep='::', header=None, names=rnames)
mnames = ['movie_id', 'title', 'genres']
movies = pd.read_table('ml-1m/movies.dat', sep='::', header=None, names=mnames)
测试一下:
In [5]: users[:5]
Out[5]:
user_id gender age occupation zip
0 1 F 1 10 48067
1 2 M 56 16 70072
2 3 M 25 15 55117
3 4 M 45 7 02460
4 5 M 25 20 55455
In [6]: ratings[:5]
Out[6]:
user_id movie_id rating timestamp
0 1 1193 5 978300760
1 1 661 3 978302109
2 1 914 3 978301968
3 1 3408 4 978300275
4 1 2355 5 978824291
In [7]: movies[:5]
Out[7]:
movie_id title genres
0 1 Toy Story (1995) Animation|Children's|Comedy
1 2 Jumanji (1995) Adventure|Children's|Fantasy
2 3 Grumpier Old Men (1995) Comedy|Romance
3 4 Waiting to Exhale (1995) Comedy|Drama
4 5 Father of the Bride Part II (1995) Comedy
MostPopular算法则是按照物品的流行度给用户推荐他没有产生过行为的物品。其准确率和召回率远远高于Random算法,但是覆盖率低。
K越大,流行度越高,覆盖率越低。
两个用户对冷门物品采取过同样的行为更能说明他们的兴趣的相似度,新公式(P.49)惩罚了用户u和用户v的共同热门物品的相似度影响。
def UserSimilarity(train):
#建立物品—_用户的倒排表
item_users = dict()
for u, items in trains.items():
for i in items_keys():
if i not in item_users:
item_users[i] = set()
item_users[i].add(u)
#计算用户间,共同评分过的物品
C = dict()
N = dict()
for i, users in item_users.items():
for u in users:
N[u] +=1
for v in users:
if u == v:
continue
C[u][v] += 1/math.log(1+len(users))
#计算最终的相似度矩阵W
W = dict()
for u, related_users in C.items():
for v, cuv in elated_users.items():
W[u][v] = cuv/math.sqrt(N[u]*N[v])
return W
基于物品的协同过滤算法主要分为两步:
(1)计算物品之间的相似度;
(2)根据物品的相似度和用户的历史行为给用户生成推荐列表。
物品相似度公式要惩罚热门物品。
def ItemSimilarity(train):
#统计对同一个物品评分过的用户
C = dict()
N = dict()
for u, items in train.items():
for u in users:
N[i] += 1
for j in users:
if i == j:
continue
C[i][j] += 1
#计算最终相似度矩阵
W = dict()
for i, related_items in C.items():
for j, cij in related_items.items():
W[u][v] = cij / math.sqrt(N[i]*N[j])
return W
未完待续