PaddleRec与Milvus深度结合,手把手带你体验工业级推荐系统召回速度

基于 Milvus 召回服务教程
概述
互联网和移动技术的快速发展,一方面让更多的个人随时随地可以接入互联网获取信息,另一方面也让个人和网站更加快捷的提供UGC(User Generated Content,用户原创内容)和PGC(Professional Generated Content,专业生产内容)的内容、商品。因此,导致了网络中出现了大量的用户和海量的内容数据。这种从信息匮乏到信息爆炸的变化,使得人找信息和信息找人,都变得越来越困难。

在这样的大环境下,搜索推荐系统及相关技术近几年迎来了高速发展。简单来说,推荐系统就是根据用户的个性化需求,在海量的信息中确定提供给用户什么样的具体内容。这种个性化需求可以是用户当前的搜索显性需求;也可以是根据用户历史浏览习惯和行为挖掘到的隐性需求。一旦确定需求,就可以将合适的个性化内容推荐给用户!

推荐系统如何根据已有的用户画像和内容画像去推荐,涉及到两个关键问题:召回和排序。召回是推荐系统的第一阶段,主要根据用户和商品部分特征,从海量的物品库里,快速找回一小部分用户潜在感兴趣的物品,然后交给排序环节。这部分需要处理的数据量通常是非常大的,并且速度要求快。而排序则是对所有召回的内容进行打分排序,选出得分最高的几个结果推荐给用户。

本教程中将详细讲解如何将向量搜索引擎 Milvus 应用于推荐系统的召回服务中。

什么是召回
召回是从全局的物料库中选取和当前要推荐的用户(根据用户属性,历史行为等信息)相关的一部分物料作为候选集。召回阶段的目标主要在于降低候选集规模,从全量的候选集中得到用户可能感兴趣的一小部分候选集。

召回阶段是整个推荐,搜索中的第一步骤,它的输出作为后续步骤(排序,策略调整)的输入,最终展示给用户的item数据是这个集合的子集。召回太多,导致后续的排序精细化排序过程计算压力大,用户被“读懂”的幸福感降低;召回太少,用户看到的内容太少,不利于用户和平台发生转化。同时召回阶段,由于全量候选集数据量通常都是非常大的,召回的速率也决定着整个推荐系统的速率。所以召回决定着推荐,搜索的天花板。

推荐系统中的召回策略主要包含两大类,即基于内容匹配的召回和基于协同过滤的召回。 基于内容匹配的召回:内容匹配即将用户画像与要推荐的内容画像进行匹配。 基于协同过滤的召回:如果仅使用上述较简单的召回策略,推荐内容会较为单一,目前业界最常用的基于协同过滤的召回,它又分为基于用户、基于项目和基于模型的协同过滤。

随着深度学习技术的发展,当前比较主流的实现推荐系统中召回的方式之一是通过模型训练将待推荐的物料用一系列向量来表示物料的特征信息,同时将用户信息(包括用户属性,历史行为灯光)转化成向量来表示该用户的特征。最后通过计算用户向量和库中所有的物料对应的向量的相似度,选出与用户向量相似度较高的物料向量,即得到了要召回的内容。

本教程中我们将使用 PaddleRec 来训练召回模型,使用 Milvus 来存储和召回向量。

接下来本教程将以电影推荐为例,来讲解如何将 Milvus 用于推荐系统的召回服务中,并且会手把手教你如何使用 Milvus 和 PaddleRec 的召回模型实现一个电影推荐系统中的召回服务。

什么是 Milvus
在介绍如何搭建召回服务之前,先简单的了解一下向量搜索引擎 Milvus.

概述
Milvus 是一款开源的向量相似度搜索引擎,支持针对 TB 级向量的增删改操作和近实时查询,具有高度灵活、稳定可靠以及高速查询等特点。 为了能让广大搜索推荐领域的AI从业者,尤其是AI应用人员可以方便快捷的基于自己的业务搭建出高性能的推荐系统,PaddleRec 结合 Milvus 推出了一个可直接用于推荐系统中的快捷高效的召回服务。Milvus 具备如下特点:

全面的相似性指标:Milvus 支持各种常用的向量相似度计算指标,包括欧氏距离、内积、汉明距离和杰卡德距离等。
高性能:Milvus 基于高度优化的 Approximate Nearest Neighbor Search (ANNS) 索引库构建,包括 faiss、 annoy、和 hnswlib 等。您可以针对不同使用场景选择不同的索引类型。
动态数据管理:您可以随时对数据进行插入、删除、搜索、更新等操作而无需受到静态数据带来的困扰。
低成本高效益:Milvus 充分利用现代处理器的并行计算能力,可以在单台通用服务器上完成对十亿级数据的毫秒级搜索。
近实时搜索:在插入或更新数据之后,您可以几乎立刻对插入或更新过的数据进行搜索。Milvus 负责保证搜索结果的准确率和数据一致性。
支持多种数据类型和高级搜索: Milvus 的数据记录中的字段支持多种数据类型。
高扩展性和可靠性:您可以在分布式环境中部署 Milvus。如果要对集群扩容或者增加可靠性,您只需增加节点。
云原生:您可以轻松在公有云、私有云、或混合云上运行 Milvus。
简单易用:Milvus 提供了易用的 Python、Java、Go 和 C++ SDK,另外还提供了 RESTful API。
整体架构

Milvus 常用术语
Collection: 又称为集合,包含一组 entity,可以等价于关系型数据库系统(RDBMS)中的表。
Partition:又称为分区,Partition 是对 Collection 中的数据的划分。一个 Collection 可以存在多个 Partition。
Entity: 实体,包含一组 field。field 与实际对象相对应。field 可以是代表对象属性的结构化数据,也可以是代表对象特征的向量。
Entity ID: 用于指代一个 entity 的唯一值。
Vector: 一种类型的 field,代表对象的特征。
Index: 索引基于原始数据构建,可以提高对 collection 数据搜索的速度。
top_k:指的是向量空间中距离目标向量最近的 k 个向量。
安装 Milvus
环境要求
操作系统 CentOS: 7.5 或以上 Ubuntu LTS: 18.04 或以上

硬件 cpu: Intel CPU Sandy Bridge 或以上

要求 CPU 支持以下至少一个指令集: SSE42, AVX, AVX2, AVX512 内存: 8GB 或以上 (取决于具体向量数据规模)

软件 Python 版本: 3.6 及以上

方法一: 使用 Docker 安装 Milvus
这里将讲解如何安装 Milvus1.0.0 的 CPU 版本,您也可以选择安装 GPU 版本的 Milvus,安装方式请参考: Milvus1.0.0 GPU 版本安装。

拉取 CPU 版本的 Milvus 镜像:

$ sudo docker pull milvusdb/milvus:1.0.0-cpu-d030521-1ea92e
下载配置文件

$ mkdir -p /home/$USER/milvus/conf
$ cd /home/$USER/milvus/conf
$ wget https://raw.githubusercontent.com/milvus-io/milvus/v1.0.0/core/conf/demo/server_config.yaml
Milvus 相关的配置可以通过该配置文件指定。

启动 Milvus Docker 容器

$ sudo docker run -d --name milvus_cpu_1.0.0 \
-p 19530:19530 \
-p 19121:19121 \
-v /home/$USER/milvus/db:/var/lib/milvus/db \
-v /home/$USER/milvus/conf:/var/lib/milvus/conf \
-v /home/$USER/milvus/logs:/var/lib/milvus/logs \
-v /home/$USER/milvus/wal:/var/lib/milvus/wal \
milvusdb/milvus:1.0.0-cpu-d030521-1ea92e
确认 Milvus 运行状态

$ sudo docker logs milvus_cpu_1.0.0
用以上命令查看 Milvus 服务是否正常启动。

方法二:源码编译安装 Milvus
此外,如果在你的环境中,无法使用 Docker, 你也可以通过编译 Milvus 源码来安装 Milvus。这里仅讲解如何安装 CPU 版本的 Milvus1.0.0, GPU 版本的 Milvus 安装请参考这里。 要求

GCC 7.0 or higher to support C++ 17
CMake 3.14 or higher
Git 对于 GPU 版本来说, 你还需要:
CUDA 10.x (10.0, 10.1, 10.2)
NVIDIA driver 418 or higher
编译

下载源码,并切换到对应版本:

git clone https://github.com/milvus-io/milvus
cd ./milvus/core
git checkout 1.0

安装依赖

这里需要根据你的操作系统来选择安装对应的依赖。

Ubuntu系统依赖安装
./ubuntu_build_deps.sh
CentOS 系统依赖安装
./centos7_build_deps.sh
build

./build.sh -t Release
启动 Milvus 服务

cd milvus/core/milvus
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:[Milvus root path]/core/milvus/lib
cd scripts
./start_server.sh
召回服务搭建
在本教程中我们以电影推荐的场景为例,教大家如何使用 Milvus 实现一个推荐系统的召回服务。本教程的电影推荐系统是对给定用户,基于该用户历史的电影评分数据,在海量的电影库中召回他可能感兴趣的其他电影。

我们尝试使用电影评分数据集,训练一个召回模型,根据电影和用户的特征信息,为用户召回一系列感兴趣的电影,然后将结果传给排序模型最终给用户推荐一部他喜欢的电影。最终我们会根据推荐的电影的类别特征与用户的喜好类别进行比较,来评价我们推荐系统是否有效。本教程仅给出召回部分的具体内容,如果想体验电影推荐系统从数据处理到召回在到排序这一完整的流程,请参考教程告别电影荒,手把手教你训练符合自己口味的私人电影推荐助手。

环境部署
在您当前的AI Studio页面中,我们已经为您安装完成了相应的环境: python3

大于2.0版本的paddlepaddle

Milvus 1.0.0 (在当前的AI Studio页面中,还没有启动 Milvus 服务, Milvus服务将在后续在线 demo 展示时启动。)

如果您需要在自己的本地环境中使用,可以使用如下方法来搭建一个操作环境。
在使用recserving运行之前,请确认您的环境:

安装 python 3.6/3.7

安装 PaddlePaddle >= 2.0。安装方法请参考这里

docker >= 19.03

Milvus 1.0.0。 安装方法参考上述讲的安装 Milvus

点击这里,进入PaddleRec的Github主页。如图所示方法复制链接。


打开自己的终端,输入命令“git” + “clone” + 复制的链接”下载PaddleRec文件夹,并进入 recserving 目录。

git clone https://github.com/PaddlePaddle/PaddleRec.git  
cd PaddleRec/recserving    
如果环境配置过程中遇到安装问题可以在Paddle Issue或PaddleRec Issue提出,会有工程师及时解答。遇到 Milvus 相关的问题也可以在 Milvus Issue 提问联系我们。 最最重要的是!请给我们一个star哦!

milvus_tool 介绍
为了能让大家快捷的学会如何使用 Milvus 实现向量导入和向量查询服务,我们提供了一个文件 milvus_tool,该文件夹下的脚本提供了使用 Milvus 存储和召回向量两个功能。

在本教程中,我们已经将 PaddleRec 项目中的 recserving 文件夹中的内容下载到了左侧的项目根目录中。而具体下载方法,在下面的实践环节会为大家详细介绍,小伙伴们不要心急哈!

recserving/movie_recommender 是一个使用到 milvus_tool 实现的电影推荐系统,该目录下的脚本也都放在了左侧项目下的根目录中。

milvus_tool 下有什么
在学习如何使用 milvus_tool 之前,先来看一下 milvus_tool 文件夹的基本组成部分。milvus_tool 的文件结构如下:

├── readme.md #介绍文档
├── config.py #参数配置
├── milvus_insert.py  #向量插入
├── milvus_recall.py #向量召回
readme.md
milvus_tool 安装和使用的说明文档。
2.config.py config.py 是配置文件。该文件中保存了使用 Milvus 召回服务时需要用到的参数。

3.milvus_insert.py milvus_insert.py 提供将向量导入 Milvus 的功能。如果用户想将向量存入 Milvus 中,可以参考和改动这个文件。

4.milvus_recall.py milvus_recall.py 提供相似向量召回。该文件提供在指定的向量库中找到与给定向量最相似 top_k 个向量的功能。

milvus_tool 参数介绍
本项目中,文件 milvus_tool 提供了向量插入和向量召回两个功能。在使用该工具的脚本前,需要先了解并根据运行环境修改该工具中的配置文件 config.py:

Parameters	Description	Reference value
MILVUS_HOST	Milvus 服务所在的机器 IP	127.0.0.1
MILVUS_PORT	提供 Milvus 服务的端口	19530
collection_param	在 Milvus 中建立的集合参数。
dimension 表示向量维度
index_file_size 表示在 Milvus 中存储的数据文件大小
metric_type 表示计算向量相似度的方式	collection_param = {
'dimension': 128,
'index_file_size': 2048,
'metric_type': MetricType.L2}
index_type	指定给 Milvus 集合建立的索引类型	IndexType.IVF_FLAT
index_param	建立索引的参数,不同索引所需要的参数不同	{'nlist': 1000}
top_k	查询时,召回的相似的向量数。	100
search_param	在 Milvus 中查询时的参数,该参数会影像查询性能和召回率	{'nprobe': 20}
使用 milvus_tool 导入向量
Milvus_insert.py 提供向量导入功能,在使用该脚本前,需要在config.py 修改对应参数。调用方式如下:

from milvus_tool.milvus_insert import VecToMilvus
client = VecToMilvus()
status, ids = client.insert(collection_name=collection_name, vectors=embeddings, ids=ids, partition_tag=partition_name)
调用 insert 方法时需要传入的参数:

collection_name: 将向量插入 Milvus 中的集合的名称。该脚本在导入数据前,会检查库中是否存在该集合,不存在的话会按照 config.py 中设置的集合参数建立一个新的集合。

vectors: 插入 Milvus 集合中的向量。这里要求的是向量格式是二维列表的形式,示例:[[2.1, 3.2, 10.3, 5.5], [3.3, 4.2, 6.5, 6.3]] ,这里表示插入两条维度为四的向量。

ids: 和向量一一对应的 ID,这里要求的 ids 是一维列表的形式,示例:[1,2],这里表示上述两条向量对应的 ID 分别是 12. 这里的 ids 也可以为空,不传入参数,此时插入的向量将由 Milvus 自动分配 ID。

partition_tag: 指定向量要插入的分区名称,Milvus 中可以通过标签将一集合分割为若干个分区 。该参数可以为空,为空时向量直接插入集合中。

返回结果:向量导入后将返回 status 和 ids 两个参数。status 返回的是插入的状态,插入成功或者失败。ids 返回的是插入向量对应的 ID,是一个一维列表。

启动 Milvus 服务后可直接运行脚本 milvus_insert.py。(在后续的demo部署教程中,有一键启动 Milvus 服务的教程,该脚本需要启动 Milvus 服务后才能正常运行。)

# 在 Milvus 的集合 `test1` 的分区 `partition_1`中插入随机生成的 100128 维的向量。
!pip install pymilvus==1.0.1
!cd PaddleRec/recserving/milvus_tool/ && python milvus_insert.py
使用 milvus_tool 实现向量召回
milvus_recall.py 提供向量召回功能,在使用该脚本前,需要在config.py 修改对应参数,调用方式如下:

from milvus_tool.milvus_recall import RecallByMilvus
milvus_client = RecallByMilvus()
status, results = self.milvus_client.search(collection_name=collection_name, vectors = query_records, partition_name=partition_name)
collection_name:指定要查询的集合名称。

vectors:指定要查询的向量。该向量格式和插入时的向量格式一样,是一个二维列表。

partition_tag:指定查询的分区标签。该参数可以为空,不指定时在 collection 的全局范围内查找。

返回结果:查询后将返回 status 和 results 两个结果。status 返回的是查询的状态,查询成功或者失败。results 返回的是查询结果,返回结果的格式示例:

以下查询两条向量,top_k =3 是的结果示例
[
 [ (id:000, distance:0.0),
   (id:020, distance:0.17),
   (id:111, distance:0.60) ]
 [ (id:100, distance:0.0),
   (id:032, distance:0.69),
   (id:244, distance:1.051) ]
 ]
向量导入和召回两个功能的具体使用将会在后续的实战中详细讲解。

启动 Milvus 服务后可直接运行脚本 milvus_recall.py。(在后续的demo部署教程中,有一键启动 Milvus 服务的教程,该脚本需要启动 Milvus 服务后才能正常运行。)

# 在 Milvus 的集合 `test1` 的分区 `partition_1`中查询与随机生成的一条向量相似度前100100条向量。用向量间的欧式距离衡量相似度。
!cd PaddleRec/recserving/milvus_tool/ && python milvus_recall.py
数据介绍
本任务中,我们在数据准备阶段将数据集划分为训练集和测试集。

MovieLens数据集是一个关于电影评分的数据集,数据来自于IMDB等电影评分网站。该数据集中包含用户对电影的评分信息,用户的人口统计学特征以及电影的描述特征,非常适合用来入门推荐系统。MovieLens数据集包含多个子数据集,为了后面能够快速展示一个完整的训练流程,教程中我们选取了其中一个较小的子数据集ml-1m,大小在1M左右。该数据集共包含了6000多位用户对近3900个电影的100多万条评分数据,评分为15的整数,其中每个电影的评分数据至少有20条,基本可以满足教学演示的需求。该数据集包含三个数据文件,分别是:

users.dat:存储用户属性信息的txt格式文件,格式为“UserID::Gender::Age::Occupation”,其中原始数据中对用户年龄和职业做了离散化处理。
user_id	性别	年龄	职业
218125	自由职业
218225	学生
218356	教师
...	...	...	...
movies.dat:存储电影属性信息的txt格式文件,格式:“MovieID::Title::Genres”。
movie_id	title	类别
260	Star Wars:Episode IV(1977)	动作,科幻
1270	Three Amigos!(1986)	喜剧
2763	Thomas Crown Affair,The(1999)	动作,惊悚
2858	American Beauty(1999)	喜剧
...	...	...
ratings.dat:存储电影评分信息的txt格式文件,格式:“UserID::MovieID::Rating::time”。
user_id	movie_id	评分	评分时间
2181	2858	4974609869
2181	260	5974608048
2181	1270	5974666558
2182	3481	2974607734
2183	2605	3974608443
2183	1210	4974607751
...	...	...	...
召回模型设计
召回模块会以多路并发的形式,在全量的商品库中从不同的角度(标签匹配、深度模型、热门等)筛选出用户可能感兴趣的商品作为候选数据集,然后再由排序模块对候选集进行精准排序,推荐给用户。这里我们主要来看下召回模型。

说明:对于一些较为复杂的场景,还需要准备用户模型和内容模型,用户模型可以从用户信息和行为数据中提取出用户特征,而内容模型,还是以电影推荐模型为例,复杂的场景还需要有个电影模型,提取电影信息的特征。召回和排序模型使用这些数据特征作为训练输入,可以更准确的判断出内容或信息与用户兴趣方向上的匹配程度,让推荐系统做出更准确的推送。

目前的深度学习召回模型有几大类型:DNN、双塔语义召回、RNN序列召回和深度树匹配召回等。

模型解析
本示例中,召回的目的是从大量电影库中选出部分候选,输入给排序模块,用以提高排序模型性能和效果,因此召回模型的候选范围比较大,可以采用比较简单的模型。经典的如ItemCF、UserCF、DSSM、Graph、TDM等,本教程选取了DSSM模型的简化版,即普通的双塔模型,模型组网如下图所示。左侧对用户特征建模,得到用户表示,右侧对电影特征建模,得到电影表示,将每个原始特征转变成Embedding表示,再合并成一个用户特征向量和一个电影特征向量。然后计算用户和电影的相似度(内积或cosine),对于用户评分较高的电影,电影的特征向量和用户的特征向量应该高度相似,反之则相异。最后与训练样本(已知的用户对电影的评分)做损失计算。Loss函数采用均方误差函数。 网络结构的说明如下所示:

第一层结构:将原始的输入特征通过Embedding变换为原始的特征集合。
第二层结构:特征变换,将原始特征集合变换为用户和电影两个特征向量。
第三层结构:计算向量相似度。为确保结果与电影评分可比较,两个特征向量的相似度从【0~1】缩放5倍到【0~5】
第四层结构:计算Loss,计算缩放后的相似度与用户对电影的真实评分的“均方误差”。
如果用户想了解示例中召回模型的组网代码,可以参考文件PaddleRec/models/demo/movie_recommand/recall/net.py。



召回模型网络结构设计 


模型配置与训练
PaddleRec 中模型超参的配置均体现在 config.yaml 文件中 hyper_parameters 模块,本示例超参配置如下所示,其中参数解释如下:

class:优化器类型;
learning_rate:学习率;
sparse_feature_number:稀疏特征数量;
sparse_feature_dim:稀疏特征维度;
fc_sizes:全连接层的规模。
接下来,我们看下如何训练一个模型。 我们在训练集上训练了五个epoch,在每个epoch后保存了训练出的模型参数文件。在config.yaml文件中的配置如下所示:

runner:
  train_data_dir: "../data/train"  #训练数据的路径
  train_reader_path: "reader"  # importlib format
  train_batch_size: 128
  model_save_path: "output_model_recall"  #模型训练完后保存在该目录下

  use_gpu: true  #是否使用gpu进行训练
  epochs: 5  #训练5个epoch
  print_interval: 20   #每隔20个batch输出一次指标
  
  test_data_dir: "../data/test"  
  infer_reader_path: "reader"  # importlib format
  infer_batch_size: 128
  infer_load_path: "output_model_recall"
  infer_start_epoch: 4  #模型从第五个epoch开始测试(epoch 0为第一个epoch)
  infer_end_epoch: 5  #模型测试到第五个epoch为止。[infer_start_epoch,infer_end_epoch)

  runner_result_dump_path: "recall_infer_result"  #模型测试完后指标保存在该目录下
在这里我们推荐使用AIstudio中带gpu的高级版环境进行训练,默认的use_gpu选项为true。若需要使用基础版进行训练,需要用户自行在PaddleRec/models/demo/movie_recommand目录下分别将recall和rank目录中的config.yaml文件中将use_gpu选项改为False。

首先执行如下命令启动训练,以下命令训练后将得到一个模型可用于生成电影向量,该模型存储在 movie_model 中,在本项目中我们将已经训练好的 movie_model的压缩文件放在目录 PaddleRec/models/demo/movie_recommand下了,解压即可使用。(以下训练每一轮耗时约5小时,共训练了5次)

# 静态图训练recall模型,每次训练数据约 100 万,共训练5次。本过程耗时较长。
cd PaddleRec/models/demo/movie_recommand && python -u ../../../tools/static_trainer.py -m recall/movie.yaml
然后再通过 Paddle Serving 的 paddle_serving_client.convert 接口将上述训练后的模型 movie_model 转换成可用于 Paddle Serving 的模型文件。我们已经将转化后的文件 serving_server 放在本项目中了,可直接使用。

pip install paddle-serving-client
pip install paddle-serving-server-gpu
pip install paddle-serving-app==0.3.1
python -m paddle_serving_client.convert --dirname ./movie_model/4/ --model_filename ./movie_model/4/rec_inference.pdmodel --params_filename  ./movie_model/4/rec_inference.pdiparams
运行脚本 get_movie_vectors.py 利用上述转化后的模型 serving_service 将电影数据转化为向量。(注意,需要将数据 movie.dat 复制一份到 get_movie_vector.py 同一目录下,在运行的时候需要直接读取数据集。此外 serving_service 也需要和 get_movie_vector.py 放在同一级目录。)

python get_movie_vectors.py
上述操作将会生成可用于转化电影数据的模型。在本项目描述的推荐系统中,还需要将用户信息转化为向量,过程与上面讲到的转化电影数据的模型一致。

# 静态图训练recall模型,每次训练数据约 100 万,共训练5次。本过程耗时较长。
!cd PaddleRec/models/demo/movie_recommand && python -u ../../../tools/static_trainer.py -m recall/user.yaml
# 上一条命令将训练好的模型保存为 user_model,这里将通过 Paddle Serving 的 paddle_serving_client.convert 接口将该模型转换成可用于 Paddle Serving 的模型文件
python -m paddle_serving_client.convert --dirname ./user_model/4/ --model_filename ./user_model/4/rec_inference.pdmodel --params_filename  ./user_model/4/rec_inference.pdiparams
# 静态图预测recall模型
python -u static_infer.py -m recall/user.yaml
如何通过上述生成的用户模型获取到对应的用户向量,可参考脚本 recall.py 中的函数 get_user_vector(self, user_info)。在后续模型部署及 demo 展示部分,将会下载我们已提前训练好的user_model。

向量存储与召回
通过上述训练的用户和电影的召回模型,我们可以获取每部电影对应的特征向量和每个用户信息的特征向量。在召回服务中,我们首先将所有电影通过模型转成特征向量然后导入 Milvus 中存储起来;当要召回某一用户感兴趣的电影时,将该用户信息通过前面训练得到的用户模型转成特征向量,然后在 Milvus 中查找与该用户向量相似的前 100 条向量(这里我们的召回服务中设置的召回数量为100,如果想要召回更多数据,请修改文件 milvus_tool/config.py 里的参数 top_k)

向量插入

本任务中,在召回之前,首先会将前面模型转化得到的电影特征向量 movie_vectors.txt 通过脚本 milvus_tool/milvus_insert.py 导入 Milvus 中。 (本教程中我门提供了一个共测试使用的保存了电影特征向量的文件 movie_vectors.txt, 该文件将在后续在线 demo 展示时下载。)

Milvus_insert.py 中,提供了将向量数据导入 Milvus 的集合或者分区中的功能。使用该文件,你可以选择将向量直接导入一个集合中或者是导入一个集合下的某个分区中。如果仅需要将导入集合中,则不需要传入参数 partition_tag,反之传入你需要导入的分区的标签即可。同样的,也可以选择是否传入向量对应的 ID,未传入 ID 的话,在 Milvus 中会自动给每个向量分配一个ID并在插入后返回该组 ID。

需要注意的是,在 Milvus 中,如果第一次向某个集合中插入向量时,指定了插入向量对应的 ID,那么在后续继续向该集合中插入向量时就必须指定 ID。第一次在某个集合中插入向量时没有指定 ID,Milvus 会自动给插入的向量分配 ID,在后续继续向该集合中插入向量时也不能指定 ID。

以下是 milvus_insert.py 将向量导入 Milvus 中的部分代码,在这部分代码中,首先会判断 Milvus 库中是否存在指定的集合,如果不存在则创建以指定名称命名的集合,在这里创建集合的时候会用到 config.py 里定义的参数 collection_param。然后会判断是否传入了参数 partition_tag,传入了该参数的话,会判断对应的集合下是否存在该分区,不存在的话会在会在指定的集合下建立该标签的分区。在建立完成集合/分区后,即可将传入的向量导入 Milvus 中。

def insert(self, vectors, collection_name, ids=None, partition_tag=None):
    try:
        if not self.has_collection(collection_name):
            self.creat_collection(collection_name)
            self.create_index(collection_name)
            print('collection info: {}'.format(self.client.get_collection_info(collection_name)[1]))
        if (partition_tag is not None) and (not self.has_partition(collection_name, partition_tag)):
            self.create_partition(collection_name, partition_tag)
        status, ids = self.client.insert(collection_name=collection_name, records=vectors, ids=ids, partition_tag=partition_tag)
        self.client.flush([collection_name])
        print('Insert {} entities, there are {} entities after insert data.'.format(len(ids), self.client.count_entities(collection_name)[1]))
        return status, ids
    except Exception as e:
        print("Milvus insert error:", e)
本项目中,我们在 to_milvus.py 文件中调用了 milvus_tool/milvus_insert.py 来实现将文件 movie_vectors.txt中的电影特征向量导入 Milvus 中。调用的部分示例代码如下,首先读出 movie_vectors.txt 中的向量和向量对应的 ID,其中读出的向量 embeddings 是一个二维列表的形式,ID 是一个一维列表。然后将读出的 embeddings 和 ID 导入集合 demo_films 下的分区 Movie 中。

import sys
sys.path.append("..")
from milvus_tool.milvus_insert import VecToMilvus

def get_vectors():
    with codecs.open("movie_vectors.txt", "r", encoding='utf-8', errors='ignore') as f:
        lines = f.readlines()
    ids = [int(line.split(":")[0]) for line in lines]
    embeddings = []
    for line in lines:
        line = line.strip().split(":")[1][1:-1]
        str_nums = line.split(",")
        emb = [float(x) for x in str_nums]
        embeddings.append(emb)
    return ids, embeddings

ids, embeddings = get_vectors()
collection_name = 'demo_films'
client = VecToMilvus()
status, ids = client.insert(collection_name=collection_name, vectors=embeddings, ids=ids, partition_tag="Movie")

您可以参考这部分代码,将自己的推荐系统中的其他向量导入 Milvus 中。也可以反复使用上述代码,向同一个集合/分区中多次导入不同的向量

相似向量召回

Milvus_recall.py 中,提供了在指定的 Milvus 集合中查找相似向量的功能。使用该文件,你可以选择将在一个集合中或者是一个集合下的某个分区中查询,如果要在某个集合中查询,则不需要传入参数 partition_tag,反之要查询某个集合下的一个分区则传入对应的分区的标签即可。 以下是 milvus_recall.py 在集合中查找相似向量的部分代码。在这部分代码中,除了传入待查询的向量 vectors, 集合名称 collection_name 和分区标签partition_tag 外,还需要用到配置文件 config.py 里的参数 top_k 和 search_param。

def search(self, vectors, collection_name, partition_tag=None):
    try:
        status, results = self.client.search(collection_name=collection_name, query_records=vectors, top_k=top_k,params=search_param, partition_tag=partition_tag)
        return status, results
    except Exception as e:
        print('Milvus recall error: ', e)
本项目中,我们在 recall.py 文件中调用了 milvus_tool/milvus_recall.py 来实现召回电影集合中与用户向量相似的电影向量。调用的部分示例代码如下,这里我们在之前导入过数据的集合和分区里做向量召回,传入用户向量,查找到的相似的电影向量对应的电影即是我们想要在召回阶段推荐给用户的电影。这里,我们选择使用两个向量间的欧式距离来评价向量相似度,结果将返回 top_k 个与用户向量最相似的电影向量的 ID 以及与用户向量的欧式距离。

def recall(self, request, context):
    user_vector = self.get_user_vector(request.user_info)
    status, results = self.milvus_client.search(collection_name=self.collection_name, vectors=[user_vector], partition_tag="Movie")
    
您可以参考这部分代码,在自己的推荐系统中使用向量召回的服务,也可以根据需求修改文件 Milvus_recall.py。

完成召回服务之后,就可以使用上面提到的排序模型对上述召回的结果进一步排序,然后将相似度得分高的电影推荐给用户。接下来我们就可以部署该服务了。

在线 Demo 展示
我们提供了模型的在线服务部署,但由于python notebook模式下无法运行后台程序,而在线服务是需要启动一个长期稳定的后台进程。因此需要在终端模式当中执行。您需要点击左上角的“终端-1”切换到终端页面,依次执行下面三条程序,启动demo展示的服务端。

启动服务
python3 -m pip install redis pymilvus==1.0.1 paddle_serving_app==0.3.1
sh get_data.sh  #执行完毕后需要敲一下回车
sh start_server.sh  #执行完毕后需要敲一下回车
上述命令中运行脚本 get_data.sh ,该脚本用于数据准备:

12 行命令解压并启动 redis。

36 行命令会解压已经编译完成的 Milvus 源码文件并启动 Milvus 服务。

812 行的命令将会下载 user_vector_model, 在后续召回阶段将使用该模型提供的服务将用户信息转化为向量。

13 行命令将下载用户信息数据 user.dat。该数据具体格式参考上述数据准备中所描述的。

14 行命令会下载电影信息数据 movie.dat.该数据具体格式参考上述数据准备中所描述的.

15 行命令下载电影向量 movie_vectors.txt,该文件中的向量是通过上述讲到的电影模型获取的。

16 行命令会执行脚本 to_redis.py。该脚本将电影数据和用户数据存入 redis 中。

17 行命令将执行脚本 to_milvus.py 将 movie_vectors.txt 中的向量导入 Milvus 中。

脚本 start_server.sh 启动推荐中的召回和排序服务。

模拟在线召回
export PYTHONPATH=$PYTHONPATH:$PWD/proto
python test_client.py as M 32 5 # 性别,年龄,工作
这里实现了根据用户性别年龄工作等召回电影。

这里我们启动的是一个完整的电影推荐系统,给出了如下几种服务。



本示例系统一共启动了5个在线服务,分别是用户模型服务,内容模型服务,召回服务,排序服务,还有应用服务(application service)。
用户模型和内容模型分别是数据集当中的 users.dat 和 movies.da t经过解析之后保存在 redis 当中,用户模型以 user_id 作为 key,内容模型以 movie_id 作为 key。用户模型服务和内容模型服务的逻辑分别是从 redis 当中按照用户传入的 key 来寻找对应的 value 并封装成 protobuf 结果返回。 本系统不仅支持已有的用户信息库,也支持用原有的用户内容训练的模型拟合新增用户,因此用户模型仅在使用 user.dat 中存在的用户信息查询,对于新增用户则不会使用该服务,直接在召回服务中产生用户向量。 召回服务目前分为两个阶段,第一个阶段是通过用户模型得到用户向量,第二个阶段是把用户向量和 Milvus 库中的电影向量做近似搜索。 排序服务是用 PaddleRec 训练好的 CTR 模型,用 Paddle Serving 启动来提供预测服务能力,用户传入一个用户信息和一组内容信息,接下来就能经过特征抽取和排序计算,求得最终的打分,按从高到低排序返回给用户。
应用服务就是以上流程的串联,设计的流程是用户传入自己的用户信息(性别,年龄,工作),从召回服务中得到用户向量,近似查询 movie 列表,接下来查询到所有的内容模型信息,最终两个结合在排序服务中得到所有候选电影的从高到低的打分,最终还原成原始的电影信息返回给用户。

python test_client.py um 5 # 查询user-id 为5的用户信息
python test_client.py cm 5 # 查询movie-id 为5的电影信息
python test_client.py recall 5 # demo召回服务预测,user id=5
python test_client.py rank # demo排序服务预测,由于rank服务参数较多,如需定制可参考代码
输出
一键看到指定用户的推荐结果
python test_client.py as M 32 5 
as服务在经过查询用户、召回、正排(查电影)、排序之后,根据分数降序,从大到小,把最适合该用户的电影信息返回回来,方便您一键直观的看到结果。结果中是由大到小排序的电影信息,每个电影信息包含了电影的id,电影名和影片类型。

error {
  code: 200
}
item_infos { 
  movie_id: "2537"
  title: "Beyond the Poseidon Adventure (1979)"
  genre: "Adventure"
}
item_infos {
  movie_id: "124"
  title: "Star Maker, The (Uomo delle stelle, L\') (1995)"
  genre: "Drama"
}
item_infos {
  movie_id: "2040"
  title: "Computer Wore Tennis Shoes, The (1970)"
  genre: "Children\'s, Comedy"
}
item_infos {
  movie_id: "3238"
  title: "Eye of the Beholder (1999)"
  genre: "Thriller"
}
item_infos {
  movie_id: "1777"
  title: "Wedding Singer, The (1998)"
  genre: "Comedy, Romance"
}
item_infos {
  movie_id: "2079"
  title: "Kidnapped (1960)"
  genre: "Children\'s, Drama"
}
...
查看每个步骤的结果
您也可以分步查看每一个环节后的输出

python test_client.py um 5 # 查询user-id 为5的用户信息
um服务用于查询用户的信息,您可以选择一名用户,通过该服务获得用户的id,性别,年龄,工作,邮政编码。其中error=200为HTTP状态码,200表示请求已成功,请求所希望的响应头或数据体将随此响应返回。出现此状态码是表示正常状态。示例结果如下:

error {
  code: 200
}
user_info {
  user_id: "5"
  gender: "M"
  age: 25
  job: "20"
  zipcode: "55455"
}

python test_client.py cm 3878 # 查询movie-id 为3878的电影信息
gcms服务用于查询电影信息,您可以选择一个电影的id,通过该服务获得电影的id,电影名和影片类型。示例结果如下:

error {
  code: 200
}
item_infos {
  movie_id: "3878"
  title: "X: The Unknown (1956)"
  genre: "Sci-Fi"
}
python test_client.py recall 5 # demo召回服务预测,user id=5
recall服务用于根据您选择的用户id召回排分前100名的电影,显示每一个电影的id和预估分值。示例结果如下:

error {
  code: 200
}
score_pairs {
  nid: "760"
  score: 888.8826293945312
}
score_pairs {
  nid: "1350"
  score: 1020.7203369140625
}
score_pairs {
  nid: "632"
  score: 1106.2396240234375
}
score_pairs {
  nid: "1258"
  score: 1121.1904296875
}
score_pairs {
  nid: "3007"
  score: 1147.3583984375
}
...
python test_client.py rank # demo排序服务预测  
由于rank服务参数较多,如需定制可参考代码。示例结果如下:

error {
  code: 200
}
score_pairs {
  nid: "1"
  score: 3.7467310428619385
}
总结
在这篇文章中,我们首先引入了推荐系统的召回服务,紧接着介绍了什么是召回,而后简单介绍了 Milvus 以及 Milvus 的安装方法等。然后讲解了本教程中我们提供的 Milvus 脚本 milvus_tool 的实现和使用。接着我们使用了 MovieLens 1M 数据集模拟了在线召回的全流程,实现了一个基本符合用户偏好的推荐系统中的召回服务。 我们选择了贴近生活的电影推荐系统作为案例,详细讲解了如何将 Milvus 应用在推荐系统的召回服务中。在整个案例中,我们带着大家跑通了从 Milvus 的安装,到插入向量,再到最后的向量召回全过程。将抽象的向量检索这一种用电影推荐来展示,使得用户更容易理解 Milvus. 我们衷心地希望大家能从这个教程中对 Milvus 有更为详细的认识,也鼓励大家自己动手使用 Milvus 和 PaddleRec 搭建自己简易的推荐系统。Milvus 除了可以应用在推荐系统中,还广泛的被用户用在了计算机视觉,自然语言处理,音频声频等领域,非常欢迎大家来使用和体验哦,Milvus 更多应用场景请点击这里。如果在使用过程中遇到困难,也欢迎扫码加入下方的社区,我们有完善的文档和教程帮您快速找到解决困难,针对真实业务用户还有专人跟进哦。

你可能感兴趣的:(milvus,python,PaddlePaddle)