使用PyTorch库TorchRec的逐步实现
在正确的时间向客户推荐合适的产品是各行各业普遍面临的挑战。例如,银行家不断寻求向现有或潜在客户推荐高度相关的服务。零售商努力推荐符合客户口味的吸引人的产品。同样,社交网络旨在建立引人入胜的动态以促进用户采用。
尽管这是一个广泛探索的用例,但由于问题的独特性,实现令人满意的性能结果仍然困难重重。主要原因包括存在丰富的分类数据,这通常导致稀缺问题,以及用例的计算方面造成的可扩展性问题。直到最近,推荐模型才开始利用神经网络。
在这种情况下,Meta开发并公开提供了一个深度学习推荐模型(DRLM)。该模型特别值得注意的是,它结合了协同过滤和预测分析的原理,并适用于大规模生产。
目标
本文的目标是通过使用PyTorch库TorchRec的逐步实现来指导您有效地解决自己的推荐用例。
阅读本文后,您将了解:
- DLRM模型是如何工作的?
- DLRM模型有何独特之处,使其强大且可扩展?
- 如何从头到尾实现自己的推荐系统?
本文需要对推荐系统问题具有一般知识,并熟悉PyTorch库。文章中描述的实验是使用TorchRec和PyTorch库进行的。您可以在GitHub上找到代码。
GitHub – linafaik08/recommender_systems_dlrm
通过在GitHub上创建一个帐户,为linafaik08/recommender_systems_dlrm做出贡献。
github.co
1. 解码DLRM模型
让我们首先深入了解DLRM模型的复杂性,探索其基本原理和机制。
1.1. 模型设计概览
为了提供更具体的说明,让我们考虑一个在线零售商希望为每个访问其网站的客户创建个性化动态的场景。
为了实现这一目标,零售商可以训练一个模型,该模型预测客户购买特定产品的概率。该模型根据各种因素为每个个体客户的每个产品分配一个分数。动态是通过对分数进行排名来构建的。
在这种情况下,模型可以从包含每个客户和产品的各种信息的历史数据中进行学习。这包括数值变量,如客户年龄和产品价格,以及产品类型、颜色等分类特征。
这就是DLRM模型的优势所在:它具有出色的能力,可以利用数值和分类变量,即使处理大量唯一的类别。这使得模型能够全面分析和理解特征之间的复杂关系。要了解原因,让我们看一下图1中的架构模型。
分类特征
DLRM为每个分类特征学习一个嵌入表,并使用它们将这些变量映射到密集表示。因此,每个分类特征都表示为相同长度的向量。
数值特征
DLRM通过一个称为底部MLP的多层感知机(MLP)处理数值特征。该MLP的输出与先前的嵌入向量具有相同的维度。
成对交互
DLRM计算所有嵌入向量和处理后的数值特征之间的点积。这使得模型能够包含二阶特征交互。
连接和最终输出
DLRM将这些点积与处理后的数值特征连接起来,并将结果用于输入另一个称为顶部MLP的多层感知机(MLP)。最终的概率是通过将该MLP的输出传递给一个sigmoid函数来获得的。
1.2. 模型实现
尽管该模型在理论上具有潜力,但其实际实现存在计算难题。
通常,推荐系统的使用案例涉及处理大量数据。特别是使用DLRM模型会引入大量的参数,超过常见的深度学习模型。因此,这加大了与其实现相关的计算需求。
- DLRM中的大部分参数都可以归因于嵌入,因为它们由多个表组成,每个表都需要大量的内存。这使得DLRM在内存容量和带宽方面都具有计算需求。
- 虽然MLP参数的内存占用较小,但仍需要大量的计算资源。
为了减轻内存瓶颈,DLRM依赖于嵌入的模型并行性和MLP的数据并行性的独特组合。
2. 从概念到实现:构建自定义推荐系统的详细逐步指南
本节提供了一个详细的逐步指南,介绍了如何从头到尾实现自己的推荐系统。
2.1. 数据转换和批处理构建
第一步是将数据转换为张量,并将其组织成用于输入模型的批处理。
为了说明这个过程,让我们以这个数据框作为例子。
对于稀疏特征,我们需要将值连接成一个单独的向量并计算长度。这可以使用KeyedJaggedTensor.from_lengths_sync函数来完成,该函数接受这两个元素作为输入。以下是Python脚本的示例:
values = sample[cols_sparse].sum(axis=0).sum(axis=0)values = torch.tensor(values).to(device)# values = tensor([1, 0, 2, 0, 2, 2, 0, 2, 0, 1, 0, 1, 2, 0], device='cuda:0')lengths = torch.tensor( pd.concat([sample[feat].apply(lambda x: len(x)) for feat in cols_sparse], axis=0).values, dtype=torch.int32).to(self.device)# lengths = tensor([1, 1, 1, 1, 1, 2, 3, 2, 2, 0], device='cuda:0', dtype=torch.int32)sparse_features = KeyedJaggedTensor.from_lengths_sync( keys=cols_sparse, values=values, lengths=lengths)
对于密集特征和标签,处理过程更为简单。以下是Python脚本的示例:
dense_features = torch.tensor(sample[cols_dense].values, dtype=torch.float32).to(device)labels = torch.tensor(sample[col_label].values, dtype=torch.int32).to(device)
通过使用前面步骤的输出,可以构建一个批处理。以下是Python脚本的示例:
batch = Batch( dense_features=dense_features, sparse_features=sparse_features, labels=labels,).to(device)
对于更全面的实现,可以参考相应的GitHub存储库中的batch.py文件。
2.2. 模型初始化和优化设置
接下来的步骤是初始化模型,如下面的Python代码所示:
# 初始化模型并设置优化# 定义模型中使用的嵌入的维度embedding_dim = 10# 计算每个特征中的嵌入数量num_embeddings_per_feature = {c: len(v) for c, v in map_sparse.items()}# 定义密集架构的层大小dense_arch_layer_sizes = [512, 256, embedding_dim]# 定义整体架构的层大小over_arch_layer_sizes = [512, 512, 256, 1]# 指定是否使用Adagrad优化器或SGD优化器adagrad = False# 设置Adagrad优化器的epsilon值eps = 1e-8# 设置优化的学习率learning_rate = 0.01# 为每个稀疏特征创建一个EmbeddingBagConfig对象的列表eb_configs = [ EmbeddingBagConfig( name=f"t_{feature_name}", embedding_dim=embedding_dim, num_embeddings=num_embeddings_per_feature[feature_name + '_enc'], feature_names=[feature_name + '_enc'], ) for feature_idx, feature_name in enumerate(cols_sparse)]# 使用嵌入包集合和架构规范初始化DLRM模型dlrm_model = DLRM( embedding_bag_collection=EmbeddingBagCollection( tables=eb_configs, device=device ), dense_in_features=len(cols_dense), dense_arch_layer_sizes=dense_arch_layer_sizes, over_arch_layer_sizes=over_arch_layer_sizes, dense_device=device,)# 创建一个用于处理训练操作的DLRMTrain实例train_model = DLRMTrain(dlrm_model).to(device)# 根据嵌入参数应用适当的优化器类embedding_optimizer = torch.optim.Adagrad if adagrad else torch.optim.SGD# 设置优化器关键字参数optimizer_kwargs = {"lr": learning_rate}if adagrad: optimizer_kwargs["eps"] = eps# 将优化器应用于稀疏架构参数apply_optimizer_in_backward( optimizer_class=embedding_optimizer, params=train_model.model.sparse_arch.parameters(), optimizer_kwargs=optimizer_kwargs,)# 使用适当的参数初始化密集优化器dense_optimizer = KeyedOptimizerWrapper( dict(in_backward_optimizer_filter(train_model.named_parameters())), optimizer_with_params(adagrad, learning_rate, eps),)# 创建一个CombinedOptimizer实例来处理优化optimizer = CombinedOptimizer([dense_optimizer])
然后可以使用以下代码对模型进行训练和评估:
loss, (loss2, logits, labels) = train_model(batch)
若要了解更全面的实现,请参考相应的 GitHub 存储库中的 model.py 文件。
主要结论
✔ DLRM 模型提供了一种有效地结合数值和类别特征的方法,通过嵌入使模型能够捕捉复杂的模式和关系。
✔ 尽管其架构需要大量的计算资源,但其实现结合了模型并行和数据并行的独特组合,使模型具有可扩展性,适用于生产环境。
✔ 然而,由于数据可用性有限,该模型在各种真实世界数据集上的性能测试并不充分,这使得其在实际场景中的有效性存在不确定性。
✔ 此外,该模型需要调整大量参数,进一步增加了复杂性。
✔ 考虑到这一点,像 LGBM 这样更简单的模型可能提供可比的性能,并且更易于实现、调优和长期维护,而不需要同样的计算开销。
参考资料
[1] M Naumov & al, 深度学习个性化和推荐系统建模 , 2019年5月
[2] Facebook 团队对 DLRM 模型的初始实现的开源 GitHub 存储库
[3] DLRM: 一种先进的开源深度学习推荐模型,Meta AI Blog,2019年7月
[4] 用于现代生产推荐系统的 Pytorch 库,torchec
[5] Vinh Nguyen、Tomasz Grel 和 Mengdi Huang,优化 NVIDIA GPU 上的深度学习推荐模型,2020年6月