考虑将Azure OpenAI和Azure Cognitive Search结合使用

首先

在巷子里,OpenAI非常受欢迎。OpenAI在Azure上提供的Azure OpenAI Service是一个托管GPT-3模型的服务,通过其GA正式提供给企业,使他们能够在企业应用中使用GPT-3模型。这样一来,企业可以享受到与Azure Cognitive Services相似的安全功能、服务水平协议(SLA)、合规性和负责任的人工智能等优势。

我想试试将我喜欢的Azure Cognitive Search和Azure OpenAI组合在一起,看看能做些什么。

?5/24终于实现了向量搜索功能!

 

组合方法

通过使用 Azure 自定义技能,您可以集成 Azure OpenAI 的各种功能,主要使用 Azure 认知搜索。

005.png

例如,可以将长篇文档分成一段一段的块,进行摘要,并将摘要结果输出到搜索结果中。通过提示工程化,还可以以各种形式输出摘要。另外,还有其他的方法,

    • 文章分類

 

    • キーフレーズ抽出

 

    • 感情分析

 

    • 固有名詞抽出

 

    翻訳

虽然也能完成一些任务,但是与原有的文本分析技能冲突了呢…这一点需要根据用户的数据、所需精确度和预算来进行选择。请务必进行验证。

而且,利用Azure OpenAI可以自行生成搜索排名。今天我打算主要讲这个方面。

嵌入式API

在Azure OpenAI中,可以轻松生成文本嵌入(Embeddings)。通过使用这个向量,可以计算文本之间的相似性。相似性计算使用余弦相似度。

 

如果您使用此教程,您可以在实验室环境中进行文档搜索。

请提供一个完整的句子或上下文来帮助更好地理解需要翻译的内容。

让我们来看看Azure Cognitive Search和由Azure OpenAI创建的排行榜之间的区别。

001.png

Azure Cognitive Search是基于Apache Lucene开发的全文搜索引擎,其评分使用了基于频率的BM25Similarity的TF-IDF类型的排名器。要使其匹配,搜索关键词必须包含在文档中。作为下一个排名器,Microsoft Research开发的基于Turing的大规模语言模型(LLM)”语义搜索(预览版)”已实现为一个重新排名器。重新排序器的目的是仅对匹配的关键词的前50个结果进行重新排序。由于此结构,用户需要使用文档中包含的关键词进行搜索。无法命中与搜索关键词不相关但接近用户意图的词汇。

嗯,這個實現方式在使用關鍵詞匹配的同時,大量使用GPU進行LLM的搜索資源減少,同時進一步提高現有搜索結果的準確度,這種精妙的平衡讓我深思。未來,我想嘗試將關鍵詞匹配和語義搜索結合得更好。

嗯,這次正好 Azure OpenAI 的 Embeddings API 可以使用,我打算首先在 Azure 上嘗試使用 GPT-3 進行向量檢索。但是,截至 2023 年 02 月,Azure Cognitive Search 尚未實現向量檢索功能? 我最近聽說 Apache Lucene 已經實現了向量的近似最近鄰搜索(ANN Search),但在 Azure Cognitive Search 上實現這一功能可能還需要更多時間…

在Azure上进行向量搜索的一种方法是使用Azure Cache for Redis。在Azure上使用的完全托管的内存数据库,非常好!然而,要进行向量搜索,需要使用RediSearch模块,这个模块只能在企业计划中使用。企业计划的价格很高。对于个人来说,可以通过建立VM并构建Redis Stack服务器来进行验证。

步骤(首先进行验证)

    1. 创建 Azure OpenAI 帐户(截至2023/02,目前采用申请制),也可以在 OpenAI 官方网站上申请。

 

    1. 准备 Python 环境,并安装所需的库。

无论是在本家的 OpenAI 还是 Azure 上,openai 库都是通用的。

transformers 库用于计算令牌。
只要按照本教程的步骤进行,即可生成嵌入。

创建 Azure Cache for Redis Enterprise(勾选 RediSearch 模块)。
准备要搜索的数据集并生成嵌入。
我们将使用之前在推荐系统研讨会上使用的 MovieLens 数据集(4803 部电影)。
构建 Redis 索引。
进行向量搜索。

准备搜索数据集和生成嵌入。

根据这个教程的参考,我将数据框(Dataframe)的列进行向量化,本次选择合并了标题(title)、关键词(keywords)、类型(genres)和演员(cast)这些列,并创建了一个名为combined_features的新列。

嵌入操作使用get_embedding。

%%time
df['davinci_search'] = df["combined_features"].apply(lambda x : get_embedding(x, engine = 'text-search-davinci-doc-001'))

CPU时间:用户19秒,系统2.61秒,总共21.7秒
墙上时间:22分钟33秒

由于使用 text-search-davinci-doc-001 生成嵌入式向量,速度比较慢。
为了避免每次都执行 get_embedding,我们会将DataFrame保存为Parquet或其他格式。

2. 构建Redis向量索引

2.1. 连接到Redis

打开Azure Portal,并找到Redis Enterprise资源,获取Endpoint和Access Key。

from redis import Redis
from redis.commands.search.field import VectorField, TagField, NumericField, TextField
from redis.commands.search.query import Query
from redis.commands.search.result import Result

redis_conn = redis.StrictRedis(host='<Your Endpoint>',
    port=10000, db=0, password='<Your Key>', ssl=True)

redis_conn.ping()

2.2. 创建向量索引

RediSearch 支持以下两种索引方法。

    • FLAT: ブルートフォース インデックス

 

    HNSW: Hierarchical Navigable Small World(HNSW) を使用した効率的で堅牢な近似最近傍検索の実装です。

这份资料对算法的解释和选择非常出色。针对 N=100万左右的情况,建议使用FLAT。而作为DISTANCE_METRIC,可以选择L2、IP、COSINE。在这种情况下,我们将使用COSINE。

def create_flat_index (redis_conn,vector_field_name,number_of_vectors, vector_dimensions=12288, distance_metric='COSINE'):
    redis_conn.ft().create_index([
        VectorField(vector_field_name, "FLAT", {"TYPE": "FLOAT32", "DIM": vector_dimensions, "DISTANCE_METRIC": distance_metric, "INITIAL_CAP": number_of_vectors, "BLOCK_SIZE":number_of_vectors }),
        TagField("itemid"),
        TextField("combined_features"),
        TextField("title")
    ])
    
def create_hnsw_index (redis_conn,vector_field_name, number_of_vectors, vector_dimensions=12288, distance_metric='COSINE',M=40,EF=200):
    redis_conn.ft().create_index([
        VectorField(vector_field_name, "HNSW", {"TYPE": "FLOAT32", "DIM": vector_dimensions, "DISTANCE_METRIC": distance_metric, "INITIAL_CAP": number_of_vectors, "M": M, "EF_CONSTRUCTION": EF}),
        TagField("itemid"),
        TextField("combined_features"),
        TextField("title")
    ])    


ITEM_KEYWORD_EMBEDDING_FIELD='davinci_search'
#Davinci is 12288 dims
TEXT_EMBEDDING_DIMENSION=12288
NUMBER_PRODUCTS=4803

# create flat index & load vectors
create_flat_index(redis_conn, ITEM_KEYWORD_EMBEDDING_FIELD, NUMBER_PRODUCTS, TEXT_EMBEDDING_DIMENSION, 'COSINE')

在Redis的文档中提到了向量类型支持FLOAT64,但实际指定时会出现错误并无法创建。因此,我更改为FLOAT32。

将向量存储为哈希。

为了分析搜索结果的内容,我们还注册了combined_features列。在注册时,向量需要转换为字节列。

%%time
def load_vectors(client:Redis, df, vector_field_name):
    p = client.pipeline(transaction=False)
    
    for i in df.index:
        key= str(i)
        item_keywords_vector = df[vector_field_name][i].astype(np.float32).tobytes()
        item_metadata = {'itemid': str(df['id'][i]), 'title': df['title'][i], 'combined_features': df['combined_features'][i], vector_field_name: item_keywords_vector}
        
        client.hset(key, mapping=item_metadata)

    p.execute()

load_vectors(redis_conn, moviedf, ITEM_KEYWORD_EMBEDDING_FIELD)

墙上时间:13分钟44秒

2.4. 向量搜索(ANN搜索)

要获取搜索关键词的嵌入式数据,请在 `get_embedding` 中指定查询模型 `text-search-davinci-query-001` 。由于 Redis 的 kNN 查询不直观,请参考示例。

我会从一些著名电影的情节摘录出搜索查询,以帮助它们取得成功。

def search_docs(user_query, topK):
    embedding = get_embedding(user_query, engine="text-search-davinci-query-001")
    query_byte_array = np.array(embedding).astype(np.float32).tobytes()

    q = Query(f'*=>[KNN {topK} @{ITEM_KEYWORD_EMBEDDING_FIELD} $vec AS vector_score]').return_fields("vector_score", "title").sort_by('vector_score').paging(0,topK).dialect(2)
    results = redis_conn.ft().search(q, query_params={"vec":  query_byte_array})

    for item in results.docs:
        print (item.id, item.vector_score, item.title)

search_docs("AI and humanity will confront each other", 5)

《黑客帝国》(634,0.734805703163)
《她》(1997,0.738143444061)
《我,机器人》(266,0.738558232784)
《终结者救赎》(43,0.739533424377)
《机械神》(2654,0.741096973419)

「AI」と「人類」という語彙は、combined_featuresには含まれていないが、ランキングはうまく生成されています!

※ Redis 的 COSINE 是指余弦距离,其实际上是减去余弦相似度的值。换句话说,越接近0表示向量越相似。

search_docs("Ex-military father lands on island alone to rescue daughter", 3)

2996 0.688981413841 指挥官
1331 0.714794993401 尼姆的岛
2953 0.715987503529 返回碧蓝泻湖

真是有趣的结果,我很喜欢。因为“女儿”、“父亲”和“拯救”都包含在combined_features中,所以当然会这样。

search_docs("元軍人の父が娘を救出するため、単身で島に上陸する", 3)

2996 0.724949836731 《特种兵》
2521 0.742291748524 《海啸》
1404 0.760077595711 《街头霸王》

这句话在日语中也是轻松搞定的。不是花招,日语并不包含在数据集中。

search_docs("Wake up, Neo", 5)

634 0.718291580677 母体
123 0.729486823082 母体革命
125 0.731914699078 母体重载
1153 0.756486654282 露西
3207 0.757386445999 觉醒

“Neo”不在于combined_features中。用日语说”起きろ、ネオ”不可以。

search_docs("Prof. Langdon investigates the mystery of Louvre.", 5)

201 《达·芬奇密码》
3082 《狮身人面像》
1449 《圣殿骑士团》
128 《天使与魔鬼》
291 《国家宝藏》

“Langdon”和”Louvre”都不包含在combined_features中。

search_docs("ラングドン教授がルーブル美術館の謎を探る", 5)

291 0.775311648846 国家宝藏
201 0.776099085808 达·芬奇密码
483 0.778588891029 时空旅人
3615 0.779091000557 荒野入侵
2389 0.779899418354 文艺复兴

2.5. 执行时间 (zhí shí

我們將更精細地測量時間。由於 OpenAI 資源部署在美國南部中部,而 Redis 部署在美國東部,從東日本訪問時,地理上的延遲會被加上。

嵌入式生成

%%timeit
user_query = "AI and humanity will confront each other"
embedding = get_embedding(user_query, engine="text-search-davinci-query-001")

每次循环用时259毫秒,标准差为40.3毫秒(平均值±标准差,共7次运行,每次循环1次)。

执行查询

%%timeit
topK=5
query_byte_array = np.array(embedding).astype(np.float32).tobytes()

q = Query(f'*=>[KNN {topK} @{ITEM_KEYWORD_EMBEDDING_FIELD} $vec AS vector_score]').return_fields("vector_score", "title").sort_by('vector_score').paging(0,topK).dialect(2)
results = redis_conn.ft().search(q, query_params={"vec":  query_byte_array})

for item in results.docs:
    print (item.id, item.vector_score, item.title)

每个循环的时间为182毫秒±703微秒(平均值±7次运行,每次10个循环的标准差)。

生成埋没点需要花费很长时间啊。GPT-3 模型大致分为四种类型,它们在精度和速度之间存在着一种权衡关系。为了在速度上牺牲精度,我们也可以使用其他模型。这次我们使用了精度最高的 Davinci 模型。

此外,对于大规模数据集,可以通过使用Spark等分布式处理方法来进一步加快速度。(在API限制内)

混合搜索

当然,有需求希望同时进行基于频率的搜索和向量搜索,但目前 Redis 无法进行日语全文搜索(可以使用过滤器)。唉,能够同时进行这两种搜索的搜索引擎,目前只有 Apache Lucene 和 ElasticSearch 吧?

最后

请允许我在这次中止于使用 Redis 进行向量搜索,但在接下来的活动中,我会继续考虑合作方法。如果您正在使用 Azure Cognitive Search 并对向量搜索感兴趣的话,请积极参与运动并帮助我们实现向量搜索。

请提供更多上下文或具体的句子以便我进行翻译。

 

广告
将在 10 秒后关闭
bannerAds