构建一个增量推荐系统

作者: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)算法。

构建一个增量推荐系统

ISGD — 增量 SGD

我们拥有的数据是元组或者说用户—物品交互。记住这些都是正的交互。算法的输入是三个数字,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的副产品。你可以很容易地运行以下命令安装库:

<code>pip install cf-step/<code>

接下来,下载movielens 1m数据集并提取ratings.dat文件放到一个方便的位置,例如,Linux中的tmp文件夹。对于这个实现,我们只需要这个文件。其余的文件(users.dat和movies.dat)包含了用户和电影的元数据。我们使用pandas加载的文件到内存:

<code># 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()/<code>
构建一个增量推荐系统

如你所见,我们将用户和电影IDs转换为类别,以便提取类别编码。现在,我们不必为嵌入矩阵的生成创建单独的词汇表。我们只需要处理用户和电影编码,而不是IDs。最后,我们通过这个dataframe在编码和IDs之间建立连接,以查找原始用户和电影。现在,让我们看看我们正在处理的用户和电影的数量。

<code>n_users = ratings_df['user_code'].max() + 1
n_movies = ratings_df['movie_code'].max() + 1/<code>

如果我们打印这些数字,我们将看到我们有6040个用户和3706个电影。接下来,我们将按照timestamp对数据进行排序,以模拟流事件。

<code>data_df = ratings_df.sort_values(by='timestamp')/<code>

如前所述,该算法只支持正反馈。因此,我们将把5的评分视为积极的反馈,并丢弃其他的评分。我们想用1表示喜欢,用0表示不喜欢,并创建一个名为preference的新列来保存它们。然后,我们可以只过滤preference == 1。

<code># 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()/<code>
构建一个增量推荐系统

接下来,让我们初始化我们的模型。为此,我们需要一个模型架构、一个目标函数(即损失函数)和一个优化器。我们将使用SimpleCF网络作为模型架构,这是CF Step提供的内置神经网络架构。对于目标函数,我们将使用一个简单的lambda函数,它接受一个prediction和一个target,并从target中减去prediction。在我们的例子中,target总是1。对于优化器,我们将使用Pytorch的SGD实现。我们选择的因子数为128,学习率为0.06。现在我们准备初始化Step模型。

<code>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)/<code>

评估方法如下:

  • 通过对前20%的数据进行训练来提升模型。
  • 模拟数据流,并使用recall@k 作为度量来评估模型的性能。
  • 如果用户是已知的,作出预测,并为这个预测计算recall@k 。然后,使用这种用户—物品交互逐步地训练增量算法。
  • 如果用户未知,使用这种用户—物品交互,训练增量算法,。

为此,让我们取前20%的数据,创建数据加载器和批次来拟合模型。

<code>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)/<code>

然后,我们获取剩余的数据并创建一个不同的数据集。

<code># 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)/<code>

最后,模拟流并使用recall@10对模型进行评估。这个步骤在GPU上需要5到6分钟。

<code>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)
/<code>

我们可以可视化我们的训练结果。为此,我们将使用一个5k滑动窗口的移动平均值,就像他们在发布的论文中所做的那样。我们可以看到,对于movielens数据集,下图与论文中给出的结果一致。要保存模型,使用model.save()内置方法并传递一个有效的路径。

构建一个增量推荐系统

总结

在这个故事中,我们展示了增量学习在推荐系统中的重要性,并重现了Joao Vinagre等人发表的*“Fast incremental matrix factorization for recommendation with positive-only feedback”*的结果。我们介绍了CF Step python库,这是一个开源库,可以快速实现增量学习推荐系统。在下一章,我们将进一步讨论这个问题,并尝试提高算法的准确性。


英文原文:https://towardsdatascience.com/building-an-incremental-recommender-system-8836e30afaef


分享到:


相關文章: