• [datawhale202211]跨模态神经搜索实践:Jina生态


    结论速递

    Jina生态有两个重要的组成成分:Jina结构本身,以及数据形式DocArray。
    Jina的基本结构包含三部分:Flow,Executor,还有客户端Client,理解他们之间的交互关系很重要。
    DocArray是Jina的数据形式,实现多模态数据处理的重要环节。本次task熟悉了文本,图像及视频三种形式的DocArray的处理。

    前情回顾

    1. 环境配置

    1 Jina简介

    1.1 了解Jina

    1.1.1 什么是Jina

    Jina可以帮助用户快速处理多模态数据,轻松打造多模态AI应用。

    Jina 可以帮助你快速把非结构化数据例如图像,文档视频等,转换为向量数据。并结合 Jina 的其他组件设计,帮助你快速的把向量数据利用起来,实现多模态的数据搜索。

    针对多模态数据的搜索难题,Jina 将深度学习和向量索引结合起来,提供了全链路的解决方案,去帮助开发者去应对神经搜索系统的三大挑战。

    神经搜索指借助深度学习技术,使用非结构化数据搜索非结构化数据。

    请添加图片描述

    具体的介绍可以看这个文档:Jina介绍

    1.1.2 Jina的安装

    Jina需要python3.7以上的版本,目前推荐使用python3.9。

    由于在上一个任务中配置环境时已经在本地环境中完成了Jina的配置,此处跳过。若需单独配置Jina可以参考其docs

    1.2 Jina的基本结构及使用

    1.2.1 Jina的三个基本概念

    Document、Executor 和 Flow 是 Jina 的三个基本概念,分别代表数据类型,算法单元,和工作流。

    • Docunment:数据类型,解决非结构化数据和向量数据之间的映射
    • Executor:算法单元,在python实现中为类,实现具体的算法功能
    • Flow:工作流,把Executor串在一起,实现一系列的工作

    1.2.2 Jina的基本使用流程

    了解Jina的使用,首先需要理解主要的构成,下图来源于:Basic Concepts - Jina 3.11.1 documentation

    在这里插入图片描述

    由两个实体的成分部分组成:

    • Client:客户端
    • Flow:实现具体功能的工作流,其中:
      • Gateway:作为整个Flow的接口,和client交互
      • Executor:实现一个个的具体功能

    同时需要实现Client和Flow交互的传输数据的网络协议,具体为gRPC,通过一个YAML文件来定义。

    1.2.3 toycode:快速开始

    此处教程提供了一个toycode,帮助理解Jina的基本使用流程。

    VCED项目的code/jina_demo里头提供了对应的toycode文件。

    • YAML定义:toy.yml文件。
      • 定义了接口的port
      • flow当中包含的Executor,及它们来源的代码文件
    • Flow:test.py文件。定义了Executor具体的执行逻辑,都以类的形式进行定义。
    • Client:client.py文件。指定接口,及客户端输出效果

    执行时,首先启动gRPC服务

    jina flow --uses toy.yml
    
    • 1

    启动成功如下图所示

    请添加图片描述

    随后启动客户端,本地的话新建一个命令行,执行

    python client.py
    
    • 1

    会输出

    ['', 'foo was here', 'bar was here']
    
    • 1

    整个逻辑简略制图如下(丑但凑合看吧):

    请添加图片描述

    1.2.4 进阶使用

    在快速开始中的test.py里,我们定义了两个Executor。

    请添加图片描述

    并且在toy.yml中用YAML文件把Flow和Executor分开了。

    请添加图片描述

    下面是其他进阶的使用形式,包括:

    • Executor:可以利用request 装饰器实现的路由的功能

    路由可以这么理解:类似于前后端分离的网站,前端通过 /index 这种形式对后端接口进行访问,后端程序收到请求后对其进行解析,并根据路由规则将该请求传递到指定的方法中执行。
    在一个 Executor 的方法中默认可以指定 @request(on="")参数,其中 on 后面接的字符串就是该方法绑定的路由。
    如果没有 on 这个参数,此时就是默认路由,当请求找不到对应的路由时会执行该方法。

    from jina import Document, DocumentArray, Flow, Executor, requests
    
    class FooExecutor(Executor):
        @requests
        def foo(self, docs: DocumentArray, **kwargs):
            docs.append(Document(text='foo was here'))
    
    class BarExecutor(Executor):
        @requests
        def bar(self, docs: DocumentArray, **kwargs):
            docs.append(Document(text='bar was here'))
    
    class MyExecutor(Executor):
        @requests
        def foo(self, **kwargs):
            print(kwargs)
    
        @requests(on='/index')
        def bar(self, **kwargs):
            print(kwargs)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • Flow :还可以通过 add 方法可以将多个 Executor 串成一套执行逻辑。实现用纯python的方法而非YAML方式启动Flow发送请求。
    # 通过add的方法串起来
    f = (
        Flow()
        .add(uses=FooExecutor, name='fooExecutor')
        .add(uses=BarExecutor, name='barExecutor')
        .add(uses=MyExecutor, name='MyExecutor')
    )  # 创建一个空的 Flow
    
    with f:  # 启动 Flow
        response = f.post(
            on='/'
        ) # 向 flow 发送一个请求
        print(response.texts)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    不过,快速开始中通过 YAML 方式将 Executor 和 Flow 分开有以下优点:

    • 服务器上的数据流是非阻塞和异步的,当 Executor 处于空闲状态时,会立即处理新的请求。
    • 必要时会自动添加负载平衡,以确保最大吞吐量。

    2 DocArray

    2.1 DocArray简介

    2.1.1 定义

    DocArray 是用于存储非结构化数据的数据结构工具包,是做跨模态应用的基础。官方文档提供了非常详细的说明:官方文档

    DocArray 的亮点在于 Hierarchy + Nested。DocArray 有不同的层级结构,分层存储,第一层可以是一个整体的视频,第二层是该视频的不同镜头,第三层可以是镜头的某一帧。也可以是其他模态,比如第四层存储台词段落,第五层存储 … 既可以通过某个画面的描述搜索,也可以通过台词的意思去搜索,这样搜索的颗粒度,结构的多样性和结果的丰富度,都比传统文本检索好很多。
    此外,DocArray 的设计对于 Python 用户来说非常直观,不需要学习新的语法。它融合了 Json、Pandas、Numpy、Protobuf 的优点,更适用于数据科学家和深度学习工程师。

    2.1.2 基本概念

    DocArray 由三个简单的概念组成:

    • Document:一种表示嵌套非结构化数据的数据结构,是 DocArray 的基本数据类型。规范化文本、图像、视频、音频、3D、表格 或它们的嵌套或组合的数据结构(图来源于官方文档
      在这里插入图片描述

    • DocumentArray:可以保存多个 Document 的列表(图来源于官方文档
      在这里插入图片描述

    • Dataclass:用于直观表示多模式数据的高级API,官方文档提供了一个把报纸转化成数据的示意。
      在这里插入图片描述

    2.2 多模态数据处理

    2.2.1 文本

    demo代码在VCED项目的code/jina_demo/text里头。

    • 创建文本:列举了两种形式,支持多语言
      1. 直接从text来
      2. 从url导入,适用于大批数据(示例里的得科学上网一下)
    from jina import Document  # 导包
    # 创建简单的文本数据
    d = Document(text='hello, world.') # 通过text获取文本数据
    # 如果文本数据很大,或者自URI,可以先定义URI,然后将文本加载到文档中
    d = Document(uri='https://www.w3.org/History/19921103-hypertext/hypertext/README.html')
    d.load_uri_to_text()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 切割文本
    from jina import Document  # 导包
    
    d = Document(text='👋	नमस्ते दुनिया!	你好世界!こんにちは世界!	Привет мир!')
    d.chunks.extend([Document(text=c) for c in d.text.split('!')])  # 按'!'分割
    d.summary()
    
    • 1
    • 2
    • 3
    • 4
    • 5

    运行效果如下:切成了三段,分割符是半角的感叹号,而原句中是既有半角感叹号也有全角感叹号的。

    在这里插入图片描述

    • text、ndarray 互转:毕竟是向量

    text转ndarray如下

    from jina import DocumentArray, Document  # 导包
    
    # DocumentArray 相当于一个 list,用于存放 Document
    da = DocumentArray([Document(text='hello world'), 
                        Document(text='goodbye world'),
                        Document(text='hello goodbye')])
    
    vocab = da.get_vocabulary()  # 输出:{'hello': 2, 'world': 3, 'goodbye': 4}
    
    # 转为ndarray
    for d in da:
        d.convert_text_to_tensor(vocab, max_length=10)  # 转为tensor向量,max_length为向量最大值,可不设置
        print(d.tensor) 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    输出是

    {'hello': 2, 'world': 3, 'goodbye': 4}
    [0 0 0 0 0 0 0 0 2 3]
    [0 0 0 0 0 0 0 0 4 3]
    [0 0 0 0 0 0 0 0 2 4]
    
    • 1
    • 2
    • 3
    • 4

    ndarray转text如下

     # ndarray转text
     for d in da:
        d.convert_tensor_to_text(vocab)
        print(d.text)
    
    • 1
    • 2
    • 3
    • 4

    输出是

    hello world
    goodbye world
    hello goodbye
    
    • 1
    • 2
    • 3
    • 还可以实现简单的文本匹配
    from jina import Document, DocumentArray
    
    d = Document(uri='https://www.gutenberg.org/files/1342/1342-0.txt').load_uri_to_text() # 链接是傲慢与偏见的电子书,此处将电子书内容加载到 Document 中
    da = DocumentArray(Document(text=s.strip()) for s in d.text.split('\n') if s.strip()) # 按照换行进行分割字符串
    da.apply(lambda d: d.embed_feature_hashing())
    
    q = (
        Document(text='she entered the room') # 要匹配的文本
        .embed_feature_hashing()  # 通过 hash 方法进行特征编码
        .match(da, limit=5, exclude_self=True, metric='jaccard', use_scipy=True) # 找到五个与输入的文本最相似的句子
    )
    
    print(q.matches[:, ('text', 'scores__jaccard')]) # 输出对应的文本与 jaccard 相似性分数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    输出是
    在这里插入图片描述

    2.2.2 图像

    demo代码在VCED项目的code/jina_demo/image里头,就不一一贴了。

    可以实现

    • 读取图像并转为Tensor:
    • 图像处理:设置大小,标准化,更改通道等
    • 图像集的读取
    • 大型图像的切割

    2.2.3 视频

    demo代码在VCED项目的code/jina_demo/video里头,就不一一贴了。

    可以实现

    • 视频导入和切分:load_uri_to_video_tensor,得到的是四维数组,相比图像多出来的是帧。
    • 提取关键帧:使用 only_keyframes
    • 张量转存为视频:save_video_tensor_to_file 进行视频的保存
  • 相关阅读:
    6. 从ods(贴源层)到 dwd(数据明细层)的两种处理方式(spark)-dsl
    IC后端设计中的shrink系数设置方法
    Doris实战——美联物业数仓
    网络安全攻防演练项目介绍
    Rust 学习笔记
    实战:fabric 用户证书吊销操作流程
    scrum敏捷开发方法论
    实验25:温度传感器实验
    微信小程序开发六(自定义组件)
    Alien Skin Exposure8免费版PS图片滤镜插件
  • 原文地址:https://blog.csdn.net/qq_40990057/article/details/127895715