AI跑车引擎第三篇——向量引擎之ElastiKnn实战

2023-06-14 16:14:17 浏览数 (1)

前言

在今天的数据驱动的世界中,在AI兴起的当下,信息检索和相似性搜索已经成为了许多领域的核心技术,包括但不限于各类AI应用、推荐系统、电子商务、社交媒体和生物信息学。这些领域的一个共同需求是能够快速、准确地找到与给定对象最相似的其他对象。为了满足这个需求,我们需要一个强大、灵活且高效的搜索引擎。这就是Elasticsearch和ElastiKNN的用武之地。

Elasticsearch是一个基于Apache Lucene的开源搜索引擎,它被广泛应用于全文搜索、日志数据处理和大数据分析等领域。然而,当涉及到处理高维度的向量数据和执行相似性搜索时,Elasticsearch的传统方法可能会遇到一些挑战。这就是ElastiKNN插件发挥作用的地方。

ElastiKNN是一个为Elasticsearch设计的插件,它利用了k近邻(KNN)算法的优势,使Elasticsearch能够处理高维度的向量数据,并执行高效的相似性搜索。在本文中,我们将从实战出发,深入探讨ElastiKNN的工作原理,以及它的安装布署和使用过程。

在AI兴起的当下,一切皆embedding,像Elastiknn这类向量存储和检索引擎也逐渐成为AI跑车引擎里不可缺少的一部分。

安装

1. 创建容器

1.1 编写Dockerfile

新建elasticsearch目录并在其内创建Dockerfile文件

代码语言:javascript复制
mkdir elasticsearch && elasticsearch
touch Dockerfile

Dockerfile中内容如下:

代码语言:javascript复制
FROM docker.elastic.co/elasticsearch/elasticsearch:8.7.1-amd64
RUN yes | ./bin/elasticsearch-plugin install https://github.com/alexklibisz/elastiknn/releases/download/8.7.1.0-PR515-SNAPSHOT/elastiknn-8.7.1.0.zip

1.2 编写docker-compose.yml

elasticsearch文件夹同级目录下创建docker-compose.yml文件,内容为:

代码语言:javascript复制
version: '3'
services:
    es-knn:
        build: ./elasticsearch
        image: elasticsearch-knn:8.7.1
        container_name: es_knn
        environment:
            - discovery.type=single-node
            - xpack.security.enabled=false
        ports:
            - 9201:9200
            - 9301:9300

上面的端口映射到Host主机的9201和9301是因为系统上已经有Elasticsearch在运行了并且使用了默认的端口,这里需要再换一下端口。

1.3 打包镜像

上面的目录结构如下:

代码语言:javascript复制
.
├── docker-compose.yml
└── elasticsearch
    ├── Dockerfile

执行如下命令以构建镜像:

代码语言:javascript复制
docker-compose build

执行docker images可以看到镜像已经成功创建:

代码语言:javascript复制
MacBook-Pro 8.7.1 % docker images
REPOSITORY          TAG       IMAGE ID       CREATED       SIZE
elasticsearch-knn   8.7.1     39f7e268768f   11 days ago   1.34GB

可以看到elasticsearch-knn这个镜像已经在里面了。

2.测试容器

2.1 启动容器

执行如下命令以启动容器:

代码语言:javascript复制
docker-compose up

2.2 查看容器启动状态

代码语言:javascript复制
MacBook-Pro 8.7.1 % docker-compose ps
NAME                COMMAND                  SERVICE             STATUS              PORTS
es_knn              "/bin/tini -- /usr/l…"   es-knn              running             0.0.0.0:9201->9200/tcp, 0.0.0.0:9301->9300/tcp

3.测试服务情况

查看插件列表:curl -XGET localhost:9201/_nodes/_all/plugins

代码语言:javascript复制
MacBook-Pro ~ % curl -XGET localhost:9201/_nodes/_all/plugins
{"_nodes":{"total":1,"successful":1,"failed":0},"cluster_name":"docker-cluster","nodes":{"DgnK_cWRQoS9T6IOswokpQ":{"name":"7fc25bb88378","transport_address":"192.168.128.2:9300","host":"192.168.128.2","ip":"192.168.128.2","version":"8.7.1","build_flavor":"default","build_type":"docker","build_hash":"f229ed3f893a515d590d0f39b05f68913e2d9b53","roles":["data","data_cold","data_content","data_frozen","data_hot","data_warm","ingest","master","ml","remote_cluster_client","transform"],"attributes":{"ml.allocated_processors":"2","ml.machine_memory":"6232621056","xpack.installed":"true","ml.allocated_processors_double":"2.0","ml.max_jvm_size":"3116367872"},"plugins":[{"name":"elastiknn","version":"8.7.1.0","elasticsearch_version":"8.7.1","java_version":"19","description":"...","classname":"com.klibisz.elastiknn.ElastiknnPlugin","extended_plugins":[],"has_native_controller":false,"licensed":false,"is_official":false,"legacy_interfaces":["EnginePlugin","MapperPlugin","SearchPlugin"]

查看节点:curl -XGET 127.0.0.1:9201/_cat/nodes

代码语言:javascript复制
MacBook-Pro ~ % curl -XGET 127.0.0.1:9201/_cat/nodes 
192.168.128.2 6 82 4 0.36 0.27 0.20 cdfhilmrstw * 7fc25bb88378

查看健康状态:curl -XGET 127.0.0.1:9201/_cat/health

代码语言:javascript复制
MacBook-Pro ~ % curl -XGET 127.0.0.1:9201/_cat/health
1684899859 03:44:19 docker-cluster green 1 1 0 0 0 0 0 0 - 100.0%

elasticknn的安装方式可参考:Installation - Elastiknn[1]

elasticknn具备的功能一览

它可以支持多种向量类型和多种向量算法模型,具体详见文档:API - Elastiknn[2]

名词解释

sparse bool vector

稀疏布尔向量(Sparse Boolean Vector)是一种经典的数据结构,通常用于应对大规模稀疏数据的存储和索引。它是一个只包含0和1的布尔数组,其中大多数元素的值为0,只有少数元素的值为1,这些1的位置称为非零(non-zero)位置。相对于密集向量(dense vector),稀疏向量可以极大地节省存储空间和计算资源,并且在许多应用中也具有更好的性能。

例如,在自然语言处理中,一个文本文档可以表示成大小为词汇表大小的向量,其中每个元素代表一个单词,而每个单词在文本中出现的次数则表示为对应元素的值。由于大多数单词在一个文本中只出现一次或不出现,因此生成的向量是一个稀疏向量。使用稀疏向量数据结构,可以更高效地处理大规模文本语料库,并解决文本分类、聚类和信息检索等问题。

在机器学习中,稀疏向量通常用于表示训练数据和模型参数,例如词袋模型、TF-IDF特征、one-hot编码等,它们被广泛应用于文本分类、图像识别和推荐系统等领域。在神经网络中,稀疏向量通常用于表示稀疏输入数据和参数共享等常见任务。在分布式计算中,稀疏向量也是一种常见的数据结构,用于表示分布式计算任务中的任务和资源,有效地提高了计算效率和资源利用率。

dense float vector

Dense float vector是指稠密的浮点向量,它是一种常用的数据结构,主要用于表示各种计算机视觉和自然语言处理任务中的向量数据。

在计算机视觉中,密集的浮点向量通常用来表示图像和视频中的特征,例如颜色直方图、梯度方向直方图、卷积神经网络中的激活层等。这些特征向量可以用于物体检测、图像分类、目标跟踪等任务。

在自然语言处理中,密集的浮点向量通常用于表示文本中的词向量、句向量和段向量。这些向量可以用于文本分类、情感分析、机器翻译等任务。

与稀疏向量(Sparse vector)不同,密集向量中的每个维度都存储一个浮点数,因此它能够更准确地表示各个维度间的权重和相似度。但是相对于稀疏向量而言,密集向量需要更多的存储空间,并且经常需要进行归一化和正则化处理,以防止过拟合和模型退化。

在自然语言处理中,什么时候需要用Sparse Boolean Vector,什么时候需要用Dense float Vector?

在自然语言处理中,Sparse Boolean Vector和Dense float Vector有不同的应用场景:

1.Sparse Boolean Vector:主要用于文本分类和文本检索等任务中的特征表示。由于文本数据通常有很高的维度和稀疏性,使用Sparse Boolean Vector可以节省存储空间和计算资源。例如,在文本分类中,我们可以使用词袋模型表示每个文本的含有哪些单词,将其转化为Sparse Boolean Vector进行分类。在文本分类和信息检索等任务中,使用词袋模型将文本转化为向量表示时,文本中绝大部分单词的出现次数为0,只有很少一部分单词出现了很多次。针对这个问题,可以将文本表示为Sparse Boolean Vector,其中每个元素表示文本中对应单词是否出现,1表示出现,0表示未出现。使用Sparse Boolean Vector可以显著降低向量的维度、提高计算效率,同时可以保留文本中出现频率较高的单词,因为它们对于文本的含义而言更为关键。2.Dense float Vector:主要用于语义表示和机器翻译等任务中的特征表示。由于这些任务需要比较较高的精度和复杂度,因此需要使用Dense float Vector来表示文本的句向量或单词向量。在这种情况下,我们通常使用深度学习模型(如word2vec、GloVe、BERT等)将文本映射到高维空间,并将其转换为Dense float Vector。在诸如自然语言生成、同义词替换和主题建模等需要对语义进行建模的任务中,使用Dense Float Vector更为适合。因为它可以包含更多的信息,例如词性、词向量,以及单词之间的语义联系等。使用Dense Float Vector可以更好地表示文本中不同单词之间的信息关联,以及单词在各种语境下的含义。例如,在机器翻译任务中,可以使用Dense Float Vector表示源语言和目标语言中的单词,以及它们之间的翻译关系。

总的来说,Sparse Boolean Vector和Dense float Vector都有各自的应用场景和优缺点。我们需要根据具体的任务和数据特点选择适当的特征表示方法来提高模型的性能和效率。

使用示例

API使用方式

这部分主要是http rest风格的调用,和es的使用比较相似,这里不做过多介绍,感兴趣的同学可参考:API - Elastiknn[3]

这段文本介绍了使用LSH(随机投影)[4]算法对密集浮点向量进行哈希和存储,以支持近似余弦相似度查询。LSH(随机投影)[5]是一种将高维数据映射到低维空间的技术,可以用于降低计算复杂度和存储空间。该实现受到《Mining Massive Datasets》[6]第3章的影响。余弦相似度是一种用于比较两个向量之间夹角的相似度度量方法,常用于文本分类、推荐系统等领域。

建一个稀疏布尔向量的索引mapping如下:

代码语言:javascript复制
PUT /my-index/_mapping
{
    "properties": {
        "my_vec": {
            "type": "elastiknn_dense_float_vector", # 1
            "elastiknn": {
                "dims": 100,                        # 2
                "model": "lsh",                     # 3
                "similarity": "cosine",             # 4
                "L": 99,                            # 5
                "k": 1                              # 6
            }
        }
    }
}

python client

安装:

代码语言:javascript复制
pip install elastiknn-client

文档地址:Libraries - Elastiknn[7]

clien连接测试代码:

代码语言:javascript复制
from elasticsearch import Elasticsearch
from elastiknn.api import Mapping, Vec, NearestNeighborsQuery, Similarity
from elastiknn.client import ElastiknnClient


class TestClient:

    def test_exact_jaccard(self):
        es = Elasticsearch(["http://localhost:9201"], request_timeout=99)
        eknn = ElastiknnClient(es=es)
        n, dim = 1200, 42
        index = "py-test-exact-jaccard"
        vec_field = "vec"
        id_field = "id"
        mapping = Mapping.SparseBool(dims=dim)

        eknn.es.indices.delete(index=index, ignore_unavailable=True)
        eknn.es.indices.refresh()
        eknn.es.indices.create(index=index)
        eknn.es.indices.refresh()
        m = eknn.put_mapping(index, vec_field, mapping, "id")

        vecs = [Vec.SparseBool.random(dim) for _ in range(n)]
        ids = [f"vec-{i}" for i in range(n)]
        (n_, errors) = eknn.index(index, vec_field, vecs, id_field, ids, refresh=True)
        assert n_ == n
        assert len(errors) == 0

        qvec = vecs[0]
        query = NearestNeighborsQuery.Exact(vec_field, qvec, Similarity.Jaccard)
        res = eknn.nearest_neighbors(index, query, id_field, 10, False)
        hits = res['hits']['hits']
        assert len(hits) == 10
        assert hits[0]["fields"]["id"][0] == ids[0]

        res = eknn.nearest_neighbors(index, query, id_field, 10, True)
        hits = res['hits']['hits']
        assert len(hits) == 10
        assert hits[0]["_id"] == ids[0]

TestClient().test_exact_jaccard()

验证查询:

curl localhost:9201/py-test-exact-jaccard

代码语言:javascript复制
{"py-test-exact-jaccard":{
"aliases":{},
"mappings":{"properties":{
"id":{"type":"keyword","store":true},
"vec":{"type":"elastiknn_sparse_bool_vector",
"elastiknn":{"dims":42,"model":"exact"}}}},
"settings":{"index":
{"routing":{"allocation":
{"include":{"_tier_preference":"data_content"}}},
"number_of_shards":"1","provided_name":"py-test-exact-jaccard",
"creation_date":"1685432186029","number_of_replicas":"1",
"uuid":"MU-rOLKPQtmYhEDU_Sa5og",
"version":{"created":"8070199"}}}}}

这样,就可以利用elastiknn的python client进行向量索引的创建、vector写入、knn相似度查询了,支持的knn算法比较多,感兴趣的同学可自行做进一步的探索。

elastiknn的github地址为:https://github.com/alexklibisz/elastiknn

References

[1] Installation - Elastiknn: https://elastiknn.com/installation/ [2] API - Elastiknn: https://elastiknn.com/api/#elasticsearch-settings [3] API - Elastiknn: https://elastiknn.com/api/#elastiknn_sparse_bool_vector [4] LSH(随机投影): https://en.wikipedia.org/wiki/Locality-sensitive_hashing#Random_projection [5] LSH(随机投影): https://en.wikipedia.org/wiki/Locality-sensitive_hashing#Random_projection [6] 《Mining Massive Datasets》: http://www.mmds.org/ [7] Libraries - Elastiknn: https://elastiknn.com/libraries/#python-client

0 人点赞