跨模态搜索系列 --- 二、Jina-跨模态搜索实现

二、Jina-跨模态搜索实现

1. Jina 的基本概念与部署指北

作为Github热榜前三的神经搜索框架之一,Jina实现了对当前绝大部分数据类型的支持,对于视频、图像、文本、PDF以及音乐等非结构化数据,可进行大规模的索引与查询。

作为分布式架构,Jina具备可拓展和云原生的设计,实现了对云容器化、并行、分片、异步调度以及HTTP/gRPC/WebSocket 协议的支持。其专为神经搜索系统所设计的搭建模式,可以在极短时间内完成对深度学习搜索系统的部署,实现多模态的数据搜索。

  • 基本概念

    • 框架结构

      整个Jina框架基本上可分为 Document、Executor以及Flow 三个基本组件,三者共同协作,构建成完整的Jina应用程序。

      • Document: 作为Jina的基础数据类型,主要负责将非结构数据映射到向量数据中,从而将视频、语音以及文本等多模态数据转化为为统一的数据结构类型进行处理。
      • Executor: 可将其理解为一个类,通过该组件可以将本地函数转化为可在Flow组件中分发的函数,从而在Jina中构造相应的方法对Document进行处理
      • Flow: 主要负责将多个Executor进行连接,组成完整的Pipeline以提供服务。其入口是Gataway,本质是一个内部通信的路由,对接收到的请求进行分发。
    • 执行逻辑与应用示例

      • Jina官方提供了明确的搭建模式,使得Jina框架可在极短时间内完成搭建与部署,具体部署流程可参照官方文档进行安装
      • 需要注意的是,当前Jina暂不支持在Windows系统环境下安装,Windows用户需要开启WSL后,在WSL中进行安装部署。
    • DocArray 结构工具

      • 定义:

        作为存储非结构化数据的工具包,DocArray的特点在于其存在不同层级的结构,其可以将数据拆分为多种粒度多种模态,存储到不同的层级中,从而提高搜索粒度和结果丰富度。值得注意的是,Jina 3.x版本中已经包含了DocArray,无需独立安装。

      • 框架组成:

        • Document:作为DocArray的基本数据类型,无论文本、视频还是音频等多模态数据,都可采用Document进行表示,使得这些数据规整存储,以供后续处理。
        • DocumentArray:作为存放多个Document的容器,类似python中的list
        • Dataclass:用于直观表示多模态数据的高级API

2. Demo 实现 —文本匹配

  • YAML文件与Client监听

    • yaml文件主要用于指定Flow的执行逻辑,以下述文件为例:

      jtype: Flow
      with:
        port: 51000
        protocol: grpc
      executors:
        - uses: FooExecutor
          name: foo
          py_modules:
            - test.py
        - uses: BarExecutor
          name: bar
          py_modules:
            - test.py
      

      Jina在yaml文件中获取到指定的type、端口和协议,这里采用GRPC协议进行通讯,也可通过纯Python的方式调用Flow

      运行jina flow --uses toy.yml 命令启动grpc服务,输出如下结果:

      跨模态搜索系列 --- 二、Jina-跨模态搜索实现_第1张图片

    • test.py文件主要负责构建yaml文件中的类,类中定义foo和bar函数

      # 创建 test.py 文件与 YAML 文件在同一目录下
      # 导入 document、executor 和 flow 以及 requests 装饰器
      from jina import DocumentArray, Executor, requests, Document
      
      # 编写 FooExecutor 与 BarExecutor 类,类中定义了函数 foo 和 bar
      # 该函数从网络请求接收 DocumentArray (先暂时不需要理解它是什么),并在其内容后面附加 "foo was here" 与 "bar was here"
      class FooExecutor(Executor):
          @requests # 用于指定路由,类似网页访问 /index 和 /login 会被路由到不同的方法上是用样的概念,关于 request 下面会再进行详细介绍
          def foo(self, docs: DocumentArray, **kwargs):
              docs.append(Document(text='This is foo'))
      
      
      class BarExecutor(Executor):
          @requests
          def bar(self, docs: DocumentArray, **kwargs):
              docs.append(Document(text='This is bar'))
      
    • 创建client.py文件,对上图端口进行监听

      # 从 Jina 中导入连接的客户端与 Document
      from jina import Client, Document
      
      c = Client(host='grpc://0.0.0.0:51000')  # 如果运行提示失败,可尝试使用localhost
      result = c.post('/', Document()) # 将一个空的 Document 传到服务端执行
      print(result.texts) 
      

      运行该文件,client会对指定端口进行监听,获取到DocumentArray类型的result,打印出其text内容

      跨模态搜索系列 --- 二、Jina-跨模态搜索实现_第2张图片

    • 执行逻辑:

      在上述过程中,Jina按顺序读取py_modules中定义的函数,这些函数在test.py内的两个类中被分别定义,这两个类继承自Executor类。Jina在对端口进行监听时,client.py以post请求的方式发送DocumentGataway中,由Gataway将其发送给Executor顺序执行定义好的函数,最后将结果返回给Gataway,由Gataway发送至Client

      跨模态搜索系列 --- 二、Jina-跨模态搜索实现_第3张图片

      通过 YAML 方式将 Executor 和 Flow 分开有以下优点:

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

    在官方给出的Demo示例中,Jina通过embed_feature_hashing函数实现了文本内容向量化,但此处的函数仅仅是一个简单的词袋模型,并未实现中文分词,仅仅通过空格来对英文文本进行分割,因此尝试采用深度学习方法实现文本向量化。

    • 使用Sentence_BERT预训练模型进行文本向量化,并采用余弦相似度取代Jaccard相似度。原因在于,jaccard相似度统计的是两个向量中交集与并集的比值,适用于词袋模型环境,根据分词获取相似度。在面对深度学习处理的文本向量时,其中存在的大量重复词会影响到Jaccard相似度的计算结果,因此需要改为余弦相似度。

      from docarray import Document, DocumentArray
      from sentence_transformers import SentenceTransformer
      from tqdm import tqdm
      from pprint import pprint
      
      with open('./龙族1·火之晨曦.txt', encoding='GBK') as f:
          text = f.read()
      
      d = Document(text=text)
      da = DocumentArray(Document(text=s.strip()) for s in d.text.split('\n') if s.strip())
      
      model = SentenceTransformer('./bert_base_chinese/')
      word_vec = model.encode
      
      for d in tqdm(da):
          d.embedding = word_vec(d.text)
      
      text = Document(text='路明非看向身边,所有人的脸色都很难看')
      text.embedding = word_vec(text.text)
      q = text.match(da, limit=10, exclude_self=True, metric='cos', use_scipy=True)
      pprint(q.matches[:, ('text', 'scores__cos')])
      

你可能感兴趣的:(jina,全文检索,nlp,bert)