Embedding:理解语义的基础
引言:当计算机学会了"理解"含义
在上一篇文章中,我们讨论了大语言模型的工作原理。但有一个根本问题还没解决:计算机是如何"理解"词语含义的?
计算机只能处理数字,它不认识"猫",不认识"爱情",更不懂"苹果"和"橘子"的相似性。但今天的AI不仅能理解这些概念,还能进行语义推理。这一切的基石就是 Embedding(嵌入)。
一、什么是Embedding?
1.1 从一个思想实验开始
想象你要向一个来自外星、不懂任何人类语言的外星人解释"苹果"这个词。你会怎么做?
你可以给他一个多维度的描述:
| 维度 | 描述 |
|---|---|
| 颜色 | 通常是红色或绿色 |
| 形状 | 圆形 |
| 大小 | 拳头大小 |
| 味道 | 甜或酸甜 |
| 口感 | 脆 |
| 用途 | 可以吃 |
| … | … |
如果把这些维度变成数字,就得到一个向量:
苹果 = [红色: 0.9, 圆形: 0.8, 大小: 0.5, 甜度: 0.7, 脆度: 0.8, 可食用: 1.0, ...]
这就是Embedding的基本思想:用一个数字数组(向量)表示一个概念的含义。
1.2 正式定义
Embedding(嵌入) 是将离散的符号(词语、句子、图片等)映射到连续向量空间的技术。每个符号被表示为一个固定长度的实数向量。
关键特性:
语义相近的符号,向量距离也相近
向量之间可以进行数学运算
向量的维度(长度)通常在几十到几千之间
1.3 直观理解:语义空间地图
可以把Embedding想象成绘制了一张"语义空间地图":
[国王]
↑
男人
↑
[苹果] ← 水果 → [橘子] [王后]
↑ /
女人 /
↑ /
[女王]
在这张地图上:
意思相近的词距离近:“苹果"和"橘子"很近
意思相关的词有方向关系:“国王” - “男人” + “女人” ≈ “王后”
不同领域的词距离远:“苹果"和"国王"很远
二、Embedding的工作原理
为了理解Embedding的革命性,我们先看传统方法:
One-Hot编码(传统方法):
$vocabulary = ['苹果', '橘子', '国王', '王后', '吃', '喝'];
// 每个词用一个超长向量表示,只有一位是1
$苹果 = [1, 0, 0, 0, 0, 0]; // 长度 = 词表大小
$橘子 = [0, 1, 0, 0, 0, 0];
$国王 = [0, 0, 1, 0, 0, 0];
// ... 以此类推
问题:
维度灾难:词表越大,向量越长
无法表示相似性:“苹果"和"橘子"的距离和其他词一样
稀疏:大部分是0,浪费空间
Embedding(现代方法):
// 每个词用一个稠密的短向量表示
$苹果 = [0.8, 0.3, -0.2, 0.5, 0.1, -0.3]; // 长度固定,如 768
$橘子 = [0.7, 0.4, -0.1, 0.6, 0.0, -0.2]; // 和苹果很接近
$国王 = [0.1, -0.5, 0.9, 0.2, -0.3, 0.8]; // 和苹果很远
2.2 Embedding的获取方式
方式一:静态Embedding(Word2Vec、GloVe)
2013年,Google的Word2Vec开创了现代Embedding时代。它的核心思想是:“你认识一个词,通过它的上下文”。
CBOW(连续词袋模型):
// 用上下文预测中心词
$context = ["我", "一碗", "面"]; // 输入:上下文
// 预测中心词:???
$predicted = "吃"; // 输出:中心词
Skip-gram:
// 用中心词预测上下文
$center = "吃"; // 输入:中心词
// 预测上下文:???
$predicted = ["我", "一碗", "面"]; // 输出:上下文
通过在海量文本上训练这样的任务,模型学会了将语义相近的词放在相近的位置。
方式二:上下文Embedding(BERT、GPT)
静态Embedding的问题:一个词在不同语境下含义不同。比如"苹果"在"吃苹果"和"买苹果"中不同。
现代模型解决了这个问题:同一个词在不同上下文中得到不同的Embedding。
// 句子1
$sentence1 = "我喜欢吃苹果";
$apple1 = $model->embed("苹果", $sentence1);
// 结果偏向 [水果, 食物, 甜...]
// 句子2
$sentence2 = "新发布的苹果手机";
$apple2 = $model->embed("苹果", $sentence2);
// 结果偏向 [科技, 公司, 产品...]
2.3 Embedding的数学性质
相似度计算:余弦相似度
两个向量的相似度通过它们夹角的余弦值计算:
<?php
function cosineSimilarity($vecA, $vecB)
{
$dotProduct = 0;
$normA = 0;
$normB = 0;
for ($i = 0; $i < count($vecA); $i++) {
$dotProduct += $vecA[$i] * $vecB[$i];
$normA += pow($vecA[$i], 2);
$normB += pow($vecB[$i], 2);
}
$normA = sqrt($normA);
$normB = sqrt($normB);
if ($normA == 0 || $normB == 0) {
return 0;
}
return $dotProduct / ($normA * $normB);
}
// 示例
$apple = [0.8, 0.3, -0.2, 0.5];
$orange = [0.7, 0.4, -0.1, 0.6];
$king = [0.1, -0.5, 0.9, 0.2];
echo cosineSimilarity($apple, $orange); // 0.95 (高度相似)
echo cosineSimilarity($apple, $king); // 0.12 (不相似)
向量运算:语义的代数
最著名的例子:
vec("国王") - vec("男人") + vec("女人") ≈ vec("王后")
用代码演示:
function vectorArithmetic($vec1, $vec2, $operation = 'add')
{
$result = [];
for ($i = 0; $i < count($vec1); $i++) {
if ($operation === 'add') {
$result[$i] = $vec1[$i] + $vec2[$i];
} else if ($operation === 'subtract') {
$result[$i] = $vec1[$i] - $vec2[$i];
}
}
return $result;
}
$king = [0.1, -0.5, 0.9, 0.2];
$man = [0.3, 0.1, -0.2, -0.1];
$woman = [0.2, -0.3, 0.5, 0.4];
$kingMinusMan = vectorArithmetic($king, $man, 'subtract');
$kingMinusManPlusWoman = vectorArithmetic($kingMinusMan, $woman, 'add');
// 结果接近王后的向量
三、Embedding的核心应用
3.1 语义搜索
传统搜索基于关键词匹配,无法理解语义:
// 传统搜索:关键词匹配
$query = "好吃的食物";
// 只能搜到包含"好吃"或"食物"的文档
// 搜不到"美味的佳肴"、"可口的饭菜"
// 语义搜索:基于Embedding
$queryEmbedding = $embeddingModel->embed("好吃的食物");
$documents = [
"美味的佳肴",
"可口的饭菜",
"难吃的料理"
];
foreach ($documents as $doc) {
$docEmbedding = $embeddingModel->embed($doc);
$similarity = cosineSimilarity($queryEmbedding, $docEmbedding);
// "美味的佳肴" 相似度 0.92
// "可口的饭菜" 相似度 0.88
// "难吃的料理" 相似度 0.45
}
3.2 推荐系统
Embedding可以表示用户偏好和物品特征:
<?php
class RecommendationEngine
{
protected $itemEmbeddings = [];
protected $userEmbeddings = [];
// 将用户历史行为转换为Embedding
public function getUserEmbedding($userId, $history)
{
// 用户看过/买过的物品
$itemIds = $history['viewed_items'];
// 计算这些物品Embedding的平均值
$userVec = array_fill(0, 768, 0);
foreach ($itemIds as $itemId) {
$itemVec = $this->itemEmbeddings[$itemId];
for ($i = 0; $i < 768; $i++) {
$userVec[$i] += $itemVec[$i];
}
}
// 归一化
$count = count($itemIds);
for ($i = 0; $i < 768; $i++) {
$userVec[$i] /= $count;
}
return $userVec;
}
// 推荐相似物品
public function recommend($userId, $userHistory)
{
$userVec = $this->getUserEmbedding($userId, $userHistory);
$recommendations = [];
foreach ($this->itemEmbeddings as $itemId => $itemVec) {
// 跳过已看过的
if (in_array($itemId, $userHistory['viewed_items'])) {
continue;
}
$score = cosineSimilarity($userVec, $itemVec);
$recommendations[$itemId] = $score;
}
// 按相似度排序
arsort($recommendations);
return array_slice($recommendations, 0, 10, true);
}
}
3.3 文本分类
用Embedding作为特征输入分类器:
<?php
class TextClassifier
{
protected $embeddingModel;
protected $classifier; // 可以是简单的KNN或神经网络
public function classify($text)
{
// 获取文本的Embedding
$embedding = $this->embeddingModel->embed($text);
// 用分类器预测
return $this->classifier->predict($embedding);
}
public function train($samples, $labels)
{
$embeddings = [];
foreach ($samples as $sample) {
$embeddings[] = $this->embeddingModel->embed($sample);
}
// 训练分类器(这里用简单的KNN)
$this->classifier = new KNNClassifier();
$this->classifier->train($embeddings, $labels);
}
}
// 使用示例
$classifier = new TextClassifier();
$classifier->train([
"这个电影太棒了",
"演技精湛,剧情感人",
"无聊透顶,浪费时间"
], [
"positive", "positive", "negative"
]);
$result = $classifier->classify("非常好看,推荐!");
echo $result; // "positive"
3.4 聚类分析
用Embedding发现文档的主题分组:
<?php
class DocumentClustering
{
public function cluster($documents, $numClusters = 5)
{
// 1. 获取每个文档的Embedding
$embeddings = [];
foreach ($documents as $doc) {
$embeddings[] = $this->getEmbedding($doc);
}
// 2. 使用K-means聚类
$kmeans = new KMeans($numClusters);
$clusters = $kmeans->cluster($embeddings);
// 3. 为每个簇找到代表词
$results = [];
foreach ($clusters as $clusterId => $clusterDocs) {
// 计算簇中心
$center = $this->computeCentroid($clusterDocs);
// 找到离中心最近的文档作为代表
$representative = $this->findClosest($center, $clusterDocs);
$results[$clusterId] = [
'documents' => $clusterDocs,
'representative' => $representative,
'size' => count($clusterDocs)
];
}
return $results;
}
}
四、Embedding与RAG的深度结合
4.1 向量数据库:Embedding的存储和检索
当我们有大量文档时,需要高效存储和检索Embedding:
<?php
// 使用Pgvector(PostgreSQL的向量扩展)
class VectorDatabase
{
protected $pdo;
public function __construct()
{
$this->pdo = new PDO('pgsql:host=localhost;dbname=vectors');
// 创建向量扩展
$this->pdo->exec('CREATE EXTENSION IF NOT EXISTS vector');
}
// 存储文档及其Embedding
public function storeDocument($id, $content, $embedding)
{
$vectorStr = '[' . implode(',', $embedding) . ']';
$stmt = $this->pdo->prepare(
'INSERT INTO documents (id, content, embedding)
VALUES (?, ?, ?::vector)'
);
$stmt->execute([$id, $content, $vectorStr]);
}
// 相似性搜索
public function search($queryEmbedding, $limit = 10)
{
$vectorStr = '[' . implode(',', $queryEmbedding) . ']';
// 使用余弦距离
$stmt = $this->pdo->prepare(
'SELECT id, content, 1 - (embedding <=> ?::vector) as similarity
FROM documents
ORDER BY embedding <=> ?::vector
LIMIT ?'
);
$stmt->execute([$vectorStr, $vectorStr, $limit]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}
4.2 RAG工作流中的Embedding
完整的RAG系统需要Embedding的地方:
<?php
class RAGSystem
{
protected $embeddingModel;
protected $vectorDB;
protected $llm;
// 1. 索引阶段:将文档库向量化
public function indexDocuments($documents)
{
foreach ($documents as $doc) {
// 文档分块
$chunks = $this->splitIntoChunks($doc['content']);
foreach ($chunks as $chunk) {
// 生成Embedding
$embedding = $this->embeddingModel->embed($chunk);
// 存入向量数据库
$this->vectorDB->storeDocument(
$doc['id'] . '_' . $chunk['id'],
$chunk['text'],
$embedding
);
}
}
}
// 2. 查询阶段:检索相关信息
public function answer($question)
{
// 问题向量化
$questionEmbedding = $this->embeddingModel->embed($question);
// 在向量数据库中搜索相似文档
$relevantDocs = $this->vectorDB->search($questionEmbedding, 5);
// 构建上下文
$context = implode("\n", array_column($relevantDocs, 'content'));
// 生成回答
$prompt = "基于以下资料回答问题:\n\n$context\n\n问题:$question";
return $this->llm->complete($prompt);
}
// 文档分块策略
protected function splitIntoChunks($text, $chunkSize = 500, $overlap = 50)
{
// 按段落或句子分割,保持语义完整性
// 返回块数组,块之间可以有重叠
}
}
4.3 Embedding模型的选择
不同Embedding模型的对比:
| 模型 | 维度 | 语言 | 特点 | 适用场景 |
|---|---|---|---|---|
| text-embedding-ada-002 | 1536 | 多语言 | OpenAI出品,质量高 | 通用场景 |
| bge-large-zh | 1024 | 中文 | 专门优化中文 | 中文场景 |
| bge-m3 | 1024 | 多语言 | 支持100+语言 | 多语言混合 |
| e5-large-v2 | 1024 | 英文 | 检索任务优化 | 英文检索 |
| m3e-base | 768 | 中文 | 轻量级 | 资源受限 |
在Ollama中使用Embedding模型:
# 拉取bge-m3模型
ollama pull bge-m3
# 通过API调用
curl http://localhost:11434/api/embeddings -d '{
"model": "bge-m3",
"prompt": "需要生成Embedding的文本"
}'
五、Embedding的进阶话题
5.1 多模态Embedding
现代Embedding可以处理多种类型的数据,并将它们映射到同一向量空间:
// CLIP模型:图像和文本的统一Embedding
$textEmbedding = $clip->embedText("一只黑猫");
$imageEmbedding = $clip->embedImage("cat.jpg");
// 可以直接比较文本和图像的相似度
$similarity = cosineSimilarity($textEmbedding, $imageEmbedding);
// 如果是猫的图片,相似度很高
应用场景:
文搜图:用文字搜索图片
图搜文:用图片搜索相关描述
图文匹配:检查图文是否对应
5.2 跨语言Embedding
将不同语言映射到同一空间:
// 用bge-m3模型
$chinese = "我爱编程";
$english = "I love programming";
$spanish = "Amo programar";
$chineseVec = $embed->embed($chinese);
$englishVec = $embed->embed($english);
$spanishVec = $embed->embed($spanish);
// 三者互相相似度都很高
echo cosineSimilarity($chineseVec, $englishVec); // 0.92
echo cosineSimilarity($chineseVec, $spanishVec); // 0.89
5.3 时间感知Embedding
概念的含义会随时间变化:“云"在10年前和今天的含义不同。时间感知Embedding试图捕捉这种变化。
5.4 可解释性Embedding
研究人员尝试让Embedding的每个维度有明确含义:
维度1: 情感 (正面/负面)
维度2: 抽象程度 (具体/抽象)
维度3: 时间性 (过去/未来)
...
这样我们就能理解"为什么这两个词相似”。
六、实践Embedding
6.1 本地实践:用Ollama生成Embedding
<?php
class OllamaEmbedding
{
protected $baseUrl = 'http://localhost:11434';
public function embed($text, $model = 'bge-m3')
{
$ch = curl_init($this->baseUrl . '/api/embeddings');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'model' => $model,
'prompt' => $text
]));
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
return $data['embedding'] ?? null;
}
public function batchEmbed($texts, $model = 'bge-m3')
{
$embeddings = [];
foreach ($texts as $text) {
$embeddings[] = $this->embed($text, $model);
}
return $embeddings;
}
}
// 使用示例
$embedder = new OllamaEmbedding();
$text = "人工智能正在改变世界";
$vector = $embedder->embed($text);
echo "向量维度: " . count($vector); // 1024
6.2 构建语义搜索系统
<?php
class SemanticSearch
{
protected $embedder;
protected $documents = [];
protected $embeddings = [];
public function __construct($embedder)
{
$this->embedder = $embedder;
}
// 添加文档到索引
public function addDocument($id, $content)
{
$this->documents[$id] = $content;
$this->embeddings[$id] = $this->embedder->embed($content);
}
// 批量添加
public function addDocuments($documents)
{
foreach ($documents as $id => $content) {
$this->addDocument($id, $content);
}
}
// 搜索
public function search($query, $topK = 5)
{
$queryVec = $this->embedder->embed($query);
$scores = [];
foreach ($this->embeddings as $id => $docVec) {
$scores[$id] = $this->cosineSimilarity($queryVec, $docVec);
}
// 按相似度排序
arsort($scores);
// 返回前K个
$results = [];
$i = 0;
foreach ($scores as $id => $score) {
if ($i++ >= $topK) break;
$results[] = [
'id' => $id,
'content' => $this->documents[$id],
'score' => $score
];
}
return $results;
}
protected function cosineSimilarity($vecA, $vecB)
{
$dot = 0;
$normA = 0;
$normB = 0;
for ($i = 0; $i < count($vecA); $i++) {
$dot += $vecA[$i] * $vecB[$i];
$normA += $vecA[$i] * $vecA[$i];
$normB += $vecB[$i] * $vecB[$i];
}
$normA = sqrt($normA);
$normB = sqrt($normB);
return $dot / ($normA * $normB);
}
}
// 使用示例
$search = new SemanticSearch(new OllamaEmbedding());
// 添加文档
$search->addDocuments([
'doc1' => 'PHP是一种流行的服务器端脚本语言',
'doc2' => 'Laravel是PHP最流行的框架之一',
'doc3' => 'JavaScript可以在浏览器和服务器端运行',
'doc4' => 'Vue.js是一个渐进式JavaScript框架',
'doc5' => 'Python在数据科学和机器学习领域很受欢迎',
]);
// 搜索
$results = $search->search('web开发框架');
foreach ($results as $result) {
echo "文档: {$result['content']}\n";
echo "相似度: {$result['score']}\n\n";
}
Embedding是AI的基石
Embedding是连接人类语言和计算机计算的桥梁。
Embedding的本质:将语义转化为数学向量
工作原理:通过上下文学习语义关系
核心应用:搜索、推荐、分类、RAG