工作室答辩
--基于用户和内容的协同过滤推荐系统
你为什么要做这个事?这个事情的关键问题是什么?
在信息量爆炸的这个时代,视频的数量和种类也变得十分庞大在如此庞大的视频数量下,怎样快速帮助用户发掘自己感兴趣的电影在网站运营中显得尤为重要.而且看电影常常被用户当做一种放松娱乐的方式,所以用户在打开电影网站时可能没有明确想看的电影.这样只有靠推荐系统通过分析用户的历史行为以及现下看的电影去分析潜在的用户可能感兴趣的电影.所以这个事情的关键问题就是实现比较好的推荐算法。
这个事情有没有人做过?他们做得怎么样?
3.电影和视频的推荐,像爱奇艺,腾讯视频的那些比较火的软件,都有进入主页后都有推荐模板,这样作为娱乐软件的他们才对用户有更大的吸引力。
总结:推荐系统在网上已经是很流行和也可以说是很成熟的算法了,但是因为自己个人的爱好和想学这类算法所以还是做了个电影推荐系统。
你是如何做的?(算法流程、实现了哪些功能)
1.基于用户的协同过滤算法(usercf):每年新学期开始,刚进实验室的师弟总会问师兄相似的问题,比如“我应该买什么专业书啊”、“我应该看什么论文啊”等。这个时候,师兄一般会给他们做出一些推荐。这就是现实中个性化推荐的一种例子。在这个例子中,师弟可能会请教很多师兄,然后做出最终的判断。师弟之所以请教师兄,一方面是因为他们有社会关系,互相认识且信任对方,但更主要的原因是师兄和师弟有共同的研究领域和兴趣。那么,在一个在线个性化推荐系统中,当一个用户A需要个性化推荐时,可以先找到和他有相似兴趣的其他用户,然后把那些用户喜欢的、而用户A没有听说过的物品推荐给A。 这种方法称为基于用户的协同过滤算法。
基于用户的协同过滤算法主要包括两个步骤:
(1) 找到和目标用户兴趣相似的用户集合。
(2) 找到这个集合中的用户喜欢的,且目标用户没有听说过的物品推荐给目标用户。
步骤(1)的关键就是计算两个用户的兴趣相似度。这里,协同过滤算法主要利用行为的相似度计算兴趣的相似度。给定用户u和用户v,令N(u)表示用户u曾经有过正反馈的物品集合,令N(v)为用户v曾经有过正反馈的物品集合。那么,我们可以通过如下的Jaccard公式简单地计算u和v的兴趣相似度或者通过余弦公式:
Jaccard :
余弦公式:
这个一个行为记录
我们可以根据余弦公式计算如下:
以余弦相似度为例,实现该相似度可以利用下面的伪代码:
def UserSimilarity(train): W = dict() for u in train.keys(): for v in train.keys(): if u == v: continue W[u][v] = len(train[u] & train[v]) W[u][v] = /= math.sqrt(len(train[u]) * len(train[v]) * 1.0) return W
这种方法的时间复杂度是O(|U|*|U|),这在用户数很大时非常耗时。事实上,很多用户相互之间并没有对同样的物品产生过行为,即很多时候N(u)^ N(v) = 0。上面的算法将很多时间浪费在了计算这种用户之间的相似度上。如果换一个思路,我们可以首先计算出N(u)^ N(v) != 0 的用户对(u,v),然后再对这种情况除以分母sqrt(N(u)*N(v)) 。
为此,可以首先建立物品到用户的倒排表,对于每个物品都保存对该物品产生过行为的用户列表。令稀疏矩阵C[u][v]= N(u)^ N(v) 。那么,假设用户u和用户v同时属于倒排表中K个物品对应的用户列表,就有C[u][v]=K。从而,可以扫描倒排表中每个物品对应的用户列表,将用户列表中的两两用户对应的C[u][v]加1,最终就可以得到所有用户之间不为0的C[u][v]
def UserSimilarity(train): # build inverse table for item_users item_users = dict() for u, items in train.items(): for i in items.keys(): if i not in item_users: item_users[i] = set() item_users[i].add(u) #calculate co-rated items between users C = dict() N = dict() for i, users in item_users.items(): for u in users: N[u] += 1 for v in users: if u == v: continue C[u][v] += 1 #calculate finial similarity matrix W W = dict() for u, related_users in C.items(): for v, cuv in related_users.items(): W[u][v] = cuv / math.sqrt(N[u] * N[v]) return W
下面是按照想法建立的稀疏矩阵,对于物品a,将W[A][B]和W[B][A]加1,对于物品b,将W[A][C]和W[C][A]加1,以此类推,扫描完所有物品后,我们可以得到最终的W矩阵,这里的W是余弦相似度中的分子部分,然后将W除以分母可以得到最终的用户兴趣相似度
得到用户之间的兴趣相似度后,针对目标用户u,选出最相似的k个用户,用集合S(u,k)表示,将S中所有用户喜欢的物品提取出来并去除目标用户u已经喜欢的物品。然后对余下的物品进行评分与相似度加权,得到的结果进行排序。最后由排序结果对目标用户u进行推荐。其中,对于每个可能推荐的物品i,用户u对其的感兴趣的程度可以用如下公式计算:
rvi表示用户v对i的喜欢程度,即对i的评分,Wuv表示用户u和v之间的相似度。。
如下代码实现了上面的UserCF推荐算法:
def Recommend(user, train, W): rank = dict() interacted_items = train[user] for v, wuv in sorted(W[u].items, key=itemgetter(1), reverse=True)[0:K]:#选取W中相似度最高的K个用户 for i, rvi in train[v].items: if i in interacted_items: #排除共同喜欢的电影 continue rank[i] += wuv * rvi return rank
用户相似度计算的改进
如果两个用户都曾经买过《新华字典》,这丝毫不能说明他们兴趣相似,因为绝大多数中国人小时候都买过《新华字典》。但如果两个用户都买过《数据挖掘导论》,那可以认为他们的兴趣比较相似,因为只有研究数据挖掘的人才会买这本书。换句话说,两个用户对冷门物品采取过同样的行为更能说明他们兴趣的相似度。因此,John S. Breese中提出了如下公式,根据用户行为计算用户的兴趣相似度:
分子中的倒数惩罚了用户u和用户v共同兴趣列表中热门物品对他们相似度的影响。N(i)是对物品i有过行为的用户集合,越热门,N(i)越大
def UserSimilarity(train): # build inverse table for item_users item_users = dict() for u, items in train.items(): for i in items.keys(): if i not in item_users: item_users[i] = set() item_users[i].add(u) #calculate co-rated items between users C = dict() N = dict() for i, users in item_users.items(): for u in users: N[u] += 1 for v in users: if u == v: continue C[u][v] += 1 / math.log(1 + len(users)) #calculate finial similarity matrix W W = dict() for u, related_users in C.items(): for v, cuv in related_users.items(): W[u][v] = cuv / math.sqrt(N[u] * N[v]) return W
2.基于物品的协同过滤算法:
前面我们简单讲解了一下基于用户的协调过滤推荐(UserCF)。但是该算法存在一些缺点,首先,随着网站的用户数目越来越大,计算用户兴趣相似度矩阵将越来越困难,其运算时间复杂度和空间复杂度的增长和用户数的增长*似于*方关系。其次,基于用户的协调过滤很难对推荐结果作出解释。因此,著名的电子商务公司亚马逊提出了另一个算法——ItemCF 。ItemCF 给用户推荐那些和他们之前喜欢的物品相似的物品。比如,该算法会因为你购买过《统计学习方法》而给你推荐《机器学习》。不过,ItemCF算法并不利用物品的内容属性计算物品之间的相似度,它主要通过分析用户的行为记录计算物品之间的相似度。该算法认为,物品 A 和物品 B 具有很大的相似度是因为喜欢物品 A 的用户大都也喜欢物品B 。
基于物品的协调过滤算法可以利用用户的历史行为给推荐结果提供推荐解释,比如给用户推荐《七里香》的解释可以是因为用户之前喜欢《晴天》。
实现方法和基于用户的差不多。
该算法主要分为两步:
1.计算物品之间的相似度
2.根据物品的相似度和用户的历史行为给用户生成推荐列表
从购买了该商品的用户也经常购买的其他商品这句话的定义出发,我们可以用下面的公式定义物品的相似度:
上述公式虽然看起来很有道理,但是却存在一个问题。如果物品 j 很热门,很多人都喜欢,那么 wijwij就会很大,接* 1 。因此,该公式会造成任何物品都会和热门的物品有很大的相似度,这对于致力于挖掘长尾信息的推荐系统来说显然不是一个好的特性。为了避免推荐出热门的物品,可以用下面的公式:
此处省略一些和上面基于用户的推荐系统一样的内容如:相似度的改进惩罚系数
Karypis在研究中发现如果将 ItemCF 的相似度矩阵按最大值归一化,可以提高推荐的准确率。其研究表明,如果已经得到了物品相似度矩阵 w ,那么可以用如下公式得到归一化之后的相似度矩阵 \'w\' :
归一化的好处不仅仅在于增加推荐的准确度,它还可以提高推荐的覆盖率和多样性。一般来说,物品总是属于很多不同的类,每一类中的物品联系比较紧密。
举一个例子,假设在一站中,有两种电影——纪录片和动画片。那么, ItemCF 算出来的相似度一般是纪录片和纪录片的相似度或者动画片和动画片的相似度大于纪录片和动画片的相似度。但是纪录片之间的相似度和动画片之间的相似度却不一定相同。假设物品分为两类—— A 和 B , A 类物品之间的相似度为 0.5 , B 类物品之间的相似度为 0.6 ,而 A 类物品和 B 类物品之间的相似度是 0.2 。在这种情况下,如果一个用户喜欢了 5 个 A 类物品和 5 个 B 类物品,用 ItemCF 给他进行推荐,推荐的就都是 B 类物品,
因为 B 类物品之间的相似度大。但如果归一化之后, A 类物品之间的相似度变成了 1 , B 类物品之间的相似度也是 1 ,那么这种情况下,用户如果喜欢 5 个 A 类物品和 5 个 B 类物品,那么他的推荐列表中 A 类物品和 B 类物品的数目也应该是大致相等的。从这个例子可以看出,相似度的归一化可以提高推荐的多样性。
那么,对于两个不同的类,什么样的类其类内物品之间的相似度高,什么样的类其类内物品相似度低呢?一般来说,热门的类其类内物品相似度一般比较大。如果不进行归一化,就会推荐比较热门的类里面的物品,而这些物品也是比较热门的。因此,推荐的覆盖率就比较低。相反,如果进行相似度的归一化,则可以提高推荐系统的覆盖率。
效果如何?(使用评价指标对结果进行评定,还可以跟其它算法做对比分析)
1.1 邻域CF
1.1 原理
基于邻域的协同过滤算法是推荐最常见的算法之一。优点是:实现成本低,不容易出错,计算速度快,效果不错,能推荐长尾的物品。缺点是尾部点击少的user和item容易出现badcase。 最*邻方法”包括“基于物品的协同过滤”和“基于用户的协同过滤”。“基于物品的协同过滤”-Item based 是指寻找与物品a相似的其他物品,当用户观看了物品a 时, 给用户推荐相似的其他物品。“基于用户的协同过滤”-User based 是指寻找与用户A相似的其他用户B,C,D, 如果用户A和用户B观看喜好相似,由于用户A和用户B很相似,那幺认为用户B看过的其他物品b也很有可能被A所喜欢。以Koren的图为例说明user-based方法, Joe喜欢左边的三部电影a,b,c,为Joe推荐电影时,找到与他相似的用户A,B,C,这三个人都看过电影1,拯救大兵瑞恩,因此,这部电影是最先推荐的。
1.2 计算方法
以item-base的方法举例,说明如何计算item-item的相似度。 假设user A观看或点击了item a,b(以下统称点击),同样,user B点击了item b, item d,user C点击了item a, item b , item c。
| item a | item b | item c | item d | |
|---|---|---|---|---|
| user A | 1 | 1 | ||
| user B | 1 | 1 | ||
| user C | 1 | 1 | 1 |
将该点击序列展开,得到矩阵
通过矩阵的余弦相似度求item-item, 或者user-user的相似度。在本例中,就是item a 与item b的相似度即为 向量(1,1,0,0)与向量(0,1,0,1)的余弦相似度。这类方法运算简单,速度快,适合在spark上并行处理。
1.3 结论
会存在一些“非用户点击”,即机器爬虫等,特点是点击量过高,需要去掉。
在物品中,尽管我们希望每个物品都至少推给用户一次,对长尾有很好的作用,但是在某些情况下,对于点击特别少的,默认质量不高,也可以去掉。
在计算时,不要真正展开成向量,而是分别计算item a 出现的次数,item b出现的次数,以及itam a 和itam b共同出现的次数,就可以了。因此该方法很适合并行。
基于物品和用户的协同过滤根据不同场景来选择。比如,在亚马逊购物网站,用户的数量远远高于物品的数量,且用户的喜好变换很大,在不同的时期购买的东西不相同,相似性不稳定,而物品数量相对用户较小,且物品与物品之间的相似性较稳定,因此更常用item-based。
2.OCF(order-cf)
2.1 原理
Item-CF是拿到全量信息后,计算item与item的相似度,对所有点击的item同等对待,没有考虑到点击的顺序。OCF则考虑了点击序列的前后顺序。例如,在新闻场景中,点击了文章A之后点击的文章B相对于文章A之前点击的文章C,文章B与文章A更相关。
2.2 计算方法
| 符号 | 意义 |
|---|---|
| i,j | item |
| order(i,j) | 在某一个transaction中,j在i之后点击记为1,否则记为0 |
| click(i) | i被点击的总次数 |
| N | transaction的总次数 |
2.3 ocf的改进
上面的方法默认了在文章A后点击的全部都是同等重要的。但是往往用户的兴趣会随着时间漂移,如在点击浏览文章a之后,立刻点击的文章b比点了10篇文章后再点击的文章c可能会更相似。因为随着文章的增多,用户的兴趣可能被不断转移。基于以上假设,因此提出基于位置假说的oicf,公式如下:
其实就是给每个位置赋予不同的权重,越靠*原文章的点击权重越大。
3. SCF
3.1 原理
该方法来自阿里的文章《基于图结构的实时推荐算法SWING的工程实现》。 假设如下图所示
user集合为[a,b,c,d,e],item集合为[s,r,p,q,w,z,t,x],要计算和s相似的item。user和item可以看做是二部图,在这个二部图里是没有三角形的,但是有很多“秋千”状的swing角形结构,比如(a,r,b)构成一个swing,(a,p,c)也构成一个swing。这个swing结构带来的信息比(e,w)这种边结构带来的信息更多。例如,多个用户在点击了r的同时,都只点了p,那幺r和p就一定是强关联的。如果,两个user pair之间和很多item构成swing,那幺这些item上每个节点分到的权重就越低。
3.2 计算
| 符号 | 意义 |
|---|---|
| i,j | item |
| u,v | user |
| 参数 | |
| 点击过i的user集合 | |
| 点击过j的user集合 |
SCF更多的利用了user的collective intelligence
3.3 结论
由公式可以看到,如果同时点击i,j的人越多, 越大。低俗的文章很多用户去点,就很容易排到前面,因此SCF很容易出热门、低俗。可以通过其他手段来控制和解决。 一个user点击的item,一天最多也就是几百左右,在计算的时候,问题不大。但是一个item会被上百万甚至上千万用户点击,这些用户会做一个两两pair去计算,因此会产生大量的时间消耗。这里需要一些优化。优化的关键是组合(u1, u2)。
4. FM 矩阵分解
不同于前面的简单计算,矩阵分解属于模型方法,需要优化求解。
4.1 原理 矩阵变换
矩阵变换的本质是一个函数,与普通函数不同的是矩阵变换接收一个向量,并输出一个向量。也就是一个向量运动变成另外一个向量,原来的单位向量移动到了新的位置。举例存在一个向量 变换前 变换后 假设 , , 得到 也就是如果得到变换后 和 的位置,也就是确定了新的坐标参考系,就可以得到该空间任意向量 的位置。把这两个向量写到一起,就是矩阵乘法
用图表示就是:
注意到这里变化后的 和 不仅在原来的 和 方向发生了拉伸,还有旋转。如果矩阵 , ,相当于仅仅拉伸了向量
。
反过来说也就是,对于某一个矩阵,大部分向量都会偏移,但是存在某些向量只会拉伸,不会偏移,这类向量就叫这个矩阵的特征向量,拉伸的大小就叫该向量对应的特征值。求该特征向量和特征值的过程就叫矩阵分解,就是将一个矩阵分解为 ,其中Q的每一列是矩阵A的一个特征向量,表示矩阵使哪些向量只发生拉伸; 是一个对角矩阵,对角线上的元素对应特征向量对应的特征值,表示对应的特征向量拉伸情况如何。假设一个向量v是方阵A的特征向量,那幺 , 为特征向量 对应的特征值,一个矩阵的一组特征向量是一组正交向量。解这个方程就可以得到 和 。我们想描述一个变换时,希望只描述其主要变换方向,即其变化最大的方向。而对于矩阵分解,Q对应的列向量是主要的变化方向,当 按照从大到小顺序排列时,越大的特征值对应的特征向量对应的变化越大。当矩阵是高维时,我们求该矩阵的top-k的特征值也就对应了该矩阵最主要的k个变换,也就是这个矩阵最主要的特征,就可以用这k个特征来*似这个矩阵。但是,特征值分解只对于方阵适用,但是实际应用中很多都不是方阵,因此提出了奇异值分解(SVD)。 奇异值分解适应于普通矩阵,用代数表示即
, U成为左奇异矩阵,右边称右奇异矩阵,中间是奇异值。用图形表示就是
k<<n且k<<m, 因此分解后的矩阵远远小于原来的矩阵。k越接*n,两边其结果越接*A。至于怎幺解 SVD,目前都是难题。SVD的进一步简化就是
,为了得到该等式*似解,有了ALS算法。
4.2 基础模型
基础的矩阵分解模型为 ,其中 为用户u对item i的评分, 为item的向量, 为用户u 的向量。优化目标为:
为已知 的(u,i)集合。 因为q 和 p都不知道,因此该等式不是凸函数,其求解方法为ALS(Alternating least squares).每次固定其中的一个,比如p,然后这个最优化问题等价于二次优化问题,用SGD求关于p最优解,然后再固定p,求关于q的最优化问题,如此迭代。
4.3 考虑偏差
由于每个用户的消费习惯不同,有用户偏向给每个电影的评分更高,有的用户偏低,因此在基础模型上,叠加了偏差。
优化目标为:
为全部item的*均值。举例来说,预测Joe对Titanic的评分,所有电影的*均得分是3.7,Titanic是一部好电影,比普通电影得分高0.7,Joe是一个谨慎的用户,评分比其他用户要低0.3左右,因此最终的得分为3.9(3.7+0.5-0.3)。
4.4 隐式反馈场景
上述矩阵分解最常用的场景是显示反馈,而我们现在需要做的是隐式反馈。显示反馈是指用户明确的知道自己的态度,例如给电影评分等。隐式反馈并不能直接反应用户的态度,例如点击数据、浏览数据,用户的一次点击并不以为这用户喜欢这个物品或者喜欢这篇新闻。隐式反馈和显示反馈很不相同,隐式反馈更稠密,更稳定;而显示反馈更稀疏。 隐式反馈更真实,数据更容易获得;显示反馈更能说明用户的态度。隐式数据的处理方法不同于显示数据。当前对于这两类数据有两种方法: point wise(预测对点的偏好) 和pair wise(对两个item的排序)。对隐式数据而言,优化方法变成了(与原文保持一致,p,q 分别改成了x,y):
性能指标:
预测准确度可以用评分预测和TopN表示。
1.评分预测:评分预测的预测准确度一般通过均方根误差(RMSE)和*均绝对误差(MAE)计算。对于测试集中一个用户u 和 物品 i,令 rui 是用户u对i 的实际评分,而^rui 是推荐算法给出的预测评分。那么 RMSE 的定义为:
MAE 采用绝对值计算预测误差,它的定义为:
TopN方法:网站在提供服务时,一般是给用户一个个性化的推荐列表,这种推荐叫做 TopN 推荐。TopN 推荐的预测准确率一般是通过 准确率(precision)/召回率(recall) 度量。
令R(u)是根据用户在训练集上的行为给用户做出的推荐列表,而T(u)是用户在测试集上的行为列表。那么,推荐结果的召回率:
推荐结果的准确率定义为:
def PrecisionRecall(test,N): hit = 0 n_recall = 0 n_precision =0 for user,items in test.items(): rank = Recommend(user,N) hit += len(rank & items) n_recall += len(items) n_precision += N return [hit / n_recall, hit / n_precision]
我们的数据集的准确率和召回率:
总结(学到了什么知识,有什么经验跟大家分享,包括环境搭建、调参等等)