一、概念

基本概念

1、解决什么问题:信息过载
2、使命:联系用户和信息
3、区别于分类网站(2345、雅虎):覆盖面广、具有个性化
4、区别于搜索引擎(Google):主动、用户不需要提供明确需求

推荐系统的数据来源

1、用户:用户属性、用户标签、社交网络、ip地址、网络设备、位置定位
2、物品:物品属性、物品标签
3、用户 《— 行为 —》物品

推荐系统的评测指标

1、用户满意度:在线指标
2、预测准确性:离线指标
3、覆盖率
4、多样性
5、新颖性
6、惊喜度
7、信任度:是否提供推荐解释
8、实时性
9、健壮性
10、商业目标

二、基础推荐算法



ItemCF基于物品的推荐算法

案例:jim、Tom、Peter本周分别在TM餐厅点选了不同的餐品,吃完后分别做了评价如下

其中5为最满意,1为最不满意,0则是未点选未评价。

* 手撕猪肉和烤牛肉的相似度计算如下

* 手撕猪肉和鳗鱼饭的相似度计算如下

这里使用的欧式距离的计算法,如果使用余弦定理的距离算法

* 手撕猪肉和烤牛肉的相似度如下

* 手撕猪肉和鳗鱼饭的相似度如下:

如果把所有菜品之间的欧式距离相似度计算出来,结果如下:


有了这个物品间的相似度矩阵,我们就能进行推荐了。
以JIM为例,计算他对未评价的日式炸鸡排和寿司饭的潜在评价值

显然,我们知道了,相比于日式炸鸡排,我们更应该推荐给Jim寿司饭。

那如果是基于用户的推荐算法是怎么样的呢?

userCF基于用户的推荐算法

1、首先根据不同用户对相同食品的评价,计算用户之间的相似度,生成用户间的相似度矩阵

2、用户A1对B1食品的评价 = 用户A1和用户A2的相似度 * 用户A2对B1食品的评价 + 用户A1和用户A3的相似度 * 用户A3对B1食品的评价度
+ 。。。。。

如下图所示:


什么时候使用itemCF,什么时候使用userCF?

这取决于计算的复杂性。


实际中,为了保证能够快速生成推荐结果,如果使用itemCF,需要预先建立并保存物品间的相似度矩阵。相反,如果使用userCF,需要预先建立并保存用户间的相似度矩阵。


所以,假设你开发了一个小说阅读器APP,在运营的初期,你可能只有1000个用户,但有10000的小说资源。那么如果你使用itemCF进行推荐,所占用的计算和存储资源是userCF的(10000^2/1000^2=100)100倍。

反过来,如果你已经拥有了类似与起点小说这样的用户规模,你的用户已经远远大于你的商品数量。那么你使用itemCF进行推荐将更为合适

Boolean问题

现实中,我们大部分情况下并不能获取到用户对物品的评价指数。通常我们只能得到非是即否的结果,即用户看了还是没看,用户吃了还是没吃,用户买了还是没买。

遇到这样的问题,怎么进行推荐呢?

假设,我们现在为一个视频网站开发推荐系统,我们已经统计了每一个用户是否观看了每一个视频,如下:(实际中,会使用特定的方法保存稀疏矩阵)

a1-a9代表不同的视频,p1-p20代表不同的用户,1代表已经观看了


首先,我们还是从建立物品间(视频)的相似度矩阵开始,计算公式如下:


最终的,相似度矩阵计算结果如下:


我们有了视频间的相似度矩阵,那么计算用户对视频的偏好程度就很简单了:

* 用户A对视频B的偏好程度 = mean(用户已看过的视频分别 * 看过视频和视频B的相似程度)
* 最终结果计算平均数,来避免结果受到用户观看视频结果的影响
* 那么,最后,我们只要将不同品类的视频选择偏好程度最高的几个(且用户没看过的)推荐给用户就可以了。
进阶算法

看完了以上的内容,是否觉得推荐算法原来就是这么简单;那么,哪些研究员们都在研究什么呢?
在现有的基础上不断的把算法升级升级升级!!!

1、升级一:降低热门视频的权重

现实中,太多热门的视频并没有什么推荐的价值,我们总希望把一些不那么热门的视频推荐给真正喜欢它的用户。
所以,当我们在计算视频间的相似度的时候,通过调整计算公式,降低了热门视频的权重:


2、升级二:减少狂热用户的权重

狂热用户就好比当当网上的书店老板,视频网站上的视频爬虫,他们会产生大量的购买和点击操作,导致其他用户和他的相似度都会很高,干扰的算法的最终结果,所以我们要降低他们的权重:

当我们在计算即观看A又观看了B视频的用户数的时候,每一个用户的贡献值不再是1了,如下图;


3、升级三:归一化
如下图显示,计算出来的相似度矩阵,看上去总是趋向于不平衡的分布的,如下图:


如果,我们将每一列的值分布除于每一列的最大值,矩阵看起来就均匀多了,也就会有更好的推荐效果:


实际案例

1、58同城:为招聘者推荐简历
本质上还是计算itemCF,计算物品间的相似度;
区别在于考虑了各种因素的权重,结合到了相似度的计算中


2、YouTube推荐算法:
我们用一个实例来说明这个推荐系统具体是如何运作的:

比如说,小明很喜欢YouTube,他有YouTube账号相关的一切。每天浏览YouTube时,他都会在浏览器登录。一旦登录,YouTube便给小明此次浏览的内容创建三个token:浏览记录、搜索记录以及关于他的统计信息。小明可能压根就不知道这三种数据的存在。


然后轮到候选生成器上场了。YouTube拿这三个token的值跟观看记录类似于小明的用户进行对比,由此筛选出小明可能会喜欢的数百个视频,过滤掉YouTube视频库中数以百万计的其他内容。


接下来,基于视频和小明的相关性,这些视频被排名算法排序。排序时该算法会考虑这样一些问题:小明有多大的可能会打开这个视频?这个视频有没有可能让小明在YouTube上打发更多时间?这个视频的新鲜程度如何?小明最近在YouTube上的活动程度如何?还有数百个其他的问题。


经过YouTube算法的读取、筛选和推荐后,排名最高的视频将被推荐给小明。之后小明看与不看的选择数据都会反馈给神经网络,以供算法后续使用。视频被点开,并吸引小明在YouTube上打发更多时间的目标,则一直持续下去。那些小明没有点开的推荐视频,等他下次登录网站时则有可能通不过候选生成器。


三、代码实现

数据源使用的是经典的ml-100k数据源
# -*- coding: utf-8 -*- """ Created on Sat Dec 2 23:29:55 2017 @author:
shanesu """ import pandas as pd import numpy as np from pandas import DataFrame
import math #读取数据到csv和data def prepareDataSet(): unames=['user_id','age',
'gender','occupation','zip'] users=pd.read_table('ml-100k/u.user',sep='|'
,header=None,names=unames) mnames = ['movie_id', 'title', 'release_data',
'video_release_date','URL',\ 'unknown','Action','Adventure','Animation',
'Childrens',\ 'Comedy','Crime','Documentary','Drama','Fantasy',\ 'Film-Noir',
'Horror','Musical','Mystery','Romance','Sci_Fi',\ 'Thriller','War','Western']
movies = pd.read_table('ml-100k/u.item', sep='|', header=None,
names=mnames,encoding='ANSI') rnames = ['user_id', 'movie_id', 'rating',
'timestamp'] ratings = pd.read_table('ml-100k/u.data', header=None,
names=rnames) all_data = pd.merge(pd.merge(ratings, users), movies) data =
DataFrame(data=all_data,columns=['user_id','movie_id']) data.to_csv('data.csv')
X=data['user_id'] Y=data['movie_id'] return X, Y, data def readDataSet():
#读取之前存储的表格并提取我们需要的信息 data=pd.read_csv('data.csv') X=data['user_id'] Y=data[
'movie_id'] return X, Y, data #建立倒排矩阵,x用户,y物品 def ItemSimilarity(X, Y):
#计算用户-物品倒排表 user_item=dict() for i in range(Y.count()): user=X.iloc[i]
item=Y.iloc[i]if user not in user_item: user_item[user]=set()
user_item[user].add(item)#对于每个拥挤,将他的物品列表中的物品两两在共现矩阵C中加1 #N(i)为某个物品出现的总数 C={}
N={}for u,items in user_item.items(): for i in items: N.setdefault(i,0) N[i]+=1
C.setdefault(i,{})for j in items: if i==j: continue C[i].setdefault(j,0)
C[i][j]+=1/math.log(1+len(user_item[u])) #活跃用户对物品相似度的贡献应该小于不活跃的用户!! #计算相似度
W=C.copy() maxw=np.zeros(len(W)+1) for i,related_items in C.items(): for j,cij
in related_items.items(): W[i][j]=cij/math.sqrt(N[i]*N[j]) if W[i][j]>maxw[j]:
maxw[j]=W[i][j]#公式惩罚了物品j的权重,减轻了热门物品会和很多物品相似的可能性!! #归一化!! for i,items in
W.items():for j,cij in items.items(): W[i][j]=W[i][j]/maxw[j] return W,
user_itemdef recommend(user,user_item,W,K=10): #取与用户喜欢的物品集合中各物品,分别最相似的排名前k个物品
rank={} interacted_items=user_item[user]for i in interacted_items: for j, wij in
sorted(W[i].items(),key=lambda x:x[1],reverse=True)[0:K]: if j in
interacted_items:continue rank.setdefault(j,0) rank[j]+=wij return rank def
dict2list(dic:dict): ''' 将字典转化为列表 ''' keys = dic.keys() vals = dic.values() lst
= [(key, val)for key, val in zip(keys, vals)] return lst ''' import itemCF X,
Y, data=itemCF.readDataSet() W, user_item = itemCF.ItemSimilarity(X,Y) rank =
itemCF.recommend(20,user_item,W,20) sorted_rank=sorted(itemCF.dict2list(rank),
key=lambda x:x[1], reverse=True)[0:10] '''
推荐参考资料
《推荐算法实践》
https://blog.csdn.net/Cherrie3/article/details/52757118?locationNum=2&fps=1
<https://blog.csdn.net/Cherrie3/article/details/52757118?locationNum=2&fps=1>
http://iyao.ren/2017/02/28/itemcf/ <http://iyao.ren/2017/02/28/itemcf/>
http://geek.csdn.net/news/detail/134876
<http://geek.csdn.net/news/detail/134876>