第一章:向量数据库概述

milvus数据库是用来存储,索引,管理嵌入向量的数据库.非结构化数据通过向量嵌入转换为向量,然后存储到milvus向量数据库中.milvus可以处理万亿级别的向量索引.

1.1milvus和mysql的区别

存储数据方面,mysql存储结构化数据(Excel,SQL表等),milvus存储非结构化数据(图片,音频,视频等)转换来的嵌入向量

运行速度方面,milvus支持gpu加速,为高维向量快速检索优化,在亿级别向量检索可实现毫秒级响应.MySQL不支持高维数据检索,针对结构化数据查询,不支持相似度检索,高维向量检索效果极差

使用场景方面,milvus适用于AI场景,比如说人脸识别,图像视频检索等需要计算向量相似度的场景.

MySQL适用于业务场景,比如订单管理用户管理等需要强一致性的场景.

1.2milvus和MySQL的关联

MySQL存储用户信息,milvus存储向量数据,通过id关联查询实现'检索+业务'一体化

1.3milvus的工作原理

milvus存储索引向量,可以通过计算相似距离来分析数据之间的相关性.

第二章:milvus关键字

2.1collection,field关键字

类似于MySQL的表和字段

2.2建表方法以及表字段

建表的时候要先搭建表结构

2.3表实现

搭建表以后再插入字段

第三章:milvus检索方式

大部分milvus向量使用近似最近邻搜索(ANNS),不指定检索方式,默认为暴力检索

检索方式包括:

3.1FLAT(暴力检索)

检索方式:平铺逐个检索
适用场景:适合在小型,百万级向量索引中使用.

优点:100%精确

缺点:在大数据集中检索效率低

3.2.IVF_FLAT(倒排索引)

检索方式:把索引分组,倒排索引,检索
适用场景:适合在大数据集中使用

优点:中和了检索速度和准确率

3.3.IVF_FQ8

在IVF_FLAT的基础上增加了量化的步骤

检索方式:先分组,对向量数据量化压缩(把四字节的浮点数压缩为一字节的整数表示),然后倒排索引,最后查询处理
适用场景:适合在大数据集中使用

优点:IVF_SQ8是IVF_FLAT的优化版,既保留了查询速度,又节省了空间.

缺点:因为压缩的缘故,精度进一步下降

3.4IVF_PQ

引入了倒排索引和乘积量化,目的是加快检索速度

检索方式:先分簇,每个簇由他的聚类中心表示.输入查询的时候,计算与每个聚类中心的距离,选择最近的几个簇再详细计算.每个簇内的向量被分成了众多子向量,每个子向量只用一个编码表示,这样完成了乘积量化.后续使用倒排索引完成查询.
适用场景:适合在大规模高维向量集使用

优点:IVF_PQ减少了存储空间的使用,同时保留了较高的查询速度和准确性

缺点:因为压缩的缘故,精度进一步下降

3.5HNSW

基于图索引

检索方式:搜索的时候不是逐个搜索,而是沿着线路找相似的站点.
适用场景:对搜索效率有高要求

优点:查询速度效率极高

第四章:相似度衡量指标

4.1欧几里得距离

地图上的距离指的就是欧几里得距离

4.2余弦相似度

取值范围是(-1,1),越接近于1相似度越高,越接近于-1,相似度越低

4.3内积

两个向量的的点积计算,值越大,相似度越高

第五章:代码实现milvus

创建milvus数据库,实现增删改查以及混合检索的操作

​

"""
    演示通过python创建milvus数据库
"""

import random
from pymilvus import MilvusClient, DataType, AnnSearchRequest, RRFRanker, WeightedRanker

data = [
    {"id": 0,
     "vector": [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592],
     "color": "pink_8682"},
    {"id": 1,
     "vector": [0.19886812562848388, 0.06023560599112088, 0.6976963061752597, 0.2614474506242501, 0.838729485096104],
     "color": "red_7025"},
    {"id": 2,
     "vector": [0.43742130801983836, -0.5597502546264526, 0.6457887650909682, 0.7894058910881185, 0.20785793220625592],
     "color": "orange_6781"},
    {"id": 3, "vector": [0.3172005263489739, 0.9719044792798428, -0.36981146090600725, -0.4860894583077995,
                         0.95791889146345], "color": "pink_9298"},
    {"id": 4, "vector": [0.4452349528804562, -0.8757026943054742, 0.8220779437047674, 0.46406290649483184,
                         0.30337481143159106], "color": "red_4794"},
    {"id": 5, "vector": [0.985825131989184, -0.8144651566660419, 0.6299267002202009, 0.1206906911183383,
                         -0.1446277761879955], "color": "yellow_4222"},
    {"id": 6, "vector": [0.8371977790571115, -0.015764369584852833, -0.31062937026679327, -0.562666951622192,
                         -0.8984947637863987], "color": "red_9392"},
    {"id": 7, "vector": [-0.33445148015177995, -0.2567135004164067, 0.8987539745369246, 0.9402995886420709,
                         0.5378064918413052], "color": "grey_8510"},
    {"id": 8, "vector": [0.39524717779832685, 0.4000257286739164, -0.5890507376891594, -0.8650502298996872,
                         -0.6140360785406336], "color": "white_9381"},
    {"id": 9, "vector": [0.5718280481994695, 0.24070317428066512, -0.3737913482606834, -0.06726932177492717,
                         -0.6980531615588608], "color": "purple_4976"}
]


# todo 1 创建数据库,返回客户端
def create_db():
    client = MilvusClient()
    databases = client.list_databases()

    if 'milvus_demo' not in databases:
        client.create_database('milvus_demo')
        print('milvus_demo创建成功')
    else:
        client.use_database('milvus_demo')

    return client


# todo 2 创建表

def create_table():
    client = MilvusClient()
    client.use_database('milvus_demo')

    # 1.定义表结构(集合的schema)
    # auto_id:主键字段需要手动赋值(不自动生成)
    # enable_dynamic_field :允许插入未在schema中定义额外字段
    schema = client.create_schema(auto_id=False, enable_dynamic_filed=True)
    # 2.向schame中添加具体的字段(表中插列)
    # id 是 主键  类型是64整型
    schema.add_field(field_name='id', datatype=DataType.INT64, is_primary=True)
    schema.add_field(field_name='vector', datatype=DataType.FLOAT_VECTOR, dim=5)
    schema.add_field(field_name='scalar', datatype=DataType.VARCHAR, max_length=256, description='标量字段')

    # 3.创建表名(集合)
    client.create_collection(collection_name='demo_v1', schema=schema)
    print('创建表成功')


# todo 3  定义客户端
client = create_db()


# todo 4 构建索引&添加&删除
def get_indexs():
    # 1.初始化索引参数配置对象,用于设置索引相关参数
    prepare_index = client.prepare_index_params()
    # # 2. 配置向量字段索引下的信息
    prepare_index.add_index(field_name='vector', index_type='IVF_FLAT', metric_type='COSINE', index_name='vector_index',
                            params={'nlist': 128})

    client.create_index(collection_name='demo_v1', index_params=prepare_index)
    # --------场景2: 查看索引信息
    # #1.列出集合中所有索引
    result = client.list_indexes(collection_name='demo_v1')
    print(result)
    # 2.查看指定索引的配置信息
    result2 = client.describe_index(collection_name='demo_v1', index_name='vector_index')
    print(result2)

    # -------- 场景3: 集合的加载与释放
    # 1.查看一下当前集合状态
    print(f'集合的加载状态:{client.get_load_state(collection_name="demo_v1")}')

    # 2.将集合加载到内存,加载后,更新集合的状态
    client.load_collection(collection_name='demo_v1')
    print(f'结合加载状态:{client.get_load_state(collection_name="demo_v1")}')

    # 3.释放集合 ,释放以后,就变成未加载状态
    client.release_collection(collection_name='demo_v1')
    print(f'结合加载状态:{client.get_load_state(collection_name="demo_v1")}')

    # ------场景4:删除索引------------------------------------------
    # 1.删除向量索引
    client.drop_index(collection_name='demo_v1', index_name='vector_index')

    # ------------场景5: 给标量字段添加索引------------------------
    # 1.配置标量字段的参数
    prepare_index = client.prepare_index_params()
    prepare_index.add_index(field_name='scalar', index_name='scalar_index')
    # 执行索引操作
    client.create_index(collection_name='demo_v1', index_params=prepare_index)
    # 3.查看索引信息
    print(f'查看索引信息:{client.list_indexes(collection_name="demo_v1")}')
    result3 = client.describe_index(collection_name='demo_v1', index_name='scalar_index')
    print(f'索引的详细信息{result3}')


# todo 5 表数据操作(实体对象) 的增删改
def get_add_del_update(data):
    # ----------------场景一: 创建collection集合(milvus表)-----------------
    # 1.直接创建集合  ,无需提前定义schema(结构)  简化方式
    # 参1: 集合名称       参2:向量的维度       参3:向量相似度计算方式
    client.create_collection(collection_name='demo_v2', dimension=5, metric_type='IP')
    # 2.执行upsert(insert+update)操作(存在就更新,不存在就添加)
    client.upsert(collection_name="demo_v2", data=data)

    # 场景2: 给集合创建分区
    # 1. 创建分组 (分组用于对集合数据分类存储,方便管理)
    client.create_partition(collection_name='demo_v2', partition_name='partitionA')
    # 2.向分区内执行upsert操作
    client.upsert(collection_name='demo_v2', data=data, partition_name='partitionA')
    client.flush(collection_name='demo_v2')


def get_del(data):
    client.create_collection(collection_name='demo_v3', dimension=5, metric_type='IP')
    client.create_partition(collection_name='demo_v3', partition_name='partitionA')
    client.upsert(collection_name='demo_v3', data=data)
    client.upsert(collection_name='demo_v3', data=data, partition_name='partitionA')
    print('数据添加成功')

    # #--------场景1: 从集合删除数据
    res = client.delete(collection_name='demo_v3', filter='id in [5,6]')
    print(res)
    res2 = client.delete(collection_name='demo_v3', partition_name='partitionA', ids=[2])
    print(res2)


def get_query():
    # -------场景1: 单一向量查询(根据单个向量找相似结果)
    # #search() 执行单一向量查询
    # #参1: 集合名 (表名)   参2:查询向量列表(用户的问题转换为向量)  参3:查询最相似的数量(top-k)  参4:查询参数  参5:返回字段列表
    result = client.search(
        collection_name='demo_v3',
        data=[[0.19886812562848388, 0.06023560599112088, 0.6976963061752597, 0.2614474506242501, 0.838729485096104]],
        limit=2,
        output_fields=['id', 'vector', 'color'],
        metric_type='IP'
    )

    print(result)
    print('* ' * 50)

    # -------场景2:批量向量搜索(根据多个向量同时去找相似结果)---------------------
    result2 = client.search(
        collection_name='demo_v3',
        data=[
            [0.19886812562848388, 0.06023560599112088, 0.6976963061752597, 0.2614474506242501, 0.838729485096104],
            [0.3172005263489739, 0.9719044792798428, -0.36981146090600725, -0.4860894583077995, 0.95791889146345]
        ],
        limit=2,
        output_fields=['id', 'vector', 'color'],
        metric_type="IP"
    )
    print(result2)

    # ------场景3: 分区搜索  仅在指定的分区去找相似的结果
    result3 = client.search(
        collection_name="demo_v2",
        data=[[0.19886812562848388, 0.06023560599112088, 0.6976963061752597, 0.2614474506242501, 0.838729485096104]],
        limit=2,
        search_params={"metric_type": "IP"},
        partition_names=["partitionA"]
    )
    print(f"result3:{result3}")

    # ------------场景4:指定输出字段搜索(仅返回需要的字段,减少数据量)
    res3 = client.search(
        collection_name="demo_v3",
        data=[[0.19886812562848388, 0.06023560599112088, 0.6976963061752597, 0.2614474506242501, 0.838729485096104]],
        limit=2,
        search_params={"metric_type": "IP"},
        output_fields=["id"]
    )
    print(res3)

    # -------场景5: 带过滤条件的搜索(先过滤,在找相似的结果)-------
    res4 = client.search(
        collection_name="demo_v3",
        data=[[0.19886812562848388, 0.06023560599112088, 0.6976963061752597, 0.2614474506242501, 0.838729485096104]],
        limit=2,
        search_params={"metric_type": "IP"},
        output_fields=["id", "color"],
        filter='color like "red%"'
    )
    print(f"res4:{res4}")

    # --------场景6:范围搜索(仅返回相似度在指定范围的结果)
    dict_params = {
        'metric_type': 'IP',
        "params": {
            'radius': 0.8,
            'range_dilter': 1
        }
    }
    res5 = client.search(
        collection_name="demo_v3",
        limit=6,
        search_params=dict_params,
        output_fields=["id", "color"],
        filter='color like "red%"',
        data=[[0.19886812562848388, 0.06023560599112088, 0.6976963061752597, 0.2614474506242501, 0.838729485096104]],

    )

    print(f"res5:{res5}")


# todo 8 实体对象(表数据)的混合查询 ->准备数据
def complex_query_add_data():
    # ----------场景1:定义schema(包含多个向量字段的表结构)
    # 1.创建集合 schema  ,禁止动态字段(仅允许使用预定义字段) 只能使用你在schema中预定义的字段
    schema = client.create_schema(enable_dynamic_filed=False)
    # 2.向schema 添加字段
    #  film_id  电影(id)      64位整数   is_primary  标注为主键
    schema.add_field(field_name='film_id', datatype=DataType.INT64, is_primary=True)
    # filmVector 电影特征向量       浮点型向量  维度  5
    schema.add_field(field_name='filmVector', datatype=DataType.FLOAT_VECTOR, dim=5)
    # posterVector 电影海报特征向量       浮点型向量  维度  5
    schema.add_field(field_name='posterVector', datatype=DataType.FLOAT_VECTOR, dim=5)

    # ---- 场景2: 定义多个向量字段的索引
    # 1.初始化索引参数配置对象
    prepare_index = client.prepare_index_params()
    prepare_index.add_index(field_name='film_id', index_type='INT64')
    # 2.为filmVector
    # 参1:要创建索引字段名  参2: 索引类型(适合大规模向量检索)   参3:相似度计算方式(欧式距离)   参4:聚类数量
    prepare_index.add_index(field_name='filmVector', index_type='IVF_FLAT', metric_type='L2', params={'nlist': 128})
    # 3.为posterVector
    prepare_index.add_index(field_name='posterVector', index_type='IVF_FLAT', metric_type='COSINE')
    # ---- 场景3:创建包含多个向量字段的集合
    client.create_collection(collection_name='demo_v4', schema=schema, index_params=prepare_index)

    # ----------场景4:生成并插入实体数据(批量添加多条记录)
    # 1.初始化实体列表(存储多条数据)
    entities = []
    for _ in range(1000):
        film_id = random.randint(1, 10000)
        filmVector = [random.random() for _ in range(5)]
        posterVector = [random.random() for _ in range(5)]
        entity = {'film_id': film_id, 'filmVector': filmVector, 'posterVector': posterVector}
        entities.append(entity)

    client.upsert(collection_name='demo_v4', entities=entities)


# todo 9 实体对象(表数据)的混合查询->代码实现
def complex_query():
    # --------------场景1:多向量字段混合检索(合并多个搜索结果)
    # 1.配置第1个向量字段的搜索要求
    query_filmVector = [
        [0.8896863042430693, 0.370613100114602, 0.23779315077113428, 0.38227915951132996, 0.5997064603128835]]
    # 1.2 配置搜索参数
    search_param1 = {
        "data": query_filmVector,  # 搜索用的向量数据
        "anns_field": "filmVector",  # 指定搜索向量字段filmVector
        "param": {"metric_type": "L2", "nprobe": 10},  # 使用欧式距离    IVF索引参数(查看前10个最相似的簇)
        "limit": 2  # 该字段搜索最多返回2条结果
    }
    # 1.3创建第1个搜索请求对象   ** 把字典值当参数进行传递
    request_1 = AnnSearchRequest(**search_param1)

    query_posterVector = [
        [0.02550758562349764, 0.006085637357292062, 0.5325251250159071, 0.7676432650114147, 0.5521074424751443]]
    # 2.配置搜索参数
    search_param2 = {
        "data": query_posterVector,
        "anns_field": "posterVector",
        "param": {"metric_type": "COSINE"},
        "limit": 2
    }
    request_2 = AnnSearchRequest(**search_param2)

    # 3.配置混合搜索(即:  合并两个字段的搜索结果)
    # 3.1组合多个搜索请求对象
    res = [request_1, request_2]

    # 3.2定义结果排序器
    # ranker=RRFRanker(100)  (倒数排序融合)
    ranker = WeightedRanker(0.7, 0.3)  # (加权排序)

    # 4.执行混合查询
    outputs = client.hybrid_search(
        collection_name="demo_v4",  # 目标集合名称
        reqs=res,  # 执行多个搜索请求列表
        ranker=ranker,  # 用于合并结果的排序器
        output_fields=["filmVector", "posterVector"],
        limit=2
    )
    print(f"混合检索结果:{outputs}")

    # -----------场景2:遍历抖音检索结果详细
    for hits in outputs:
        print("Top K结果")
        for hit in hits:
            print(hit)


if __name__ == '__main__':
    # 1.1测试建库
    # create_db()
    # 1.2测试建表
    # create_table()
    # get_indexs()
    # get_add_del_update(data)
    # get_del(data)
    # get_query()
    # complex_query_add_data()
    # complex_query()
    client.drop_collection(collection_name="demo_v4")

​

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐