楼主: 李建卓
47 0

[战略与规划] Chroma 基础与索引 [推广有奖]

  • 0关注
  • 0粉丝

等待验证会员

学前班

80%

还不是VIP/贵宾

-

威望
0
论坛币
0 个
通用积分
0
学术水平
0 点
热心指数
0 点
信用等级
0 点
经验
30 点
帖子
2
精华
0
在线时间
0 小时
注册时间
2018-11-14
最后登录
2018-11-14

楼主
李建卓 发表于 2025-12-5 20:06:27 |AI写论文

+2 论坛币
k人 参与回答

经管之家送您一份

应届毕业生专属福利!

求职就业群
赵安豆老师微信:zhaoandou666

经管之家联合CDA

送您一个全额奖学金名额~ !

感谢您参与论坛问题回答

经管之家送您两个论坛币!

+2 论坛币

Chroma 基础与索引详解(day23)

在构建基于大语言模型的知识库系统中,向量数据库扮演着至关重要的角色。Chroma 作为一种轻量级的向量存储工具,因其简洁易用、支持本地持久化和高效的相似性检索能力而广受青睐。

Chroma 的核心概念解析

Chroma 本质上是一个内嵌式向量数据库(Vector Store),无需独立部署服务即可运行,适用于快速搭建本地知识库场景。其主要特性包括:

  • 支持内嵌持久化,数据可保存至本地文件系统
  • 以 Collection 为单位组织数据,类似于传统数据库中的“表”
  • 可同时管理三种类型的数据:embeddings(向量)、documents(原始文本)、metadata(元信息)
  • 内置近似最近邻(ANN)索引机制,默认使用 HNSW 算法,提升搜索效率
- 数据插入时,Chroma 会在后台维护一个 向量索引结构

- 查询时,Chroma 不会暴力比对所有向量,而是用索引快速找到最相似的几个

Chroma 的结构组成

理解 Chroma 的基本架构有助于更高效地进行开发与调试。整个系统由以下几个关键组件构成:

  • Client:作为访问 Chroma 的入口,负责连接并操作数据库实例。
  • Collection:代表一个逻辑上的数据集合,用于集中存放文档及其对应的向量表示与元数据。
  • Record:每条记录包含以下字段:
    • id:唯一标识符
    • embedding:浮点数列表形式的向量表达
    • document:原始文本内容
    • metadata:附加信息,如来源文件名、页码等自定义键值对

以下是使用原生 Chroma API 添加数据的基本示例:

import chromadb

client = chromadb.Client()
collection = client.create_collection("my_collection")

collection.add(
    ids=["1"],
    documents=["这是一个示例文档"],
    metadatas=[{"source": "demo.txt"}],
    embeddings=[[0.1, 0.2, 0.3]]  # 通常由 embedding 模型生成
)

尽管原生接口功能完整,但在实际项目中,尤其是结合文档加载器和 LLM 应用时,开发者更倾向于使用 LangChain 封装后的 Chroma 接口,便于与其他模块无缝集成。

持久化模式的选择

Chroma 提供两种运行模式,根据使用场景灵活选择:

  1. 内存模式(默认)
    • 通过 chromadb.Client() 启动
    • 数据仅存在于程序运行期间,进程终止后自动清除
    • 适合临时测试或演示用途
  2. 持久化模式(推荐用于知识库建设)
    • 使用 chromadb.PersistentClient(path="chroma_db")
    • 所有数据将被写入指定目录(如 chroma_db)
    • 重启程序后可通过相同路径恢复原有数据集

在 LangChain 中启用持久化只需设置参数:persist_directory="chroma_db",即可实现本地向量库的长期存储与复用。

索引机制与相似度度量方式

Chroma 默认采用 HNSW(Hierarchical Navigable Small World)算法构建 ANN(Approximate Nearest Neighbor)索引,能够在高维空间中快速完成向量间的相似性匹配。

- cosine:余弦相似度(最常见,用于文本 embedding)

- l2:欧氏距离

- ip:Inner Product(点积)

常见的距离度量方式包括:

  • Cosine Similarity(余弦相似度)——常用于衡量方向一致性
  • L2 距离(欧氏距离)——反映数值上的绝对差异
  • Inner Product(内积)——适用于归一化后的向量比较

在创建 Collection 时,可通过 metadata 显式指定所使用的空间度量标准:

collection = client.create_collection(
    name="my_collection",
    metadata={"hnsw:space": "cosine"}
)

而在 LangChain 的封装中,一般默认使用 cosine 相似度,该设定与主流 embedding 模型(如 BGE 系列)输出的归一化向量相匹配。

整合文档加载器构建知识库流程

结合 LangChain 提供的文档加载器(如 TextLoader、PDFLoader),可以实现从原始文件到向量化知识库的完整链路。以下是一个完整的处理流程示例:

# 用于加速pip下载
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
//进入虚拟环境之后:
pip install "langchain>=0.2.0" chromadb sentence-transformers pypdf
from typing import List
from langchain_core.embeddings import Embeddings
from sentence_transformers import SentenceTransformer

class SentenceTransformerEmbeddings(Embeddings):
    # 推荐用于中文文本的模型:bge-small-zh-v1.5
    def __init__(self, model_name: str = "BAAI/bge-small-zh-v1.5"):
        self.model = SentenceTransformer(model_name)

    def embed_query(self, text: str) -> List[float]:
        return self.model.encode(text, show_progress_bar=True, normalize_embeddings=True).tolist()

    def embed_documents(self, texts: List[str]) -> List[List[float]]:
        return self.model.encode(texts, normalize_embeddings=True).tolist()
import os
import shutil
from langchain_community.document_loaders import PyPDFLoader

通过上述代码,我们可以加载 PDF 文件,并利用 Sentence-BERT 类模型将其转换为高质量的语义向量,最终存入 Chroma 数据库中,形成可查询的知识体系。

def loadPdfFile():
    print(">>>>>>>>>>>>>>> loading pdf file Start<<<<<<<<<<<<<<<<<<")
    docs = []
    pdf_loader = PyPDFLoader("health.pdf")
    pdf_docs = pdf_loader.load()
    docs.extend(pdf_docs)
    print(f"已经加载{len(pdf_docs)}页pdf文件")
    print(">>>>>>>>>>>>>>> loading pdf file End<<<<<<<<<<<<<<<<<<")
    return docs
- 数据插入时,Chroma 会在后台维护一个 向量索引结构

- 查询时,Chroma 不会暴力比对所有向量,而是用索引快速找到最相似的几个
def split_docs(docs):
    text_splitter = RecursiveCharacterTextSplitter(
        separators=["\n\n", "\n", "。", "!", "?", ",", ";", "、", ":"],
        chunk_size=800,
        chunk_overlap=150,
        length_function=len,
        is_separator_regex=False
    )
    split_docs = text_splitter.split_documents(docs)
    print(f"已经分词{len(split_docs)}个段落")
    return split_docs
- cosine:余弦相似度(最常见,用于文本 embedding)

- l2:欧氏距离

- ip:Inner Product(点积)
def build_or_load_vector_store(docs, persist_directory="./chroma_db"):
    embeddings = SentenceTransformerEmbeddings()
    if os.path.exists(persist_directory) and len(os.listdir(persist_directory)) > 0:
        print("---------------------- Found existing index, loading ----------------------")
        vector_store = Chroma(persist_directory=persist_directory,
                            embedding_function=embeddings)
        print(f"Found {vector_store._collection.count()} existing documents")
        print("---------------------- Found existing index, loading END----------------------")
    else:
        print("======================= Creating new index =======================")
        vector_store = Chroma.from_documents(docs,
                                          embeddings,
                                          persist_directory=persist_directory)
def query_vector_store(vector_store, query, k: int = 1):
    print(">>>>>>>>>>>>>>>>>>>>>>> Querying vector store Start<<<<<<<<<<<<<<<<<<<<<<<<")
    
    # 方法1: 带分数的相似度搜索
    print("\n=== 方法1: 标准相似度搜索 ===")
    results = vector_store.similarity_search_with_score(query, k=k)
    for i, (doc, score) in enumerate(results, start=1):
        print(f"\n--- 结果 {i} (相似度: {score:.4f}) ---")
        print(f"Similarity Score: {score:.4f}")
        print(f"来源: {doc.metadata.get('source', '未知')}")
        print(f"页码: {doc.metadata.get('page', '未知')}")
        print(f"内容预览:\n{doc.page_content[:300]}...")

def build_or_load_vector_store(docs):
    print("======================= Creating new index ,START =======================")
    vector_store = Chroma.from_documents(
        documents=docs,
        embedding=OllamaEmbeddings(model="nomic-embed-text"),
        persist_directory="./chroma_db"
    )
    print(f"Created {vector_store._collection.count()} documents")
    print("======================= Creating new index ,END =======================")
    return vector_store

def split_docs(docs):
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=500,
        chunk_overlap=50
    )
    return text_splitter.split_documents(docs)

def loadPdfFile():
    loader = PyPDFLoader("health.pdf")
    return loader.load()

if __name__ == "__main__":
    docs = loadPdfFile()
    split_docs = split_docs(docs)
    vector_store = build_or_load_vector_store(split_docs)
    
    while True:
        query = input("\n请输入查询 (输入 'q' 退出): ").strip()
        if query.lower() in ["q"]:
            print("程序已退出")
            break
        query_vector_store(vector_store, query, k=3)

# === 方法1: 标准相似度搜索 ===
# Batches: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 122.31it/s]

# --- 结果 1 (相似度: 0.4841) ---
# Similarity Score: 0.4841
# 来源: health.pdf
# 页码: 0
# 内容预览:
# 养生冷知识:"逆腹式呼吸法" - 被遗忘的古法呼吸术
# 核心知识点:
# 普通腹式呼吸是吸气时腹部鼓起,呼气时腹部收缩。而逆腹式呼吸正好相反:
# 吸气时:腹部自然内收,横膈膜上升
# 呼气时:腹部放松外鼓,横膈膜下降
# 冷门数据点:
# 1.
# 历史渊源(适合时间维度向量):
# 源自道家"丹田呼吸法",记载于《云笈七签》
# 明清武术家用于内功修炼(形意拳、八卦掌秘传)
# 现代被 NASA 研究用于宇航员抗G 力训练
# 2.
# 生理机制对比(适合数值向量):
# 普通腹式呼吸:
# - 潮气量:500-700ml
# - 呼吸频率:12-20 次/分钟
# - 副交感神经激活度:中等
# 逆腹式呼吸:
# - 潮气量:800-1000ml(增加 4...
- 数据插入时,Chroma 会在后台维护一个 向量索引结构

- 查询时,Chroma 不会暴力比对所有向量,而是用索引快速找到最相似的几个
# --- 结果 2 (相似度: 0.5579) --- # Similarity Score: 0.5579 # 来源: health.pdf # 页码: 1 # 内容预览: # 2019 年《呼吸医学》期刊研究:逆腹式呼吸组 vs 普通呼吸组 # 血压下降:12.4/8.2 mmHg vs 5.3/3.1 mmHg # 皮质醇降低:28% vs 11% # 肠道蠕动频率:增加 45% vs 15% # 日本研究:激活"第二大脑"(肠神经系统) # 产生血清素量:提高 30%(通过迷走神经通路)
- cosine:余弦相似度(最常见,用于文本 embedding)

- l2:欧氏距离

- ip:Inner Product(点积)

现代应用领域(适用于分类向量)

该技术在多个专业人群中展现出显著效果:

  • 运动员:有效增强核心肌群的稳定性,研究显示其训练效率相较传统普拉提提升约20%。
  • 程序员:有助于改善因长时间专注屏幕导致的“屏幕呼吸暂停症”,调节呼吸节律。
  • 音乐家:不仅能提升肺活量,还能优化发声控制,此法为声乐教学中的隐秘技巧之一。

需要注意的是,孕妇在进入孕中期后应避免使用该方法,因其可能诱发宫缩反应,存在潜在风险。

鲜为人知的科学机制

其背后的作用原理涉及人体深层生理调节:

  • 可激活“横膈膜-盆底肌联合泵”系统,进而促进淋巴液的回流,增强代谢循环。
  • 同时引发“肝源性 IGF...”相关通路的生理响应,具体机制仍在进一步研究中。
chromadb.errors.InvalidArgumentError: Collection expecting embedding with dimension of 384, got 512

技术提示:关于向量维度不匹配的问题

运行过程中若出现如下错误:

chromadb.errors.InvalidArgumentError: Collection expecting embedding with dimension of 384, got 512

这通常是由于新模型生成的向量维度(如512维)与已存在的Chroma数据库所用的旧维度(如384维,常见于使用 all-MiniLM-L6-v2 模型时创建)不一致所致。解决方案如下:

需清除原有的向量数据库,以确保后续索引重建时使用当前模型的正确维度。

import shutil
def build_or_load_vector_store(docs, persist_directory="./chroma_db"):
    embeddings = SentenceTransformerEmbeddings()
    if os.path.exists(persist_directory):
        print(f"删除旧的向量数据库: {persist_directory}")
        shutil.rmtree(persist_directory)
    print("======================= 创建新索引 =======================")
    vector_store = Chroma.from_documents(
        docs,
        embeddings,
        persist_directory=persist_directory
    )
    print(f"创建了 {vector_store._collection.count()} 个文档")
    return vector_store
二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

关键词:CHROM CHR OMA Rom Transformers

您需要登录后才可以回帖 登录 | 我要注册

本版微信群
jg-xs1
拉您进交流群
GMT+8, 2025-12-9 14:00