亚马逊AWS官方博客
使用Amazon SageMaker与Amazon ES KNN特征构建支持NLU的搜索应用程序
原文链接:
语义搜索引擎的兴起使电子商务与零售企业能够更轻松地为消费者提供搜索服务。基于自然语言理解(NLU)的搜索引擎使您可以通过首选会话语言直接表述自己的需求,而不再只能硬性依赖于以输入设备写下相应关键字。您可以使用母语以单词或句子执行查询,并由搜索引擎负责理解并提供最佳结果。
Amazon SageMaker 是一项完全托管服务,可为每位开发人员及数据科学家提供快速构建、训练以及部署机器学习(ML)模型的能力。Amazon Elasticsearch Service (Amazon ES)同样是一项完全托管服务,可帮助您轻松以极具成本效益的方式大规模部署、保护并运行Elasticsearch。Amazon ES提花KNN搜索功能,可以在多种用例(例如产品推荐、欺诈检测以及图像、视频等)以及某些特定的语义场景下(例如文档与查询相似性等)当中实现搜索增强。另外,您也可以选择使用Amazon Kendra,这是一种高精度且易于使用的企业搜索服务,由机器学习技术提供支持,且无需用户具备任何机器学习经验。在本文中,我们将介绍如何使用Amazon SageMaker与Amazon ES k最近邻(KNN)在某些类型的应用程序中实现基于NLU的产品搜索功能。
在《使用Amazon SageMaker与Amazon ES构建视觉搜索应用程序》一文中,我们已经探讨了如何使用Amazon SageMaker与Amazon ES KNN的欧氏几何距离指标构建视觉搜索应用程序。Amazon ES现可支持开源Elasticsearch 7.7版本,且包含KNN索引的余弦相似性指标。余弦相似度衡量的是两个向量在同一方向上夹角的余弦,余弦角越小、表示向量之间的相似度越高。通过余弦相似度,您可以测量两上向量之间的方向,并据此在某些特定语义搜索应用程序中做出理想选择。Amazon ES的高度分布式架构使您能够实现增强的KNN排名、高召回率与高性能的企业级搜索引擎。
在本文中,我们将构建一款简单的搜索应用程序,借此展示与传统Amazon ES排名方法相比,将KNN与Amazon ES结合使用所将发展的巨大潜力。文中还将提供一款在浏览器中测试基于KNN的搜索查询结果的Web应用程序。此应用程序还能够将搜索结果与Elasticsearch匹配查询进行比较,以证明KNN搜索与全文搜索之间的区别。
解决方案概述
在使用文本进行搜索时,常规的Elasticsearch文本匹配搜索能够带来很好的效果;但相比之下,基于KNN的搜索往往更加自然。例如,当您使用基于KNN的搜索应用程序搜索婚纱时,如果您键入“婚礼礼服”或者“结婚礼服”,系统就会给出相关结果。但要构建起基于KNN的搜索应用程序,我们需要完成两个阶段:
- KNN参考索引 – 在此阶段,我们需要通过深度学习模型传递一组语料库文档,从中提取特征或嵌入。文本嵌入是语料库的数字表示。您可以将这些特征保存在Amazon ES上的KNN索引当中。实现KNN的理论基础在于,相似的数据点将在向量空间中彼此相邻。例如,“夏装”与“夏季碎花裙”由于存在文本嵌入并转而彼此相似;相比之下,“夏装”与“婚纱”则互不相似。
- KNN索引查询 – 应用程序的推理阶段。在此阶段,我们需要通过深度学习模型提交文本搜索查询,借此提取特征。接下来,使用这些鞂查询参考KNN索引。KNN索引会从KNN微量空间返回相似的文本嵌入。例如,如果传递“结婚礼服”文本的特征向量,则其会将“婚礼礼服”嵌入作为类似项返回。
接下来,让我们深入探索各个阶段及其相关的AWS架构。
创建KNN参考索引
在本用例中,您可以使用Feidegger数据集中的着装图像及其视频描述。此数据集是一套多模语料库,专门关注时尚项目并配合德语版本的视觉描述。此数据集属于Zalando当前时尚领域文本图像多模研究的一部分。
在此步骤中,我们将使用 Amazon Translate将每种服饰的描述从德语翻译为英语。接下来,我们从每条英语描述中提取特征微量,其代表着衣物的数字特征的n维向量。您可以使用Amazon SageMaker中托管的预训练BERT模型。这里我们提取出服饰的每项视觉描述中的768个特征向量,并将其作为KNN索引存储在Amazon ES域当中。
以下截屏所示,为创建KNN索引的工作流程。
整个流程包含以下步骤:
- 与Amazon SageMaker notebook实例上的Jupyter notebook进行交互的多个用户。每个Amazon SageMaker notebook实例代表运行在Jupyter Notebook应用上的机器学习计算实例。Amazon SageMaker负责创建实例以及与之相关的资源。
- 各个条目描述的源语言为德语,需要使用Amazon Translate将其转换为英语。
- 下载预训练的BERT模型,并将模型工件序列化并存储在Amazon Simple Storage Service (Amazon S3)当中。此模型用于从Amazon SageMaker实时端点上的PyTorch模型服务器处提供服务。
- 翻译后的描述会通过SageMaker端点进行推送,借此提取固定长度的特征(嵌入)。
- Notebook代码将文本嵌入以及Amazon ES域内的产品Amazon S3 URI写入至KNN索引。
通过查询文本进行KNN搜索
在此步骤中,我们需要表达一条来自应用程序的搜索查询文本字符串,由其通过Amazon SageMaker托管并传递的模型提取768项特征。您可以使用这些特征来查询Amazon ES中的KNN索引。Amazon ES的KNN允许您在向量空间搜索各个点,并通过其余弦相似度(默认值为欧氏几何距离)找到这些点的最近邻。当找到特定查询文本的最近邻向量(例如k = 3最近邻)时,它会将关联的Amazon S3图像返回至应用程序。下图所示,为KNN搜索全栈应用程序的基本架构。
该流程包含以下步骤:
- 最终用户通过其浏览器或者移动设备访问Web应用程序。
- 用户提供的搜索查询字符串将被发送至Amazon API Gateway以及AWS Lambda。
- Lambda函数调用Amazon SageMaker实时端点,且该模型返回搜索查询嵌入的向量。Amazon SageMaker托管并提供HTTPS端点以供预测,并使用Application Auto Scaling.以提供应用程序所需要的性能资源。
- 该函数将搜索查询嵌入向量作为Amazon ES域中索引KNN搜索的搜索值并进行传递,返回的则为包含k个相似项的列表以及各项所对应的Amazon S3 URI。
- 此函数生成经过预签名的Amazon S3 URL并将其返回至客户端Web应用程序,此URL用于在浏览器中显示相似项。
先决条件
在本演练中,我们需要一个拥有适当AWS身份与访问管理(IAM)权限的AWS账户,用以启动AWS CloudFormation模板。
部署解决方案
这里,我们使用CloudFormation栈以部署解决方案。此栈将创建所有必要资源,具体包括:
- 一个Amazon SageMaker notebook实例,用于在Jupyter notebook中运行Python代码。
- 与notebook实例相关联的IAM角色。
- 一个Amazon ES域,用于将句子嵌入向量存储在KNN索引内以供检索。
- 两个S3存储桶:一个用于存储源服饰图像,另一个用于托管静态网站。
通过Jupyter notebook,我们还可以部署以下资源:
- 一个Amazon SageMaker端点,用于实时获取定长句子的嵌入向量。
- 一个AWS无服务器应用程序模型(AWS SAM)模板,配合API Gateway与Lambda建立无服务器后端。
- 在S3存储桶上托管的静态前端网站,用于演示现实场景下的端到端机器学习应用程序。前端代码使用ReactJS与AWS Amplify JavaScript库。
首先,我们需要完成以下操作步骤:
- 使用您的IAM用户名与密码登录至AWS管理控制台。
- 选择Launch Stack并在新选项卡中打开:
- 在Quick create stack页面上,选中复选框以确认创建IAM资源。
- 选择 Create stack。
- 等待栈完成打嘴仗。
您可以在Events选项卡上的栈创建过程中检查各类事件。栈创建完成之后,您将看到状态转换为CREATE_COMPLETE。
您可以在Resources选项卡上,查看CloudFormation模板所创建的所有资源。
- 在Outputs选项卡上, 选择SageMakerNotebookURL。
此超链接将在您用于完成演练其余步骤的Amazon SageMaker notebook实例上打开Jupyter notebook。
现在您应该已经转向Jupyter notebook的登录页面。
- 选择 nlu-based-item-search.ipynb。
在Amazon ES上构建KNN索引
在此步骤中,我们应该在notebook的开头使用NLU based Item Search标题。请遵循notebook中的步骤并按顺序运行各个单元。
您可以使用sentence-transformers句子转换器中的预训练BERT模型(distilbert-base-nli-stsb-mean-tokens),并将其托管在Amazon SageMaker PyTorch模型服务器端点之上,借此生成定长的句子嵌入。嵌入内容将被保存在CloudFormation栈所创建的Amazon ES域内。关于更多详细信息,请参阅notebook中的各markdown单元。
按notebook一步操作,直至Deploying a full-stack NLU search application单元。
此notebook中包含多个重要的单元,我们将引导您完成其中所对应的操作。
首先从Feidegger处下载多模语料库数据集,其中包含时尚图片与德语描述。具体参见以下代码:
## 数据准备
import os
import shutil
import json
import tqdm
import urllib.request
from tqdm import notebook
from multiprocessing import cpu_count
from tqdm.contrib.concurrent import process_map
images_path = 'data/feidegger/fashion'
filename = 'metadata.json'
my_bucket = s3_resource.Bucket(bucket)
if not os.path.isdir(images_path):
os.makedirs(images_path)
def download_metadata(url):
if not os.path.exists(filename):
urllib.request.urlretrieve(url, filename)
#将metadata.json下载至本地notebook
download_metadata('https://raw.githubusercontent.com/zalandoresearch/feidegger/master/data/FEIDEGGER_release_1.1.json')
def generate_image_list(filename):
metadata = open(filename,'r')
data = json.load(metadata)
url_lst = []
for i in range(len(data)):
url_lst.append(data[i]['url'])
return url_lst
def download_image(url):
urllib.request.urlretrieve(url, images_path + '/' + url.split("/")[-1])
#生成图片列表
url_lst = generate_image_list(filename)
workers = 2 * cpu_count()
#将图片下载至本地磁盘
process_map(download_image, url_lst, max_workers=workers)
Upload the dataset to Amazon S3:
#将数据集上传至S3
files_to_upload = []
dirName = 'data'
for path, subdirs, files in os.walk('./' + dirName):
path = path.replace("\\","/")
directory_name = path.replace('./',"")
for file in files:
files_to_upload.append({
"filename": os.path.join(path, file),
"key": directory_name+'/'+file
})
def upload_to_s3(file):
my_bucket.upload_file(file['filename'], file['key'])
#将图片上传至s3
process_map(upload_to_s3, files_to_upload, max_workers=workers)
此数据集中包含德语产品描述,因此我们需要使用Amazon Translate将各德语句子翻译为英语:
with open(filename) as json_file:
data = json.load(json_file)
#定义翻译器函数
def translate_txt(data):
results = {}
results['filename'] = f's3://{bucket}/data/feidegger/fashion/' + data['url'].split("/")[-1]
results['descriptions'] = []
translate = boto3.client(service_name='translate', use_ssl=True)
for i in data['descriptions']:
result = translate.translate_text(Text=str(i),
SourceLanguageCode="de", TargetLanguageCode="en")
results['descriptions'].append(result['TranslatedText'])
return results
将句子转换器模型保存至notebook实例:
!pip install sentence-transformers
#将模型保存至由sagemaker托管的磁盘当中
from sentence_transformers import models, SentenceTransformer
saved_model_dir = 'transformer'
if not os.path.isdir(saved_model_dir):
os.makedirs(saved_model_dir)
model = SentenceTransformer('distilbert-base-nli-stsb-mean-tokens')
model.save(saved_model_dir)
使用以下代码,将模型工件(model.tar.gz)上传至Amazon S3:
#将模型压缩为.gz格式
import tarfile
export_dir = 'transformer'
with tarfile.open('model.tar.gz', mode='w:gz') as archive:
archive.add(export_dir, recursive=True)
#将模型上传至S3
inputs = sagemaker_session.upload_data(path='model.tar.gz', key_prefix='model')
inputs
使用Amazon SageMaker Python SDK将模型部署至Amazon SageMaker PyTorch模型服务器当中。具体参见以下代码:
from sagemaker.pytorch import PyTorch, PyTorchModel
from sagemaker.predictor import RealTimePredictor
from sagemaker import get_execution_role
class StringPredictor(RealTimePredictor):
def __init__(self, endpoint_name, sagemaker_session):
super(StringPredictor, self).__init__(endpoint_name, sagemaker_session, content_type='text/plain')
pytorch_model = PyTorchModel(model_data = inputs,
role=role,
entry_point ='inference.py',
source_dir = './code',
framework_version = '1.3.1',
predictor_cls=StringPredictor)
predictor = pytorch_model.deploy(instance_type='ml.m5.large', initial_instance_count=3)
使用以下代码定义余弦相似度Amazon ES KNN索引映射(要定义余弦相似度KNN索引映射,您需要使用Amazon ES 7.7或者更高版本):
#KNN索引映射
knn_index = {
"settings": {
"index.knn": True,
"index.knn.space_type": "cosinesimil",
"analysis": {
"analyzer": {
"default": {
"type": "standard",
"stopwords": "_english_"
}
}
}
},
"mappings": {
"properties": {
"zalando_nlu_vector": {
"type": "knn_vector",
"dimension": 768
}
}
}
}
每种产品都对应5条视觉描述,我们需要将5条描述结合起来以获得一个固定长度的句子嵌入。具体请参见以下代码:
def concat_desc(results):
obj = {
'filename': results['filename'],
}
obj['descriptions'] = ' '.join(results['descriptions'])
return obj
concat_results = map(concat_desc, results)
concat_results = list(concat_results)
concat_results[0]
使用以下代码,将句子嵌入与关联的Amazon S3图像URI导入至Amazon ES KNN索引当中。您还可以全文翻译描述内容,以便之后在Elasticsearch当中比较KNN搜索与标准匹配文本查询之间的差异。
# 定义一项函数,将对应于各S3 URI的特征向量导入至Elasticsearch KNN索引
# 此过程大约需要10分钟。
def es_import(concat_result):
vector = json.loads(predictor.predict(concat_result['descriptions']))
es.index(index='idx_zalando',
body={"zalando_nlu_vector": vector,
"image": concat_result['filename'],
"description": concat_result['descriptions']}
)
workers = 8 * cpu_count()
process_map(es_import, concat_results, max_workers=workers)
构建一款全栈KNN搜索应用程序
在拥有一个能够正常工作的Amazon Sagemaker端点之后,我们可以在Amazon ES上提取文本特征与KNN索引,接下来即可构建一款真实的全栈机器学习驱动型Web应用程序。这里我们使用AWS SAM模板通过API Gateway与Lambda部署无服务器REST API。REST API接受新的搜索字符串、生成嵌入,并将相似的相关项返回至客户端。接下来,您可以将与REST API交互的前端网站上传至Amazon S3。前端代码使用Amplify与您的REST API相集成。
- 在以下单元中,预填充一个CloudFormation模板,此模板将为全栈应用程序创建必要的资源,例如Lambda与API Gateway:
s3_resource.Object(bucket, 'backend/template.yaml').upload_file('./backend/template.yaml', ExtraArgs={'ACL':'public-read'})
sam_template_url = f'https://{bucket}.s3.amazonaws.com/backend/template.yaml'
# 生成CloudFormation快速创建链接
print("Click the URL below to create the backend API for NLU search:\n")
print((
'https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/create/review'
f'?templateURL={sam_template_url}'
'&stackName=nlu-search-api'
f'¶m_BucketName={outputs["s3BucketTraining"]}'
f'¶m_DomainName={outputs["esDomainName"]}'
f'¶m_ElasticSearchURL={outputs["esHostName"]}'
f'¶m_SagemakerEndpoint={predictor.endpoint}'
))
以下截屏所示为输出结果:预生成的CloudFormation模板链接。
- 选择此链接。
您将跳转至Quick create stack页面。
- 选中复选框以确认IAM资源,具有自定义名称的IAM资源并创建CAPABILITY_AUTO_EXPAND。
- 选择 Create stack。
栈创建完成之后,您将看到状态转换为CREATE_COMPLETE。您可以在Resources选项卡上查看CloudFormation模板创建的所有资源。
- 创建栈后,继续遍历各单元。
以下单元表示您的全栈应用程序(包括前端与后端代码)已成功部署:
print('Click the URL below:\n')
print(outputs['S3BucketSecureURL'] + '/index.html')
以下截屏所示为URL输出。
- 选择此链接。
您将跳转至应用程序页面,可以在这里提供您自己的搜索文本,以使用KNN方法及常规的全文本搜索方法查找产品。
- 完成KNN搜索应用程序的测试与实验之后,请运行notebook末尾的最后两个单元:
# 删除端点
predictor.delete_endpoint()
# 清空S3内容
training_bucket_resource = s3_resource.Bucket(bucket)
training_bucket_resource.objects.all().delete()
hosting_bucket_resource = s3_resource.Bucket(outputs['s3BucketHostingBucketName'])
hosting_bucket_resource.objects.all().delete()
这些单元将终止您的Amazon SageMaker端点并清空S3存储桶,为资源清理做好准备。
资源清理
要删除其余AWS资源,请前往AWS CloudFormation控制台并删除nlu-search-api与nlu-search栈。
总结
在本文中,我们共同了解了如何使用Amazon SageMaker与Amazon ES KNN索引功能创建基于KNN的搜索应用程序。我们使用了句子转换器Python库内的预训练BERT模型。您也可以使用自己的数据集对BERT模型进行调优。关于更多详细信息,请参阅使用Amazon Elastic Inference在Amazon SageMaker上微调并部署PyTOrch BERT模型。
本文建议您使用GPU实例处理大多数深度学习项目。在大部分情况下,在GPU实例上训练新模型的速度要比CPU实例快。如果使用多个GPU实例,或者在多个GPU实例之间使用分布式训练,则可实现亚线性资源扩展。但是,我们在此用例中使用了CPU实例,因此您可以在AWS Free Tier下完成演练。
关于本文中代码示例的更多信息,请参阅GitHub repo。