「数据量太大了,召回怎么这么慢呐!」
「业务数据更新太快了,动态更新跟不上啊!」
「部署好难……」
做推荐系统工程的朋友们,你们是不是时常听到诸如此类的抱怨?相信阅读完这篇文章后,你可能会得到一些新思路、新方法。
在介绍具体项目之前,我们先来了解一下推荐系统。简单来说,推荐系统就是根据用户的个性化需求,在海量的信息中确定提供给用户什么样的具体内容。通常推荐系统分为两个阶段:「召回」和「排序」。「召回」是推荐系统的第一阶段,主要根据用户和商品部分特征,从海量的物品库里,快速找出一部分用户可能感兴趣的物品,然后交给排序环节;而「排序」则是对所有召回的内容再次进行打分排序,选出得分最高的几个结果并推荐给用户。
想高效落地一个推荐系统?本文将以商品召回为例,介绍如何通过推荐召回算法 MIND 、百度飞桨生态下的大规模模型库 PaddleRec 以及开源向量数据库 Milvus,部署一个稳定易用的工业级推荐系统。PaddleRec 和 Milvus 的结合可以让开发更简单、部署更灵活,还可以快速进行模型效果验证并提升迭代效率,实现快速召回的同时兼顾系统稳定性。
系统架构
本项目分为四个步骤:数据处理、模型训练、模型测试、商品召回案例。在整个商品召回的过程中,该系统先从模型中读取出训练好的模型中的 item 向量,然后将 item 向量导入 Milvus 中存储起来。在召回阶段,该系统将用户的历史点击序列通过 MIND 模型转化得到四个用户向量,代表用户不同方面的兴趣。然后,在 Milvus 库中做商品向量的相似度搜索,每个兴趣向量得到 top_k 个相似商品,最后将四组商品按照相似度排序得到前 top_k 个商品,得到我们要召回的商品。
接下来,我将介绍本项目中所用到的主要组件:MIND、PaddleRec、Milvus。
MIND
MIND 算法全称为:Multi-Interest Network with Dynamic Routing for Recommendation at Tmall,是一个由阿里算法团队开发的推荐召回算法。
在 MIND 诞生之前,大多现存的推荐系统模型都是用一个向量来表示一个用户的多个兴趣,但是这无法很好地表示用户的多方面兴趣,而 MIND 尝试使用多个兴趣向量去表示同一个用户不同方向的兴趣。
MIND 算法提出了具有动态路由的多兴趣网络,用于在召回阶段处理用户的不同兴趣。具体来说,其设计了一个基于胶囊路由机制的多兴趣提取器层,适用于聚类历史行为和提取不同的兴趣。
MIND 完整的网络结构如下图所示:
MIND 输入用户行为和用户画像特征,输出表示用户兴趣的向量。MIND 首先将来自输入层 (Embedding Layer) 的 item 特征通过嵌入层转换为 embedding,接着每个 item 的 embedding 通过池化层 (Pooling Layer) 进一步平均。然后,用户行为 embedding 将被送入多兴趣提取层,产生兴趣胶囊。最后,通过将兴趣胶囊与用户行为 embedding 连接起来,并通过几个 ReLU 层转换连接后的胶囊,获得用户表示向量。此外,在训练过程中,还引入了一个额外的标签感知注意力层 (Label-aware Attention) 来指导训练过程。
PaddleRec
PaddleRec 是源于百度 PaddlePaddle 生态的大规模搜索推荐模型库,其目的在于为广大用户提供搭建推荐系统的一站式解决方案,让广大搜索推荐领域的 AI 从业者,尤其是 AI 应用开发人员可以方便快捷地基于自己的业务搭建出推荐系统。
开篇提到,对于推荐系统从业者来说,要基于业务本身搭建自己的推荐系统,常常会遇到诸如易用性差、部署困难等问题。针对传统的搭建推荐系统方式的缺点,PaddleRec 利用自身优势解决了这类痛点,具体表现为:
易用性强:
开源了召回、排序、融合、多任务等多种类型的业内经典模型,能够快速进行模型效果验证并提升模型的迭代效率。
PaddleRec 支持易用且性能极佳的分布式训练能力,针对大规模稀疏场景极限优化,具有良好的水平扩展能力及加速比,用户可以基于 K8s 快速搭建训练环境。
支持部署:
提供模型线上部署方案,即训即用,兼顾灵活开发和高性能
此外,PaddleRec 在其项目中提供了各种经典推荐相关的模型,可以在 Github 项目中找到,具体可参考:
https://github.com/PaddlePaddle/PaddleRec
Milvus
Milvus 是一款基于云原生架构开发的开源向量数据库,支持查询和管理由机器学习模型或神经网络生成的向量数据。Milvus 在一流的近似最近邻(ANN)搜索库(例如 Faiss、NMSLIB、Annoy)的功能基础上进行扩展,具有按需扩展、流批一体和高可用等特点。Milvus 致力于简化非结构化数据管理,并在不同的部署环境中提供一致的用户体验。
基于高性能的列式存储和 Faiss、HNSWLib 等向量索引,Milvus 数据库可以高效实现数据查询,千万级向量数据毫秒级召回。
基于云原生设计,Milvus 数据库可以轻松横向扩展,能够支持任意规模的存储和计算。
Milvus 帮助用户关注非结构化数据的语意本身,用户无需再关注数据持久化,负载均衡等复杂问题。
Milvus 采用存储与计算分离的架构设计。
基于上述特点,Milvus 可以很好地解决推荐系统中数据更新频繁的问题,满足召回阶段对召回速度的要求,并且能够兼顾系统稳定性。
这也是我们在本文介绍的召回系统中,针对海量向量的相似度检索选择使用 Milvus 而不是直接使用诸如 Faiss、Annoy 等近似最近邻算法,来存储以及检索向量的原因之一。
在 Milvus 开源社区
(https://milvus.io/community)中,你还可以找到更多 Milvus 的应用场景:以图搜图、智能问答、相似文本检索、视频检索……如果你在 AI 领域有向量检索需求,引入 Milvus 会对你有所帮助。
系统实现
该项目的具体实现目前已经发布在 Baidu AI Studio 上,你可以在 AI Studio 平台上启动环境并直接运行该项目:
https://aistudio.baidu.com/aistudio/projectdetail/2250360
下面将分别从数据、模型实现与训练、模型测试来介绍本项目的具体实现,以及如何使用训练好的模型和 Milvus 搭建一个召回服务。
数据介绍
本文使用的原始数据集来自论文 ComiRec 提供的 AmazonBook 数据集。
本项目直接使用了 PaddleRec 中提供的数据下载和数据处理方式,具体可参考 Github 上 PaddleRec 项目中的 dataset 下的 AmazonBook:
https://github.com/PaddlePaddle/PaddleRec/tree/release/2.1.0/datasets/AmazonBook
得到的训练数据集格式如下:
0,17978,0
0,901,1
0,97224,2
0,774,3
0,85757,4
其中每一列分别表示:
uid: 用户 id.
item_id: 用户点击的 item id
time: 点击的顺序(时
测试数据集格式如下:
user_id:487766 hist_item:17784 hist_item:126 hist_item:36 hist_item:124 hist_item:34 hist_item:1 hist_item:134 hist_item:6331 hist_item:141 hist_item:4336 hist_item:1373 eval_item:1062 eval_item:867 eval_item:62
user_id:487793 hist_item:153428 hist_item:132997 hist_item:155723 hist_item:66546 hist_item:335397 hist_item:1926 eval_item:1122 eval_item:10105
user_id:487820 hist_item:268524 hist_item:44318 hist_item:35153 hist_item:70847 eval_item:238318
其中每一列分别表示:
uid: 用户 id
hist_item: 用户点击的历史 item id,多个 hist_item 是根据用户历史点击的时间戳排序的
eval_item: 召回评估序列
模型实现与训练
该步骤将使用 PaddleRec 基于 MIND 实现一个推荐系统中的召回模型,并使用 AmazonBook 的数据训练模型。
模型输入:
本项目中读取原始训练数据集的代码参考脚本
/home/aistudio/recommend/model/mind/mind_reader.py
dygraph_model.py 使用如下代码处理数据,作为模型的输入数据。该部分将上述原始数据中同一用户的点击率按照时间戳排序,组合成一个序列。然后,从序列中随机选取一个 item_id 作为 target_item,将序列 target_item 的前长度为 maxlen 的部分表示为模型输入的 hist_item (长度不足用 0 补足),seq_len 为 hist_item 序列的实际长度。
def create_feeds_train(self, batch_data):
hist_item = paddle.to_tensor(batch_data[0], dtype="int64")
target_item = paddle.to_tensor(batch_data[1], dtype="int64")
seq_len = paddle.to_tensor(batch_data[2], dtype="int64")
return [hist_item, target_item, seq_len]
模型组网:
模型 MIND 的网络具体构造参考
/home/aistudio/recommend/model/mind/net.py
组网部分 net.py 的代码如下所示:
class Mind_Capsual_Layer(nn.Layer):
def __init__(self):
super(Mind_Capsual_Layer, self).__init__()
self.iters = iters
self.input_units = input_units
self.output_units = output_units
self.maxlen = maxlen
self.init_std = init_std
self.k_max = k_max
self.batch_size = batch_size
# B2I routing
self.routing_logits = self.create_parameter(
shape=[1, self.k_max, self.maxlen],
attr=paddle.ParamAttr(
name="routing_logits", trainable=False),
default_initializer=nn.initializer.Normal(
mean=0.0, std=self.init_std))
# bilinear mapping
self.bilinear_mapping_matrix = self.create_parameter(
shape=[self.input_units, self.output_units],
attr=paddle.ParamAttr(
name="bilinear_mapping_matrix", trainable=True),
default_initializer=nn.initializer.Normal(
mean=0.0, std=self.init_std))
class MindLayer(nn.Layer):
def label_aware_attention(self, keys, query):
weight = paddle.sum(keys * query, axis=-1, keepdim=True)
weight = paddle.pow(weight, self.pow_p) # [x,k_max,1]
weight = F.softmax(weight, axis=1)
output = paddle.sum(keys * weight, axis=1)
return output, weight
def forward(self, hist_item, seqlen, labels=None):
hit_item_emb = self.item_emb(hist_item) # [B, seqlen, embed_dim]
user_cap, cap_weights, cap_mask = self.capsual_layer(hit_item_emb, seqlen)
if not self.training:
return user_cap, cap_weights
target_emb = self.item_emb(labels)
user_emb, W = self.label_aware_attention(user_cap, target_emb)
return self.sampled_softmax(
user_emb, labels, self.item_emb.weight,
self.embedding_bias), W, user_cap, cap_weights, cap_mask
其中类 Mind_Capsual_Layer 定义了基于胶囊路由机制的用户多兴趣提取器层。函数 label_aware_attention() 实现了 MIND 算法中标签感知注意力这一技术。在类 MindLayer 的 forward() 函数中,对用户特征建模,构成用户特征权重向量。
模型优化:
本项目使用 Adam 算法作为模型优化器,具体实现部分在脚本
/home/aistudio/recommend/model/mind/dygraph_model.py
代码如下:
def create_optimizer(self, dy_model, config):
lr = config.get("hyper_parameters.optimizer.learning_rate", 0.001)
optimizer = paddle.optimizer.Adam(
learning_rate=lr, parameters=dy_model.parameters())
return optimizer
此外,PaddleRec 中将超参数都写在 config.yaml 中,所以只需要对 config.yaml 一个文件进行修改,就能够清晰地对比模型效果,并快速进行模型效果验证,极大地提升模型的迭代效率。在训练模型的时候,模型效果较差可能是由于欠拟合或者过拟合引起。我们可以通过修改训练的轮数,让模型获得更充分的训练,以此来提高模型效果,而这里仅需要改变 config.yaml 中的参数 epochs 来调整训练训练的轮次即可。
此外,你还可以通过更改模型优化器 optimizer.class 或者是尝试修改学习率(learning_rate)来调试模型。config.yaml 中的部分参数如下:
runner:
use_gpu: True
use_auc: False
train_batch_size: 128
epochs: 20
print_interval: 10
model_save_path: "output_model_mind"
# hyper parameters of user-defined network
hyper_parameters:
# optimizer config
optimizer:
class: Adam
learning_rate: 0.005
模型训练:
本项目中,将模型训练的脚本放在:
/home/aistudio/recommend/model/trainer.py
直接运行以下命令即可开始训练模型。
python -u trainer.py -m mind/config.yaml
模型测试
该步骤将使用测试数据集,测试训练生成的模型的召回率等特性。
测试时,会先从模型中读出训练时保存的所有 item 的向量,随后导入 Milvus 数据库中。接着,通过脚本
/home/aistudio/recommend/model/mind/mind_infer_reader.py
读取将测试数据集中的数据。
加载上述保存的模型,并将测试数据集输入模型,得到输入的用户的多兴趣向量。对于每个用户,模型将返回四个向量。最后,将得到的用户向量在 Milvus 的 item 库中搜索得到最相似的 50 个 item,即为我们要向用户推荐的向量。
通过运行以下命令来测试模型:
python -u infer.py -m mind/config.yaml -top_n 50
模型测试部分提供了 Recall@50、NDCG@50、HitRate@50 这几个评测指标指标,可以根据这个值判断模型的效果。由于本项目仅是作为演示和教程,所以训练并不充足,导致这几个评估值并不理想,在实际业务中,需要多训练几个 epoch,以保证模型的效果。
相应的,训练过程中也会保存更多的模型参数,一般建议大家选择最后保存几个模型进行测试,再根据测试和分析的结果选出最优的模型。此外,还可以通过更改使用不同的优化器和学习率等参数来训练模型,多次训练和测试,选出最优的模型应用于实际项目中。
召回服务
我们使用上述训练的模型,并结合 Milvus 数据库实现了一个推荐召回的服务。
在该召回服务中,使用 FASTAPI 对外提供服务,启动后你可以通过 http 方式直接在终端执行命令,来实现召回服务。
执行以下命令,启动召回服务:
uvicorn main:app
该服务一共提供了四个接口:
Item 向量导入:
启动服务后,执行以下命令,该服务会读取保存在模型中的 item 向量并导入 Milvus 数据库的集合中。
curl -X 'POST' \
'http://127.0.0.1:8000/rec/insert_data' \
-H 'accept: application/json' \
-d ''
召回:
本接口提供最重要的召回服务。
输入任意用户的 item 点击序列,召回该用户下一个可能点击的 item。
这里可批量召回多个用户的兴趣 item。
下面命令行中的 hist_item 是一个二维向量, 每一行表示任意一个用户历史点击的 item 序列,这里的序列允许是变长序列。
返回的结果也是一组二维向量,每一行分别对应输入序列中的一个用户,对其召回多个的 item id。
curl -X 'POST' \
'http://127.0.0.1:8000/rec/recall' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"top_k": 50,
"hist_item": [[43,23,65,675,3456,8654,123454,54367,234561],[675,3456,8654,123454,76543,1234,9769,5670,65443,123098,34219,234098]]
}'
查询 item 总量:
执行以下命令,会返回 Milvus 数据库中保存的总 item 向量数。
curl -X 'POST' \
'http://127.0.0.1:8000/rec/count' \
-H 'accept: application/json' \
-d ''
删除:
本接口用于删除保存在 Milvus 数据库中的数据。
curl -X 'POST' \
'http://127.0.0.1:8000/qa/drop' \
-H 'accept: application/json' \
-d ''
如果你在本地服务器上启动该召回服务,你还可以通过访问 127.0.0.1:8000/docs 来查看和访问该召回服务提供的各个接口。如图:
点击对应的接口,并输入相应的参数,即可体验相应的服务。例如在召回服务中,点击 rec/recall,然后点击 try it out,在 Request body 框中输入相应的参数后点击 Execute 执行就可得到对应的结果:
小结
本项目选择使用 PaddleRec 来实现算法 MIND,是由于 PaddleRec 提供的训练脚本 trainer.py 和配置文件 config.yaml 同样适用于训练其他模型,这使得模型训练和部署起来非常简单。此外,PaddleRec 项目中还提供了许多推荐领域的经典模型的具体实现,包括本项目中用到的 MIND 模型,可供我们参考使用。PaddleRec 的出现,使得我们在实现和训练一个模型的过程中,只需要关注算法本身而不用去过多的关注模型部署等。
Milvus 数据库在向量相似度搜索方面的高性能满足了推荐系统的对召回速度的要求。同时,由于 Milvus 云原生的特性,其在高可用和稳定性方面也能很好的满足推荐系统的需求。除了在推荐领域, Milvus 数据库还广泛应用于计算机视觉(以图搜图、以图搜视频等),自然语言处理(智能问答,文本相似搜索)等领域。Milvus 数据库在 Github 上开源了这些项目的具体实现
(https://github.com/milvus-io/bootcamp),用户在应用 Milvus 数据库时可以直接参考这些项目是如何使用 Milvus 数据库的,对 Milvus 数据库新手来说入门也变得更加的容易。
参考文献
[1] Li C, Liu Z, Wu M, et al. Multi-interest network with dynamic routing for recommendation at Tmall[C]//Proceedings of the 28th ACM International Conference on Information and Knowledge Management. 2019: 2615-2623.
[2] Cen Y, Zhang J, Zou X, et al. Controllable multi-interest framework for recommendation[C]//Proceedings of the 26th ACM SIGKDD International Conference on Knowledge Discovery & Data Mining. 2020: 2942-2951.
长按下方二维码立即
Star
更多信息:
飞桨官方QQ群:793866180
飞桨官网网址:
www.paddlepaddle.org.cn/
飞桨开源框架项目地址:
GitHub:
github.com/PaddlePaddle/Paddle
Gitee:gitee.com/paddlepaddle/Paddle
欢迎在飞桨论坛讨论交流~~
http://discuss.paddlepaddle.org.cn