🧮 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-small | OpenAI | 1536 | $0.02 | 性價比最高 |
| text-embedding-3-large | OpenAI | 3072 | $0.13 | 精度最高 |
| voyage-3 | Voyage AI | 1024 | $0.06 | 長文 + 程式碼最強 |
| embed-v4 | Cohere | 1024 | $0.10 | 多語言最好 |
| bge-m3 | BAAI | 1024 | 免費(開源) | 開源最強、多語言 |
| nomic-embed-text | Nomic AI | 768 | 免費(開源) | 可用 Ollama 本地跑 |
怎麼選?
- 💰 預算有限 →
text-embedding-3-small(最便宜、品質夠用) - 🏆 要求最高精度 →
text-embedding-3-large或voyage-3 - 🇹🇼 中文為主 →
bge-m3或cohere embed-v4 - 🔒 資料不能外傳 →
bge-m3或nomic-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)。