Tutorial 4: 向量嵌入
什么是向量嵌入?
向量嵌入(Embedding) 是将文本转换为数值向量的技术,使计算机能够理解文本的语义。
文本 向量 (简化示意)
"猫" ──────────────────► [0.2, 0.8, 0.1, ...]
"狗" ──────────────────► [0.3, 0.7, 0.2, ...]
"汽车" ──────────────────► [0.9, 0.1, 0.8, ...]
语义相似的文本,向量也相似:
· "猫" 和 "狗" 的向量接近(都是动物)
· "猫" 和 "汽车" 的向量较远
为什么需要向量嵌入?
语义理解: 捕捉文本的含义,而不仅仅是字面匹配
相似度计算: 通过向量距离衡量文本相似度
高效检索: 向量数据库支持快速近似搜索
使用嵌入模型
1. Sentence Transformers
# pip install sentence-transformers
from sentence_transformers import SentenceTransformer
# 加载模型
model = SentenceTransformer('all-MiniLM-L6-v2')
# 单个文本嵌入
text = "人工智能正在改变世界"
embedding = model.encode(text)
print(f"文本: {text}")
print(f"向量维度: {len(embedding)}")
print(f"向量前5个值: {embedding[:5]}")
# 批量嵌入
texts = [
"机器学习是人工智能的分支",
"深度学习使用神经网络",
"今天天气很好"
]
embeddings = model.encode(texts)
print(f"\n批量嵌入: {len(embeddings)} 个向量")
2. OpenAI Embeddings
# pip install openai
from openai import OpenAI
client = OpenAI()
def get_openai_embedding(text, model="text-embedding-3-small"):
"""获取 OpenAI 嵌入"""
response = client.embeddings.create(
input=text,
model=model
)
return response.data[0].embedding
# 单个文本
text = "人工智能正在改变世界"
embedding = get_openai_embedding(text)
print(f"向量维度: {len(embedding)}")
# 批量嵌入
def get_openai_embeddings(texts, model="text-embedding-3-small"):
response = client.embeddings.create(
input=texts,
model=model
)
return [item.embedding for item in response.data]
3. LangChain Embeddings
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_openai import OpenAIEmbeddings
# HuggingFace 嵌入
hf_embeddings = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-MiniLM-L6-v2"
)
# OpenAI 嵌入
openai_embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
# 使用
text = "这是一段测试文本"
vector_hf = hf_embeddings.embed_query(text)
vector_openai = openai_embeddings.embed_query(text)
print(f"HuggingFace 向量维度: {len(vector_hf)}")
print(f"OpenAI 向量维度: {len(vector_openai)}")
# 批量嵌入
texts = ["文本1", "文本2", "文本3"]
vectors = hf_embeddings.embed_documents(texts)
计算相似度
1. 余弦相似度
import numpy as np
def cosine_similarity(v1, v2):
"""计算余弦相似度"""
dot_product = np.dot(v1, v2)
norm1 = np.linalg.norm(v1)
norm2 = np.linalg.norm(v2)
return dot_product / (norm1 * norm2)
# 示例
model = SentenceTransformer('all-MiniLM-L6-v2')
texts = [
"猫是一种可爱的宠物",
"狗是人类最好的朋友",
"Python是一种编程语言"
]
embeddings = model.encode(texts)
# 计算相似度矩阵
print("相似度矩阵:")
for i, text1 in enumerate(texts):
for j, text2 in enumerate(texts):
sim = cosine_similarity(embeddings[i], embeddings[j])
print(f" '{text1[:10]}...' vs '{text2[:10]}...': {sim:.3f}")
2. 欧氏距离
def euclidean_distance(v1, v2):
"""计算欧氏距离"""
return np.linalg.norm(np.array(v1) - np.array(v2))
# 距离越小,越相似
dist = euclidean_distance(embeddings[0], embeddings[1])
print(f"欧氏距离: {dist:.4f}")
3. 点积相似度
def dot_product_similarity(v1, v2):
"""计算点积相似度(适用于归一化向量)"""
return np.dot(v1, v2)
# 先归一化
def normalize(v):
return v / np.linalg.norm(v)
v1_norm = normalize(embeddings[0])
v2_norm = normalize(embeddings[1])
sim = dot_product_similarity(v1_norm, v2_norm)
嵌入模型选择
模型 |
维度 |
语言 |
特点 |
适用场景 |
|---|---|---|---|---|
all-MiniLM-L6-v2 |
384 |
英文 |
快速、轻量 |
通用检索 |
all-mpnet-base-v2 |
768 |
英文 |
高质量 |
高精度检索 |
text-embedding-3-small |
1536 |
多语言 |
OpenAI 最新 |
生产环境 |
text-embedding-3-large |
3072 |
多语言 |
最高质量 |
高要求场景 |
bge-large-zh |
1024 |
中文 |
中文优化 |
中文检索 |
m3e-base |
768 |
中文 |
中文优化 |
中文检索 |
中文嵌入模型
# 使用中文优化的嵌入模型
# 方式1: 使用 sentence-transformers
from sentence_transformers import SentenceTransformer
# BGE 中文模型
model = SentenceTransformer('BAAI/bge-large-zh-v1.5')
texts = [
"机器学习是人工智能的重要分支",
"深度学习在图像识别中应用广泛",
"今天的天气非常晴朗"
]
embeddings = model.encode(texts)
# 计算相似度
for i in range(len(texts)):
for j in range(i+1, len(texts)):
sim = cosine_similarity(embeddings[i], embeddings[j])
print(f"'{texts[i][:15]}...' vs '{texts[j][:15]}...': {sim:.3f}")
# 方式2: 使用 LangChain
from langchain_community.embeddings import HuggingFaceEmbeddings
embeddings_model = HuggingFaceEmbeddings(
model_name="BAAI/bge-large-zh-v1.5",
model_kwargs={'device': 'cpu'},
encode_kwargs={'normalize_embeddings': True}
)
实战:语义搜索
from sentence_transformers import SentenceTransformer
import numpy as np
class SemanticSearch:
"""语义搜索引擎"""
def __init__(self, model_name='all-MiniLM-L6-v2'):
self.model = SentenceTransformer(model_name)
self.documents = []
self.embeddings = None
def index(self, documents):
"""索引文档"""
self.documents = documents
self.embeddings = self.model.encode(documents)
print(f"索引了 {len(documents)} 个文档")
def search(self, query, top_k=3):
"""搜索相关文档"""
query_embedding = self.model.encode([query])[0]
# 计算相似度
similarities = []
for i, doc_embedding in enumerate(self.embeddings):
sim = np.dot(query_embedding, doc_embedding) / (
np.linalg.norm(query_embedding) * np.linalg.norm(doc_embedding)
)
similarities.append((i, sim))
# 排序
similarities.sort(key=lambda x: x[1], reverse=True)
# 返回 top_k 结果
results = []
for idx, sim in similarities[:top_k]:
results.append({
'document': self.documents[idx],
'similarity': sim,
'index': idx
})
return results
# 使用示例
search_engine = SemanticSearch()
# 知识库
documents = [
"Python是一种高级编程语言,以简洁易读著称。",
"机器学习是让计算机从数据中学习的技术。",
"深度学习使用多层神经网络处理复杂数据。",
"自然语言处理让计算机理解人类语言。",
"计算机视觉让机器能够理解图像和视频。",
"强化学习通过奖励机制训练智能体。",
"RAG结合检索和生成来增强大语言模型。",
]
# 索引
search_engine.index(documents)
# 搜索
queries = [
"如何让机器理解图片?",
"什么是神经网络?",
"Python语言有什么特点?"
]
for query in queries:
print(f"\n查询: {query}")
results = search_engine.search(query, top_k=2)
for r in results:
print(f" [{r['similarity']:.3f}] {r['document']}")
嵌入缓存
import hashlib
import json
import os
class EmbeddingCache:
"""嵌入缓存"""
def __init__(self, model, cache_dir="./embedding_cache"):
self.model = model
self.cache_dir = cache_dir
os.makedirs(cache_dir, exist_ok=True)
def _get_cache_key(self, text):
"""生成缓存键"""
return hashlib.md5(text.encode()).hexdigest()
def _get_cache_path(self, key):
"""获取缓存文件路径"""
return os.path.join(self.cache_dir, f"{key}.json")
def encode(self, texts):
"""编码文本(带缓存)"""
if isinstance(texts, str):
texts = [texts]
embeddings = []
texts_to_encode = []
cache_indices = {}
for i, text in enumerate(texts):
key = self._get_cache_key(text)
cache_path = self._get_cache_path(key)
if os.path.exists(cache_path):
# 从缓存加载
with open(cache_path, 'r') as f:
embedding = json.load(f)
embeddings.append((i, embedding))
else:
texts_to_encode.append(text)
cache_indices[len(texts_to_encode) - 1] = i
# 编码未缓存的文本
if texts_to_encode:
new_embeddings = self.model.encode(texts_to_encode).tolist()
for j, embedding in enumerate(new_embeddings):
original_idx = cache_indices[j]
embeddings.append((original_idx, embedding))
# 保存到缓存
key = self._get_cache_key(texts_to_encode[j])
cache_path = self._get_cache_path(key)
with open(cache_path, 'w') as f:
json.dump(embedding, f)
# 按原始顺序排序
embeddings.sort(key=lambda x: x[0])
return [e[1] for e in embeddings]
# 使用缓存
model = SentenceTransformer('all-MiniLM-L6-v2')
cached_model = EmbeddingCache(model)
# 第一次编码(会保存缓存)
embeddings1 = cached_model.encode(["测试文本1", "测试文本2"])
# 第二次编码(从缓存加载)
embeddings2 = cached_model.encode(["测试文本1", "测试文本2"])
关键概念总结
概念 |
解释 |
|---|---|
Embedding |
将文本映射到向量空间的表示 |
向量维度 |
嵌入向量的长度 |
余弦相似度 |
衡量两个向量方向的相似程度 |
语义搜索 |
基于语义相似度的搜索 |
归一化 |
将向量缩放到单位长度 |
下一步
在下一个教程中,我们将学习向量数据库的使用。