Slope One進行評分預測

Slope One是一種基於物品的協同過濾算法,在2005年的paper《Slope One Predictors for Online Rating-Based Collaborative Filtering》被提出,用於預測用戶對某一給定的物品的評分。python

依然使用上一篇中提到的本身編造的少許評分數據來描述該算法的運做機制。git

首先依然是加載數據和生成用戶物品關係矩陣以下。github

import pandas as pd
import numpy as np


data_url = 'https://gist.githubusercontent.com/guerbai/3f4964350678c84d359e3536a08f6d3a/raw/f62f26d9ac24d434b1a0be3b5aec57c8a08e7741/user_book_ratings.txt'
df = pd.read_csv(data_url, sep = ',', header = None, names = ['user_id', 'book_id', 'rating'])
user_count = df['user_id'].unique().shape[0]
item_count = df['book_id'].unique().shape[0]
user_id_index_series = pd.Series(range(user_count), index=['user_001', 'user_002', 'user_003', 'user_004', 'user_005', 'user_006'])
item_id_index_series = pd.Series(range(item_count), index=['book_001', 'book_002', 'book_003', 'book_004', 'book_005', 'book_006'])

def construct_user_item_matrix(df):
    user_item_matrix = np.zeros((user_count, item_count), dtype=np.int8)
    for row in df.itertuples():
        user_id = row[1]
        book_id = row[2]
        rating = row[3]
        user_item_matrix[user_id_index_series[user_id], item_id_index_series[book_id]] = rating
    return user_item_matrix

user_item_matrix = construct_user_item_matrix(df)
print (user_item_matrix)
複製代碼
[[4 3 0 0 5 0]
 [5 0 4 0 4 0]
 [4 0 5 3 4 0]
 [0 3 0 0 0 5]
 [0 4 0 0 0 4]
 [0 0 2 4 0 5]]
複製代碼

構造物品評分差別矩陣

接下來生成兩個shape爲(item_count, item_count)的矩陣differential_matrixweight_matrix
前者記錄物品兩兩之間的被評分差別狀況,後者記錄對某兩個物品共同評分的人數,用於以後的計算作加權。算法

以上面user_item_matrix舉例來說,index爲2與4的item的共同評分人數爲2(index爲1與2的用戶),則計算這二者的評分差別爲:
((4-4)+(5-4))/2 = 0.5,故在differential_matrix[2][4]的位置填上0.5,同時在weight_matrix[2][4]的位置填上2。
同時,反過來,differential_matrix[4][2]的值爲-0.5,而weight_matrix[4][2]的位置依然爲2,這種相對應的位置不須要重複計算了。函數

下面的函數接受一個用戶物品關係矩陣,按照上述方法計算出這兩個矩陣。url

def compute_differential(ratings):
    item_count = ratings.shape[1]
    differential_matrix = np.zeros((item_count, item_count))
    weight_matrix = np.zeros((item_count, item_count))
    for i in range(item_count):
        for j in range(i+1, item_count):
            differential = 0
            i_rating_user_indexes = ratings[:, i].nonzero()[0]
            j_rating_user_indexes = ratings[:, j].nonzero()[0]
            rating_i_j_user = set(i_rating_user_indexes).intersection(set(j_rating_user_indexes))
            user_count = len(rating_i_j_user)
            if user_count == 0:
                continue
            for user_index in rating_i_j_user:
                differential += ratings[user_index][i] - ratings[user_index][j]
            weight_matrix[i][j] = user_count
            weight_matrix[j][i] = user_count
            differential_matrix[i][j] = round(differential/user_count, 2)
            differential_matrix[j][i] = -differential_matrix[i][j]
    return differential_matrix, weight_matrix

differential_matrix, weight_matrix = compute_differential(user_item_matrix)

print ('differential_matrix')
print (differential_matrix)
print ('-----')
print ('weight_matrix')
print (weight_matrix)
複製代碼
differential_matrix
[[ 0.   1.   0.   1.   0.   0. ]
 [-1.   0.   0.   0.  -2.  -1. ]
 [-0.   0.   0.   0.   0.5 -3. ]
 [-1.   0.  -0.   0.  -1.  -1. ]
 [-0.   2.  -0.5  1.   0.   0. ]
 [ 0.   1.   3.   1.   0.   0. ]]
-----
weight_matrix
[[ 0.  1.  2.  1.  3.  0.]
 [ 1.  0.  0.  0.  1.  2.]
 [ 2.  0.  0.  2.  2.  1.]
 [ 1.  0.  2.  0.  1.  1.]
 [ 3.  1.  2.  1.  0.  0.]
 [ 0.  2.  1.  1.  0.  0.]]
複製代碼

進行評分預測

獲得上述兩個矩陣後能夠根據用戶的歷史評分,爲其進行未發生過評分關聯的某物品的評分預測。spa

好比要爲index爲1的用戶user_002預測其對index爲3的物品item_004的評分,計算過程以下:
先取出該用戶看過的全部書,index分別爲[0, 2, 4];
以index爲0的物品item_001開始,查differential_matrix[3][0]值爲-1,表示item_004平均上比item_001低1分,以該用戶對item_001的評分爲5爲基準,5+(-1)=4,則利用item_001可對item_004作出的評分判斷爲4分,查weight_matrix表知道同時評分過這兩個物品的用戶只有一個,置信度不夠高,使用4*1=4,這即是加權的含義;
但這還沒完,再根據index爲二、4的item分別作上一步,並將獲得的值加和爲15,做爲分子,分母爲每次計算的人數之和,即加權平均,爲4;
最後得這次預測評分爲15/4=3.75.net

下面的函數接受五個參數,分別爲三個矩陣,用戶id,物品id,結果爲預測值。code

def predict(ratings, differential_matrix, weight_matrix, user_index, item_index):
    if ratings[user_index][item_index] != 0: return ratings[user_index][item_index]
    fenzi = 0
    fenmu = 0
    for rated_item_index in ratings[user_index].nonzero()[0]:
        fenzi += weight_matrix[item_index][rated_item_index] * \
            (differential_matrix[item_index][rated_item_index] + ratings[user_index][rated_item_index])
        fenmu += weight_matrix[rated_item_index][item_index]
    return round(fenzi/fenmu, 2)
複製代碼
predict(user_book_matrix, book_differential, weight_matrix, 1, 3)
複製代碼
3.75
複製代碼

新的評分數據

當某用戶對某個其之間未評分過的物品進行一次新的評分時,須要更新三個矩陣的值。使人欣喜的是,Slope One的計算過程使得這種更新很是迅速,時間複雜度僅爲O(x),其中x爲該用戶以前評過度的全部物品的數量。ip

理所固然要在user_item_matrix填入評分值,此外,對此index爲i的物品,須要與那x個物品依次組合在weight_matrix中將值增長1。同理differential_matrix也只須要累計上新的差值便可。
一個用戶評價過的物品數目是頗有限的,這種更新模型的方法可謂飛快。

def update_matrices(user_index, item_index, rating):
    rated_item_indexes = user_item_matrix[user_index].nonzero()[0]
    user_item_matrix[user_index][item_index] = rating
    for rated_item_index in rated_item_indexes:
        old_weight = weight_matrix[rated_item_index][item_index]
        weight_matrix[rated_item_index][item_index] += 1
        weight_matrix[item_index][rated_item_index] += 1
        differential_matrix[rated_item_index][item_index] = (differential_matrix[rated_item_index][item_index] \
            * old_weight + (user_item_matrix[user_index][rated_item_index] - rating)) / (old_weight + 1)
        differential_matrix[item_index][rated_item_index] = (differential_matrix[item_index][rated_item_index] \
            * old_weight + (rating - user_item_matrix[user_index][rated_item_index])) / (old_weight + 1)
複製代碼

評價

簡單易懂:參見代碼;
存儲:存儲上除了user_item_matrix,還須要存下differential_matrixweight_matrix,爲節省空間,能夠只存後二者的對角線的右上部分便可;
預測時間複雜度:用戶評價過的物品數爲x,由predict代碼,則作一次預測的時間複雜度爲O(x);
更新時間複雜度:當用戶新進行一次評分時,由update_matrices代碼,時間複雜度爲O(x);
新用戶友好:當用戶僅進行少許評分時,便可爲其進行較高質量的推薦。

參考

《Slope One Predictors for Online Rating-Based Collaborative Filtering》
Slope One wiki

相關文章
相關標籤/搜索