尝试使用 Azure OpenAI 服务和虚拟机上的 Redis 进行嵌入和向量搜索
我尝试使用Azure OpenAI服务中的嵌入向量来进行最近流行的向量搜索。这是因为我想尝试在不使用SaaS向量数据库的情况下解决这个问题。
在使用方法上,我们使用了Redis,但是在进行向量搜索时需要使用RediSearch模块。然而,在Azure Cache for Redis的PaaS中,只有企业版计划才可以使用RediSearch。
哎呀呀!这个Enterprise计划简直贵得令人难以置信!
即使选择最便宜的E10计划,每月也要花费大约13万。
因此,我决定在Azure虚拟机上搭建Redis环境,因为据说只需搭建Redis Stack Server就可以实现。
这次我非常参考了下面的文章。
在Azure OpenAI Service上创建嵌入模型。
将部署text-embedding-ada-002模型。
由于Azure OpenAI服务需要申请,因此也可以使用官方的OpenAI。


生成嵌入到数据集中的嵌入
首先,我们将进行代码的分割并进行编写。
首先,导入我们将要使用的库。
import os
import ast
from dotenv import load_dotenv
import numpy as np
import openai
import pandas as pd
import redis
import tiktoken
from openai.embeddings_utils import get_embedding, cosine_similarity
from redis.commands.search.query import Query, Filter
from redis.commands.search.result import Result
from redis.commands.search.field import VectorField, TextField
请将先前的密钥和模型名称写入.env文件中。
OPENAI_NAME=<モデル名>
OPENAI_KEY=<アクセスキー>
OpenAI的初始设定等等。
load_dotenv()
openai_name = os.environ.get("OPENAI_NAME")
openai_uri = f"https://{openai_name}.openai.azure.com/"
openai.api_type = "azure"
openai.api_base = openai_uri
openai.api_version = "2022-12-01"
openai.api_key = os.environ.get("OPENAI_KEY")
# embedding_model_for_[doc/query] = "デプロイしたモデルの名前"
embedding_model_for_doc = "text-search-doc"
embedding_model_for_query = "text-search-query"
embedding_encoding = "gpt2"
max_tokens = 2000
■用于搜索的数据集
本次使用的搜索数据集是之前使用过的酒店菜单类似的东西。
項目,タイトル,価格
焼酎,(芋)大隅、(芋)金黒,530円,
焼酎,(芋)農家の嫁,630円,
焼酎,(麦)村正,560円,
焼酎,(麦)吟麗玄海,580円,
焼酎,(米)もっこす,560円,
焼酎,(米)鳥飼,680円,
焼酎,(泡盛)三年古酒 瑞泉,800円,
日本酒,松竹梅 上撰 豪快(兵庫),720円,
日本酒,越乃景虎 本醸造 超辛口(新潟),"1,080円",
...
进行嵌入式操作。
input_datapath = "data/data.csv"
df = pd.read_csv(input_datapath)
#タイトルと価格をあわせてCombined列を作る
df["Combined"] = (
"title: " + df.title.str.strip() + "; Cost: " + df.cost.str.strip()
)
encoding = tiktoken.get_encoding(embedding_encoding)
# 埋め込み実行 data_embeddings.csvとして保存しておく。
df["Embedding"] = df["Combined"].apply(lambda x: get_embedding(x, engine=embedding_model_for_doc))
df.to_csv("data/data_embeddings.csv")
#できてるか確認
df["Embedding"][0]
[0.009056703187525272,
-0.01744893752038479,
-0.002270820550620556,
0.005382193252444267,
0.004461904522031546,
-0.007069943007081747,
-0.009907222352921963,
-0.026645179837942123,
-0.0163459200412035,
-0.022964024916291237,
...
看起来很坚强。
我们要确认生成向量的维度数。
len(df["Embedding"][0])
# ベクトル空間の次元数は1536
embedding_dimension = 1536
在三个虚拟机上部署Redis Stack Server。
我們按照以下文件進行參考。
本文将Azure虚拟机创建步骤略过。
我们已经验证了Ubuntu 20.04操作系统。
由于使用Docker构建速度较快,所以我们将按照官方的指引继续进行。
首先, 安装 Docker.
sudo apt-get update
sudo apt-get install apt-transport-https ca-certificates curl software-properties-common
#Dockerの公式GPGキーを追加
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
#Dockerリポジトリを追加。
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
#Dockerリポジトリを更新して、Dockerパッケージをインストール。
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io
#確認
sudo docker --version
Docker version 23.0.5, build bc4487a
# redis-cliを使うためにインストールしておく
sudo apt install redis-tools
使用Docker启动RedisStack服务器。
有关启动选项,请参考官方文档。
sudo docker run -v ~/local-data/:/data -d --name redis-stack-server -p 6379:6379 redis/redis-stack-server:latest
连接到Redis并进行确认检查
redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379>
127.0.0.1:6379> FT._LIST
(empty list or set)
由于没有建立索引,所以目前是空无一物的状态。
尝试进行4向量搜索
为了让本地连接到虚拟机上的Redis,需要先打开端口。

#hostは仮想マシンのパブリックIP
redis_conn = redis.StrictRedis(host="**.***.***.***",port=6379)
# インデックスを作成する。
schema = ([
TextField("itemid"),
VectorField("Embedding", "HNSW", {"TYPE": "FLOAT32", "DIM": embedding_dimension, "DISTANCE_METRIC": "COSINE" }),
TextField("title"),
TextField("cost"),
TextField("Combined"),
])
redis_conn.ft().create_index(schema)
当检查虚拟机上的Redis时,可以发现已经创建了索引。
127.0.0.1:6379> FT._LIST
1) "idx"
我将数据添加到Redis中
# Vm上のRedisにdfのデータを追加
def load_vectors(client,df):
p = client.pipeline(transaction=False)
for i in df.index:
key = f"doc:{str(i)}"
data = {
"itemid": str(i),
"Embedding": np.array(df["Embedding"][i]).astype(np.float32).tobytes(),
"title": df["title"][i],
"cost": df["cost"][i],
"Combined": df["Combined"][i],
}
# データを追加する
client.hset(key, mapping=data)
p.execute()
load_vectors(redis_conn, df)
#データが追加されたか確認する
print("Index size: ", redis_conn.ft().info()['num_docs'])
Index size: 119
似乎已经正确添加了。
就算这样,还是在Redis那边确认一下
127.0.0.1:6379> KEYS *
1) "doc:97"
2) "doc:115"
3) "doc:18"
4) "doc:31"
5) "doc:103"
6) "doc:19"
127.0.0.1:6379> FT.INFO idx
1) index_name
2) idx
3) index_options
...
9) num_docs
10) "119"
由于文档数量为119,因此也已在索引中注册。好的,没问题。
# クエリにマッチするメニューを検索して返す関数
def search_reviews_redis(query, n=3, pprint=True, engine=embedding_model_for_query):
q_vec = np.array(get_embedding(query, engine=engine)).astype(np.float32).tobytes()
q = Query(f"*=>[KNN {n} @Embedding $vec_param AS vector_score]").sort_by("vector_score").paging(0,n).return_fields("vector_score", "Combined").return_fields("vector_score").dialect(2)
params_dict = {"vec_param": q_vec}
ret_redis = redis_conn.ft().search(q, query_params = params_dict)
columns = ["Similarity", "Ret_Combined"]
ret_df = pd.DataFrame(columns=columns)
for doc in ret_redis.docs:
# コサイン距離をコサイン類似度に変換
sim = 1 - float(doc.vector_score)
com = doc.Combined[:200].replace("title: ", "").replace("; cost:", ": ")
append_df = pd.DataFrame(data=[[sim, com]], columns=columns)
ret_df = pd.concat([ret_df, append_df], ignore_index=True, axis=0)
if pprint:
print("%s | %s\n" % (sim, com))
return ret_df
我将尝试进行搜索。
results_redis = search_reviews_redis("くたびれたサラリーマンが好むお酒")
结果
0.01746213436099997 | title: 雁木 純米 無濾過生原酒(山口); Cost: 1,080円
0.005576133728000032 | title: 純米超辛口 船中八策(高知); Cost: 1,280円
0.003577470778999947 | title: 酔鯨 吟麗 純米吟醸(高知); Cost: 1,280円
你觉得怎样?有人可以告诉我酒方面很懂的人吗??
顺便说一下,假设你在查询中要包含搜索词,情况就会是这样。
q = Query(f"@title:(*{query}*)=>[KNN {n} @Embedding $vec_param AS vector_score]").sort_by("vector_score").paging(0,n).return_fields("vector_score", "Combined").return_fields("vector_score").dialect(2)
由于公式中记载了各种查询的写法,我打算尝试一下各种方式。
最后
还有很多问题,或者说,这个问题不能解决吗?这个问题不能解决吗?还有一些类似于“奇怪啊”的地方,但总的来说,我知道它可以在虚拟机的Redis上运行!
我认为根据具体情况,有时候可能无法使用SaaS服务,所以在那种情况下这个会非常方便。
嗯,确实,如果能够在Azure认知搜索中进行向量搜索,那就可以解决一切问题了?
不久之后,我们也来试试SaaS服务的PineCone吧。
我参考了这篇文章。