15

image

天天混迹互联网的你,肯定曾经用过,或者至少见过周围人使用下面这样的应用:

  • 逛街看到前面的路人穿的衣服挺漂亮,拍张照就能在电商应用中搜到同款
  • 路边小花开的正艳,拍张照搜索一下,学名、门纲目科属种,甚至拉丁名,各种信息一目了然
  • 前面那人貌似是个明星,可是叫啥忘了,拍照搜索,一般都能很快得到答案
    确实!一些情况下,我们可能很难找到合适的词汇来描述自己想要寻找的东西。而正如俗语所言:「一图抵千言」,一般来说,展示真实示例或者图像,对于目标的表达效果确实要比纯文字描述好上不少。这一点,在使用搜索引擎查找所需内容时显得尤其突出。

类似的能力在很多应用中都有,不过你是否想过自己该如何实现?本文我们将用大约一个小时的演练,从零开始构建一款视觉图像搜索应用程序,其中包含用于提供视觉搜索结果的全栈Web 应用程序。

视觉搜索能够提高客户在零售业务与电子商务中的参与度,这种功能对于时尚及家庭装饰零售商而言尤其重要。视觉搜索允许零售商向购物者推荐与主题或特定造型相关的商品,这是传统纯文本查询所无法做到的。根据Gartner的报告,「到2021年,重新设计网站以支持视觉及语音搜索的早期采用者品牌,将推动数字商务收入增长30%」。

视觉搜索的高级示例

Amazon SageMaker是一项全托管服务,可为每位开发人员及数据科学家提供快速构建、训练以及部署机器学习(ML)模型的能力。Amazon Elasticsearch Service(Amazon ES)同样为全托管服务,可帮助我们轻松以符合成本效益的方式大规模部署、保护并运行Elasticsearch。Amazon ES还提供k最近邻(KNN)搜索,能够在相似性用例中增强搜索能力,适用范围涵盖产品推荐、欺诈检测以及图像、视频与语义文档检索。KNN使用轻量化且效率极高的非度量空间库(NMSLIB)构建而成,可在数千个维度上对数十亿个文档进行大规模、低延迟最近邻搜索,且实现难度与常规Elasticsearch查询完全一致。

下图所示,为这套视觉搜索架构的基本形式。
image

解决方案概述

视觉搜索架构的实现分为两个阶段:

  • 通过示例图像数据集在Amazon ES上构建参考KNN索引。
  • 向Amazon SageMaker端点提交一份新图像,并由Amazon ES返回相似图像。

    创建KNN推理索引

    在此步骤中,托管在Amazon SageMaker当中、且经过预先训练的Resnet50模型将从每幅图像中提取2048个特征向量。每个向量都被存储在Amazon ES域的KNN索引当中。在本用例中,我们将使用来自FEIDEGGER的图像——FEIDEGGER是一套Zalando研究数据集,其中包含8732张高分辨率时尚图像。以下截屏所示,为KNN索引的创建工作流程。
    image

整个流程包含以下操作步骤:

  • 用户与Amazon SageMaker Noteoobk实例上的Jupyter notebook进行交互。
  • 从Keras处下载经过预先训练的Resnet50深层神经网络,删除最后一个分类器层,并对新的模型工件进行序列化,而后存储在Amazon Simple Storage Service(Amazon S3)当中。该模型负责在Amazon SageMaker实时端点上启动TensorFlow Serving API。
  • 将时尚图像推送至端点,端点通过神经网络运行图像以提取其中的特征(或称嵌入)。
  • Notebook代码将图像嵌入写入至Amazon ES域中的KNN索引。

通过查询图像进行视觉搜索

在此步骤中,我们提供来自应用程序的查询图像,该图像通过Amazon SageMaker托管模型进行传递,并在期间提取2048项特征。我们可以使用这些特征来查询Amazon ES中的KNN索引。Amazon ES的KNN将帮助我们在向量空间中搜索各点,并根据欧几里德距离或余弦相似度(默认值为欧几里德距离)找到这些点的「最近邻」。在找到给定图像的最近邻向量(例如k=3最近邻)时,它会将与之关联的Amazon S3图像返回至应用程序。下图所示,为视觉搜索全栈应用程序的基本架构。
image

整个流程包含以下操作步骤:

  • 终端用户通过自己的浏览器或移动设备访问Web应用程序。
  • 用户上传的图像以Base64编码字符串的形式被发送至Amazon API GatewayAmazon Lambda,并在Lambda函数中重新编码为字节形式。
  • 在该函数中,公开可读的图像URL以字符串形式传递,并可下载为字节形式。
  • 各字节作为载荷被发送至Amazon SageMaker实时端点,而后由模型返回图像嵌入的向量。
  • 该函数将搜索查询中的图像嵌入向量传递至Amazon ES域内索引中的k近邻,而后返回一份包含k相似图像及其对应Amazon S3 URI的列表。
  • 该函数生成经过预签名的Amazon S3 URL并返回至客户端Web应用程序,此URL用于在浏览器中显示相似图像。

    相关亚马逊云科技 (Amazon Web Services)服务

    要构建这样一款端到端应用程序,需要使用以下亚马逊云科技服务:
    *Amazon AmplifyAmazon Amplify是一套面向前端与移动开发人员的JavaScript库,用于构建云应用程序。关于更多详细信息,请参阅GitHub repo

  • Amazon API Gateway:一项全托管服务,用于以任意规模创建、发布、维护、监控以及保护API。
  • Amazon CloudFormationAmazon CloudFormation为开发人员及企业提供一种简便易行的方法,借此创建各亚马逊云科技与相关第三方资源的集合,并以有序且可预测的方式进行配置。
  • Amazon ES:一项托管服务,能够帮助用户以任意规模轻松部署、运营以及扩展Elasticsearch集群。
  • Amazon IAM:AWS身份与访问管理(Amazon Identity and Access Management,简称IAM)帮助用户安全地管理指向各AWS服务与资源的访问操作。
  • Amazon Lambda:一套事件驱动型、无服务器计算平台,可运行代码以响应事件,并自动管理代码所需的各项计算资源。
  • Amazon SageMaker:一套全托管端到端机器学习平台,用于以任意规模构建、训练、调优以及部署机器学习模型。
  • Amazon SAMAmazon Serverless Application Model(Amazon SAM)是一套开源框架,用于构建无服务器应用程序。
  • Amazon S3:一项对象存储服务,可提供具备极高持久性、可用性、成本极低且可无限扩展的数据存储基础设施。

先决条件

在本次演练中,我们需要准备一个具有适当IAM权限的亚马逊云科技账户,用于启动CloudFormation模板。

部署解决方案

在解决方案的部署方面,我们需要使用CloudFormation栈。请准备栈创建所涉及的一切必要资源,具体包括:

  • 一个Amazon SageMaker notebook实例,用于在Jupyter notebook中运行Python代码。
  • 一个与该Notebook实例相关联的IAM角色。
  • 一个Amazon ES域,用于将图像嵌入向量存储在KNN索引内并进行检索。
  • 两个S3存储桶:其一用于存储源时尚图像,其二用于托管静态网站。
    在Jupyter notebook 中,我们还需要部署以下几项:
  • Amazon SageMaker端点,用于实时获取图像特征向量与嵌入。
  • 一套Amazon SAM模板,用于通过API Gateway与Lambda建立一套无服务器后端。
  • 一个托管在S3存储桶上的静态前端网站,用于呈现端到端机器学习应用程序的使用界面。前端代码使用ReactJS与Amplify JavaScript库。
    首先,请完成以下操作步骤:
  • 使用IAM用户名与密码登录至Amazon管理控制台。
  • 选择Launch Stack并在新选项卡中将其打开:
    image
  • 在Quick create stack页面中,勾选复选框以确认创建IAM资源。
  • 选择Create stack。
    image
  • 等待栈执行完成。
    我们可以在Events选项卡中的栈创建进度中查看各种事件。栈创建完成之后,将看到状态转为CREATE_COMPLETE。
    在CloudFormation模板创建完成后,可以在Resources选项卡中看到所有资源。
  • 在Outputs选项卡中,选择SageMakerNotebookURL值。
    此超链接将在我们的Amazon SageMaker notebook实例上打开Jupyter notebook,用于完成选项卡中的其余部分。
    image
    这时,我们应该已经处于Jupyter notebook的登录页面中。
  • 选择Visual-image-search.ipynb。
    image

在Amazon ES上构建KNN索引

在此步骤中,我们应该在Notebook开头的Visual image search标题位置。遵循notebook中的步骤并按次序运行各单元。

这里,我们使用托管在Amazon SageMaker端点上的预训练Resnet50模型生成图像特征向量(嵌入)。嵌入将被保存在CloudFormation栈所创建的Amazon ES域中。关于更多详细信息,请参阅notebook中的markdown单元。

找到notebook当中的Deploying a full-stack visual search application单元。

该notebook中包含多个重要单元。

要加载预训练ResNet50模型,同时剔除最终CNN分类器层,请使用以下代码(此模型仅作为图像特征提取器使用):

#Import Resnet50 model
model = tf.keras.applications.ResNet50(weights='imagenet', include_top=False,input_shape=(3, 224, 224),pooling='avg')

我们将模型另存为TensorFlow SavedModel格式,其中包含完整的TensorFlow程序,包括权重与计算。详见以下代码:

#Save the model in SavedModel format
model.save('./export/Servo/1/', save_format='tf')

将模型工件(model.tar.gz)上传至Amazon S3,具体代码如下:

#Upload the model to S3
sagemaker_session = sagemaker.Session()
inputs = sagemaker_session.upload_data(path='model.tar.gz', key_prefix='model')
inputs

我们可以使用Amazon SageMaker Python SDK将模型部署至基于Amazon SageMaker TensorFlow Serving的服务器当中。此服务器负责提供TensorFlow Serving REST API中的一套超集,详见以下代码:

# define a function to extract image features
from time import sleep
sm_client = boto3.client('sagemaker-runtime')
ENDPOINT_NAME = predictor.endpoint
def get_predictions(payload):
 return sm_client.invoke_endpoint(EndpointName=ENDPOINT_NAME,
 ContentType='application/x-image',
 Body=payload)
def extract_features(s3_uri):
 key = s3_uri.replace(f's3://{bucket}/', '')
 payload = s3.get_object(Bucket=bucket,Key=key)['Body'].read()
 try:
 response = get_predictions(payload)
 except:
 sleep(0.1)
 response = get_predictions(payload)
 del payload
 response_body = json.loads((response['Body'].read()))
 feature_lst = response_body['predictions'][0]
 return s3_uri, feature_lst
predictor = sagemaker_model.deploy(initial_instance_count=3, instance_type='ml.m5.xlarge')

使用以下代码从Amazon SageMaker端点处提取参考图像特征:

# define a function to extract image features
from time import sleep
sm_client = boto3.client('sagemaker-runtime')
ENDPOINT_NAME = predictor.endpoint
def get_predictions(payload):
 return sm_client.invoke_endpoint(EndpointName=ENDPOINT_NAME,
 ContentType='application/x-image',
 Body=payload)
def extract_features(s3_uri):
 key = s3_uri.replace(f's3://{bucket}/', '')
 payload = s3.get_object(Bucket=bucket,Key=key)['Body'].read()
 try:
 response = get_predictions(payload)
 except:
 sleep(0.1)
 response = get_predictions(payload)
 del payload
 response_body = json.loads((response['Body'].read()))
 feature_lst = response_body['predictions'][0]
 return s3_uri, feature_lst

使用以下代码定义Amazon ES KNN索引映射:

#Define KNN Elasticsearch index mapping
kn_index = {
 "settings": {
 "index.knn": True
 },
 "mappings": {
 "properties": {
 "zalando_img_vector": {
 "type": "knn_vector",
 "dimension": 2048
 }
 }
 }
}

使用以下代码将图像特征向量与关联的Amazon S3图像URI导入至Amazon ES KNN索引:

# defining a function to import the feature vectors corrosponds to each S3 URI into Elasticsearch KNN index
# This process will take around ~3 min.
def es_import(i):
 es.index(index='idx_zalando',
 body={"zalando_img_vector": i[1],
 "image": i[0]}
 )
process_map(es_import, result, max_workers=workers)

构建一款全栈视觉搜索应用程序

现在,我们已经拥有了一个能够正常工作的Amazon SageMaker端点,并可以在Amazon ES上提取图像特征与KNN索引,接下来应该构建一款实际可用的全栈ML支持型Web应用程序了。我们使用Amazon SAM模板通过API Gateway与Lambda部署无服务器REST API。该REST API负责接收新图像、生成嵌入,并将得到的相似图像返回至客户端。接下来,我们将与新REST API交互的前端网站上传至Amazon S3。前端代码使用Amplify与我们的REST API相集成。

在以下单元中,预填充一套CloudFormation模板。此模板负责为全栈应用程序创建API Gateway与Lambda等必要资源:

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'
# Generate the CloudFormation Quick Create Link
print("Click the URL below to create the backend API for visual search:n")
print((
 'https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/create/review'
 f'?templateURL={sam_template_url}'
 '&stackName=vis-search-api'
 f'&param_BucketName={outputs["s3BucketTraining"]}'
 f'&param_DomainName={outputs["esDomainName"]}'
 f'&param_ElasticSearchURL={outputs["esHostName"]}'
 f'&param_SagemakerEndpoint={predictor.endpoint}'
))

以下截屏所示,为预生成的CloudFormation模板链接。
image

  • 选择该链接。
    这时我们会跳转至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的输出结果。
    image

  • 选择该链接。
    这时我们将跳转至应用程序页面,并可以在这里上传服饰图像服饰URL链接,借此获取相似服饰推荐。
    image
    在完成对视觉搜索应用程序的测试与试验之后,请运行notebook下方的最后两个单元:
# Delete the endpoint
predictor.delete_endpoint()
# Empty S3 Contents
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存储桶,为资源清理步骤做好准备。

资源清理

要删除其余亚马逊云科技资源,请转至!Amazon CloudFormation控制台并删除其中的vis-search-api与vis-search栈。
image

总结

在本文中,我们介绍了如何使用Amazon SageMaker与Amazon ES KNN索引创建基于机器学习的视觉搜索应用程序。我们还使用到在ImageNet数据集上经过预训练的ResNet50模型。当然,大家也可以使用其他预训练模型,例如VGG、Inception以及MobileNet等,并使用自己的数据集进行调优。

对于大部分深度学习类用例,我们建议使用GPU实例。在GPU实例上训练新模型,将带来远超CPU实例的训练速度。如果拥有多个GPU实例,或者需要在多个GPU实例之间使用分布式训练,则可以进行次线性扩展。本用例中仅使用CPU实例,因此我们可以在Amazon Free Tier中通过免费资源完成演练。

关于本文所使用代码示例的更多详细信息,请参阅GitHub repo。关于Amazon ES的更多详细信息,请参考以下扩展资源:

image


亚马逊云开发者
2.9k 声望9.6k 粉丝

亚马逊云开发者社区是面向开发者交流与互动的平台。在这里,你可以分享和获取有关云计算、人工智能、IoT、区块链等相关技术和前沿知识,也可以与同行或爱好者们交流探讨,共同成长。