最近天气有点热, 三伏天得了空调病, 最后发现是颈椎引起的问题, 期间还拔了颗顽固的智齿, 也算是一波三折了.
这次介绍 Item(User)相似度的计算方法, 其广泛运用于基于邻域的协同过滤算法的推荐系统. 简而言之, 基于邻域, 就是基于相邻的元素进行推荐, 而相邻元素的得到过程就是相似度的计算过程.
对于空间上的点来说: 传统机器学习模型中 KNN 的距离度量方法(如欧式距离等), 距离越近的点我们把他们归为一类, 也可以说他们更相似.
对于空间上的向量来说: 方向更相同, 向量越相似, 这就是 cosine 度量方法的原理.
问题来了, 我们得到不同物品 / 用户的相似度有什么用呢
回答: 从 ItemCF 的角度来说, 在得到物品之间的相似度 \(w_{ij}\)(物品 i 和 j )之后, 通过如下公式可以计算用户 u 对一个物品 j 的兴趣:
\(p_{uj}=\sum\limits_{i \in N(u)\cap S(j,k)}w_{ji}r_{ui}\tag{0}\)
这里 N(u)是用户喜欢的物品的集合, S(j, k)是物品 j 最相似的 K 个物品的集合,\(r_{ui}\)是用户 u 对物品 i 的兴趣程度.
Jaccard 公式
这是一个在推荐系统实践中看到的公式, 这里我们研究两个用户 users 的兴趣相似度: 给定用户 u 和用户 v, 令 N(u),N(v)分别表示用户 u,v 曾经有过正反馈的物品集合. 那么用户 u 和 v 的相似度为:
\(\omega_{uv}=\frac{|N(u)\cap N(v)|}{|N(u)\cup N(v)|} \tag{1}\)
上述公式简单表述就是:\(\frac{两个用户都感兴趣物品数目}{两个用户中只要有一个用户感兴趣的物品数目}\)
cosine 公式
与上述公式相同, 只是在分母中加了个根号, 这里我们研究物品 items 的相似度:
\(\omega_{ij}=\frac{|N(i)\cap N(j)|}{\sqrt{|N(i)\cup N(j)|}}\tag{2}\)
这里 N(i)和 N(j)分别表示喜欢物品 i 和物品 j 的人数.
到这里为止, 我们研究的对象只有喜欢不喜欢两种度量, 如果对象换做是评分, 如电影评分(分数 ratings 有: 1,2,3,4,5), 那么相应的 cosine 公式变换为:
\(\text{cosine_sim}(i, j) = \frac{\sum\limits_{u \in U_{ij}} r_{ui} \cdot r_{uj}}{\sqrt{\sum\limits_{u \in U_{ij}} r_{ui}^2} \cdot\sqrt{\sum\limits_{u \in U_{ij}} r_{uj}^2}}\tag{3}\)
其中 \(r_{ui}\)和 \(r_{uj}\)分别表示用户 u 对物品 i 和 j 的评分,\(U_{ij}\)代表同时喜欢物品 i 和 j 的用户集合.
以下为 https://github.com/NicolasHug/Surprise/blob/711fb80748140c44e0ed870e573c735307e6c3cc/surprise/similarities.pyx 库的 cosine 函数源码和分析:
- def cosine(n_x, yr, min_support):
- ### 此处省略了一些东西
- for y, y_ratings in iteritems(yr):
- ### xi 和 xj 分别表示物品 i 和 j
- ### 以下为生成 (3) 式中的分母和分子
- for xi, ri in y_ratings:
- for xj, rj in y_ratings:
- freq[xi, xj] += 1
- prods[xi, xj] += ri * rj
- sqi[xi, xj] += ri**2
- sqj[xi, xj] += rj**2
- ### 以下为使用 (3) 式进行计算
- for xi in range(n_x):
- sim[xi, xi] = 1
- for xj in range(xi + 1, n_x):
- if freq[xi, xj] < min_sprt:
- sim[xi, xj] = 0
- else:
- denum = np.sqrt(sqi[xi, xj] * sqj[xi, xj])
- sim[xi, xj] = prods[xi, xj] / denum
- sim[xj, xi] = sim[xi, xj]
- return sim
- ### 返回的结果 sim 是一个对称矩阵, 行列的 index 表示对应每个物品 item, 矩阵元素表示行列对应物品的相似度
- Pearson Correlation(PC)
如果在 (3) 式的基础上进行去均值的话, 那么就得到了 (4) 式:
\(\text{pearson_sim}(i, j) = \frac{ \sum\limits_{u \in U_{ij}}(r_{ui} - \mu_i) \dot (r_{uj} - \mu_{j})} {\sqrt{\sum\limits_{u\in U_{ij}} (r_{ui} - \mu_i)^2} \cdot \sqrt{\sum\limits_{u \in U_{ij}} (r_{uj} - \mu_{j})^2} }\tag{4}\)
注意一点, 这里的均值计算只考虑到同时喜欢物品 i 和 j 的用户集合 \(U_{ij}\), 对于其他不涉及物品 i 和 j 的用户, 不要加到均值计算的过程中.
通常来说, 不同用户评分标准的差别要比不同物品评分标准差别要高很多(The differences in the rating scales of individual users are often more pronounced than the differences in ratings given to individual items), 因为不同人的评分标准不一样, 对于某人来说, 他评分的所有物品分数都偏低. 但是对于一个物品来说, 不同物品之间所依据的评分标准都是大众评价的结果, 这是一个被不同标准泛化了的标准.
所以, 当我们计算物品相似度 \(\text{pearson_sim}(i, j)\)时, 减去的均值应该针对于用户, 而不是物品. 所以, PC 可以优化为 AC(Adjusted):
\(\text{ adjusted_sim}(i, j) = \frac{ \sum\limits_{u \in U_{ij}}(r_{ui} - \mu_u) \dot (r_{uj} - \mu_{u})} {\sqrt{\sum\limits_{u\in U_{ij}} (r_{ui} - \mu_u)^2} \cdot \sqrt{\sum\limits_{u \in U_{ij}} (r_{uj} - \mu_{u})^2} }\tag{5}\)
均方差(MSD)
仍然考虑物品 i 和 j 的相似度, MSD 考虑的角度为同时喜欢物品 i 和 j 的用户对于这两个物品的评分差距程度:
\(\text{msd}(i, j) = \frac{1}{|U_{ij}|} \cdot \sum\limits_{u \in U_{ij}} (r_{ui} - r_{uj})^2\tag{6}\)
(6)式表示均方差, 值越小, 物品 i 和 j 相似度越大. 为了与之前的相似度表示一致(值越大, 物品相似度越大), 定义相似度为:
$ \text{msd_sim}(i,j) = \frac{1}{\text{msd}(i,j) + 1}\tag{7}$
一些考虑
Accounting for significance
对于推荐系统来说, 考虑到用户的数量, 评分数据是相当稀疏的. 上述方法得到的所有相似度权重通常只使用了很小一部分的评分. 举个例子, 假设两部很小众的电影正好同时只被两个人喜欢, 运用上面的方法, 我们得到这两部影片相似度很高. 然而实际情况可能并不是这样, 这可能我们取的样本太少的缘故. 所以, 有这样一个思想很重要, 即: 当计算只用到很小范围的评分时, 减小这个计算的相似度的权重
Reduce the magnitude of a similarity weight when this weight is computed using only a few ratings
我们可以给计算出来的相似度一个惩罚 (penalized), 所用的评分集合 \(U_{ij}\) 越小, 惩罚越大:
\(w_{ij}=\frac{min\{|U_{ij}|, \gamma\}}{\gamma} \times w_{ij}\tag{8}\)
当评分的用户集合大到一定程度时, 惩罚消失.
Accounting for variance
活跃度跟高的用户通常会评分很多物品, 覆盖范围也更广, 也就是方差 (var) 越大, 他们的评分多, 但是贡献度却要少.
为什么呢? 假如一个人非常爱购物, 在淘宝上疯狂买各种各样的东西, 那么他的一个购买跟物品种类的相关性就很低. 同样的, 对于物品来说, 如电影教父, 被很多人喜欢, 那么根据它也很难找到与他相似的电影. 简单来说: 活跃用户对物品相似度的贡献应该小于不活跃用户.
那么, 我们引入一个参数:
\(\lambda_{u} = log\frac{|I|}{|I_{u}|}\tag{9}\)
这个参数 \(\lambda_{u}\)定义为用户 u 的活跃程度的倒数,\(I\)为所有物品,\(I_{u}\)为用户 u 有操作的物品, 两者之商越大, 代表活跃程度越低, 即权重越高.
将该参数运用到 Pearson 中, 即:
\(\text{pearson_sim}(i, j) = \frac{ \sum\limits_{u \in U_{ij}}\lambda_{u} (r_{ui} - \mu_i) \dot (r_{uj} - \mu_{j})} {\sqrt{\sum\limits_{u\in U_{ij}} \lambda_{u} (r_{ui} - \mu_i)^2} \cdot \sqrt{\sum\limits_{u \in U_{ij}} \lambda_{u} (r_{uj} - \mu_{j})^2} }\tag{10}\)
一般化, 我们可以把 Pearson-baseline correlation 定义如下:
\(\begin{align}\begin{aligned}\text{pearson_baseline_shrunk_sim}(u, v) = \frac{|I_{uv}| - 1} {|I_{uv}| - 1 + \text{shrinkage}} \cdot \omega_{uv}\\\text{pearson_baseline_shrunk_sim}(i, j)= \frac{|U_{ij}| - 1} {|U_{ij}| - 1 + \text{shrinkage}} \cdot \omega_{ij}\end{aligned}\end{align}\)
这也是 surprise 中 pearson_baseline()的计算方法.
性能比较
下面使用 surprise 库对上面介绍的几种相似度度量进行比较:
- import pandas as pd
- import numpy as np
- from surprise.prediction_algorithms.knns import KNNBasic
- from surprise import Dataset, Reader
- from surprise.model_selection import train_test_split
1, 读取数据, 预处理
为了方便, 这里只使用 ml-latest_small 的 movielens 数据集进行操作
- reader = Reader(rating_scale=(1, 5), line_format='user item rating timestamp')
- df_data = pd.read_csv('./data/ml-latest-small/ratings.csv', usecols=['userId','movieId','rating'])
- data = Dataset.load_from_df(df_data, reader)
- trainset, testset = train_test_split(data, test_size=0.2)
2, 建立模型
建立 KNN 基于邻域的模型, 其预测函数为 (0) 式的一个优化, 即:
\(\hat{r}_{ui} = \frac{\sum\limits_{j \in N^k_u(i)} \text{sim}(i, j) \cdot r_{uj}}{\sum\limits_{j \in N^k_u(j)} \text{sim}(i, j)}\tag{11}\)
我们分别使用 cosine, msd, pearson 以及 pearson-baseline 作为相似度度量进行比较, 分别得到其 precision 和 recall(这里使用 Top5 作为 metric)
PS:precision_recall_at_k()函数见这里 https://github.com/NicolasHug/Surprise/blob/711fb80748140c44e0ed870e573c735307e6c3cc/examples/precision_recall_at_k.py
- sim = ['cosine', 'msd', 'pearson','pearson_baseline']
- for s in sim:
- params = {'name': s, 'user_based': False}
- knn = KNNBasic(k=40, min_k=1, sim_options=params)
- knn.fit(trainset)
- predictions = knn.test(testset)
- precisions, recalls = precision_recall_at_k(predictions, k=5, threshold=3.5)
- print('Precision:', sum(prec for prec in precisions.values()) / len(precisions))
- print('Recall:', sum(rec for rec in recalls.values()) / len(recalls))
- print('')
Precision | Recall | |
---|---|---|
cosine | 0.765 | 0.343 |
msd | 0.807 | 0.367 |
pearson | 0.729 | 0.346 |
pearson-base | 0.776 | 0.391 |
由于数据量很小, 上述的评测指数仅作参考
Reference:
推荐系统实践. 项亮
http://surprise.readthedocs.io/en/stable/similarities.html
Recommender Systems Handbook.Francesco Ricci . Lior Rokach . Bracha Shapira . Paul B. Kantor
来源: https://www.cnblogs.com/bjwu/p/9448043.html