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

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

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)

评价

简单易懂:参见代码;
存储:存储上除了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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,039评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,223评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,916评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,009评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,030评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,011评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,934评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,754评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,202评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,433评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,590评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,321评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,917评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,568评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,738评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,583评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,482评论 2 352

推荐阅读更多精彩内容

  • 本文结构安排 Item-Based Collaboration Filtering Slope One Matri...
    泽泽馥泽泽阅读 802评论 0 1
  • 太长不读版:由推荐系统带来的推荐服务基本上已经渗透到我们生活的方方面面,本文作为浅谈推荐系统的基础篇,主要从下面几...
    stayrascal阅读 31,568评论 5 60
  • 项目管理术语英汉对照表2018-7-20 A Abstract Resource 抽象资源 Abstraction...
    007明_阳阅读 6,183评论 0 51
  • 毕业这个词听起来也不是特别的重要,但这个词的背后有暗暗的伤感。大家一想到忍不住要离开了心里的眼泪就流个不停...
    青春_4683阅读 250评论 0 1
  • 晚来天欲暗风来,冬风呲呲钻衣裳。 到床上窝暖入睡,清静午夜无人语。 忽梦一山与一屋,日入屋来人悠闲。 手捧茶来手翻...
    杨翎阅读 283评论 0 0