推荐系统

参考博客:https://www.cnblogs.com/rongyux/articles/5396844.html

目的:

1、帮助用户找到想要的商品(新闻/音乐/……),发掘长尾 

帮用户找到想要的东西,谈何容易。商品茫茫多,甚至是我们自己,也经常点开淘宝,面对眼花缭乱的打折活动不知道要买啥。在经济学中,有一个著名理论叫长尾理论(The Long Tail)。 

套用在互联网领域中,指的就是最热的那一小部分资源将得到绝大部分的关注,而剩下的很大一部分资源却鲜少有人问津。这不仅造成了资源利用上的浪费,也让很多口味偏小众的用户无法找到自己感兴趣的内容。 

2、降低信息过载 

互联网时代信息量已然处于爆炸状态,若是将所有内容都放在网站首页上用户是无从阅读的,信息的利用率将会十分低下。因此我们需要推荐系统来帮助用户过滤掉低价值的信息。 

3、提高站点的点击率/转化率 

好的推荐系统能让用户更频繁地访问一个站点,并且总是能为用户找到他想要购买的商品或者阅读的内容。 

4、加深对用户的了解,为用户提供定制化服务 

可以想见,每当系统成功推荐了一个用户感兴趣的内容后,我们对该用户的兴趣爱好等维度上的形象是越来越清晰的。当我们能够精确描绘出每个用户的形象之后,就可以为他们定制一系列服务,让拥有各种需求的用户都能在我们的平台上得到满足。

 

算法分类:

  • 基于流行度的算法

  • 协同过滤算法

  • 基于内容的算法

  • 基于模型的算法

  • 混合算法

1.基于流行度的算法

基于流行度的算法非常简单粗暴,类似于各大新闻、微博热榜等,根据PV、UV、日均PV或分享率等数据来按某种热度排序来推荐给用户。 

优点是简单,适用于刚注册的新用户。

缺点也很明显,它无法针对用户提供个性化的推荐。

基于这种算法也可做一些优化,比如加入用户分群的流行度排序,例如把热榜上的体育内容优先推荐给体育迷,把政要热文推给热爱谈论政治的用户。

2.协同过滤算法

协同过滤算法(Collaborative Filtering, CF)是很常用的一种算法,在很多电商网站上都有用到。CF算法包括基于用户的CF(User-based CF)和基于物品的CF(Item-based CF)。 

基于用户的CF原理如下: 

1、分析各个用户对item的评价(通过浏览记录、购买记录等);

2、依据用户对item的评价计算得出所有用户之间的相似度;

3、选出与当前用户最相似的N个用户;

4、将这N个用户评价最高并且当前用户又没有浏览过的item推荐给当前用户。 

基于物品的CF原理大同小异,只是主体在于物品: 

1、分析各个用户对item的浏览记录。

2、依据浏览记录分析得出所有item之间的相似度;

3、对于当前用户评价高的item,找出与之相似度最高的N个item;

4、将这N个item推荐给用户。

 

举个栗子,基于用户的CF算法大致的计算流程如下: 

首先我们根据网站的记录计算出一个用户与item的关联矩阵,如下: 

图中,行是不同的用户,列是所有物品,(x, y)的值则是x用户对y物品的评分(喜好程度)。我们可以把每一行视为一个用户对物品偏好的向量,然后计算每两个用户之间的向量距离,这里我们用余弦相似度来算: 

然后得出用户向量之间相似度如下,其中值越接近1表示这两个用户越相似: 

最后,我们要为用户1推荐物品,则找出与用户1相似度最高的N名用户(设N=2)评价的物品,去掉用户1评价过的物品,则是推荐结果。

 

基于物品的CF计算方式大致相同,只是关联矩阵变为了item和item之间的关系,若用户同时浏览过item1和item2,则(1,1)的值为1,最后计算出所有item之间的关联关系如下: 

我们可以看到,CF算法确实简单,而且很多时候推荐也是很准确的。然而它也存在一些问题: 

1、依赖于准确的用户评分;

2、在计算的过程中,那些大热的物品会有更大的几率被推荐给用户;

3、冷启动问题。当有一名新用户或者新物品进入系统时,推荐将无从依据;

4、在一些item生存周期短(如新闻、广告)的系统中,由于更新速度快,大量item不会有用户评分,造成评分矩阵稀疏,不利于这些内容的推荐。

 

对于矩阵稀疏的问题,有很多方法来改进CF算法。比如通过矩阵因子分解(如LFM),我们可以把一个nm的矩阵分解为一个nk的矩阵乘以一个k*m的矩阵,如下图: 

这里的k可以是用户的特征、兴趣爱好与物品属性的一些联系,通过因子分解,可以找到用户和物品之间的一些潜在关联,从而填补之前矩阵中的缺失值。

注:LFM参考网址:http://www.cnblogs.com/hxsyl/p/4882768.html

example:

# A dictionary of movie critics and their ratings of a small
# set of movies
critics={'Lisa Rose': {'Lady in the Water': 2.5, 'Snakes on a Plane': 3.5,
'Just My Luck': 3.0, 'Superman Returns': 3.5, 'You, Me and Dupree': 2.5, 
'The Night Listener': 3.0},
'Gene Seymour': {'Lady in the Water': 3.0, 'Snakes on a Plane': 3.5, 
'Just My Luck': 1.5, 'Superman Returns': 5.0, 'The Night Listener': 3.0, 
'You, Me and Dupree': 3.5}, 
'Michael Phillips': {'Lady in the Water': 2.5, 'Snakes on a Plane': 3.0,
'Superman Returns': 3.5, 'The Night Listener': 4.0},
'Claudia Puig': {'Snakes on a Plane': 3.5, 'Just My Luck': 3.0,
'The Night Listener': 4.5, 'Superman Returns': 4.0, 
'You, Me and Dupree': 2.5},
'Mick LaSalle': {'Lady in the Water': 3.0, 'Snakes on a Plane': 4.0, 
'Just My Luck': 2.0, 'Superman Returns': 3.0, 'The Night Listener': 3.0,
'You, Me and Dupree': 2.0}, 
'Jack Matthews': {'Lady in the Water': 3.0, 'Snakes on a Plane': 4.0,
'The Night Listener': 3.0, 'Superman Returns': 5.0, 'You, Me and Dupree': 3.5},
'Toby': {'Snakes on a Plane':4.5,'You, Me and Dupree':1.0,'Superman Returns':4.0}}


from math import sqrt
from pandas import DataFrame
import numpy as np
import operator
import scipy.stats.stats as scss
import pandas as pd
#
#import pydelicious
#基于距离的相似度
def sim_distance(prefs, person1, person2): 
    #prefs = DataFrame(prefs)
    sub = prefs[person1] - prefs[person2]
    commonCount = len(sub) - len(sub[np.isnan(sub)])
    if commonCount == 0: 
        #print('%s and %s has nothing in common.'%(person1,person2))
        return 0
    sim = 1/ (1+sqrt(sum(sub[~np.isnan(sub)]**2)))
    return sim
#基于皮尔森相关系数的相似度
def sim_pearson(prefs, person1, person2):
    #prefs = DataFrame(prefs)
    pref = prefs[[person1,person2]]
    pref = pref.dropna(axis=0, how = 'any')
    sub = prefs[person1] - prefs[person2]
    commonCount = len(sub) - len(sub[np.isnan(sub)])
    if commonCount == 0: 
        #print('%s and %s has nothing in common.'%(person1,person2))
        return 0
    sim = scss.pearsonr(pref[person1], pref[person2])[0]
    return sim

def topMatches(prefs, person, n=3, similarity = sim_distance):
    #prefs = DataFrame(prefs)
    sims = {}
    for other in prefs.columns:
        if other == person: continue
        sim = similarity(prefs, person, other)
        sims[other] = sim
    sortedSims = sorted(sims.items(), key = operator.itemgetter(1), reverse = True)
    return sortedSims[:n]
#基于用户的CF
def getRecommendations(prefs, person, similarity = sim_pearson):
    prefs = DataFrame(prefs).T
    totalSums = {}
    simSums = {}
    for other in prefs.columns:
        if other == person: continue
        sim = similarity(prefs, other, person)
        if sim <= 0: continue
        for item in prefs[other].index:  
            if np.isnan(prefs[person][item]) or prefs[person][item] == 0: 
                if np.isnan(prefs[other][item] * sim): continue
                
                totalSums.setdefault(item,0)
                totalSums[item] += prefs[other][item] * sim
                simSums.setdefault(item,0)
                simSums[item] += sim
    rankings = [(total/simSums[item], item) for item, total in totalSums.items()] 
    rankings.sort()
    rankings.reverse()
    return rankings
#计算每个物品与其他物品的相似度,并取相似度最高的前n个
def caculateSimilarItems(prefs, n=10):
    simularItems = {}
    for item in prefs.columns:
        topMatch = topMatches(prefs, item, n = n, similarity = sim_distance)
        simularItems[item] = topMatch
    return simularItems

#基于物品的CF
def getRecommandationItems(prefs, simularItems, person):
    totals = {}
    sumSims = {}
    pref = prefs.loc[person, :]
    alreadyWatched = pref[~pref.isna()]
    for i in alreadyWatched.index:     #所有當前用戶看過的電影
        if i not in simularItems.keys(): continue     #其他人都沒看過的電影
        
        for item, sim in simularItems[i]:
            if item in alreadyWatched.index: continue
            totals.setdefault(item,0)
            sumSims.setdefault(item,0)
            totals[item] += alreadyWatched[i] * sim
            sumSims[item] += sim
    ranking = [(score/ sumSims[item],item) for item,score in totals.items()]
    ranking.sort()
    ranking.reverse()
    return ranking

if __name__ == "__main__":

    homePath = '***' #数据文件夹
    #homePath1 = os.path.dirname(os.path.abspath('__filen__'))
    #os.path.join()
    
    with open(homePath + '\\ratings.csv','rb') as ratings_f:
        ratings = pd.read_csv(ratings_f)
    with open(homePath + '\\movies.csv', 'rb') as movies_f:
        movies = pd.read_csv(movies_f)
    with open(homePath + '\\tags.csv', 'rb') as tags_f:
        tags = pd.read_csv(tags_f)
    
    movies = movies.drop_duplicates(['title'])    
    data = pd.merge(ratings, movies, on = 'movieId', how ='outer') #.merge(tags, on = ['movieId','userId'])
    data = data.pivot(index = 'userId', columns = 'title',values = 'rating')
    
    #ratings = ratings.pivot(index = 'movieId', columns = 'userId', values = 'rating')
    #ratings = ratings.pivot(index = 'userId', columns = 'movieId', values = 'rating').loc[:,0:100]
    
    
    
    similarity = caculateSimilarItems(data.iloc[:,0:500])
    #print(similarity)
    
    recommItems = getRecommandationItems(data, similarity, 6)
    print(recommItems)

 

 

3 基于内容的算法 

CF算法看起来很好很强大,通过改进也能克服各种缺点。那么问题来了,假如我是个《指环王》的忠实读者,我买过一本《双塔奇兵》,这时库里新进了第三部:《王者归来》,那么显然我会很感兴趣。然而基于之前的算法,无论是用户评分还是书名的检索都不太好使,于是基于内容的推荐算法呼之欲出。

举个栗子,现在系统里有一个用户和一条新闻。通过分析用户的行为以及新闻的文本内容,我们提取出数个关键字,如下图: 

将这些关键字作为属性,把用户和新闻分解成向量,如下图: 

之后再计算向量距离,便可以得出该用户和新闻的相似度了。这种方法很简单,如果在为一名热爱观看英超联赛的足球迷推荐新闻时,新闻里同时存在关键字体育、足球、英超,显然匹配前两个词都不如直接匹配英超来得准确,系统该如何体现出关键词的这种“重要性”呢?这时我们便可以引入词权的概念。在大量的语料库中通过计算(比如典型的TF-IDF算法),我们可以算出新闻中每一个关键词的权重,在计算相似度时引入这个权重的影响,就可以达到更精确的效果。 

sim(user, item) = 文本相似度(user, item) * 词权 

然而,经常接触体育新闻方面数据的同学就会要提出问题了:要是用户的兴趣是足球,而新闻的关键词是德甲、英超,按照上面的文本匹配方法显然无法将他们关联到一起。在此,我们可以引用话题聚类: 

利用word2vec一类工具,可以将文本的关键词聚类,然后根据topic将文本向量化。如可以将德甲、英超、西甲聚类到“足球”的topic下,将lv、Gucci聚类到“奢侈品”topic下,再根据topic为文本内容与用户作相似度计算。 

综上,基于内容的推荐算法能够很好地解决冷启动问题,并且也不会囿于热度的限制,因为它是直接基于内容匹配的,而与浏览记录无关。然而它也会存在一些弊端,比如过度专业化(over-specialisation)的问题。这种方法会一直推荐给用户内容密切关联的item,而失去了推荐内容的多样性。

 

4 基于模型的算法 

基于模型的方法有很多,用到的诸如机器学习的方法也可以很深,这里只简单介绍下比较简单的方法——Logistics回归预测。我们通过分析系统中用户的行为和购买记录等数据,得到如下表: 

表中的行是一种物品,x1~xn是影响用户行为的各种特征属性,如用户年龄段、性别、地域、物品的价格、类别等等,y则是用户对于该物品的喜好程度,可以是购买记录、浏览、收藏等等。通过大量这类的数据,我们可以回归拟合出一个函数,计算出x1~xn对应的系数,这即是各特征属性对应的权重,权重值越大则表明该属性对于用户选择商品越重要。 

在拟合函数的时候我们会想到,单一的某种属性和另一种属性可能并不存在强关联。比如,年龄与购买护肤品这个行为并不呈强关联,性别与购买护肤品也不强关联,但当我们把年龄与性别综合在一起考虑时,它们便和购买行为产生了强关联。比如(我只是比如),20~30岁的女性用户更倾向于购买护肤品,这就叫交叉属性。通过反复测试和经验,我们可以调整特征属性的组合,拟合出最准确的回归函数。最后得出的属性权重如下: 

基于模型的算法由于快速、准确,适用于实时性比较高的业务如新闻、广告等,而若是需要这种算法达到更好的效果,则需要人工干预反复的进行属性的组合和筛选,也就是常说的Feature Engineering。而由于新闻的时效性,系统也需要反复更新线上的数学模型,以适应变化。

 

5 混合算法 

现实应用中,其实很少有直接用某种算法来做推荐的系统。在一些大的网站如Netflix,就是融合了数十种算法的推荐系统。我们可以通过给不同算法的结果加权重来综合结果,或者是在不同的计算环节中运用不同的算法来混合,达到更贴合自己业务的目的。

 

6 结果列表 

在算法最后得出推荐结果之后,我们往往还需要对结果进行处理。比如当推荐的内容里包含敏感词汇、涉及用户隐私的内容等等,就需要系统将其筛除;若数次推荐后用户依然对某个item毫无兴趣,我们就需要将这个item降低权重,调整排序;另外,有时系统还要考虑话题多样性的问题,同样要在不同话题中筛选内容。

 

推荐结果评估 

当推荐算法完成后,怎样来评估这个算法的效果?CTR(点击率)、CVR(转化率)、停留时间等都是很直观的数据。在完成算法后,可以通过线下计算算法的RMSE(均方根误差)或者线上进行ABTest来对比效果。

 

改进策略

用户画像是最近经常被提及的一个名词,引入用户画像可以为推荐系统带来很多改进的余地,比如: 

1、打通公司各大业务平台,通过获取其他平台的用户数据,彻底解决冷启动问题;

2、在不同设备上同步用户数据,包括QQID、设备号、手机号等;

3、丰富用户的人口属性,包括年龄、职业、地域等;

4、更完善的用户兴趣状态,方便生成用户标签和匹配内容。 

另外,公司的优势——社交平台也是一个很好利用的地方。利用用户的社交网络,可以很方便地通过用户的好友、兴趣群的成员等更快捷地找到相似用户以及用户可能感兴趣的内容,提高推荐的准确度。

 

posted @ 2019-05-16 18:05  Colleen-Blog  阅读(185)  评论(0)    收藏  举报