• 【向量数据库】相似向量检索Faiss数据库的安装及余弦相似度计算(C++)


    简介

    Faiss 是一个强大的向量相似度搜索库,具有以下优点:

    1. 高效的搜索性能:Faiss 在处理大规模向量数据时表现出色。它利用了高度优化的索引结构和近似搜索算法,可以快速地执行最近邻搜索和相似度匹配,具有很低的查询延迟。

    2. 高度可扩展:Faiss 提供了多种索引结构和算法的选择,包括 k-d树、IVF(Inverted File System)和 PQ(Product Quantization)等。这些索引结构能够轻松应对大规模的向量数据集,并支持高效的并行计算和分布式处理。

    3. 高精度的近似搜索:Faiss 通过采用近似搜索技术,在保证搜索速度的同时,尽量接近精确搜索的结果。这使得 Faiss 在许多实际应用中能够有效地处理高维度的向量数据,如图像、文本和推荐系统等。

    4. 多语言支持:Faiss 提供了多种编程语言的接口,如 Python、Java 和 Go 等,使得它能够方便地集成到各种应用和平台中。

    然而,Faiss 也有一些局限性和缺点:

    1. 内存消耗:Faiss 在处理大规模向量数据时可能需要大量的内存。特别是在使用一些高级索引结构和算法时,内存消耗可能会很高。

    2. 学习曲线陡峭:对于初次接触 Faiss 的开发者来说,学习曲线可能会比较陡峭。理解并配置 Faiss 的索引结构、算法和参数可能需要一定的时间和经验。

    综上所述,Faiss 是一个强大而高效的向量相似度搜索库,适用于大规模的向量数据集和高维度的向量数据。它提供了高速的近似搜索和优化的索引结构,有助于构建复杂的检索系统和应用。然而,在使用 Faiss 时需要注意内存消耗和学习曲线的挑战。

    安装方法

    安装OpenBLAS

    OpenBLAS 是一个开源的数值线性代数库,用于高性能科学计算和数据处理。它提供了基于多核处理器和向量指令集的并行化实现,以加速矩阵运算和其他数值计算操作。

    git clone https://github.com/xianyi/OpenBLAS.git
    
    • 1

    gfortran 是 GNU Compiler Collection(简称 GCC)的一部分,它是 GNU 项目开发的免费开源的编译器套件。gfortran 是 GCC 提供的 Fortran 编译器,用于编译和执行 Fortran 程序。
    使用gfortran进行编译:

    sudo apt install gfortran
    cd OpenBLAS
    make FC=gfortran
    make install
    ln -s /opt/OpenBLAS/lib/libopenblas.so  /usr/lib/libopenblas.so
    LD_LIBRARY_PATH=/opt/OpenBLAS/lib
    export LD_LIBRARY_PATH
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这个特定的命令中,FC=gfortran将设置FC变量的值为"gfortran",指示构建过程使用gfortran作为Fortran编译器。

    安装lapack

    LAPACK(Linear Algebra Package)是一个用于数值线性代数计算的库,它提供了一系列高性能的算法和子程序,用于解决线性方程组、特征值问题、奇异值分解和相关的数值计算任务。

    wget http://www.netlib.org/lapack/lapack-3.4.2.tgz
    tar -zxf lapack-3.4.2.tgz
    cd lapack-3.4.2
    cp ./INSTALL/make.inc.gfortran ./
    mv make.inc.gfortran make.inc
    vi Makefile # 修改如下
    [#lib: lapacklib tmglib
    lib: blaslib variants lapacklig tmglib]
    make
    cd lapacke
    make  
    cp include/*.h /usr/include   
    cd .. 
    cp *.a /usr/lib
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    编译Faiss

    git clone https://github.com/facebookresearch/faiss.git
    cd faiss
    #这个命令是使用 CMake 构建一个项目,并将构建产物放置在名为 “build” 的目录中。
    cmake -B build .  -DFAISS_ENABLE_GPU=OFF -DFAISS_ENABLE_PYTHON=OFF
    #这个命令是使用 make 构建名为 “faiss” 的目标,并且指定构建目录为 “build”。
    make -C build -j faiss
    #这个命令是使用 make 进行构建,并将构建产物安装到系统中。同样在build目录下构建
    make -C build install
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    代码示例

    余弦相似度计算

    给定一个向量,在已有的两个向量中,找到余弦相似度最优的一个向量:

    #include 
    #include 
    #include 
    #include 
    #include 
    int main() {
      // 创建索引对象
      faiss::IndexFlatIP index(2); // 使用L2距离度量,2维向量
      // 添加向量数据
      float xb[4] = {1.0, 1.0, 0.0, 0.5}; // 2个2维向量
      //存储向量个数:
      int n=2;
      // 为了使用内积求出余弦相似度,要对每个向量进行归一化操作
      size_t d = 2; // 向量维度
      float norm[d]={0,0};
      // 计算向量的 L2 范数,是已经过开方的
      // 函数有四个参数:
      // x:指向输入向量数组的指针,所有向量在内存中是连续存储的。即向量数组的布局是 [x₀₀, x₀₁, ..., xₙ₋₁(d-1)]。
      // norms:指向输出结果数组的指针,用于存储计算出的 L2 范数。结果数组的布局和输入向量相同,即 [norm₀, norm₁, ..., normₙ₋₁]。
      // d:向量的维度,即每个向量的元素数目。
      // n:向量的数量(或者说是向量数组的长度)。
      faiss::fvec_norms_L2(norm, xb,d,n); 
    
      // 将每个向量归一化为单位向量,同时添加到索引中
      #pragma omp parallel
      {
        #pragma omp for
          for (int i = 0; i < n; i++) {
              // 每个向量的起始地址
              float* vector = &xb[i * d]; 
              // 将向量归一化为单位向量
              for (size_t j = 0; j < d; j++) {
                  vector[j] /= norm[i];
              }
              // 每次将1个向量添加到索引中
              #pragma omp critical
              {
                index.add(1, vector);
              }
          }
        //进行同步
        #pragma omp barrier
      }
      // 保存索引到文件
      faiss::write_index(&index, "index.faissindex");
    
      // 从文件加载索引
      faiss::Index* loaded_index = faiss::read_index("index.faissindex");
    
      // 执行搜索
      float xq[2] = {1.0, 0.0}; // 查询向量
      int k = 1; // 返回最接近的2个邻居
      faiss::idx_t* I = new faiss::idx_t[k]; // 邻居索引
      float* D = new float[k]; // 邻居距离
    
    // search 方法的主要参数如下:
    // n:表示要搜索的查询向量的数量,即查询向量的个数。
    // x:一个指向浮点数的指针,表示查询向量的数据。x 的大小应该为 n 乘以向量的维度。
    // k:表示要返回的相似邻居的数量。
    // distances:一个指向浮点数的指针,用于存储查询向量与相似邻居之间的距离。distances 的大小应该为 n 乘以 k。
    // labels:一个指向整数的指针,用于存储相似邻居的索引(标签)。labels 的大小应该为 n 乘以 k。
    
       loaded_index->search(1, xq, k, D, I);
    
      // 打印结果
      std::cout << "查询结果:" << std::endl;
      std::cout << "邻居索引: " << I[0] << ", 距离: " << D[0] << std::endl;
      //返回原始向量:
      // 获取索引为 i 的向量
      // 向量的维度
      std::vector<float> vectors(index.d);
      index.reconstruct(I[0], vectors.data());
      // 使用循环遍历并打印该向量每个元素
      for (const auto& element : vectors) {
            std::cout << element << " ";
      }
      std::cout << std::endl;
      // 释放内存
      delete loaded_index;
      delete[] I;
      delete[] D;
    
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84

    对应cmakelist:

    cmake_minimum_required(VERSION 3.5)
    project(fangdou)
    FIND_PACKAGE( OpenMP REQUIRED)
    if(OPENMP_FOUND)
    message("OPENMP FOUND")
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}")
    endif()
    
    # 添加 Faiss 库的路径
    set(FAISS_INCLUDE_DIR /usr/local/include/faiss)
    set(FAISS_LIBRARY_DIR /usr/local/lib)
    
    include_directories(
    ${FAISS_INCLUDE_DIR}
    )
    
    SET(OpenCV_DIR /usr/local/lib/cmake/opencv4/)
    FIND_PACKAGE(OpenCV REQUIRED)
    
    file(GLOB_RECURSE cpp_srcs ${CMAKE_SOURCE_DIR}/src/*.cpp ${CMAKE_SOURCE_DIR}/src/*.cc ${CMAKE_SOURCE_DIR}/src/*.h)
    
    link_directories(
    /usr/lib/x86_64-linux-gnu/
    ${FAISS_LIBRARY_DIR}
    )
    
    add_executable(${PROJECT_NAME} ${cpp_srcs})
    
    target_link_libraries(${PROJECT_NAME}  ${OpenCV_LIBS} faiss openblas)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    输出ID号而非索引的改进版

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    int main() {
    
      // 创建一个IndexFlatIP内积索引对象作为内部索引
      faiss::IndexFlatIP inner_index(2);
    
      // 创建一个IndexIDMap对象,将内部索引设置为IndexFlatIP
      faiss::IndexIDMap index(&inner_index);
      // 添加向量数据
      float xb[4] = {1.0, 1.0, 0.0, 0.5}; // 2个2维向量
      //ids
      faiss::idx_t ids[] = {1001, 1002};
      //存储向量个数:
      int n=2;
      // 为了使用内积求出余弦相似度,要对每个向量进行归一化操作
      size_t d = 2; // 向量维度
      float norm[d]={0,0};
      // 计算向量的 L2 范数,是已经过开方的
      // 函数有四个参数:
      // x:指向输入向量数组的指针,所有向量在内存中是连续存储的。即向量数组的布局是 [x₀₀, x₀₁, ..., xₙ₋₁(d-1)]。
      // norms:指向输出结果数组的指针,用于存储计算出的 L2 范数。结果数组的布局和输入向量相同,即 [norm₀, norm₁, ..., normₙ₋₁]。
      // d:向量的维度,即每个向量的元素数目。
      // n:向量的数量(或者说是向量数组的长度)。
      faiss::fvec_norms_L2(norm, xb,d,n); 
    
      // 将每个向量归一化为单位向量,同时添加到索引中
      #pragma omp parallel
      {
        #pragma omp for
          for (int i = 0; i < n; i++) {
              // 每个向量的起始地址
              float* vector = &xb[i * d]; 
              // 将向量归一化为单位向量
              for (size_t j = 0; j < d; j++) {
                  vector[j] /= norm[i];
              }
              // 每次将1个向量添加到索引中
              #pragma omp critical
              {
                index.add_with_ids(1, vector,&ids[i]);
              }
          }
        //进行同步
        #pragma omp barrier
      }
      // 保存索引到文件
      faiss::write_index(&index, "index.faissindex");
    
      // 从文件加载索引
      faiss::Index* loaded_index = faiss::read_index("index.faissindex");
    
      // 执行搜索
      float xq[2] = {1.0, 0.0}; // 查询向量
      int k = 1; // 返回最接近的2个邻居
      faiss::idx_t* I = new faiss::idx_t[k]; // 邻居索引
      float* D = new float[k]; // 邻居距离
    
    // search 方法的主要参数如下:
    // n:表示要搜索的查询向量的数量,即查询向量的个数。
    // x:一个指向浮点数的指针,表示查询向量的数据。x 的大小应该为 n 乘以向量的维度。
    // k:表示要返回的相似邻居的数量。
    // distances:一个指向浮点数的指针,用于存储查询向量与相似邻居之间的距离。distances 的大小应该为 n 乘以 k。
    // labels:一个指向整数的指针,用于存储相似邻居的索引(标签)。labels 的大小应该为 n 乘以 k。
    
       loaded_index->search(1, xq, k, D, I);
    
      // 打印结果
      std::cout << "查询结果:" << std::endl;
      std::cout << "邻居索引: " << I[0] << ", 距离: " << D[0] << std::endl;
      //此时不再支持返回原向量。
      // 释放内存
      delete loaded_index;
      delete[] I;
      delete[] D;
    
      return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
  • 相关阅读:
    XenServer 控制台和图形化安装Centos6
    【对象——对象及原型链上的属性——对象的操作方法】
    Java实现添加文字水印、图片水印
    linux中的开发工具
    暑假值得参加的写作竞赛有哪些?
    【云原生系列】云计算概念与架构设计介绍
    Qt MinGW opencv环境配置测试
    【案例教学】华为云API图引擎服务 GES的便捷性—AI帮助快速处理图片小助手
    Apollo 应用与源码分析:Apollo工程概述与AUTOSAR架构
    nginx+uwsgi+django部署(前后端不分离)
  • 原文地址:https://blog.csdn.net/hh1357102/article/details/132688566