C# 向量数据库实战入门:从基础概念到简单实现

1次阅读
没有评论

共计 3801 个字符,预计需要花费 10 分钟才能阅读完成。

image.webp

1. 背景介绍

传统关系型数据库在处理结构化数据时表现出色,但当我们需要处理高维向量数据时(比如图像特征、文本嵌入、用户偏好向量等),传统数据库就显得力不从心了。

C# 向量数据库实战入门:从基础概念到简单实现

向量数据库是专门为存储和查询向量数据而设计的数据库系统,它能够高效地执行相似度搜索,找到与查询向量最相似的向量。这在推荐系统、图像搜索、自然语言处理等领域有着广泛应用。

为什么需要向量数据库?

  • 高效相似度搜索:传统数据库无法高效执行 ” 找到与这个向量最相似的 10 个向量 ” 这样的查询
  • 高维数据处理:向量通常有几百甚至几千个维度,传统索引方法不适用
  • 实时性能:专业向量数据库针对向量查询做了大量优化

2. 核心概念

2.1 向量嵌入(Vector Embedding)

向量嵌入是将现实世界的数据(如文本、图像)转换为固定长度的数值向量的过程。例如:

  • 一段文本可以通过 BERT 等模型转换为 768 维的向量
  • 一张图片可以通过 ResNet 转换为 2048 维的特征向量

2.2 相似度计算

衡量两个向量相似度的常用方法:

余弦相似度(Cosine Similarity)

计算两个向量之间夹角的余弦值,范围[-1,1],值越大越相似。

公式:

cos(θ) = (A·B) / (||A|| * ||B||)

欧式距离(Euclidean Distance)

计算两个向量之间的直线距离,值越小越相似。

公式:

√Σ(Ai - Bi)²

2.3 近似最近邻搜索(ANN)

精确计算所有向量的相似度在大数据量时性能很差,ANN 算法通过牺牲少量精度换取查询速度的大幅提升。常见算法:

  • HNSW (Hierarchical Navigable Small World)
  • IVF (Inverted File Index)
  • LSH (Locality-Sensitive Hashing)

3. 技术实现:C# 内存向量数据库

下面我们实现一个简单的内存向量数据库,支持基本的 CRUD 和相似度搜索功能。

3.1 数据结构设计

public class VectorItem
{public string Id { get; set;}
    public float[] Vector { get; set;}
    public Dictionary<string, object> Metadata {get; set;} = new();}

public class SimpleVectorDatabase
{private readonly List<VectorItem> _items = new();
    private readonly int _dimensions;

    public SimpleVectorDatabase(int dimensions)
    {_dimensions = dimensions;}
}

3.2 相似度计算实现

public static class VectorMath
{public static float CosineSimilarity(float[] a, float[] b)
    {if (a.Length != b.Length)
            throw new ArgumentException("Vectors must be of same length");

        float dot = 0.0f, magA = 0.0f, magB = 0.0f;

        for (int i = 0; i < a.Length; i++)
        {dot += a[i] * b[i];
            magA += a[i] * a[i];
            magB += b[i] * b[i];
        }

        return dot / (MathF.Sqrt(magA) * MathF.Sqrt(magB));
    }

    public static float EuclideanDistance(float[] a, float[] b)
    {if (a.Length != b.Length)
            throw new ArgumentException("Vectors must be of same length");

        float sum = 0.0f;
        for (int i = 0; i < a.Length; i++)
        {float diff = a[i] - b[i];
            sum += diff * diff;
        }

        return MathF.Sqrt(sum);
    }
}

3.3 CRUD 操作实现

public class SimpleVectorDatabase
{
    // ... 前面的代码

    public void Add(VectorItem item)
    {if (item.Vector.Length != _dimensions)
            throw new ArgumentException($"Vector must have {_dimensions} dimensions");

        _items.Add(item);
    }

    public VectorItem Get(string id)
    {return _items.FirstOrDefault(x => x.Id == id);
    }

    public void Update(VectorItem item)
    {var existing = Get(item.Id);
        if (existing != null)
        {
            existing.Vector = item.Vector;
            existing.Metadata = item.Metadata;
        }
    }

    public void Delete(string id)
    {_items.RemoveAll(x => x.Id == id);
    }
}

3.4 相似度搜索实现

public class SimpleVectorDatabase
{
    // ... 前面的代码

    public IEnumerable<(VectorItem Item, float Score)> Search(float[] queryVector, 
        int limit = 10, 
        Func<float[], float[], float> similarityMetric = null)
    {if (queryVector.Length != _dimensions)
            throw new ArgumentException($"Query vector must have {_dimensions} dimensions");

        similarityMetric ??= VectorMath.CosineSimilarity;

        return _items
            .Select(item => (Item: item, Score: similarityMetric(queryVector, item.Vector)))
            .OrderByDescending(x => x.Score)
            .Take(limit);
    }
}

4. 完整代码示例

using System;
using System.Collections.Generic;
using System.Linq;

namespace VectorDatabaseDemo
{
    class Program
    {static void Main(string[] args)
        {
            // 创建一个 3 维的向量数据库
            var db = new SimpleVectorDatabase(3);

            // 添加一些向量
            db.Add(new VectorItem { 
                Id = "item1", 
                Vector = new[] { 1.0f, 2.0f, 3.0f},
                Metadata = new Dictionary<string, object> {{ "name", "红苹果"}, {"category", "水果"} }
            });

            db.Add(new VectorItem { 
                Id = "item2", 
                Vector = new[] { 4.0f, 5.0f, 6.0f},
                Metadata = new Dictionary<string, object> {{ "name", "香蕉"}, {"category", "水果"} }
            });

            // 搜索相似的向量
            var query = new[] { 1.1f, 2.1f, 2.9f};
            var results = db.Search(query);

            Console.WriteLine("最相似的结果:");
            foreach (var result in results)
            {Console.WriteLine($"ID: {result.Item.Id}, 相似度: {result.Score}, 名称: {result.Item.Metadata["name"]}");
            }
        }
    }
}

5. 性能考量

我们这个简单实现有几个性能问题:

  1. 线性扫描:每次查询都需要计算所有向量的相似度,时间复杂度 O(N)
  2. 内存限制:所有数据保存在内存中,不适合大数据量
  3. 无并行化:查询过程是单线程的

对于生产环境,当数据量超过几千条时,这种简单实现就会遇到性能瓶颈。

6. 生产环境建议

当你的应用需要:

  • 存储数百万以上向量
  • 低延迟查询(<100ms)
  • 高查询吞吐量
  • 分布式部署

就应该考虑使用专业向量数据库,如:

  • Milvus:开源的向量数据库,功能全面
  • Pinecone:全托管的向量数据库服务
  • Weaviate:支持向量搜索的图数据库
  • Qdrant:高性能 Rust 实现的向量数据库

7. 进阶学习指引

  1. 向量索引算法:深入学习 HNSW、IVF 等近似最近邻算法
  2. 向量压缩技术 :如 PQ(Product Quantization) 减少内存占用
  3. 混合查询:结合向量搜索和传统过滤条件
  4. 分布式向量数据库:学习如何水平扩展向量数据库

实践建议

  1. 尝试用真实数据(如商品图片特征)测试我们的简单实现
  2. 添加持久化功能,将向量数据保存到文件
  3. 实现批处理接口,支持批量添加向量
  4. 尝试集成一个开源 ANN 库(如 FAISS)来替换线性扫描

希望这篇入门教程能帮助你理解向量数据库的核心概念和基本实现。在实际项目中,根据数据规模和性能需求选择合适的解决方案。

正文完
 0
评论(没有评论)