补充章节:向量化、向量数据库与模型工作原理

303 阅读17分钟

补充章节:向量化、向量数据库与模型工作原理

S.1 引言

在前五章中,我们详细讲解了如何使用 Python 实现 RAG 系统并通过 RESTful API 与 Spring Boot 项目集成。然而,作为没有 AI 开发经验的 Java 程序员,你可能对 RAG 系统中的一些核心概念(如向量化、向量数据库、嵌入模型和 LLM 模型)感到陌生。这些概念是 RAG 的基石,直接影响系统的性能和效果。

本补充章节旨在以通俗的方式解答以下问题:

  1. 什么是向量化?为什么文本需要向量化?
  2. 向量化的维度增加意味着什么?代价是什么?
  3. 向量数据库内部存储的结构是什么样的?
  4. 嵌入式模型和 LLM 模型的工作原理是什么?

我们将从基础概念入手,逐步深入,使用类比和示例帮助你理解这些复杂的技术。本章节面向小白,假设你熟悉 Java 和 Spring Boot,但对 AI 和 Python 的知识有限。所有代码示例都与前五章的 RAG 系统保持一致,确保内容无缝衔接。

S.2 什么是向量化?为什么文本要向量化?

S.2.1 什么是向量化?

向量化(Vectorization)是将非数值数据(如文本、图像或音频)转换为数字向量(一组有序的数字)的过程。在 RAG 系统中,我们主要关注文本的向量化,即将一段文字(单词、句子或文档)转换为一个高维向量(通常是数百到数千维的数字数组)。

类比:想象你在一家图书馆工作,图书馆里有成千上万本书。你需要一种方法快速判断两本书是否内容相似。一种简单的办法是为每本书创建一个“特征清单”,比如:

  • 包含多少科技词汇?
  • 包含多少历史事件?
  • 情感是积极还是消极?

如果我们用数字表示这些特征(例如 [10, 5, 0.8]),每本书就变成了一个向量。通过比较这些向量,我们可以判断两本书的相似性。向量化就是为文本创建这样的“特征清单”,但它由计算机自动生成,包含更复杂的语义信息。

代码示例(基于第二章):

from sentence_transformers import SentenceTransformer

# 加载嵌入模型
model = SentenceTransformer('all-MiniLM-L6-v2')

# 文本向量化
text = "This is a test sentence."
vector = model.encode(text)

print(f"Vector shape: {vector.shape}")
print(f"Vector sample: {vector[:5]}...")

输出

Vector shape: (384,)
Vector sample: [0.123, -0.456, 0.789, -0.234, 0.567]...

说明

  • all-MiniLM-L6-v2 模型将文本转换为一个 384 维的向量。
  • 每个数字表示文本在某个语义维度上的特征值。

S.2.2 为什么文本要向量化?

计算机无法直接理解文本,因为它们处理的是数字。向量化将文本转换为计算机可以处理的数字格式,同时保留文本的语义信息。在 RAG 系统中,文本向量化有以下关键作用:

  1. 语义相似性比较

    • RAG 需要根据用户查询(如“产品支持哪些操作系统?”)从知识库中检索相关文档。
    • 通过将查询和文档都转换为向量,我们可以计算它们的相似性(如余弦相似度),找到语义上最接近的文档。
    • 类比:向量化就像给每本书打上一个“语义标签”,让图书馆管理员快速找到与查询最匹配的书。
  2. 高效检索

    • 向量化的文本可以存储在向量数据库(如 FAISS)中,支持快速的相似性搜索。
    • 相比传统的关键词搜索,基于向量的语义搜索能捕捉更深层次的含义。例如,“操作系统”和“OS”在语义上相似,向量化的表示会反映这一点。
  3. 上下文增强

    • RAG 将检索到的文档向量与查询向量结合,生成丰富的上下文,输入到生成模型(如 LLM)。
    • 向量化的表示确保上下文包含语义相关的信息,提高生成回答的准确性。

示例
假设知识库中有以下文档:

  • 文档 1:“产品支持 Windows 和 Linux。”
  • 文档 2:“本产品是一款智能设备。”

用户查询:“产品支持哪些系统?”。通过向量化,查询和文档 1 的向量会更接近(因为语义相关),而与文档 2 的向量距离较远。RAG 系统因此优先检索文档 1。

S.2.3 向量化如何工作?

向量化通常由嵌入模型(Embedding Model)完成。嵌入模型是一个预训练的神经网络,能够将文本映射到高维向量空间。以下是简单的工作流程:

  1. 输入文本:将文本(单词、句子或段落)输入模型。
  2. 分词:模型将文本分解为更小的单元(如单词或子词)。
  3. 特征提取:模型分析文本的语义、语法和上下文,生成数字特征。
  4. 输出向量:将特征组合成一个固定长度的向量。

类比:嵌入模型就像一个“超级翻译员”,把人类的语言翻译成计算机能理解的“数字语言”,而且这个翻译保留了原文的含义。

S.3 向量化的维度增加意味着什么?代价是什么?

S.3.1 维度增加的意义

向量的维度是指向量包含的数字数量。例如,384 维向量是一个包含 384 个数字的数组。维度增加意味着以下几点:

  1. 更丰富的语义表示

    • 更高的维度可以捕捉更多的语义特征。例如,低维向量(10 维)可能只能表示基本的词汇信息,而高维向量(384 维或更高)可以捕捉复杂的语义关系,如同义词、上下文和情感。
    • 类比:低维向量像一张简单的地图,只能标注主要城市;高维向量像一张详细的地图,可以标注街道、建筑甚至地形。
  2. 更好的区分能力

    • 高维向量可以将语义上相似的文本区分得更细致。例如,“操作系统”和“系统软件”在低维空间可能难以区分,但在高维空间中会有不同的表示。
    • 这对于 RAG 的检索阶段尤为重要,因为它需要精确匹配查询和文档。
  3. 支持复杂任务

    • 高维向量适合处理复杂的 NLP 任务(如问答、翻译或对话),因为它们能表示更细粒度的信息。

代码示例
比较不同维度的嵌入模型:

from sentence_transformers import SentenceTransformer

# 低维模型
model_low = SentenceTransformer('all-MiniLM-L6-v2')  # 384 维
text = "Operating system"
vector_low = model_low.encode(text)
print(f"Low-dim vector shape: {vector_low.shape}")

# 高维模型
model_high = SentenceTransformer('all-mpnet-base-v2')  # 768 维
vector_high = model_high.encode(text)
print(f"High-dim vector shape: {vector_high.shape}")

输出

Low-dim vector shape: (384,)
High-dim vector shape: (768,)

说明

  • all-mpnet-base-v2 的 768 维向量比 all-MiniLM-L6-v2 的 384 维向量包含更多信息,可能在检索精度上更优。

S.3.2 维度增加的代价

虽然高维向量有优势,但也带来以下代价:

  1. 计算复杂度增加

    • 高维向量的计算(如相似性搜索)需要更多 CPU/GPU 资源。
    • 类比:处理一张详细地图需要更多时间和精力,计算机也是如此。
  2. 存储需求增加

    • 高维向量占用更多存储空间。例如,768 维向量比 384 维向量需要两倍的存储。
    • 对于大型知识库(数百万文档),存储和索引的成本会显著增加。
  3. 训练和推理成本

    • 生成高维向量的嵌入模型通常更大(更多参数),训练和推理时间更长。
    • 例如,all-mpnet-base-v2(约 400MB)比 all-MiniLM-L6-v2(约 80MB)更耗资源。
  4. 过拟合风险

    • 在某些情况下,高维向量可能捕捉过多噪声(不相关的细节),导致检索精度下降。
    • 类比:地图过于详细可能包含无关的小路,反而让人迷失方向。

权衡

  • 小型项目:选择低维模型(如 all-MiniLM-L6-v2),节省资源,适合快速原型。
  • 大型项目:选择高维模型(如 all-mpnet-base-v2),提高精度,但需更多硬件支持。
  • 在 RAG 系统中,我们通常在 384 到 768 维之间选择,以平衡性能和成本。

S.4 向量数据库内部存储的结构是什么样的?

S.4.1 向量数据库简介

向量数据库(如 FAISS、Milvus 或 Pinecone)是专门为存储和查询高维向量设计的数据库。与传统数据库(如 MySQL)不同,向量数据库针对向量相似性搜索进行了优化,广泛用于 RAG 系统的检索阶段。

类比:向量数据库像一个“智能图书馆”,不仅存储书的“特征清单”(向量),还能快速找到与查询最相似的书。

S.4.2 内部存储结构

向量数据库的内部存储结构主要包括以下部分:

  1. 向量数据

    • 每个文档片段的向量表示(例如 384 维浮点数数组)。

    • 存储为连续的内存块,优化检索效率。

    • 示例(FAISS):

      import faiss
      import numpy as np
      dimension = 384
      vectors = np.random.random((1000, dimension)).astype('float32')  # 1000 个向量
      index = faiss.IndexFlatL2(dimension)
      index.add(vectors)
      
  2. 元数据

    • 与向量关联的附加信息,如文档的文件名、片段 ID 或时间戳。

    • 元数据通常存储在键值对或数据库表中,与向量一一对应。

    • 示例(LangChain + FAISS):

      from langchain_community.vectorstores import FAISS
      texts = ["Doc 1", "Doc 2"]
      metadatas = [{"file": "doc1.txt"}, {"file": "doc2.txt"}]
      vector_store = FAISS.from_texts(texts, embedding_model, metadatas=metadatas)
      
  3. 索引结构

    • 向量数据库使用索引加速相似性搜索,常见索引类型包括:

      • Flat 索引:直接存储所有向量,精确但慢,适合小型数据集。
      • IVF 索引(Inverted File):将向量分组,加速搜索,适合大型数据集。
      • HNSW 索引(Hierarchical Navigable Small World):基于图结构的近似搜索,速度快但可能牺牲精度。
    • 类比:索引像图书馆的目录,Flat 索引是完整的书单,IVF 和 HNSW 是分组或快捷目录。

  4. 查询引擎

    • 提供相似性搜索的算法,如余弦相似度或欧几里得距离(L2 距离)。

    • 支持批量查询和 Top-K 搜索。

    • 示例(FAISS 查询):

      query_vector = np.random.random((1, dimension)).astype('float32')
      distances, indices = index.search(query_vector, k=5)  # 找 Top-5 相似向量
      

存储结构示意图(以 FAISS 为例):

向量数据库
├── 向量存储
│   ├── [0.123, -0.456, 0.789, ...]  # 文档 1 的向量
│   ├── [0.234, 0.567, -0.123, ...]  # 文档 2 的向量
│   └── ...
├── 元数据存储
│   ├── { "file": "doc1.txt", "chunk_id": "doc1_0" }
│   ├── { "file": "doc2.txt", "chunk_id": "doc2_0" }
│   └── ...
├── 索引
│   ├── FlatL2 或 IVF 或 HNSW
│   └── 指向向量存储的指针

S.4.3 FAISS 的具体实现

在 RAG 系统中,我们使用了 FAISS 作为向量数据库(参考第二章)。以下是 FAISS 的存储细节:

  • 向量存储:FAISS 使用内存中的浮点数数组存储向量,通常是 float32 类型以节省空间。

  • 索引构建

    • IndexFlatL2:直接存储所有向量,搜索时逐一计算距离。
    • IndexIVFFlat:将向量分成多个簇(cluster),只搜索最近的簇,加速查询。
  • 元数据管理:FAISS 本身不直接存储元数据,LangChain 通过额外的映射表关联向量和元数据。

  • 持久化:FAISS 索引可以保存到磁盘(.faiss 文件),便于重用。

代码示例(保存和加载 FAISS 索引):

# 保存索引
faiss.write_index(index, "knowledge_base_index.faiss")

# 加载索引
loaded_index = faiss.read_index("knowledge_base_index.faiss")

S.5 嵌入式模型的工作原理

S.5.1 嵌入模型简介

嵌入模型(Embedding Model)是一种神经网络,专门用于将文本转换为向量表示。在 RAG 系统中,我们使用了 sentence-transformers/all-MiniLM-L6-v2,它基于 Transformer 架构。

类比:嵌入模型像一个“语义压缩机”,把一段文字压缩成一个数字向量,保留最重要的语义信息。

S.5.2 工作原理(面向小白)

嵌入模型的工作可以分为以下步骤:

  1. 分词(Tokenization)

    • 将输入文本分解为更小的单元(tokens),如单词或子词。

    • 示例

      文本: "Operating system"
      Tokens: ["Oper", "##ating", "system"]
      
    • 分词器(如 BERT 的 WordPiece)将生词拆分为常见子词,确保模型能处理新词。

  2. 嵌入层(Embedding Layer)

    • 将每个 token 转换为一个初始向量(通常是 768 维或更高)。
    • 这些初始向量是可学习的参数,通过预训练优化。
    • 类比:每个 token 像一个“单词卡片”,嵌入层给它贴上一个数字标签。
  3. Transformer 编码

    • Transformer 是嵌入模型的核心,使用多层神经网络处理 token 序列。

    • 主要组件:

      • 自注意力机制(Self-Attention) :让模型关注文本中不同 token 之间的关系。例如,“operating” 和 “system” 一起构成一个有意义的短语。
      • 前馈神经网络:进一步提取特征。
    • 输出是每个 token 的上下文感知向量。

    • 类比:Transformer 像一个“语义分析器”,理解每个单词在句子中的角色。

  4. 池化(Pooling)

    • 将所有 token 的向量合并为一个固定长度的句子向量。

    • 常见方法:

      • 平均池化:取所有 token 向量的平均值。
      • CLS 池化:使用特殊 token(如 [CLS])的向量。
    • 示例all-MiniLM-L6-v2 使用平均池化生成 384 维句子向量。

  5. 输出向量

    • 最终输出是一个高维向量,表示整个文本的语义。
    • 向量经过预训练优化,确保语义相似的文本在向量空间中更接近。

代码示例(可视化嵌入过程):

from sentence_transformers import SentenceTransformer

model = SentenceTransformer('all-MiniLM-L6-v2')
texts = ["Operating system", "System software"]
vectors = model.encode(texts)

# 计算余弦相似度
from sklearn.metrics.pairwise import cosine_similarity
similarity = cosine_similarity([vectors[0]], [vectors[1]])[0][0]
print(f"Similarity between '{texts[0]}' and '{texts[1]}': {similarity:.4f}")

输出

Similarity between 'Operating system' and 'System software': 0.8923

说明

  • 高相似度(接近 1)表明两个文本在语义上非常接近。
  • 嵌入模型通过预训练学习了“operating system”和“system software”的语义关系。

S.5.3 预训练与微调

嵌入模型通常通过以下步骤开发:

  1. 预训练

    • 在大规模文本数据集(如 Wikipedia、Common Crawl)上训练,学习通用语言知识。
    • 任务:预测掩码词(Masked Language Modeling)或句子关系(Next Sentence Prediction)。
  2. 微调

    • 在特定任务(如句子相似性或问答)上进一步训练,优化向量质量。
    • all-MiniLM-L6-v2 在句子对任务上微调,擅长生成语义嵌入。

类比:预训练像让模型读遍图书馆的书,微调像让它专注于某一类书(如技术文档)。

S.6 LLM 模型的工作原理

S.6.1 LLM 简介

大型语言模型(Large Language Model,LLM)是用于生成自然语言的神经网络。在 RAG 系统中,我们使用了 google/flan-t5-base(参考第三章),它是一个基于 Transformer 的生成模型。

类比:LLM 像一个“超级作家”,根据给定的提示(prompt)创作连贯的文本。它通过学习大量文本,掌握了语言的模式和知识。

S.6.2 工作原理(面向小白)

LLM 的工作可以分为以下步骤:

  1. 分词(Tokenization)

    • 与嵌入模型类似,LLM 将输入文本分解为 token。

    • 示例

      提示: "Answer: What is the capital of France?"
      Tokens: ["Answer", ":", "What", "is", "the", "capital", "of", "France", "?"]
      
  2. 嵌入层

    • 将 token 转换为初始向量,输入到 Transformer。
  3. Transformer 解码

    • LLM 使用 Transformer 的编码器和解码器(或仅解码器)处理输入:

      • 编码器(如 T5):分析输入提示的语义,生成上下文表示。
      • 解码器:逐个生成输出 token,预测下一个最可能的 token。
    • 自注意力机制让模型关注提示中的关键部分(如“France”)。

    • 类比:编码器像理解问题,解码器像逐字写答案。

  4. 生成过程

    • LLM 按照自回归方式生成文本:每次预测一个 token,添加到输出序列,直到结束。

    • 常见策略:

      • 贪心搜索:选择概率最高的 token。
      • 束搜索(Beam Search) :保留多个候选序列,选择最佳。
    • 示例

      输入: "Answer: What is the capital of France?"
      输出: ["The", "capital", "of", "France", "is", "Paris", "."]
      
  5. 输出文本

    • 将生成的 token 序列转换回自然语言。

代码示例(参考第三章):

from transformers import T5Tokenizer, T5ForConditionalGeneration

tokenizer = T5Tokenizer.from_pretrained("google/flan-t5-base")
model = T5ForConditionalGeneration.from_pretrained("google/flan-t5-base")

input_text = "Answer: What is the capital of France?"
inputs = tokenizer(input_text, return_tensors="pt")
outputs = model.generate(**inputs, max_length=50)
answer = tokenizer.decode(outputs[0], skip_special_tokens=True)

print(f"Answer: {answer}")

输出

Answer: The capital of France is Paris.

S.6.3 预训练与微调

LLM 的开发过程包括:

  1. 预训练

    • 在海量文本数据上训练,学习语言的语法、语义和常识。
    • 任务:预测下一个词(Causal Language Modeling)或生成序列(Seq2Seq)。
    • 类比:让模型读完整个互联网,学会如何造句。
  2. 微调

    • 在特定任务(如问答、翻译)上优化,提高生成质量。
    • flan-t5-base 在多种指令任务上微调,擅长处理问答提示。
  3. RAG 中的作用

    • 在 RAG 中,LLM 接收检索到的文档和查询作为提示,生成准确的回答。
    • 检索到的文档提供额外上下文,弥补 LLM 预训练知识的局限。

类比:没有 RAG 的 LLM 像一个闭卷考试的学生,只能靠记忆;RAG 像开卷考试,学生可以参考课本(知识库),回答更准确。

S.7 嵌入模型与 LLM 的区别

为了帮助小白理解,以下是嵌入模型和 LLM 的对比:

特性嵌入模型LLM
目的将文本转换为向量,用于检索或比较生成自然语言文本
输出高维向量(如 384 维)文本序列(如句子或段落)
任务语义嵌入、相似性搜索问答、对话、翻译等生成任务
模型大小较小(几十到几百 MB)较大(几百 MB 到几十 GB)
计算需求较低(CPU 可运行)较高(推荐 GPU)
RAG 中的角色向量化查询和文档,构建向量数据库根据检索结果生成最终回答

类比

  • 嵌入模型像“图书管理员”,负责整理和查找书籍(文档)。
  • LLM 像“作家”,根据找到的书籍内容创作答案。

S.8 常见问题与解答

  1. 向量化为什么不直接用关键词?

    • 关键词搜索(如 Elasticsearch)依赖字面匹配,无法捕捉语义关系(如“操作系统”和“OS”)。
    • 向量化通过语义嵌入支持更智能的搜索,适合复杂查询。
  2. 如何选择向量维度?

    • 小型项目用 384 维(如 all-MiniLM-L6-v2),节省资源。
    • 大型项目用 768 维(如 all-mpnet-base-v2),提高精度。
    • 权衡计算和存储成本。
  3. 向量数据库与传统数据库的区别?

    • 传统数据库(如 MySQL)存储结构化数据,查询基于精确匹配。
    • 向量数据库存储高维向量,查询基于相似性,适合语义搜索。
  4. 嵌入模型和 LLM 需要自己训练吗?

    • 通常使用预训练模型(如 Hugging Face 提供的模型),无需自己训练。
    • 对于特定领域,可以微调模型,但需要数据和计算资源。
  5. 为什么 LLM 需要 RAG?

    • LLM 的知识来自预训练数据,可能过时或不完整。
    • RAG 通过检索外部知识库,提供最新、最相关的上下文,增强回答质量。

S.9 本章总结

本补充章节详细解答了向量化、向量数据库、嵌入模型和 LLM 的核心概念,面向没有 AI 经验的 Java 程序员。你现在应该掌握了以下内容:

  • 向量化:将文本转换为数字向量,保留语义信息,用于检索和比较。
  • 向量化的维度:高维向量捕捉更多语义,但增加计算和存储成本。
  • 向量数据库:存储向量、元数据和索引,支持快速相似性搜索。
  • 嵌入模型:基于 Transformer 将文本转换为向量,核心是分词、编码和池化。
  • LLM:基于 Transformer 生成自然语言,通过编码和解码处理提示。

通过这些知识,你可以更深入地理解 RAG 系统的运作原理,并为优化和扩展现有系统打下基础。如果你有进一步的问题,可以参考 Hugging Face 文档或 LangChain 社区资源。