回到頂部

🧮 Embedding 向量嵌入實戰

把文字變成數字——語意搜尋、RAG、推薦系統的核心技術。

🧮 Embedding 是什麼?

💡 一句話理解 Embedding = 把文字轉成一串數字(向量),讓電腦能「理解」文字的意思而不只是文字本身。

生活化比喻

想像一個超大的地圖,每個詞都有一個位置:

  • 「貓」和「狗」在地圖上很近(都是寵物)
  • 「貓」和「汽車」在地圖上很遠(毫無關聯)
  • 「國王」和「皇后」很近,「男人」和「女人」也很近

Embedding 就是把文字放到這張地圖上的過程。每個文字的位置用一組坐標(向量)表示。

技術本質

"今天天氣很好" → [0.023, -0.156, 0.892, ..., 0.045]
                    ↑ 一個 1536 維的向量(以 text-embedding-3-small 為例)

"天氣晴朗"    → [0.019, -0.148, 0.901, ..., 0.038]
                    ↑ 和上面的向量很接近!因為意思相近

"量子力學"    → [-0.234, 0.567, 0.012, ..., -0.891]
                    ↑ 完全不同的向量方向,因為意思無關

🎯 Embedding 能做什麼?

應用場景說明案例
語意搜尋搜「水果」能找到「蘋果」「香蕉」企業知識庫搜尋
RAG在問 AI 前,先找到最相關的文件段落客服機器人
推薦系統找到和使用者興趣相近的內容推薦文章、商品
重複檢測找出意思相同但用詞不同的文件客服工單去重
分類自動計算文字屬於哪個類別信件分類、情感分析
聚類把相似的文件自動分組客戶回饋主題分析

📊 Embedding 模型比較(2026)

模型開發者維度價格/M tokens特色
text-embedding-3-smallOpenAI1536$0.02性價比最高
text-embedding-3-largeOpenAI3072$0.13精度最高
voyage-3Voyage AI1024$0.06長文 + 程式碼最強
embed-v4Cohere1024$0.10多語言最好
bge-m3BAAI1024免費(開源)開源最強、多語言
nomic-embed-textNomic AI768免費(開源)可用 Ollama 本地跑

怎麼選?

  • 💰 預算有限text-embedding-3-small(最便宜、品質夠用)
  • 🏆 要求最高精度text-embedding-3-largevoyage-3
  • 🇹🇼 中文為主bge-m3cohere embed-v4
  • 🔒 資料不能外傳bge-m3nomic-embed-text(本地離線跑)

💻 實作:產生 Embedding

OpenAI

from openai import OpenAI
import os

client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])

def get_embedding(text, model="text-embedding-3-small"):
    """取得文字的 embedding 向量"""
    response = client.embeddings.create(
        input=text,
        model=model
    )
    return response.data[0].embedding

# 單一文字
vector = get_embedding("今天天氣很好")
print(f"維度: {len(vector)}")  # 1536
print(f"前 5 個值: {vector[:5]}")

# 批次處理(更高效)
texts = ["蘋果很好吃", "iPhone 16 上市了", "香蕉也不錯"]
response = client.embeddings.create(
    input=texts,
    model="text-embedding-3-small"
)
vectors = [item.embedding for item in response.data]

本地模型(用 Sentence-Transformers)

from sentence_transformers import SentenceTransformer

# 下載並載入模型(第一次會自動下載)
model = SentenceTransformer("BAAI/bge-m3")

texts = ["今天天氣很好", "天氣晴朗", "量子力學"]
vectors = model.encode(texts)

print(f"維度: {vectors.shape}")  # (3, 1024)

📏 計算相似度

有了 Embedding,怎麼比較兩段文字的「像不像」?

Cosine Similarity(餘弦相似度)

import numpy as np

def cosine_similarity(a, b):
    """計算兩個向量的餘弦相似度(-1 ~ 1,越接近 1 越相似)"""
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

# 範例
v1 = get_embedding("蘋果很好吃")
v2 = get_embedding("水果真美味")
v3 = get_embedding("量子力學原理")

print(cosine_similarity(v1, v2))  # ~0.85(很相似!)
print(cosine_similarity(v1, v3))  # ~0.12(不相關)

相似度讓你建立語意搜尋

def semantic_search(query, documents, top_k=3):
    """語意搜尋:找出和查詢最相關的文件"""
    query_vec = get_embedding(query)
    doc_vecs = [get_embedding(doc) for doc in documents]

    # 計算所有文件和查詢的相似度
    scores = [cosine_similarity(query_vec, dv) for dv in doc_vecs]

    # 按相似度排序,取前 k 筆
    ranked = sorted(zip(documents, scores), key=lambda x: x[1], reverse=True)
    return ranked[:top_k]

# 範例
docs = [
    "我們的退貨政策是 7 天內無條件退貨",
    "運費滿 500 元免運,未滿收 60 元",
    "營業時間為週一到週五 9:00-18:00",
    "如需退貨請聯繫客服,無需提供理由",
    "我們接受信用卡、LinePay 和銀行轉帳"
]

results = semantic_search("怎麼退貨?", docs)
for doc, score in results:
    print(f"{score:.3f} | {doc}")
# 0.891 | 如需退貨請聯繫客服,無需提供理由
# 0.856 | 我們的退貨政策是 7 天內無條件退貨
# 0.423 | 運費滿 500 元免運...

✂️ Chunking(文件切割策略)

要把長文件做 Embedding,必須先切割成小段落。切法直接決定 RAG 的品質

三種切法比較

策略做法優點缺點
固定長度每 500 字切一段簡單快速可能從句子中間斷開
句子分段按句號/段落分段語意完整段落長度不一
遞迴切割先段落 → 再句子 → 再固定長度平衡語意和長度實作稍複雜

遞迴切割實作

from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,        # 每段最大 500 字
    chunk_overlap=50,      # 相鄰段重疊 50 字(保持上下文連續)
    separators=["\n\n", "\n", "。", "!", "?", ",", " "],
    # 優先在段落邊界切,其次句子邊界,最後才切字
)

document = "這是一篇很長的文件...(略)"
chunks = splitter.split_text(document)

print(f"切成 {len(chunks)} 段")
for i, chunk in enumerate(chunks):
    print(f"段落 {i+1} ({len(chunk)} 字): {chunk[:50]}...")

Chunking 最佳實踐

  • 🎯 Chunk size 500-1000 字(中文場景)——太短語意不完整,太長檢索不精確
  • 🔄 Overlap 10-15%——防止關鍵資訊被切在邊界
  • 📑 保留 metadata——每個 chunk 記住它來自哪個文件、哪個章節
  • 🧪 實際測試——沒有萬能參數,必須用你的資料測試

🗄️ 存入向量資料庫

Embedding 產生後,要存入專門的向量資料庫才能高效搜尋。

用 ChromaDB(最簡單、本地)

import chromadb

# 建立本地資料庫
client = chromadb.Client()
collection = client.create_collection("my_docs")

# 存入文件
collection.add(
    documents=["退貨政策是 7 天", "運費 500 免運", "營業時間 9-18"],
    ids=["doc1", "doc2", "doc3"],
    metadatas=[
        {"source": "faq.pdf", "page": 1},
        {"source": "faq.pdf", "page": 2},
        {"source": "about.pdf", "page": 1},
    ]
)

# 搜尋
results = collection.query(
    query_texts=["怎麼退貨"],
    n_results=2
)
print(results["documents"])
# [['退貨政策是 7 天', ...]]

用 Pinecone(雲端、生產)

from pinecone import Pinecone

pc = Pinecone(api_key=os.environ["PINECONE_API_KEY"])
index = pc.Index("my-index")

# 存入向量
index.upsert(vectors=[
    {"id": "doc1", "values": get_embedding("退貨政策是 7 天"),
     "metadata": {"source": "faq", "text": "退貨政策是 7 天"}},
    {"id": "doc2", "values": get_embedding("運費 500 免運"),
     "metadata": {"source": "faq", "text": "運費 500 免運"}},
])

# 搜尋
results = index.query(
    vector=get_embedding("怎麼退貨"),
    top_k=3,
    include_metadata=True
)

⚠️ Embedding 的限制

限制說明應對策略
最大長度大部分模型限 8192 tokens先切割再 embed
語意漂移太長的文字 embedding 會「模糊」切成 500-1000 字的小段
跨語言部分模型中文效果差選中文優化的模型(bge-m3, Cohere)
精準匹配搜 “PO-2024-0831” 可能找不到搭配關鍵字搜尋(Hybrid Search)
即時性模型的知識有截止日期結合 RAG 補充最新資訊

→ 學會 Embedding 後,你已經具備建構 RAG 系統的核心能力。


❓ FAQ

Embedding 和 Token 有什麼關係?

Token 是 LLM 處理文字的基本單位。Embedding 是把一整段文字轉成一個固定長度的向量。一段 100 tokens 的文字會被轉成一個 1536 維(text-embedding-3-small)的向量。

Embedding 模型需要 GPU 嗎?

用 API(OpenAI、Cohere)不需要 GPU。自行跑開源模型(bge-m3)用 CPU 也行但較慢,有 GPU 會快 10-50 倍。一般電腦可用 Ollama 跑小型 embedding 模型。

Chunk size 該設多大?

中文場景建議 500-1000 字。太短(< 200)語意不完整,太長(> 2000)搜尋不精確。沒有萬能答案,務必用你的實際資料測試比較。

向量資料庫和普通資料庫差在哪?

普通資料庫用關鍵字搜尋(SQL WHERE),向量資料庫用語意搜尋(找最接近的向量)。搜「想退貨」在傳統 DB 找不到「退貨政策」,但向量 DB 可以。兩者可結合使用(Hybrid Search)。

📚 延伸閱讀