点击上方“AI公园”,关注公众号,选择加“星标“或“置顶”
作者:Dimitris Poulopoulos
编译:ronghuaiyang
导读
推荐系统应该要可以适应发生的变化。
需求或先决条件
虽然我会尽量少用数学术语,但这篇文章希望读者熟悉一些概念,比如用户—项目交互矩阵、矩阵分解、嵌入空间,以及基本的机器学习术语。这个故事不是介绍推荐系统。这是对它们的增量变换的介绍。无论如何,这个故事都是针对机器学习和推荐系统领域的初学者的。
介绍
启动一个机器学习项目,数据科学家收集数据,处理它们,训练一个模型并将其部署到生产中。当模型的性能开始变差时,数据科学家通常会从头开始重复这个循环。只是这一次,他们有了新的数据来更新模型并提高其性能。然而,这通常是一种反生产实践,也是一种低效的实践,特别是在对当前数据做出决策是至关重要的业务领域。
现在,进入推荐系统的世界,用户的偏好经常根据季节、预算、时尚趋势等变化。此外,到达的客户和新的库存产生了所谓的冷启动问题,即系统没有足够的信息来匹配消费者与产品或服务。一个推荐系统应该在这些变化发生的时候理想地适应这些变化,修改它的模型以始终代表当前状态,同时需要一次数据传递。这就是增量学习的概念。
这个故事是这个系列的第一部分,在这个系列中,我们将探索如何将增量学习的思想应用到推荐系统中,并使用实际操作的方法。在第一部分中,我们使用一个基于Pytorch的python库CF Step,重现了Joao Vinagre等人发表的*“Fast incremental matrix factorization for recommendation with positive-only feedback”*中的结果。接下来,我们将通过应用一些技巧来达到更高的目标。
快速增量矩阵分解
我们将要实现的算法将使用隐式的、只有正的反馈。让我们试着揭开这两个关键词的神秘面纱。“隐性反馈”指的是,用户从未对他们接触过的内容表达过直接的意见(如评分)。隐式反馈的一个例子是客户购买产品的次数,或者他们看电影的时间。顾客购买产品或使用服务越多,我们就越有信心认为这是一种偏好。只有正的反馈通常与隐式反馈一起出现。这是因为,在隐式反馈的情况下,我们很少知道什么构成了消极的交互。一个用户不与一个项目交互没有什么意义。想象一个超市里的消费者。如果客户还没有购买特定的产品,我们无法确定原因。
回到我们的实现中,仅为正数意味着用户—物品交互矩阵R
只包含布尔值,其中true
表示默认值,false
表示缺失值。这个假设有两个主要含义,保持R
的稀疏性,因为在训练期间只使用正反馈,对于任意的用户—物品交互,false
值的地方都是完全有效的推荐候选。
算法&方案
现在让我们进一步研究一下本文提出的增量随机梯度下降(ISGD)算法。
我们拥有的数据是元组或者说用户—物品交互。记住这些都是正的交互。算法的输入是三个数字,feat
这是用户或物品嵌入空间的维度,λ
,即正则化系数,还有η
,学习率。算法的输出是两个嵌入矩阵:用户矩阵A
和物品矩阵B
。这些矩阵的维数,A
是number_of_users x feat
,B
是number_of_items x feat
。然后我们有几个步骤:
检查活跃用户是否已知。如果不是,则创建一个具有随机潜在特征的新用户,该用户来自均值为0、标准差为1的正态分布。对活跃物品执行相同的操作。
计算损失。因为我们只需要处理正反馈,所以目标总是“1”。因此,我们只需要从“1”中减去我们的预测。
使用通用的更新规则更新活跃用户的潜在特征(用户嵌入矩阵中的参数)。对活跃物品执行相同的操作。
转到下一个数据点。这样,我们就可以处理任意长度的流数据。
实现&评估
对于这个实现,我们将使用CF Step Python库和著名的Movielens数据集。CF Step是一个开源的库,用python编写,以Pytorch为基础,支持实现快速增量式学习推荐系统。该库是欧洲研究项目CloudDBAppliance的副产品。你可以很容易地运行以下命令安装库:
pip install cf-step
接下来,下载movielens 1m数据集并提取ratings.dat
文件放到一个方便的位置,例如,Linux中的tmp
文件夹。对于这个实现,我们只需要这个文件。其余的文件(users.dat
和movies.dat
)包含了用户和电影的元数据。我们使用pandas加载的文件到内存:
# load the data
col_names = ['user_id', 'movie_id', 'rating', 'timestamp']
ratings_df = pd.read_csv('/tmp/ratings.dat', delimiter='::', names=col_names, engine='python')
# transform users and movies to categorical features
ratings_df['user_id'] = ratings_df['user_id'].astype('category')
ratings_df['movie_id'] = ratings_df['movie_id'].astype('category')
# use the category codes to avoid creating separate vocabularies
ratings_df['user_code'] = ratings_df['user_id'].cat.codes.astype(int)
ratings_df['movie_code'] = ratings_df['movie_id'].cat.codes.astype(int)
ratings_df.head()
如你所见,我们将用户和电影IDs转换为类别,以便提取类别编码。现在,我们不必为嵌入矩阵的生成创建单独的词汇表。我们只需要处理用户和电影编码,而不是IDs。最后,我们通过这个dataframe在编码和IDs之间建立连接,以查找原始用户和电影。现在,让我们看看我们正在处理的用户和电影的数量。
n_users = ratings_df['user_code'].max() + 1
n_movies = ratings_df['movie_code'].max() + 1
如果我们打印这些数字,我们将看到我们有6040个用户和3706个电影。接下来,我们将按照timestamp对数据进行排序,以模拟流事件。
data_df = ratings_df.sort_values(by='timestamp')
如前所述,该算法只支持正反馈。因此,我们将把5
的评分视为积极的反馈,并丢弃其他的评分。我们想用1
表示喜欢,用0
表示不喜欢,并创建一个名为preference
的新列来保存它们。然后,我们可以只过滤preference == 1
。
# more than 4 -> 1, less than 5 -> 0
data_df['preference'] = np.where(data_df['rating'] > 4, 1, 0)
# keep only ones and discard the others
data_df_cleaned = data_df[(data_df['preference'] == 1)]
data_df_cleaned.head()
接下来,让我们初始化我们的模型。为此,我们需要一个模型架构、一个目标函数(即损失函数)和一个优化器。我们将使用SimpleCF
网络作为模型架构,这是CF Step提供的内置神经网络架构。对于目标函数,我们将使用一个简单的lambda
函数,它接受一个prediction和一个target,并从target中减去prediction。在我们的例子中,target总是1
。对于优化器,我们将使用Pytorch的SGD实现。我们选择的因子数为128
,学习率为0.06
。现在我们准备初始化Step
模型。
net = SimpleCF(n_users, n_movies, factors=128, init=torch.nn.init.normal_, mean=0., std=.1)
objective = lambda pred, target: target - pred
optimizer = SGD(net.parameters(), lr=6e-2)
device = 'cuda' if torch.cuda.is_available() else 'cpu'
model = Step(net, objective, optimizer, device=device)
评估方法如下:
通过对前20%的数据进行训练来提升模型。
模拟数据流,并使用
recall@k
作为度量来评估模型的性能。如果用户是已知的,作出预测,并为这个预测计算
recall@k
。然后,使用这种用户—物品交互逐步地训练增量算法。如果用户未知,使用这种用户—物品交互,训练增量算法,。
为此,让我们取前20%的数据,创建数据加载器和批次来拟合模型。
pct = int(data_df_cleaned.shape[0] * .2)
bootstrapping_data = data_df_cleaned[:pct]
features = ['user_code', 'movie_code', 'rating']
target = ['preference']
data_set = TensorDataset(torch.tensor(bootstrapping_data[features].values), torch.tensor(bootstrapping_data[target].values))
data_loader = DataLoader(data_set, batch_size=BATCH_SIZE, shuffle=False)
model.batch_fit(data_loader)
然后,我们获取剩余的数据并创建一个不同的数据集。
# get the remaining data
data_df_step = data_df_cleaned.drop(bootstrapping_data.index)
data_df_step = data_df_step.reset_index(drop=True)
# create the DataLoader
stream_data_set = TensorDataset(torch.tensor(data_df_step[features].values), torch.tensor(data_df_step[target].values))
stream_data_loader = DataLoader(stream_data_set, batch_size=1, shuffle=False)
最后,模拟流并使用recall@10
对模型进行评估。这个步骤在GPU上需要5到6分钟。
k = 10 # we keep only the top 10 recommendations
recalls = []
known_users = []
with tqdm(total=len(stream_data_loader)) as pbar:
for idx, (features, preferences) in enumerate(stream_data_loader):
itr = idx + 1
user = features[:, 0]
item = features[:, 1]
rtng = features[:, 2]
pref = preferences
if user.item() in known_users and rtng.item() == 5:
predictions = model.predict(user, k)
recall = recall_at_k(predictions.tolist(), item.tolist(), k)
recalls.append(recall)
model.step(user, item, rtng, pref)
else:
model.step(user, item, rtng, pref)
known_users.append(user.item())
pbar.update(1)
我们可以可视化我们的训练结果。为此,我们将使用一个5k
滑动窗口的移动平均值,就像他们在发布的论文中所做的那样。我们可以看到,对于movielens数据集,下图与论文中给出的结果一致。要保存模型,使用model.save()
内置方法并传递一个有效的路径。
总结
在这个故事中,我们展示了增量学习在推荐系统中的重要性,并重现了Joao Vinagre等人发表的*“Fast incremental matrix factorization for recommendation with positive-only feedback”*的结果。我们介绍了CF Step python库,这是一个开源库,可以快速实现增量学习推荐系统。在下一章,我们将进一步讨论这个问题,并尝试提高算法的准确性。
—END—
英文原文:https://towardsdatascience.com/building-an-incremental-recommender-system-8836e30afaef
请长按或扫描二维码关注本公众号
喜欢的话,请给我个好看吧!