Press "Enter" to skip to content

Slope One进行评分预测

Slope One是一种基于物品的协同过滤算法,在2005年的paper《Slope One Predictors for Online Rating-Based Collaborative Filtering》被提出,用于预测用户对某一给定的物品的评分。

 

依然使用上一篇中提到的自己编造的少量评分数据
来描述该算法的运作机制。

 

首先依然是加载数据和生成用户物品关系矩阵如下。

 

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_matrix
weight_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,这种相对应的位置不需要重复计算了。

 

下面的函数接受一个用户物品关系矩阵,按照上述方法计算出这两个矩阵。

 

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.]]

 

进行评分预测

 

得到上述两个矩阵后可以根据用户的历史评分,为其进行未发生过评分关联的某物品的评分预测。

 

比如要为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为2、4的item分别做上一步,并将得到的值加和为15,作为分子,分母为每次计算的人数之和,即加权平均,为4;

 

最后得此次预测评分为15/4=3.75

 

下面的函数接受五个参数,分别为三个矩阵,用户id,物品id,结果为预测值。

 

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为该用户之前评过分的所有物品的数量。

 

理所当然要在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)

Be First to Comment

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注