上周我们介绍了我理解的推荐系统,主要分两种,个性化的和非个性化的。个性化好理解,千人千面,根据用户行为和浏览记录去生成你的推荐。非个性化呢,一般存在于最流行的东西,比如基金中“最多人买”的模块。一般来说,如果商品量不多,是没有必要做个性化推荐的,只有当商品量过多,发生了信息过载的现象,才有使用个性化推荐的必要。
那我们今天介绍最基本的——基于临域的个性化推荐,也被称为协同过滤。
(几乎全部取材于《推荐系统实践》,想学习推荐系统的都建议看一下这本书)
目录
UserCF
itemCF
如何优化效果
基于movielen的usercf的代码
两种方式大同小异,都是通过用户行为去判断用户与用户的相似度或物品与物品的相似度。所以被称为基于临域的。
1.UserCF
首先我们来看这样一个图片,A,B,C,D是用户,abcde是物品,那usercf是基于用户的协同过滤,就是通过它的行为记录中的物品去计算用户的相似度,我们才用余弦相似度的公式。
那就可以算出AB,AC,AD的用户相似度
可是如果是这样计算,要算每两个用户的相似度,复杂度是O(u2),会耽误计算时间。可是很多用户没有对相同的物品有过行为,分子为0的时候我们就没必要计算分母了,所以我们可以优化一下,先计算物品与用户的倒排表,在转化成用户表,具体如图:
倒排表的建立是看物品被哪个用户使用,以物品为中心,这样的话,我们在建立一个大小为U2的矩阵,存储着用户使用的相同物品的个数。这样的话,分子就直接求出来了,分母为用户使用过的物品个数。
那我们现在知道了用户的相似度,如何做推荐呢?
以用户A举例,他用过abd,没用过的是ce。那如果我们只能推荐一个物品,就是计算出A对c,e的兴趣度。c被B,D用过,那我们就用AB的用户相似度*B对c物品的兴趣度加上AD的用户相似度*D对c物品的兴趣度,有点绕,这里用笔在纸上花几个圈就理解了,文字不如图表来的实在。通用公式如下:
其中,p(u,i)表示用户u对物品i的感兴趣程度,S(u,k)表示和用户u兴趣最接近的K个用户,N(i)表示对物品i有过行为的用户集合,Wuv表示用户u和用户v的兴趣相似度,Rvi表示用户v对物品i的兴趣(这里简化,所有的Rvi都等于1)。
其实就是人以群分,相似的人爱相似的东西。
把我们就算出A对c的兴趣度和A对e的兴趣度,这就是最简单的Usercf。
2.itemcf
itemcf是计算物品与物品的相似度。公式与usercf的很像。
其中,|N(i)|是喜欢物品i的用户数,|N(j)|是喜欢物品j的用户数,|N(i)^N(j)|是同时喜欢物品i和物品j的用户数。
最终也是得到物品与物品被同一个人使用的个数的矩阵。不过这个方式也是要计算物品——用户的倒排表的。这样可以直接看出物品被那些人使用。
这样的话,就像上面一样计算出物品的相似度。
推荐时,因为物品的相似度已经有了。比如我们给A做推荐,他用过abd,没用过的是ce,他对c的兴趣度就是他对a的兴趣度*物品ac的相似度+他对b的兴趣度*物品bc的相似度+他对d的兴趣度*物品dc的相似度,公式和上面也很像。
其中,Puj表示用户u对物品j的兴趣,N(u)表示用户喜欢的物品集合(i是该用户喜欢的某一个物品),S(i,k)表示和物品i最相似的K个物品集合(j是这个集合中的某一个物品),Wji表示物品j和物品i的相似度,Rui表示用户u对物品i的兴趣(这里简化Rui都等于1)。
该公式的含义是:和用户历史上感兴趣的物品越相似的物品,越有可能在用户的推荐列表中获得比较高的排名。
如何优化推荐的效果呢:
1.如果有时间戳的记录的话,我们可以加入时间的因素。在分母中加入一个log(time),这样的话近期的用户行为对推荐的影响大,也是符合逻辑的。
2.如果有评分,我们之前提到的Rui也就是用户对物品的兴趣度,也可以不做简化,而是采用评分的转化,比如分数范围为0~5,那5分对应1的兴趣度,这是最简单的方式,我们可以自定义兴趣度的算法。
3.对于不够热门的商品,可能很难被推荐到。那为了挖掘长尾分布,我们可以对热门商品做惩罚,加入一个惩罚因子,在分母中加入log(热门商品被使用数)。
我们以这样的行为记录举例:
userid是用户ID,movieid是电影ID,rating是评分,timestamp是时间戳
(时间戳是自 1970 年 1 月 1 日(00:00:00 GMT)以来的秒数,比如 1260759144 就可以转换成2009-12-14 10:52:24)
userId movieId rating timestamp
1 31 2.5 1260759144
1 1029 3 1260759179
1 1061 3 1260759182
usercf的代码如下:
(代码在过github上也有,以后会多发布推荐系统的代码
#! /usr/bin/env python
# -*- coding: utf-8 -*-#!
#time:2018/2/9 ____author____= pengxiaoxin
import pandas as pd
import numpy as np
import math
from operator import *
rating = pd.read_csv('C:\\Users\\Administrator\\Desktop\\movie-data\\ml-latest-small\\ratings.csv')
#ratings.csv's columns are user,movieId,rating,timestamp
user = rating['userId']
movieId = rating['movieId']
rating1 = rating['rating'] #人对电影的评分
timestamp = rating['timestamp'] #打分的时间
user_item = dict()#构造一个字典,格式{userid:[movieid,movieid],……}因为一个人会看多部电影
for i in range(len(user)):
if user[i] not in user_item:
user_item[user[i]] = set()
user_item[user[i]].add(movieId[i])
item_user = dict()#建立物品——用户的倒排表,为了降低表的稀疏性
for i in range(len(user)):
if movieId[i] not in item_user:
item_user[movieId[i]] = set()#set结构可以看成一个不重复的数组
item_user[movieId[i]].add(user[i])
N = dict()#计数,看看每个电影被看了多少次
C = dict()#得出两个物品同时被一个用户看的次数
for i,items in item_user.items():
for item in items:
if item not in N:
N[item] = 0
N[item]+=1
for item2 in items:
if item ==item2:
continue
if (item, item2) not in C:
C[item,item2] = 0
C[item,item2]+=1
w = dict()#通过C(1,2)/N[1]*N[2] 算出系数表,等于把物品的关联程度算出来啦;如果想加上时间的因素,就用log加入
for i ,item in C.items():
w[i[0],i[1]] = C[i[0], i[1]]/math.sqrt(N[i[0]] * N[i[1]] *1.0)
rank = dict()#推荐列表
target_user = 1 #input("write the user you want to recommand:")
train = user_item[target_user]
ralated_item = dict()
for i in w:
if i[0] in train and i[1] not in train:#找出同时出现的两部电影,两部电影有1部被target-user看过
if i[1] not in ralated_item:
ralated_item[i[1]] = 0
ralated_item[i[1]] = w[i[0],i[1]]
print(sorted(ralated_item.items(), key = itemgetter(1), reverse=True)[:10])#打印出前十的。