关于机器学习:系统召回太慢上-Milvus-×-PaddleRec-双剑合璧大法

6次阅读

共计 9310 个字符,预计需要花费 24 分钟才能阅读完成。

作者简介
李云梅,Zilliz 数据工程师,毕业于华中科技大学计算机系。退出 Zilliz 以来,致力于为开源向量数据库 Milvus 摸索解决方案,帮忙用户打造场景利用。深刻关注自然语言解决技术和搜寻举荐零碎,日常喜爱一个人猫着乱翻书。

「数据量太大了,召回怎么这么慢呐 😫」

「业务数据更新太快,动静更新跟不上啊 💥」

「部署好难 🤮」

做举荐系统工程的敌人们,你们是不是时常听到诸如此类的埋怨?置信浏览完这篇文章后,你可能会失去一些新思路、新办法。

在介绍具体我的项目之前,咱们先来理解一下举荐零碎。简略来说,举荐零碎就是依据用户的个性化需要,在海量的信息中确定提供给用户什么样的具体内容。通常举荐零碎分为两个阶段:「召回」和「排序」。「召回」是举荐零碎的第一阶段,次要依据用户和商品局部特色,从海量的物品库里,疾速找出一部分用户可能感兴趣的物品,而后交给排序环节;而「排序」则是对所有召回的内容再次进行打分排序,选出得分最高的几个后果并举荐给用户。

想高效落地一个举荐零碎?本文将以商品召回为例,介绍如何通过举荐召回算法 MIND、百度飞桨生态下的大规模模型库 PaddleRec 以及开源向量数据库 Milvus,部署一个稳固易用的工业级举荐零碎。PaddleRec 和 Milvus 的联合能够让开发更简略、部署更灵便,还能够疾速进行模型成果验证并晋升迭代效率,实现疾速召回的同时兼顾零碎稳定性。

零碎架构

本我的项目分为四个步骤:数据处理、模型训练、模型测试、商品召回案例。在整个商品召回的过程中,该零碎先从模型中读取出训练好的模型中的 item 向量,而后将 item 向量导入 Milvus 中存储起来。在召回阶段,该零碎将用户的历史点击序列通过 MIND 模型转化失去四个用户向量,代表用户不同方面的趣味。而后,在 Milvus 库中做商品向量的类似度搜寻,每个趣味向量失去 top_k 个类似商品,最初将四组商品依照类似度排序失去前 top_k 个商品,失去咱们要召回的商品。

接下来,我将介绍本我的项目中所用到的次要组件:

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/PaddlePadd…

Milvus 数据库

Milvus 是一款基于云原生架构开发的开源向量数据库,反对查问和治理由机器学习模型或神经网络生成的向量数据。Milvus 在一流的近似最近邻(ANN)搜寻库(例如 Faiss、NMSLIB、Annoy)的性能根底上进行扩大,具备按需扩大、流批一体和高可用等特点。

Milvus 致力于简化非结构化数据管理,并在不同的部署环境中提供统一的用户体验。
基于高性能的列式存储和 Faiss、HNSWLib 等向量索引,Milvus 数据库能够高效实现数据查问,千万级向量数据毫秒级召回。

基于云原生设计,Milvus 数据库能够轻松横向扩大,可能反对任意规模的存储和计算。

Milvus 帮忙用户关注非结构化数据的语意自身,用户无需再关注数据长久化,负载平衡等简单问题。

Milvus 采纳存储与计算拆散的架构设计。

基于上述特点,Milvus 能够很好地解决举荐零碎中数据更新频繁的问题,满足召回阶段对召回速度的要求,并且可能兼顾零碎稳定性。
这也是咱们在本文介绍的召回零碎中,针对海量向量的类似度检索抉择应用 Milvus 而不是间接应用诸如 Faiss、Annoy 等近似最近邻算法,来存储以及检索向量的起因之一。
在 Milvus 开源社区中,你还能够找到更多 Milvus 的利用场景:以图搜图、智能问答、类似文本检索、视频检索……如果你在 AI 畛域有向量检索需要,引入 Milvus 会对你有所帮忙。

零碎实现

该项目标具体实现目前曾经公布在 Baidu AI Studio 上,你能够在 AI Studio 平台上启动环境并间接运行该我的项目:https://aistudio.baidu.com/ai…

上面将别离从数据、模型实现与训练、模型测试来介绍本我的项目的具体实现,以及如何应用训练好的模型和 Milvus 搭建一个召回服务。

数据介绍

本文应用的原始数据集来自论文 ComiRec 提供的 AmazonBook 数据集。
本我的项目间接应用了 PaddleRec 中提供的数据下载和数据处理形式,具体可参考 GitHub 上 PaddleRec 我的项目中的 dataset 下的 AmazonBook: https://github.com/PaddlePadd…

失去的训练数据集格局如下:

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:62user_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:10105user_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 提供的训练脚本 trainner.py 和配置文件 config.yaml 同样实用于训练其余模型,这使得模型训练和部署起来非常简单。此外,PaddleRec 我的项目中还提供了许多举荐畛域的经典模型的具体实现,包含本我的项目中用到的 MIND 模型,可供咱们参考应用。PaddleRec 的呈现,使得咱们在实现和训练一个模型的过程中,只须要关注算法自身而不必去过多的关注模型部署等。

Milvus 数据库在向量类似度搜寻方面的高性能满足了举荐零碎的对召回速度的要求。同时,因为 Milvus 云原生的个性,其在高可用和稳定性方面也能很好的满足举荐零碎的需要。除了在举荐畛域,Milvus 数据库还广泛应用于计算机视觉(以图搜图,以图搜视频等),自然语言解决(智能问答,文本类似搜寻)等畛域。Milvus 数据库在 GitHub 上开源了这些我的项目的具体实现(https://github.com/milvus-io/…),用户在利用 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.


Arch Meetup 深圳站开始报名啦,点击查看流动议程!

GitHub @Milvus-io|CSDN @Zilliz Planet|Bilibili @Zilliz-Planet

Zilliz 以从新定义数据迷信为愿景,致力于打造一家寰球当先的开源技术创新公司,并通过开源和云原生解决方案为企业解锁非结构化数据的暗藏价值。

Zilliz 构建了 Milvus 向量数据库,以放慢下一代数据平台的倒退。Milvus 数据库是 LF AI & Data 基金会的毕业我的项目,可能治理大量非结构化数据集,在新药发现、举荐零碎、聊天机器人等方面具备宽泛的利用。

正文完
 0