Python利用Faiss库实现ANN近邻搜索的方法详解
作者:蚂蚁学Python 发布时间:2021-11-17 19:35:02
Embedding的近邻搜索是当前图推荐系统非常重要的一种召回方式,通过item2vec、矩阵分解、双塔DNN等方式都能够产出训练好的user embedding、item embedding,对于embedding的使用非常的灵活:
输入user embedding,近邻搜索item embedding,可以给user推荐感兴趣的items
输入user embedding,近邻搜搜user embedding,可以给user推荐感兴趣的user
输入item embedding,近邻搜索item embedding,可以给item推荐相关的items
然而有一个工程问题,一旦user embedding、item embedding数据量达到一定的程度,对他们的近邻搜索将会变得非常慢,如果离线阶段提前搜索好在高速缓存比如redis存储好结果当然没问题,但是这种方式很不实时,如果能在线阶段上线几十MS的搜索当然效果最好。
Faiss是Facebook AI团队开源的针对聚类和相似性搜索库,为稠密向量提供高效相似度搜索和聚类,支持十亿级别向量的搜索,是目前最为成熟的近似近邻搜索库。
接下来通过jupyter notebook的代码,给大家演示下使用faiss的简单流程,内容包括:
读取训练好的Embedding数据
构建faiss索引,将待搜索的Embedding添加进去
取得目标Embedding,实现搜索得到ID列表
根据ID获取电影标题,返回结果
对于已经训练好的Embedding怎样实现高速近邻搜索是一个工程问题,facebook的faiss库可以构建多种embedding索引实现目标embedding的高速近邻搜索,能够满足在线使用的需要
安装命令:
conda install -c pytorch faiss-cpu
提前总结下faiss使用经验:
1. 为了支持自己的ID,可以用faiss.IndexIDMap包裹faiss.IndexFlatL2即可
2. embedding数据都需要转换成np.float32,包括索引中的embedding以及待搜索的embedding
3. ids需要转换成int64类型
1. 准备数据
import pandas as pd
import numpy as np
df = pd.read_csv("./datas/movielens_sparkals_item_embedding.csv")
df.head()
id | features | |
---|---|---|
0 | 10 | [0.25866490602493286, 0.3560594320297241, 0.15… |
1 | 20 | [0.12449632585048676, -0.29282501339912415, -0… |
2 | 30 | [0.9557555317878723, 0.6764761805534363, 0.114… |
3 | 40 | [0.3184879720211029, 0.6365472078323364, 0.596… |
4 | 50 | [0.45523127913475037, 0.34402626752853394, -0…. |
构建ids
ids = df["id"].values.astype(np.int64)
type(ids), ids.shape
(numpy.ndarray, (3706,))
ids.dtype
dtype('int64')
ids_size = ids.shape[0]
ids_size
3706
构建datas
import json
import numpy as np
datas = []
for x in df["features"]:
datas.append(json.loads(x))
datas = np.array(datas).astype(np.float32)
datas.dtype
dtype('float32')
datas.shape
(3706, 10)
datas[0]
array([ 0.2586649 , 0.35605943, 0.15589039, -0.7067125 , -0.07414215,
-0.62500805, -0.0573845 , 0.4533663 , 0.26074877, -0.60799956],
dtype=float32)
# 维度
dimension = datas.shape[1]
dimension
10
2. 建立索引
import faiss
index = faiss.IndexFlatL2(dimension)
index2 = faiss.IndexIDMap(index)
ids.dtype
dtype('int64')
index2.add_with_ids(datas, ids)
index.ntotal
3706
4. 搜索近邻ID列表
df_user = pd.read_csv("./datas/movielens_sparkals_user_embedding.csv")
df_user.head()
id features
id | features | |
---|---|---|
0 | 10 | [0.5974288582801819, 0.17486965656280518, 0.04… |
1 | 20 | [1.3099910020828247, 0.5037978291511536, 0.260… |
2 | 30 | [-1.1886241436004639, -0.13511677086353302, 0…. |
3 | 40 | [1.0809299945831299, 1.0048035383224487, 0.986… |
4 | 50 | [0.42388680577278137, 0.5294889807701111, -0.6… |
user_embedding = np.array(json.loads(df_user[df_user["id"] == 10]["features"].iloc[0]))
user_embedding = np.expand_dims(user_embedding, axis=0).astype(np.float32)
user_embedding
array([[ 0.59742886, 0.17486966, 0.04345559, -1.3193961 , 0.5313592 ,
-0.6052168 , -0.19088413, 1.5307966 , 0.09310367, -2.7573566 ]],
dtype=float32)
user_embedding.shape
(1, 10)
user_embedding.dtype
dtype('float32')
topk = 30
D, I = index.search(user_embedding, topk) # actual search
I.shape
(1, 30)
I
array([[3380, 2900, 1953, 121, 3285, 999, 617, 747, 2351, 601, 2347,
42, 2383, 538, 1774, 980, 2165, 3049, 2664, 367, 3289, 2866,
2452, 547, 1072, 2055, 3660, 3343, 3390, 3590]])
5. 根据电影ID取出电影信息
target_ids = pd.Series(I[0], name="MovieID")
target_ids.head()
0 3380
1 2900
2 1953
3 121
4 3285
Name: MovieID, dtype: int64
df_movie = pd.read_csv("./datas/ml-1m/movies.dat",
sep="::", header=None, engine="python",
names = "MovieID::Title::Genres".split("::"))
df_movie.head()
MovieID | Title | Genres | |
---|---|---|---|
0 | 1 | Toy Story (1995) | Animation|Children's|Comedy |
1 | 2 | Jumanji (1995) | Adventure|Children's|Fantasy |
2 | 3 | Grumpier Old Men (1995) | Comedy|Romance |
3 | 4 | Waiting to Exhale (1995) | Comedy|Drama |
4 | 5 | Father of the Bride Part II (1995) | Comedy |
df_result = pd.merge(target_ids, df_movie)
df_result.head()
MovieID | Title | Genres | |
---|---|---|---|
0 | 3380 | Railroaded! (1947) | Film-Noir |
1 | 2900 | Monkey Shines (1988) | Horror|Sci-Fi |
2 | 1953 | French Connection, The (1971) | Action|Crime|Drama|Thriller |
3 | 121 | Boys of St. Vincent, The (1993) | Drama |
4 | 3285 | Beach, The (2000) | Adventure|Drama |
来源:http://www.crazyant.net/2646.html


猜你喜欢
- 图像(包括动图)是传递信息的一种高效方式,往往能增强表象、记忆与思维等方面的反应强度。所谓一图胜千言,说的就是这个道理。今天为大家整理了十张
- 这份代码不是那种时间没有改变也输出innerHTML的高消耗代码。innerHTML和style的改变是非常消耗游览器性能的,如果你将来希望
- 我就废话不多说了,大家还是直接看代码吧~<Form.Item label="作用对象"> &n
- 当我们准备建立一个Web站点时,就必须向域名登记机构申请一个Internet域名,因此,我们通常希望了解自己准备使用的域名是否已经被注册,这
- 前言今天为大家带来解闷用的过迷宫小游戏分享给大家好了。让我们愉快地开始吧~开发工具Python版本: 3.6.4相关模块:pygame模块;
- Vue中内置了很多的指令,如v-model、v-show、v-html等,但是有时候这些指令并不能满足我们,或者说我们想为元素附加一些特别的
- 代码如下: 代码如下:///<summary> /// 将两个列不同的DataTable合并成一个新的DataTab
- 下面是ThoughtBot 的Git使用规范流程。我从中学到了很多,推荐你也这样使用Git。第一步:新建分支首先,每次开发新功能,都应该新建
- 推荐阅读:使用python检测主机存活端口及检查存活主机下面给大家分享使用python语言实现获取主机名根据端口杀死进程代码。ip=os.p
- 如何使用pycharm连接SQL Sever:应该是所有的错误都经历了(不得不说挺崩溃的)Tip:不要跳步操作。步骤一:先检测自己的SQL
- sql server中如何判断表或者数据库的存在,但在实际使用中,需判断Status状态位:其中某些状态位可由用户使用 sp_dboptio
- 目录瞎比比与 print 相比 logging 有什么优势?基础用法保存到文件多模块使用 logging使用配置文件配置 logging瞎比
- 使用python爬虫库requests,urllib爬取今日头条街拍美图代码均有注释import re,json,requests,osfr
- //屏蔽鼠标右键、Ctrl+n、shift+F10、F5刷新、退格键 function KeyDown(){ 
- 阅读上一篇教程:WEB2.0网页制作标准教程(8)CSS布局入门接下来开始要真正设计布局了。和传统的方法一样,你首先要在脑海里有大致的轮廓构
- RSS 是一种基于 XML的文件标准,通过符合 RSS 规范的 XML文件可以简单实现网站之间的内容共享。Ajax 是Asynchronou
- 引言入门python-docx很容易。让我们来看一下基础知识。官方文档打开文档你需要的第一件事是工作的文档。最简单的方法是:from doc
- 前言最近在搞 Python 课程设计,想要搞一个好看的 UI,惊艳全班所有人。但打开 Qt Creator,Win7 风格的复古的按钮是在让
- 为了防止某些别有用心的人从外部访问数据库,盗取数据库中的用户姓名、密码、信用卡号等其他重要信息,在我们创建数据库驱动的解决方案时,我们首先需
- 最近发现了一个宝藏动态可视化库,非常简单,即使是小白也能轻松上手。这个库就是motionchart,它能够用 pandas 的 datafr