推荐系统是一种在电商、广告、内容等互联网平台发挥着巨大商业价值的数据挖掘产品形态,它可以提高用户黏性、提高用户商业转化行为,一款好的推荐系统可以明显有效提升平台的经济效益。通俗地打个比方,如果说互联网平台是一块鱼塘,用户是水里的鱼,那么推荐系统就是一款使鱼塘变成活水,以便让鱼多翻腾几下的机器。
协同过滤算法(Collaborative Filtering)
是一款经典的推荐算法,也是推荐系统入门最好的机器学习算法。协同过滤算法主要可以分为两类:基于用户相似的UserCF算法以及基于物品相似的ItemCF算法。简明地解释这两类的基本思想:如果user1和user2相似度高,那么user1买了一款物品item,就可以把这款物品也推荐给user2,这是UserCF的基本思想;如果item1和item2相似度高,那么一个用户user买了item1,就可以推荐他再买item2,这是ItemCF的基本思想。
因为已经握有知乎500w+用户的行为数据,就可以以此为例练习一下推荐算法:如何给一个知乎用户推荐问题?
因为知乎内容的根基在于它有一棵由几万个话题组成的有向无环图的话题网络,每一个问题(当然也包括专栏、文章、live等)都可以映射至几个话题标签上。所以一开始的思路是想从这标签上入手,看看可不可以基于这些话题标签内在的层级关系,基于标签的相似度计算标签组的相似度,从而衡量问题之间的相似度——后来发现太复杂就放弃了。
只到理解了协同过滤算法的思想,就深刻地体会到这个算法的强大之处:根本不用考虑问题之间的内在联系,只要基于大量用户的行为数据(因为大量用户选择性的结果本身就隐藏着规律性,此谓协同过滤),就可以衡量这些问题在大量用户中被倾向的相似度。
ItemCF算法计算过程
因为知乎用户行为维度比较多(关注、提问、回答、点赞、评论、收藏等),为了简单起见,这里只考虑用户关注问题的维度。以用户关注问题为例,结合《推荐系统实战》一书中的理论,总结一下ItemCF的计算过程:
计算问题之间的相似度
先上公式:
问题i与问题j之间的相似度这么定义:同时关注问题i与问题j的人数/关注问题i人数*关注问题j的人数的平方根。
由此我们就需要计算n个问题之间两两相似度,就是一个对角线为1的对称邻接矩阵,最少也要计算n*(n-1)/2次。考虑到我那个8年前的笔记本已经垂暮,为了方便,这里只截取20w个问题作为样本,计算两两相似度。
import pandas as pd
import numpy as np
mat = np.mat(np.zeros([len(question_ids),len(question_ids)],dtype=int))
def check_2topics(topic1,topic2):
m = sum(list(map(lambda x: topic1 in x and topic2 in x,user_follow_question1.urls)))
print("%s与%s的交集数为%d"%(topic1,topic2,m))
return m
def write_mat(i,j):
topic1 = question_ids[i]
topic2 = question_ids[j]
print("第%d行第%d列:" % (i, j))
m = check_2topics(topic1, topic2)
mat[i, j] = mat[i, j] + m
mat[j, i] = mat[j, i] + m
先定义一个函数check_2topics()
计算任意2个问题id之间的交集数,然后再用一个write_mat()
函数写入邻接矩阵中。
这样经过计算39999800000次计算(我大概跑了10几个小时,orz……),得到一张大表:
(因为自己跟自己比没有意义,所以没有算对角线的元素,所以都是0……)
已经求出两个问题交集数了,再求各个问题的关注人数,就可以代入公式求相似度了。
ts = pd.read_csv(r"question_matrix.csv")
mat = np.mat(ts,dtype=float)
def cal_topics(topic):
return sum(list(map(lambda x:topic in x,user_follow_question1.urls)))
topic_ns = [cal_topics(i) for i in ts.columns]
import math
for i in range(mat.shape[0]):
for j in range(mat.shape[1]):
mat[i,j] = mat[i,j]/(math.sqrt(topic_ns[i])*math.sqrt(topic_ns[j]))
来来来,让我们检验一下这些问题相似度的靠谱程度——
import requests
from bs4 import BeautifulSoup
question_ids = question_similarity.columns
def get_question_title(question_id):
url = "https://www.zhihu.com/question/%s"%question_id
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36'}
soup = BeautifulSoup(requests.get(url,headers=headers).text,"lxml")
title = soup.select("h1.QuestionHeader-title")[0].text
return title
def find_similar_topic(question_id):
url1 = "https://www.zhihu.com/question/%s"%question_id
title1 = get_question_title(question_id)
sim = question_similarity[question_id].max()
idx = question_similarity[question_id].idxmax()
similar_question_id = question_ids[idx]
url2 = "https://www.zhihu.com/question/%s"%similar_question_id
title2 = get_question_title(similar_question_id)
return("[%s](%s)相似度最高的问题是[%s](%s),相似度为%s"%(title1,url1,title2,url2,sim))
随机抽几个问题测一下:
find_similar_topic('37226968')
'如何逼自己做到真正的自律?相似度最高的问题是如何从一个空有上进心的人,变成行动上的巨人?,相似度为0.174764048104'
find_similar_topic('25023733')
'「学霸变学渣」和「学渣变学霸」分别是怎样的一番体验?相似度最高的问题是如何长时间高效学习?,相似度为0.15617054136'
find_similar_topic('20151457')
'二十多岁该做什么,将来才不后悔?相似度最高的问题是要怎样努力,才能成为很厉害的人?,相似度为0.244334679327'
find_similar_topic('51760357')
'女生如何高效地打扮自己?相似度最高的问题是你的日常搭配是什么样子?,相似度为0.171730447621'
看起来还是很make sense的……
基于问题相似度及用户关注历史推荐问题
一般情况下权重r取1即可,这个公式就是指一个用户u对问题j的兴趣度计算方法是:找到用户关注问题的集合N(u),以及问题j前K个最相似的问题集合S(j,K),取这两个集合的交集,把交集的相似度加权累加即可。最后筛选出兴趣度最高的问题推荐给该用户即可。
接下来无非就是计算用户对各个问题的兴趣度,再倒排即可,逻辑很简单,代码就懒得继续写了……
问题总结
前面提到了协同过滤算法的优点,它可以不关心物品的内在属性,只考虑用户行为的影响。但从这个案例中也可以看出不少问题:
- 计算量庞大。它的时间复杂度是n2。事实上知乎注册用户破亿,有效用户至少也有几百万,有效问题同样也至少几百万,这样体量的数据,无论是UserCF还是ItemCF计算量都大得可怕;那么对于这种情况,该怎么处理?分布式并行计算能撑起多大的规模?如果抽样,抽太少结果不准,抽太多计算量还是大,怎么折中?
- 如何评价推荐系统的性能?当然推荐系统作为机器学习成熟的分支,当然有成熟的评价指标和方法,但真正应用时如何设计、实施?
- 推荐算法有极端化倾向——类似的东西看得越多就推得越多,可是用户都已经看了这么多类似的东西你还推这个,烦不烦?如何把新颖度融入推荐系统中以制衡?
这些问题真正应用时再考虑也不迟。最后再次推荐一下《推荐系统实战》这本书。