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-0021536多语言OpenAI出品,质量高通用场景
bge-large-zh1024中文专门优化中文中文场景
bge-m31024多语言支持100+语言多语言混合
e5-large-v21024英文检索任务优化英文检索
m3e-base768中文轻量级资源受限

在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