欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

基于上下文的推荐系统

程序员文章站 2024-03-18 13:48:58
...

基于上下文的推荐系统

1 推荐系统上下文

荐系统算法主要集中研究了如何联系用户兴趣和物品,将最符合用户兴趣的物品推荐给用户,但这些算法都忽略了一点,就是用户所处的上下文(context)。这些上下文包括用户访问推荐系统的时间、地点、心情等,对于提高推荐系统的推荐系统是非常重要的。推荐系统常见的上下文包括:

  • 时间上下文

    • 用户的兴趣可能成周期性(工作|下班、平日|周末)
    • 用户的兴趣是变化的
    • 物品具有时效性
    • 物品的流行度可能具有周期性
    • 建模需要考虑的地方:
      • 商品的时效性衰减程度
      • 用户的历史兴趣随时间衰减
  • 地点上下文

    • 明尼芬达大学研究院提出位置感知推荐系统(LARS)
    • 物品分为两类:
      • 无地域属性:图书、电影
      • 有地域属性:餐厅、旅游景点
    • 兴趣本地化:不同地方的用户的兴趣存在很大的差异
    • 活动本地化:在基于位置的推荐系统中,不能给用户推荐推荐太远的地方

2 基于时间的上下文

在给定时间信息后,推荐系统从一个静态系统变成了一个时变的系统,而用户行为数据也变成了时间序列。研究一个时变系统,需要首先研究这个系统的时间特性。包含时间信息的用户行为数据集由一系列三元组构成,其中每个三元组(u,i,t)代表了用户u在时刻t对物品i产生过行为。我们可以研究的时间特性如下:

  • 系统的物品变化情况:比如物品的平均在线天数,如果一个物品在某天被至少一个用户产生过行为,就定义该物品在 这一天在线。因此,我们可以通过物品的平均在线天数度量一类物品的生存周期。

  • 用户访问情况:我们可以统计用户的平均活跃天数,同时也可以统计相隔T天来系统的用户的重合度。

  • 相隔T天系统物品流行度向量的平均相似度:取系统中相邻T天的两天,分别计算这两天 的物品流行度,从而得到两个流行度向量。然后,计算这两个向量的余弦相似度,如果相似度大,说明系统的物品在相隔T天的时间内没有发生大的变化,从而说明系统的时效 性不强,物品的平均在线时间较长。

  • 推荐系统的实时性:要求每个用户在访问系统时,都根据用户在这个时间点前的行为实时计算推荐列表;推荐算法需要平衡用户的近期行为和长期行为;

  • 时间上下文推荐算法—ItemCF和UserCF: 在之前的CF算法基础上,加上时间因素

  • 推荐算法的时间多样性:推荐系统每天推荐结果的变化程度被定义为推荐系统的时间多样性。时间多样性高的推荐系统中用户会经常看到不同的推荐结果

2.1 基于时间的上下文建模

  • 问题思考

    • 在十年前,计算出用户u对物品i的喜好程度:p(u,i) = 100 ,当天,计算出用户u对物品j的喜好程度:p(u,j) = 80 ,如果只能为用户u推荐物品i和物品j中的一个,推荐哪个比较好?
  • 思路分析

    • 出发点: 减少远期行为产生的喜好度评分 增加近期行为产生的喜好度评分

      已知的是: 在tui时刻,用户u对物品i的喜好度为:p(u,i)当前时间t0

  • 时间惩罚公式

基于上下文的推荐系统

  • 为保证推荐系统的时效性,应该加大新物品的推荐权重

  • 已知:物品特征(包括上线时间ti),用户对物品的喜好度:p(u,i)(惩罚长期行为的结果) 当前时间:(to)

  • 物品时效性惩罚公式

    基于上下文的推荐系统

  • 公式总结

    • 注意:

      tui:计算出用户 u 对物品 i 的喜好度的时刻

      ti :物品 i 上线的时间

基于上下文的推荐系统

3 UserCF和ItemCF的再度优化

3.1 时间上下文相关的UserCF算法

UserCF算法可以利用时间信息提高预测的准确率。UserCF算法的基本思想:给用户推荐和他兴趣相似的其他用户喜欢的物品。从这个基本思想出发,我们可以在以下两个方面利用时间信息改进UserCF算法。

用户兴趣相似度两个用户兴趣相似是因为他们喜欢相同的物品,或者对相同的物品产生过行为。但是,如果两个用户同时喜欢相同的物品,那么这两个用户应该有更大的兴趣相似度。相似兴趣用户的最近行为在找到和当前用户u兴趣相似的一组用户后,这组用户最近的兴趣显然相比这组用户很久之前的兴趣更加接近用户u今天的兴趣。也就是说,我们应该给用户推荐和他兴趣相似的用户最近喜欢的物品。

  • UserCF算法优化,考虑到用户u兴趣相似度用户的最近兴趣

基于上下文的推荐系统

3.2 时间上下文相关的ItemCF算法

基于物品(item-based)的个性化推荐算法是商用推荐系统中应用最广泛的,该算法由两个核心部分构成:

  • 利用用户行为离线计算物品之间的相似度;
  • 根据用户的历史行为和物品相似度矩阵,给用户做在线个性化推荐。

时间信息在上面两个核心部分中都有重要的应用,这体现在两种时间效应上:

物品相似度 用户在相隔很短的时间内喜欢的物品具有更高相似度。以电影推荐为例,用户今天看的电影和用户昨天看的电影其相似度在统计意义上应该大于用户今天看的电影和用户一年前看的电影的相似度。在线推荐 用户近期行为相比用户很久之前的行为,更能体现用户现在的兴趣。因此在预测用户现在的兴趣时,应该加重用户近期行为的权重,优先给用户推荐那些和他近期喜欢的物品相似的物品。

  • ItemCF算法优化,用户的近期行为,更能体现物品的相似性

基于上下文的推荐系统

4 项目总结

4.1 处理数据集

import numpy as np
import pandas as pd

df_rating_training = pd.read_csv('./data/rating_training.csv')
df_rating_training.head()

def df_id_to_index(df,obj_id_name:str,obj_index_name:str=None):
    obj_id_to_index_dict = {}
    obj_index_to_id_dict = {}
    
    ids = df[obj_id_name].unique().tolist()
    
    for index,_id in enumerate(ids):
        obj_id_to_index_dict[_id] = index
        obj_index_to_id_dict[index] = _id
    
    df[obj_id_name] = df[obj_id_name].apply(
        lambda obj_id : obj_id_to_index_dict[obj_id]
    )
    if obj_index_name:
        column_names = df.columns.tolist()
        for index,column_name in enumerate(column_names):
            if column_name == obj_id_name:
                column_names[index] = obj_index_name
        df.columns = column_names
        df[obj_index_name] = df[obj_index_name].astype(int)
    return obj_id_to_index_dict,obj_index_to_id_dict

user_id_to_index_dict,user_index_to_id_dict = df_id_to_index(df_rating_training,'user_id','user_index')

df_rating_training.head()

df_rating_training.info()

movie_id_to_index_dict,movie_index_to_id_dict = df_id_to_index(df_rating_training,'movie_id','movie_index')

df_rating_training.head()

df_rating_training.info()

import time

a = '2005-04-02 23:32:07'
int(time.mktime(time.strptime(a,'%Y-%m-%d %H:%M:%S')))

df_rating_training['timestamp'] = df_rating_training['timestamp'].apply(
    lambda time_str :
    int(time.mktime(time.strptime(time_str,'%Y-%m-%d %H:%M:%S')))
)
df_rating_training['timestamp'] = df_rating_training['timestamp'].astype(int)
df_rating_training.head()

df_rating_training.info()

4.2 打分矩阵

# 用户和商品的打分矩阵 用户和商品时间矩阵 用户的活跃度向量

user_quantity = len(df_rating_training['user_index'].unique())
movie_quantity = len(df_rating_training['movie_index'].unique())
print(user_quantity)
print(movie_quantity)

user_movie_rating_array = np.zeros(shape=(user_quantity,movie_quantity))
user_movie_time_array = np.zeros(shape=(user_quantity,movie_quantity))
user_popular_v = np.zeros(shape=user_quantity)

for user_index,groupby_userindex in df_rating_training.groupby('user_index'):
    movies_rating = groupby_userindex.groupby('movie_index')['rating'].mean()
    movies_time = groupby_userindex.groupby('movie_index')['timestamp'].mean()
    for movie_index in movies_rating.index:
        user_movie_rating_array[user_index][movie_index] = movies_rating[movie_index]
    for movie_index in movies_time.index:
        user_movie_time_array[user_index][movie_index] = movies_time[movie_index]
    user_popular_v[user_index] = len(movies_rating)
    if user_index % 100 == 0:print(user_index,end='..')

user_movie_rating_array

user_movie_time_array

user_popular_v

user_popular_punish_v = np.around(1 / np.log(1 + user_popular_v),3)
user_popular_punish_v

4.3 相似度矩阵

# 构建电影与电影之间的相似度矩阵
movie_sim_array = np.zeros(shape=(movie_quantity,movie_quantity))

# movie_user_dict = {}
# for movie_index in range(movie_quantity):
#     movie_user_dict[movie_index] = df_rating_training.query('movie_index == @movie_index')['user_index'].unique().tolist()
#     if movie_index % 100 == 0:print(movie_index,end='..')

movie_user_dict = {}
for movie_index in range(movie_quantity):
    movie_user_dict[movie_index] = np.where(user_movie_rating_array[:,movie_index] > 0)[0].tolist()
    if movie_index % 100 == 0:print(movie_index,end='..')

import random

for movie_index1 in range(movie_quantity):
#     movie_user_index1 = set(np.where(user_movie_rating_array[:,movie_index1] > 0)[0].tolist())
    movie_user_index1 = set(movie_user_dict[movie_index1])
    for movie_index2 in range(movie_index1 + 1 ,movie_quantity):
        movie_user_index2 = set(movie_user_dict[movie_index2])
        movie_user_union = list(
            movie_user_index1 & set(movie_user_index2)
        )
        if len(movie_user_union) > 300:
            movie_user_union = random.sample(movie_user_union,300)
        # 公共用户的打分向量
        movie1_user_rating_v = user_movie_rating_array[:,movie_index1][movie_user_union]
        movie2_user_rating_v = user_movie_rating_array[:,movie_index2][movie_user_union]
        # 时间惩罚向量
        time_punish_v = np.around(1 / (1 + np.abs(user_movie_time_array[:,movie_index1][movie_user_union] - user_movie_time_array[:,movie_index2][movie_user_union]) / 200000),3)
        # 用户活跃度惩罚向量
        this_user_popular_punish_v = user_popular_punish_v[movie_user_union]
        sim = np.around((movie1_user_rating_v * movie2_user_rating_v * time_punish_v * this_user_popular_punish_v).sum() / np.sqrt( np.square(movie1_user_rating_v).sum() * np.square(movie2_user_rating_v).sum() ) ,3)
        movie_sim_array[movie_index1][movie_index2] = sim
        movie_sim_array[movie_index2][movie_index1] = sim
    print(movie_index1,end='..')

movie_sim_array

4.4 生成推荐

# 生成推荐

user_recommend = {}

for user_index in range(user_quantity):
    fav_movie_indexs = np.where(
        user_movie_rating_array[user_index] >= 3
    )[0].tolist()
    df_sim_sum = pd.DataFrame(movie_sim_array[fav_movie_indexs].sum(axis=0),columns=['sim_sum'])
    user_recommend[user_index] = df_sim_sum.query(
        'sim_sum > 0'
    ).sort_values(by='sim_sum',ascending=False).index[:100].tolist()
    if user_index % 100 == 0:print(user_index,end='..')

4.5 读取测试集

# 读取测试集

df_rating_text = pd.read_csv('./data/rating_text.csv')
df_rating_text.head()

def deal_with_userid(user_id):
    return user_id_to_index_dict[user_id] if user_id in user_id_to_index_dict.keys() else None

def deal_with_movieid(movie_id):
    return movie_id_to_index_dict[movie_id] if movie_id in movie_id_to_index_dict.keys() else None

df_rating_text['user_id'] = df_rating_text['user_id'].apply(deal_with_userid)
df_rating_text['movie_id'] = df_rating_text['movie_id'].apply(deal_with_movieid)
df_rating_text = df_rating_text.dropna()
df_rating_text.columns = ['user_index','movie_index','rating','timestamp']
df_rating_text['user_index'] = df_rating_text['user_index'].astype(int)
df_rating_text['movie_index'] = df_rating_text['movie_index'].astype(int)
df_rating_text.head()

user_fav = {}

for user_index,groupby_userindex in df_rating_text.groupby('user_index'):
    movies_rating = groupby_userindex.groupby('movie_index')['rating'].mean()
    user_fav[user_index] = movies_rating[
        movies_rating >= 3
    ].index.tolist()
    if user_index % 100 == 0:print(user_index,end='..')

4.6 准确率与召回率

# 计算准确率和召回率

union_quantity = 0
recommend_quantity = 0
fav_quantity = 0


for user_index in user_recommend.keys():
    if user_index in user_fav.keys():
        union_quantity += len(
            set(user_recommend[user_index]) & set(user_fav[user_index])
        )
        recommend_quantity += len(user_recommend[user_index])
        fav_quantity += len(user_fav[user_index])
print('precision',union_quantity / recommend_quantity)
print('recall',union_quantity / fav_quantity)

user_movie_rating_array[:,[i for i in range(2,movie_quantity)]]

user_movie_rating_array[:,2:movie_quantity]

5 推荐引擎架构

推荐引擎的构建来源于不同的数据源(也就是用户的特征有很多种类,例如统计的、行为的、主题的)+不同的推荐模型算法,推荐引擎的架构可以试多样化的(实时推荐的+离线推荐的),然后融合推荐结果(人工规则+模型结果),融合方式多样的,有线性加权的或者切换式的等。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ejxAUJf9-1593515771275)(image\1576129875801.png)]

部分 A 主要负责从数据库或者缓存中拿到用户行为数据,通过分析不同的行为,生成当前用户的特征向量。
部分 B 负责将用户的特征向量通过特征-物品相关矩阵转化为初始推荐物品列表。

部分 C 负责对初始化的推荐列表进行过滤、排名等处理,从而生成最终的推荐结果。

生成用户特征向量:在利用用户行为计算特征向量时需要考虑以下因素
用户行为的种类-- 在一个网站中,用户可以对物品产生很多种不同种类的行为。如浏览,购买,评分等。一般标准就是用户付出代价越大的行为权重越高。比如,用户购买行为需要掏钱,所以用户会三思而后行,因此购买行为最重要。

用户产生行为的时间 – 一般来说,近期的行为比过去重要。
用户行为的次数 – 有时用户对一个物品会产生多次行为,行为次数多的物品对应的特征权重越高。

物品的热门程度 – 用户对不热门的物品产生的行为更能代表用户的个性。

特征-物品相关推荐:从架构图可以看出,特征-物品相关推荐可以接受一个候选物品集合。候选物品集合的目的是保证推荐结果只包括含候选物品集合中的物品。比如有些产品要求给用户推荐最近一周加入的新的物品,那么候选物品集合就包括最近一周新加的物品。

过滤和排名:将初始推荐列表中一些用户已经看过的,或者总体评分低的,质量差的,或者推荐后用户可以自己根据条件刷选的(比如价格区间)的物品过滤掉,并按照一定的排序规则(比如新颖度)展现给用户,生成最终的推荐结果。