考虑将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 认知搜索。
例如,可以将长篇文档分成一段一段的块,进行摘要,并将摘要结果输出到搜索结果中。通过提示工程化,还可以以各种形式输出摘要。另外,还有其他的方法,
-
- 文章分類
-
- キーフレーズ抽出
-
- 感情分析
-
- 固有名詞抽出
- 翻訳
虽然也能完成一些任务,但是与原有的文本分析技能冲突了呢…这一点需要根据用户的数据、所需精确度和预算来进行选择。请务必进行验证。
而且,利用Azure OpenAI可以自行生成搜索排名。今天我打算主要讲这个方面。
嵌入式API
在Azure OpenAI中,可以轻松生成文本嵌入(Embeddings)。通过使用这个向量,可以计算文本之间的相似性。相似性计算使用余弦相似度。
如果您使用此教程,您可以在实验室环境中进行文档搜索。
请提供一个完整的句子或上下文来帮助更好地理解需要翻译的内容。
让我们来看看Azure Cognitive Search和由Azure OpenAI创建的排行榜之间的区别。
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服务器来进行验证。
步骤(首先进行验证)
-
- 创建 Azure OpenAI 帐户(截至2023/02,目前采用申请制),也可以在 OpenAI 官方网站上申请。
-
- 准备 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 并对向量搜索感兴趣的话,请积极参与运动并帮助我们实现向量搜索。
请提供更多上下文或具体的句子以便我进行翻译。