按 Enter 键跳转到正文

Embedding:理解语义的基础

Embedding:理解语义的基础

引言:当计算机学会了"理解"含义

在上一篇文章中,我们讨论了大语言模型的工作原理。但有一个根本问题还没解决:计算机是如何"理解"词语含义的?

计算机只能处理数字,它不认识"猫",不认识"爱情",更不懂"苹果"和"橘子"的相似性。但今天的AI不仅能理解这些概念,还能进行语义推理。这一切的基石就是 Embedding(嵌入)

一、什么是Embedding?

1.1 从一个思想实验开始

想象你要向一个来自外星、不懂任何人类语言的外星人解释"苹果"这个词。你会怎么做?

你可以给他一个多维度的描述:

维度描述
颜色通常是红色或绿色
形状圆形
大小拳头大小
味道甜或酸甜
口感
用途可以吃

如果把这些维度变成数字,就得到一个向量:

1苹果 = [红色: 0.9, 圆形: 0.8, 大小: 0.5, 甜度: 0.7, 脆度: 0.8, 可食用: 1.0, ...]

这就是Embedding的基本思想:用一个数字数组(向量)表示一个概念的含义

1.2 正式定义

Embedding(嵌入) 是将离散的符号(词语、句子、图片等)映射到连续向量空间的技术。每个符号被表示为一个固定长度的实数向量。

关键特性

  • 语义相近的符号,向量距离也相近

  • 向量之间可以进行数学运算

  • 向量的维度(长度)通常在几十到几千之间

1.3 直观理解:语义空间地图

可以把Embedding想象成绘制了一张"语义空间地图":

1                   [国王]
23                   男人
45[苹果] ← 水果 → [橘子]   [王后]
6                     ↑    /
7                   女人  /
8                     ↑  /
9                   [女王]

在这张地图上:

  • 意思相近的词距离近:“苹果"和"橘子"很近

  • 意思相关的词有方向关系:“国王” - “男人” + “女人” ≈ “王后”

  • 不同领域的词距离远:“苹果"和"国王"很远

二、Embedding的工作原理

为了理解Embedding的革命性,我们先看传统方法:

One-Hot编码(传统方法):

1$vocabulary = ['苹果', '橘子', '国王', '王后', '吃', '喝'];
2
3// 每个词用一个超长向量表示,只有一位是1
4$苹果 = [1, 0, 0, 0, 0, 0];  // 长度 = 词表大小
5$橘子 = [0, 1, 0, 0, 0, 0];
6$国王 = [0, 0, 1, 0, 0, 0];
7// ... 以此类推

问题

  • 维度灾难:词表越大,向量越长

  • 无法表示相似性:“苹果"和"橘子"的距离和其他词一样

  • 稀疏:大部分是0,浪费空间

Embedding(现代方法):

1// 每个词用一个稠密的短向量表示
2$苹果 = [0.8, 0.3, -0.2, 0.5, 0.1, -0.3];  // 长度固定,如 768
3$橘子 = [0.7, 0.4, -0.1, 0.6, 0.0, -0.2];  // 和苹果很接近
4$国王 = [0.1, -0.5, 0.9, 0.2, -0.3, 0.8];   // 和苹果很远

2.2 Embedding的获取方式

方式一:静态Embedding(Word2Vec、GloVe)

2013年,Google的Word2Vec开创了现代Embedding时代。它的核心思想是:“你认识一个词,通过它的上下文”

CBOW(连续词袋模型)

1// 用上下文预测中心词
2$context = ["我", "一碗", "面"];  // 输入:上下文
3// 预测中心词:???
4$predicted = "吃";  // 输出:中心词

Skip-gram:

1// 用中心词预测上下文
2$center = "吃";  // 输入:中心词
3// 预测上下文:???
4$predicted = ["我", "一碗", "面"];  // 输出:上下文

通过在海量文本上训练这样的任务,模型学会了将语义相近的词放在相近的位置。

方式二:上下文Embedding(BERT、GPT)

静态Embedding的问题:一个词在不同语境下含义不同。比如"苹果"在"吃苹果"和"买苹果"中不同。

现代模型解决了这个问题:同一个词在不同上下文中得到不同的Embedding

1// 句子1
2$sentence1 = "我喜欢吃苹果";
3$apple1 = $model->embed("苹果", $sentence1);  
4// 结果偏向 [水果, 食物, 甜...]
5
6// 句子2  
7$sentence2 = "新发布的苹果手机";
8$apple2 = $model->embed("苹果", $sentence2);
9// 结果偏向 [科技, 公司, 产品...]

2.3 Embedding的数学性质

相似度计算:余弦相似度

两个向量的相似度通过它们夹角的余弦值计算:

 1<?php
 2
 3function cosineSimilarity($vecA, $vecB)
 4{
 5    $dotProduct = 0;
 6    $normA = 0;
 7    $normB = 0;
 8    
 9    for ($i = 0; $i < count($vecA); $i++) {
10        $dotProduct += $vecA[$i] * $vecB[$i];
11        $normA += pow($vecA[$i], 2);
12        $normB += pow($vecB[$i], 2);
13    }
14    
15    $normA = sqrt($normA);
16    $normB = sqrt($normB);
17    
18    if ($normA == 0 || $normB == 0) {
19        return 0;
20    }
21    
22    return $dotProduct / ($normA * $normB);
23}
24
25// 示例
26$apple = [0.8, 0.3, -0.2, 0.5];
27$orange = [0.7, 0.4, -0.1, 0.6];
28$king = [0.1, -0.5, 0.9, 0.2];
29
30echo cosineSimilarity($apple, $orange);  // 0.95 (高度相似)
31echo cosineSimilarity($apple, $king);    // 0.12 (不相似)

向量运算:语义的代数

最著名的例子:

1vec("国王") - vec("男人") + vec("女人") ≈ vec("王后")

用代码演示:

 1function vectorArithmetic($vec1, $vec2, $operation = 'add')
 2{
 3    $result = [];
 4    for ($i = 0; $i < count($vec1); $i++) {
 5        if ($operation === 'add') {
 6            $result[$i] = $vec1[$i] + $vec2[$i];
 7        } else if ($operation === 'subtract') {
 8            $result[$i] = $vec1[$i] - $vec2[$i];
 9        }
10    }
11    return $result;
12}
13
14$king = [0.1, -0.5, 0.9, 0.2];
15$man = [0.3, 0.1, -0.2, -0.1];
16$woman = [0.2, -0.3, 0.5, 0.4];
17
18$kingMinusMan = vectorArithmetic($king, $man, 'subtract');
19$kingMinusManPlusWoman = vectorArithmetic($kingMinusMan, $woman, 'add');
20// 结果接近王后的向量

三、Embedding的核心应用

3.1 语义搜索

传统搜索基于关键词匹配,无法理解语义:

 1// 传统搜索:关键词匹配
 2$query = "好吃的食物";
 3// 只能搜到包含"好吃"或"食物"的文档
 4// 搜不到"美味的佳肴"、"可口的饭菜"
 5
 6// 语义搜索:基于Embedding
 7$queryEmbedding = $embeddingModel->embed("好吃的食物");
 8$documents = [
 9    "美味的佳肴",
10    "可口的饭菜", 
11    "难吃的料理"
12];
13
14foreach ($documents as $doc) {
15    $docEmbedding = $embeddingModel->embed($doc);
16    $similarity = cosineSimilarity($queryEmbedding, $docEmbedding);
17    // "美味的佳肴" 相似度 0.92
18    // "可口的饭菜" 相似度 0.88
19    // "难吃的料理" 相似度 0.45
20}

3.2 推荐系统

Embedding可以表示用户偏好和物品特征:

 1<?php
 2
 3class RecommendationEngine
 4{
 5    protected $itemEmbeddings = [];
 6    protected $userEmbeddings = [];
 7    
 8    // 将用户历史行为转换为Embedding
 9    public function getUserEmbedding($userId, $history)
10    {
11        // 用户看过/买过的物品
12        $itemIds = $history['viewed_items'];
13        
14        // 计算这些物品Embedding的平均值
15        $userVec = array_fill(0, 768, 0);
16        foreach ($itemIds as $itemId) {
17            $itemVec = $this->itemEmbeddings[$itemId];
18            for ($i = 0; $i < 768; $i++) {
19                $userVec[$i] += $itemVec[$i];
20            }
21        }
22        
23        // 归一化
24        $count = count($itemIds);
25        for ($i = 0; $i < 768; $i++) {
26            $userVec[$i] /= $count;
27        }
28        
29        return $userVec;
30    }
31    
32    // 推荐相似物品
33    public function recommend($userId, $userHistory)
34    {
35        $userVec = $this->getUserEmbedding($userId, $userHistory);
36        
37        $recommendations = [];
38        foreach ($this->itemEmbeddings as $itemId => $itemVec) {
39            // 跳过已看过的
40            if (in_array($itemId, $userHistory['viewed_items'])) {
41                continue;
42            }
43            
44            $score = cosineSimilarity($userVec, $itemVec);
45            $recommendations[$itemId] = $score;
46        }
47        
48        // 按相似度排序
49        arsort($recommendations);
50        return array_slice($recommendations, 0, 10, true);
51    }
52}

3.3 文本分类

用Embedding作为特征输入分类器:

 1<?php
 2
 3class TextClassifier
 4{
 5    protected $embeddingModel;
 6    protected $classifier; // 可以是简单的KNN或神经网络
 7    
 8    public function classify($text)
 9    {
10        // 获取文本的Embedding
11        $embedding = $this->embeddingModel->embed($text);
12        
13        // 用分类器预测
14        return $this->classifier->predict($embedding);
15    }
16    
17    public function train($samples, $labels)
18    {
19        $embeddings = [];
20        foreach ($samples as $sample) {
21            $embeddings[] = $this->embeddingModel->embed($sample);
22        }
23        
24        // 训练分类器(这里用简单的KNN)
25        $this->classifier = new KNNClassifier();
26        $this->classifier->train($embeddings, $labels);
27    }
28}
29
30// 使用示例
31$classifier = new TextClassifier();
32$classifier->train([
33    "这个电影太棒了",
34    "演技精湛,剧情感人",
35    "无聊透顶,浪费时间"
36], [
37    "positive", "positive", "negative"
38]);
39
40$result = $classifier->classify("非常好看,推荐!");
41echo $result; // "positive"

3.4 聚类分析

用Embedding发现文档的主题分组:

 1<?php
 2
 3class DocumentClustering
 4{
 5    public function cluster($documents, $numClusters = 5)
 6    {
 7        // 1. 获取每个文档的Embedding
 8        $embeddings = [];
 9        foreach ($documents as $doc) {
10            $embeddings[] = $this->getEmbedding($doc);
11        }
12        
13        // 2. 使用K-means聚类
14        $kmeans = new KMeans($numClusters);
15        $clusters = $kmeans->cluster($embeddings);
16        
17        // 3. 为每个簇找到代表词
18        $results = [];
19        foreach ($clusters as $clusterId => $clusterDocs) {
20            // 计算簇中心
21            $center = $this->computeCentroid($clusterDocs);
22            
23            // 找到离中心最近的文档作为代表
24            $representative = $this->findClosest($center, $clusterDocs);
25            
26            $results[$clusterId] = [
27                'documents' => $clusterDocs,
28                'representative' => $representative,
29                'size' => count($clusterDocs)
30            ];
31        }
32        
33        return $results;
34    }
35}

四、Embedding与RAG的深度结合

4.1 向量数据库:Embedding的存储和检索

当我们有大量文档时,需要高效存储和检索Embedding:

 1<?php
 2
 3// 使用Pgvector(PostgreSQL的向量扩展)
 4class VectorDatabase
 5{
 6    protected $pdo;
 7    
 8    public function __construct()
 9    {
10        $this->pdo = new PDO('pgsql:host=localhost;dbname=vectors');
11        // 创建向量扩展
12        $this->pdo->exec('CREATE EXTENSION IF NOT EXISTS vector');
13    }
14    
15    // 存储文档及其Embedding
16    public function storeDocument($id, $content, $embedding)
17    {
18        $vectorStr = '[' . implode(',', $embedding) . ']';
19        
20        $stmt = $this->pdo->prepare(
21            'INSERT INTO documents (id, content, embedding) 
22             VALUES (?, ?, ?::vector)'
23        );
24        $stmt->execute([$id, $content, $vectorStr]);
25    }
26    
27    // 相似性搜索
28    public function search($queryEmbedding, $limit = 10)
29    {
30        $vectorStr = '[' . implode(',', $queryEmbedding) . ']';
31        
32        // 使用余弦距离
33        $stmt = $this->pdo->prepare(
34            'SELECT id, content, 1 - (embedding <=> ?::vector) as similarity
35             FROM documents
36             ORDER BY embedding <=> ?::vector
37             LIMIT ?'
38        );
39        $stmt->execute([$vectorStr, $vectorStr, $limit]);
40        
41        return $stmt->fetchAll(PDO::FETCH_ASSOC);
42    }
43}

4.2 RAG工作流中的Embedding

完整的RAG系统需要Embedding的地方:

 1<?php
 2
 3class RAGSystem
 4{
 5    protected $embeddingModel;
 6    protected $vectorDB;
 7    protected $llm;
 8    
 9    // 1. 索引阶段:将文档库向量化
10    public function indexDocuments($documents)
11    {
12        foreach ($documents as $doc) {
13            // 文档分块
14            $chunks = $this->splitIntoChunks($doc['content']);
15            
16            foreach ($chunks as $chunk) {
17                // 生成Embedding
18                $embedding = $this->embeddingModel->embed($chunk);
19                
20                // 存入向量数据库
21                $this->vectorDB->storeDocument(
22                    $doc['id'] . '_' . $chunk['id'],
23                    $chunk['text'],
24                    $embedding
25                );
26            }
27        }
28    }
29    
30    // 2. 查询阶段:检索相关信息
31    public function answer($question)
32    {
33        // 问题向量化
34        $questionEmbedding = $this->embeddingModel->embed($question);
35        
36        // 在向量数据库中搜索相似文档
37        $relevantDocs = $this->vectorDB->search($questionEmbedding, 5);
38        
39        // 构建上下文
40        $context = implode("\n", array_column($relevantDocs, 'content'));
41        
42        // 生成回答
43        $prompt = "基于以下资料回答问题:\n\n$context\n\n问题:$question";
44        return $this->llm->complete($prompt);
45    }
46    
47    // 文档分块策略
48    protected function splitIntoChunks($text, $chunkSize = 500, $overlap = 50)
49    {
50        // 按段落或句子分割,保持语义完整性
51        // 返回块数组,块之间可以有重叠
52    }
53}

4.3 Embedding模型的选择

不同Embedding模型的对比:

模型维度语言特点适用场景
text-embedding-ada-0021536多语言OpenAI出品,质量高通用场景
bge-large-zh1024中文专门优化中文中文场景
bge-m31024多语言支持100+语言多语言混合
e5-large-v21024英文检索任务优化英文检索
m3e-base768中文轻量级资源受限

在Ollama中使用Embedding模型:

1# 拉取bge-m3模型
2ollama pull bge-m3
3
4# 通过API调用
5curl http://localhost:11434/api/embeddings -d '{
6  "model": "bge-m3",
7  "prompt": "需要生成Embedding的文本"
8}'

五、Embedding的进阶话题

5.1 多模态Embedding

现代Embedding可以处理多种类型的数据,并将它们映射到同一向量空间:

1// CLIP模型:图像和文本的统一Embedding
2$textEmbedding = $clip->embedText("一只黑猫");
3$imageEmbedding = $clip->embedImage("cat.jpg");
4
5// 可以直接比较文本和图像的相似度
6$similarity = cosineSimilarity($textEmbedding, $imageEmbedding);
7// 如果是猫的图片,相似度很高

应用场景:

  • 文搜图:用文字搜索图片

  • 图搜文:用图片搜索相关描述

  • 图文匹配:检查图文是否对应

5.2 跨语言Embedding

将不同语言映射到同一空间:

 1// 用bge-m3模型
 2$chinese = "我爱编程";
 3$english = "I love programming";
 4$spanish = "Amo programar";
 5
 6$chineseVec = $embed->embed($chinese);
 7$englishVec = $embed->embed($english);
 8$spanishVec = $embed->embed($spanish);
 9
10// 三者互相相似度都很高
11echo cosineSimilarity($chineseVec, $englishVec);   // 0.92
12echo cosineSimilarity($chineseVec, $spanishVec);   // 0.89

5.3 时间感知Embedding

概念的含义会随时间变化:“云"在10年前和今天的含义不同。时间感知Embedding试图捕捉这种变化。

5.4 可解释性Embedding

研究人员尝试让Embedding的每个维度有明确含义:

1维度1: 情感 (正面/负面)
2维度2: 抽象程度 (具体/抽象)
3维度3: 时间性 (过去/未来)
4...

这样我们就能理解"为什么这两个词相似”。

六、实践Embedding

6.1 本地实践:用Ollama生成Embedding

 1<?php
 2
 3class OllamaEmbedding
 4{
 5    protected $baseUrl = 'http://localhost:11434';
 6    
 7    public function embed($text, $model = 'bge-m3')
 8    {
 9        $ch = curl_init($this->baseUrl . '/api/embeddings');
10        
11        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
12        curl_setopt($ch, CURLOPT_POST, true);
13        curl_setopt($ch, CURLOPT_HTTPHEADER, [
14            'Content-Type: application/json'
15        ]);
16        
17        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
18            'model' => $model,
19            'prompt' => $text
20        ]));
21        
22        $response = curl_exec($ch);
23        curl_close($ch);
24        
25        $data = json_decode($response, true);
26        return $data['embedding'] ?? null;
27    }
28    
29    public function batchEmbed($texts, $model = 'bge-m3')
30    {
31        $embeddings = [];
32        foreach ($texts as $text) {
33            $embeddings[] = $this->embed($text, $model);
34        }
35        return $embeddings;
36    }
37}
38
39// 使用示例
40$embedder = new OllamaEmbedding();
41$text = "人工智能正在改变世界";
42$vector = $embedder->embed($text);
43echo "向量维度: " . count($vector); // 1024

6.2 构建语义搜索系统

 1<?php
 2
 3class SemanticSearch
 4{
 5    protected $embedder;
 6    protected $documents = [];
 7    protected $embeddings = [];
 8    
 9    public function __construct($embedder)
10    {
11        $this->embedder = $embedder;
12    }
13    
14    // 添加文档到索引
15    public function addDocument($id, $content)
16    {
17        $this->documents[$id] = $content;
18        $this->embeddings[$id] = $this->embedder->embed($content);
19    }
20    
21    // 批量添加
22    public function addDocuments($documents)
23    {
24        foreach ($documents as $id => $content) {
25            $this->addDocument($id, $content);
26        }
27    }
28    
29    // 搜索
30    public function search($query, $topK = 5)
31    {
32        $queryVec = $this->embedder->embed($query);
33        
34        $scores = [];
35        foreach ($this->embeddings as $id => $docVec) {
36            $scores[$id] = $this->cosineSimilarity($queryVec, $docVec);
37        }
38        
39        // 按相似度排序
40        arsort($scores);
41        
42        // 返回前K个
43        $results = [];
44        $i = 0;
45        foreach ($scores as $id => $score) {
46            if ($i++ >= $topK) break;
47            $results[] = [
48                'id' => $id,
49                'content' => $this->documents[$id],
50                'score' => $score
51            ];
52        }
53        
54        return $results;
55    }
56    
57    protected function cosineSimilarity($vecA, $vecB)
58    {
59        $dot = 0;
60        $normA = 0;
61        $normB = 0;
62        
63        for ($i = 0; $i < count($vecA); $i++) {
64            $dot += $vecA[$i] * $vecB[$i];
65            $normA += $vecA[$i] * $vecA[$i];
66            $normB += $vecB[$i] * $vecB[$i];
67        }
68        
69        $normA = sqrt($normA);
70        $normB = sqrt($normB);
71        
72        return $dot / ($normA * $normB);
73    }
74}
75
76// 使用示例
77$search = new SemanticSearch(new OllamaEmbedding());
78
79// 添加文档
80$search->addDocuments([
81    'doc1' => 'PHP是一种流行的服务器端脚本语言',
82    'doc2' => 'Laravel是PHP最流行的框架之一',
83    'doc3' => 'JavaScript可以在浏览器和服务器端运行',
84    'doc4' => 'Vue.js是一个渐进式JavaScript框架',
85    'doc5' => 'Python在数据科学和机器学习领域很受欢迎',
86]);
87
88// 搜索
89$results = $search->search('web开发框架');
90foreach ($results as $result) {
91    echo "文档: {$result['content']}\n";
92    echo "相似度: {$result['score']}\n\n";
93}

Embedding是AI的基石

Embedding是连接人类语言和计算机计算的桥梁。

  • Embedding的本质:将语义转化为数学向量

  • 工作原理:通过上下文学习语义关系

  • 核心应用:搜索、推荐、分类、RAG

发表评论