Embeddings:把文字變成座標
想讓 AI 根據你的文件回答,第一步是「找到相關段落」。這堂課認識 embedding:把一段文字變成一串數字座標,讓電腦能依照「意思」而不是「字面」找出相近的內容。
歡迎來到這個單元。先說說我們要去哪裡:五堂課後,你會做出一個能讀懂你自己文件的問答小幫手。它回答問題時會附上「這句話出自哪份文件」,找不到答案時會老實說找不到,而且你有一套方法檢查它答得好不好。
這種做法叫 RAG(Retrieval-Augmented Generation,檢索增強生成)。名字很長,意思很簡單:先找資料,再回答。
為什麼不直接把文件丟給模型?
你可能會想:現在的模型能讀很長的內容,把整份員工手冊貼進去不就好了?
文件少的時候,這確實是最簡單的做法,也值得先試。但當資料變成幾百份文件、幾千頁時,就會遇到三個問題:
- 放不下:再大的上下文視窗也有上限。
- 又慢又貴:還記得 LLM API 單元學的嗎?費用依照輸入的 token 計算,每問一次就送一整座圖書館進去,帳單會很可觀。
- 容易分心:重點埋在一大堆不相關的內容裡,模型比較容易漏看。
所以 RAG 的策略是:每次只挑出最相關的幾段交給模型。問題就變成了,要怎麼「挑」?
關鍵字搜尋不夠聰明
最直覺的方法是關鍵字搜尋:問題裡有「特休」,就去找含有「特休」的段落。
但人說話不會照著文件用字。員工問「我想請特休」,手冊寫的可能是「年假申請流程」;問「計程車錢可以拿回來嗎」,手冊寫的是「交通費報銷」。字面上完全不同,意思卻一樣。
我們需要一種能比較「意思」的方法。這就是 embedding 登場的時候。
Embedding:文字的座標
Embedding(嵌入向量)是把一段文字轉換成一串數字的技術。負責轉換的是一種專門的模型,叫做 embedding 模型。
這串數字也叫做「向量」(vector)。它的長度是固定的,由模型決定,常見的是幾百到幾千個數字。
神奇的地方在於:embedding 模型讀過大量文字,學會了把意思相近的文字,轉成很接近的數字。
你可以把每串數字想像成地圖上的一個座標。在這張地圖上,講請假的句子住在同一區,講報帳的句子住在另一區:
找相關內容,就變成了「找地圖上離問題最近的點」。
真實的座標不是兩個數字,而是幾百個維度,人腦沒辦法想像,但數學上「距離」的概念一樣成立。
怎麼量「有多接近」?
最常用的方法叫餘弦相似度(cosine similarity)。不必記公式,只要知道它的結果:
| 數值 | 意思 |
|---|---|
| 接近 1 | 意思非常相近 |
| 0 附近 | 沒什麼關係 |
| 負數 | 方向相反(實務上比較少見) |
如果先把向量「標準化」(讓每個向量的長度都是 1),餘弦相似度就等於兩個向量逐項相乘再加總,算起來非常快。等一下的程式會這樣做。
動手算算看
在虛擬環境裡安裝 sentence-transformers,這是一個很常用的開源套件:
pip install sentence-transformers
它會連帶安裝深度學習套件,檔案比較大,安裝需要幾分鐘,請耐心等一下。
建立 try_embeddings.py:
from sentence_transformers import SentenceTransformer
# 一個支援中文的小型多語言模型,第一次執行會自動下載
model = SentenceTransformer("sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
sentences = [
"我想請特休",
"年假要怎麼申請?",
"計程車費可以報帳嗎",
"影印機卡紙了",
]
# normalize_embeddings=True:先把向量標準化,之後相乘就是餘弦相似度
vectors = model.encode(sentences, normalize_embeddings=True)
print("形狀:", vectors.shape) # (句子數, 每句的數字個數)
print("第一句的前五個數字:", vectors[0][:5])
query = model.encode("休假規定是什麼?", normalize_embeddings=True)
scores = vectors @ query # 每一句和問題的相似度
for sentence, score in sorted(zip(sentences, scores), key=lambda x: -x[1]):
print(f"{score:.3f} {sentence}")
執行:
python try_embeddings.py
第一次執行會下載模型檔(幾百 MB),之後就會直接使用已下載的檔案。你會看到類似這樣的結果(實際數字會不同):
形狀: (4, 384)
第一句的前五個數字: [ 0.03 -0.08 0.01 0.05 -0.02]
0.71 年假要怎麼申請?
0.64 我想請特休
0.18 計程車費可以報帳嗎
0.05 影印機卡紙了
注意看:問題「休假規定是什麼?」和「我想請特休」幾乎沒有共同的字,卻排在前面;「影印機卡紙了」則被排到最後。電腦開始能比較「意思」了。
逐行看懂
SentenceTransformer(...):載入 embedding 模型。模型名稱就像 Claude 的模型 ID,可以換成其他模型。model.encode(...):把文字變成向量。可以一次丟一整個清單,速度比一句一句算快。vectors @ query:@是矩陣相乘,一次算出每一句和問題的相似度。因為向量都標準化過,結果就是餘弦相似度。
其他選擇:雲端 embedding 服務
除了在自己電腦上算,也可以呼叫雲端的 embedding API。Voyage AI、OpenAI、Google 等供應商都有提供,用法通常是「送出一批文字,拿回一批向量」,和你剛剛做的事一樣。
怎麼選?
- 自己電腦上算:免費、資料不外流,適合學習和小量資料;缺點是要下載模型,電腦慢的話會比較久。
- 雲端服務:不用下載、通常品質更好、速度穩定;缺點是要付費,而且文件內容會送到供應商那裡。
這個單元用開源模型示範,但後面的流程完全一樣。之後想換成雲端服務,只要改「把文字變成向量」那一小段。
我正在學 embedding。這是我的 try_embeddings.py,請幫我改成:讓我在終端機輸入任意一句話,程式列出 sentences 裡和它最相似的前兩句與分數。也請用白話解釋為什麼 normalize_embeddings=True 之後,用 @ 就能算出餘弦相似度。
- 安裝
sentence-transformers,執行try_embeddings.py,確認看得到向量形狀與相似度排名。 - 把
sentences換成你工作或生活中的十句話,例如常被問的問題、筆記標題。 - 想三個「意思相同、用字完全不同」的問題去查詢,看排名第一的句子對不對。
- 再試一個和所有句子都無關的問題,例如「今天晚餐吃什麼」,記下最高分是多少。這個數字在第四堂課會派上用場:它告訴你「不相關」大概長什麼樣子。
讀完了嗎?
標記完成後,單元與專案的進度會自動更新(只存在這台裝置的瀏覽器)。
留言與討論
卡關了、有心得,或發現內容需要更新?都歡迎留言。留言使用 GitHub 帳號登入。