尝试使用 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。

image.png
image.png

生成嵌入到数据集中的嵌入

首先,我们将进行代码的分割并进行编写。
首先,导入我们将要使用的库。

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,需要先打开端口。

由于验证目的,一旦试验结束,将删除整个虚拟机。
image.png
#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吧。

使用完毕的Azure资源不要忘记删除!

我参考了这篇文章。

 

广告
将在 10 秒后关闭
bannerAds