推荐系统初探:ItemCF算法实现知乎问题推荐

推荐系统是一种在电商、广告、内容等互联网平台发挥着巨大商业价值的数据挖掘产品形态,它可以提高用户黏性、提高用户商业转化行为,一款好的推荐系统可以明显有效提升平台的经济效益。通俗地打个比方,如果说互联网平台是一块鱼塘,用户是水里的鱼,那么推荐系统就是一款使鱼塘变成活水,以便让鱼多翻腾几下的机器。

协同过滤算法(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计算量都大得可怕;那么对于这种情况,该怎么处理?分布式并行计算能撑起多大的规模?如果抽样,抽太少结果不准,抽太多计算量还是大,怎么折中?
  • 如何评价推荐系统的性能?当然推荐系统作为机器学习成熟的分支,当然有成熟的评价指标和方法,但真正应用时如何设计、实施?
  • 推荐算法有极端化倾向——类似的东西看得越多就推得越多,可是用户都已经看了这么多类似的东西你还推这个,烦不烦?如何把新颖度融入推荐系统中以制衡?

这些问题真正应用时再考虑也不迟。最后再次推荐一下《推荐系统实战》这本书。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260