写点什么

所见即搜,3 分钟教你搭建一个服装搜索系统!

发布于: 2021 年 03 月 10 日

摘要:用 MindSpore+Jina,基于 Fashion-MNIST Dataset 搭建的服装搜索系统。


引言


各位算法萌新们,是不是经常训练了模型却不知道如何部署和应用?或者只会调参但不会前端后端所以没法向老板们解释这个模型可以做啥?如果有一种非常简单的方式,让你在 3 分钟内就能建立一个以深度学习为支撑的搜索系统,并能在前端展示出来 show 给各位老板们看?想不想尝试呢?本文来自 MindSpore 社区技术治理委员会(TSC)的成员肖涵博士——Jina 的创始人,用 MindSpore+Jina,基于 Fashion-MNIST Dataset 搭建的服装搜索系统。


[本文目录]


  • 如何用 Jina①步搞定?

  • Jina 的 hello-world 是如何运行的?

  • 如何使用 MindSpore+Jina 来搭建搜索系统?

  • 创建一个 MindSpore Executor

  • 修改 MindSpore 的 Encoder 和网络代码

  • 写一个单元测试

  • 准备 Dockerfile

  • 最后一步:终于可以 Build 了!

  • 来看 MindSpore 的成品吧!

  • 总结


喜欢逛淘宝或者各大海淘网站的各位程序员(的女朋友们),你们在浏览服装的时候,是不是会经常看见模特们身上的衣服,全!都!想!要!但是,不知道从哪儿买,货号是什么?就算从各大穿搭博主那儿知道货号了,也懒得一一去搜索。现在,完全不需要这么麻烦,只要你花 3 分钟建立这个服装搜索系统,当你的女朋友再看到模特身上的衣服,就可以搜索出最相似的衣服,是不是很赞!



图 1 Shop the look


在做之前,先了解一下我们今天需要使用的两个框架:MindSpore 和 Jina


  • MindSpore 是 2020 年 3 月 28 日华为开源的深度学习框架,它能原生支持自家的昇腾芯片,极大的提升了运行性能!

  • Jina 是一个由最先进的 AI 和深度学习驱动的云端神经搜索框架,可以在多个平台和架构上实现任何类型的大规模索引和查询。无论你搜索图片、视频片段还是音频片段,Jina 都能处理。



这里使用的数据集是 Fashion-MNIST dataset。它包含 70,000 张图片,其中 60,000 张为训练集,10,000 张为测试集。每张图片都是 28x28 的灰度图像,一共 10 个类别。下面我们正式开始吧!


如何用 Jina①步搞定?


首先你需要一台电脑,确认一下环境是否 ok:


  • Mac OS or Linux

  • Python 3.7, 3.8

  • Docker


然后执行以下一行命令即可:


pip install jina && jina hello-world
复制代码


或者直接用 docker:


docker run -v "$(pwd)/j:/j" jinaai/jina hello-world --workdir /j && open j/hello-world.html  # replace "open" with "xdg-open" on Linux
复制代码


现在开始运行程序,就可以看到运行结果了:



图 3 Jina hello-world 运行结果


是不是很神奇?那么 Jina 是如何实现的呢?可以先花 1 分钟时间了解 Jina 的十个基本组件,在本文中最重要的三个信息分别是:


  • YAML config:让用户可以自定义的描述对象的属性。

  • Executor:代表了 Jina 中的算法单元。譬如把图像编码成向量、对结果进行排序等算法等都可以用 Executor 来表述。我们可以用 Crafter 来把制作/分割和转化要搜索的内容,然后用 Encoder 来将制作好的搜索对象表示为向量,再用 Indexer 保存和检索搜索的向量和键值信息,最后用 Ranker 来对搜索出的结果排序。

  • Flow:表示一个高阶的任务, 譬如我们所说的索引(index)、搜索(search)、训练(train),都属于一个 flow。


Jina 的 hello-world 是如何运行的?


想知道 hello-world 运行的细节嘛?其实在很简单,在 hello-world 里,我们使用 YAML 文件来描述 index 和 search 的 flow,可以导入 YAML 文件,并通过.plot() 命令来可视化:


from pkg_resources import resource_filenamefrom jina.flow import Flow
f = Flow.load_config(resource_filename('jina', '/'.join(('resources', 'helloworld.flow.index.yml')))).plot()
复制代码



图 4 hello-world YAML 文件流程图


YAML 文件里的信息是如何表示成图的呢?下面可以看看直观的对比:



图 5 YAML 文件信息


其实,这个 flow 中包含了两步(在 Jina 中也可以叫两个 Pod):第一步它将数据通过并行的方式喂给 encoder,输出向量和 meta 信息分片存储在索引器中。查询 flow 也是以同样的方式运行,只不过在参数上有些小变化。


既然原理这么简单,如果我们自己训练的模型,是不是也可以替换呢?下面我们来手把手教大家如何只用 4 步,就可以用 MindSpore+Jina 来搭建服装搜索系统。


如何使用 MindSpore+Jina 来搭建搜索系统?


创建一个 MindSpore Executor


MindSpore 的 ModelZoo 里有很多深度学习模型,本文使用的是最经典的 CV 网络:LeNet。我们可以通过 jina hub 来创建一个新的 MindSpore Executor,本文使用的 Jina Hub 版本是 v0.7 的,可以输入以下命令安装:


pip install "jina[hub]"
复制代码


安装好后,如果你想创建一个新的 executor,可以直接输入:


jina hub new
复制代码


执行这个命令后会弹出一下指导命令,按照下面的要求输入即可,有些设置直接用默认的就行,直接按 Enter 键就可以啦。


比较重要的是这几个命令:



所有命令输入完成后,你会看到 MindSporeLeNet 这个文件夹已经创建成功了。然后下载 MindSpore 的 LeNet 代码库和 Fashion MNIST 的训练数据,按照下面的方式把它们放到 MindSporeLeNet 模块下即可:



图 7 MindSporeLeNet 代码结构


修改 MindSpore 的 Encoder 和网络结构代码


  • 1.修改__init__.py


这是原始的__init__.py 代码,有一个基础类 BaseEncoder ,我们要改变一下 encode 的方式,把它变成 BaseMindsporeEncoder。


from jina.executors.encoders import BaseEncoder
class MindsporeLeNet(BaseEncoder): """ :class:`MindsporeLeNet` What does this executor do?. """
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # your customized __init__ below raise NotImplementedError
def encode(self, data, *args, **kwargs): raise NotImplementedError
复制代码


BaseMindsporeEncoder 是 Jina 中的抽象类,它在__init__构造函数中会导入 MindSpore 模型的 checkpoint。此外,它还能通过 self.model 提供 MindSpore 模型的属性接口。下面这张表显示了 MindSporeLeNet 通过构造函数继承的类。



图 8 MindSporeLeNet 中继承的类


修改完以后如下所示:


from jina.executors.encoders.frameworks import BaseMindsporeEncoder
class MindsporeLeNet(BaseMindsporeEncoder): """ :class:`MindsporeLeNet` Encoding image into vectors using mindspore. """
def encode(self, data, *args, **kwargs): # do something with `self.model` raise NotImplementedError
def get_cell(self): raise NotImplementedError
复制代码


  • 2.执行 encode() 方法。


给定一堆 batch size 为 B 的图像数据(用的 numpy 的 ndarray 来表示,shape 为[B, H, W]),encode() 把图像数据转换成向量的 embeddings(shape 为[B, D])。通过 self.model 导入 MindSpore LeNet 模型后,我们可以通过 self.model(Tensor(data)).asnumpy()来进行转换即可。


注意:self.model 的输入 shape 很容易出错。原始的 LeNet 模型的输入是三通道的图片,shape 是 32x32,所以输入必须是[B, 3, 32, 32]。然而 Fashion-MNIST 是灰度图片,单通道,图像的 shape 是 28x28,所以我们要么调整图片的尺寸,要么给图片补零。这里我们就用简单的补零操作了。最终的 encode()函数如下所示:


def encode(self, data, *args, **kwargs):    # LeNet only accepts BCHW format where H=W=32    # hence we need to do some simple padding    data = numpy.pad(data.reshape([-1, 1, 28, 28]),                  [(0, 0), (0, 0), (0, 4), (0, 4)]).astype('float32')    return self.model(Tensor(data)).asnumpy()
复制代码


  • 3.执行 get_cell()方法。


在 MindSpore 中,我们通常把神经网络中的层叫做『cell』,它可以是一个单独的神经网络层(譬如 conv2d, relu, batch_norm)。为了得到向量的 embedding,我们只需要从 LeNet 中移除 classification head 即可(譬如最后一个 softmax 层)。这个很好实现,只需要从原始的 LeNet5 类中继承,然后重写 construct() 函数即可。


def get_cell(self):    from .lenet.src.lenet import LeNet5
class LeNet5Embed(LeNet5): def construct(self, x): x = self.conv1(x) x = self.relu(x) x = self.max_pool2d(x) x = self.conv2(x) x = self.relu(x) x = self.max_pool2d(x) x = self.flatten(x) x = self.fc1(x) x = self.relu(x) x = self.fc2(x) x = self.relu(x) return x
return LeNet5Embed()
复制代码


写一个单元测试


当你在创建一个 Jina executor 的时候,一定不要忘了写单元测试,如果在 executor 里没有单元测试的话,是无法通过 Jina Hub API 来创建的哦~


在这个样例中已经生成了一个测试模板,你可以在 tests 文件夹里面找到 test_mindsporelenet.py 文件。先检查下 MindSpore 是否运行,如果可以运行的话,看看输出的 shape 是否是我们所希望的。


import numpy as np
from .. import MindsporeLeNet

def test_mindsporelenet(): """here is my test code
https://docs.pytest.org/en/stable/getting-started.html#create-your-first-test """ mln = MindsporeLeNet(model_path='lenet/ckpt/checkpoint_lenet-1_1875.ckpt') tmp = np.random.random([4, 28 * 28])
# The sixth layer is a fully connected layer (F6) with 84 units. # it is the last layer before the output assert mln.encode(tmp).shape == (4, 84)
复制代码


准备 Dockerfile


python 层面的准备工作已经完成了,下面我们准备 Docker image。我们可以基于已有的 Dockerfile 来创建,只需要加一行运行 train.py 代码来生成 checkpoint 文件的代码即可。


FROM mindspore/mindspore-cpu:1.0.0
# setup the workspaceCOPY . /workspaceWORKDIR /workspace
# install the third-party requirementsRUN pip install --user -r requirements.txt
+ RUN cd lenet && \+ python train.py --data_path data/fashion/ --ckpt_path ckpt --device_target="CPU" && \+ cd -
# for testing the imageRUN pip install --user pytest && pytest -s
ENTRYPOINT ["jina", "pod", "--uses", "config.yml"]
复制代码


这一行使用了 MindSpore LeNet 代码库里的 train.py 来生成训练的 checkpoint。我们在测试和部署的时候会用到这个 checkpoint。在 config.yml 文件中,需要把 checkpoint 的文件地址放在 model_path 这个参数里。requests.on 定义了 MindSporeLeNet 在 index 和 search 的 request 下应该如何执行。如果上面这些内容不理解也没关系,其实都是从 helloworld.encoder.yml 这个文件里复制和改动的。


!MindsporeLeNetwith:  model_path: lenet/ckpt/checkpoint_lenet-1_1875.ckptmetas:  py_modules:     - __init__.py    # - You can put more dependencies hererequests:  on:    [IndexRequest, SearchRequest]:      - !Blob2PngURI {}      - !EncodeDriver {}      - !ExcludeQL        with:          fields:            - buffer            - chunks
复制代码


最后一步:终于可以 Build 了!


终于可以把 MindSporeLeNet build 成 Docker 镜像了!!执行以下命令:


jina hub build MindsporeLeNet/ --pull --test-uses
复制代码


  • --pull :当你的图片数据集不在本地时,这个命令会告诉 Hub builder 来下载数据集

  • --test-uses :增加一个额外的测试来检查创建的镜像是否可以通过 Jina Flow API 试运行成功。


现在终端已经开始打印日志了,如果时间太久的话,可以在 MindsporeLeNet/lenet/src/config.py 中将 epoch_size 调小。


最后成功的信息:


HubIO@51772[I]:Successfully built cfa38dcfc1f9HubIO@51772[I]:Successfully tagged jinahub/pod.encoder.mindsporelenet:0.0.1HubIO@51772[I]:building MindsporeLeNet/ takes 57 seconds (57.86s)HubIO@51772[S]: built jinahub/pod.encoder.mindsporelenet:0.0.1 (sha256:cfa38dcfc1) uncompressed size: 1.1 GB
复制代码


现在你可以通过下面的命令将它作为一个 Pod 来使用了:


jina pod --uses jinahub/pod.encoder.mindsporelenet:0.0.1
复制代码


对比 jina pod --uses abc.yml, 我们会发现 jinahub/pod.encoder.mindsporelenet:0.0.1 的日志信息的开头处有一个 docker 容器。这些 log 日志是从 Docker 的 container 传输到 host 端的,下面描述了两者具体的差异。



图 9 差异对比


当然,你也可以上传这个镜像到 Docker 仓库里:


jina hub build MindsporeLeNet/ --pull --test-uses --repository YOUR_NAMESPACE --push
复制代码


来看 MindSpore 的成品吧!


最后,直接在 index 和 search 的 flow 中来使用新创建的 MindSpore Executor 吧,很简单,只需要替换 pods.encode.uses 这行代码就行:



图 10 index 与 query 的 YAML 文件差异


jina hello-world 的参数可以自定义,只要指定我们刚刚编写好的 index 和 query 的 YAML 文件,输入以下命令即可:


jina hello-world --uses-index helloworld.flow.index.yml --uses-query helloworld.flow.query.yml
复制代码


哈哈,完成了!几分钟之内你就可以看到开头动图显示的查询结果了!



图 11 最终输出结果


总结


本文中使用了 MindSpore+Jina 来共同搭建一个服装搜索系统,代码非常简单,其实只要学会修改 encode 的代码,根据需要构建网络层,然后打包成 docker 的 image,修改 YAML 文件就可以用 Jina 来实现最终的展示效果了,这样大家只要可以根据自己的需求,修改少量的代码,即可自行搭建一个基于 MindSpore 的搜索系统,是不是非常简单呢~感兴趣的同学可以直接点击以下链接,就可以直接运行:


https://gitee.com/mindspore/community/tree/master/mindspore-jina


本文分享自华为云社区《3分钟教你用MindSpore和Jina搭建一个服装搜索系统!》,原文作者:chengxiaoli


点击关注,第一时间了解华为云新鲜技术~


发布于: 2021 年 03 月 10 日阅读数: 13
用户头像

提供全面深入的云计算技术干货 2020.07.14 加入

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算! 传送门:https://bbs.huaweicloud.com/

评论

发布
暂无评论
所见即搜,3分钟教你搭建一个服装搜索系统!