推薦系統中物品相似度計算


這次介紹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{兩個用戶都感興趣物品數目}{兩個用戶中只要有一個用戶感興趣的物品數目}\)

it’s the ratio of the size of the intersection to the size of the union of their preferred items

這是一個忽略了Preference value的相似度計算方式。

使用這個度量方法通常有兩種情況:

  1. Data中只有boolean值,並沒有rating值;

  2. 你認為數據的噪聲不是很大。

cosine公式

與上述公式相同,只是在分母中加了個根號,這里我們研究物品items的相似度:

\(\omega_{ij}=\frac{|N(i)\cap N(j)|}{\sqrt{|N(i)\cup N(j)|}}\tag{2}\)

這里N(i)和N(j)分別表示喜歡物品i 和物品j 的人數。

到這里為止,我們研究的對象忽略的rating的具體分數,如果對象換做是評分,如電影評分(分數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 的用戶集合。

以下為surprise庫的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()函數見這里

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:

  1. 《推薦系統實踐》. 項亮
  2. http://surprise.readthedocs.io/en/stable/similarities.html
  3. 《Recommender Systems Handbook》.Francesco Ricci · Lior Rokach · Bracha Shapira · Paul B. Kantor
  4. 《Mahout in Action》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM