你有沒有試過在公司內部網站搜尋「報銷流程」,結果跳出十篇跟「報銷」兩個字有關、但完全沒回答你問題的文件?或者在自己的筆記庫裡找一段話,明明記得意思,卻怎麼打關鍵字都找不到?這種「關鍵字對得上、意思對不上」的痛,幾乎每個用過傳統搜尋的人都遇過。

這篇文章要教你的,就是用 Google 上星期在 HuggingFace 上爆紅的開源模型 EmbeddingGemma-2,自己動手做一個「懂意思」的中文語意搜尋。它一週內累積超過 2,500 個讚、下載量接近 16 萬次,是目前社群討論度最高的免費嵌入模型之一。整篇教學不需要 GPU、不需要付費 API,一台普通筆電加上 Python 就能跑完。讀完你會知道:什麼是嵌入模型、怎麼把中文文件切成適合的段落、怎麼算相似度,以及怎樣用向量資料庫把它變成真正可用的搜尋系統。

嵌入模型到底是什麼?為什麼它能「聽懂」你的意思?

先講一個生活化的比喻。傳統關鍵字搜尋像圖書館管理員只認得書名上的字,你講「請假」,他不會拿「休假申請」給你;嵌入模型則像一位讀過所有書的老師傅,他知道「請假」和「休假申請」在意思上根本是同一件事。

技術上,嵌入模型會把一段文字轉換成一串數字,也就是「向量」。意思相近的文字,轉換出來的向量在數學空間裡就會靠得很近。EmbeddingGemma-2 的強項在於它是 Google 基於 Gemma 架構訓練、針對多語言(包括中文)優化過的版本,輸出維度適中,跑在 CPU 上也不會慢到讓人想放棄。對香港、台灣的開發者來說,最實際的好處是:它支援繁體中文,而且完全免費、可離線使用,公司內部文件不必上傳到任何第三方伺服器。

第一步:安裝環境與載入模型

開始之前,你需要 Python 3.10 以上版本。打開終端機,先安裝必要的套件:

pip install sentence-transformers torch numpy

接著用短短幾行載入模型:

from sentence_transformers import SentenceTransformer

model = SentenceTransformer("google/embeddinggemma-2")

sentences = [
    "員工請假需要提前三天申請",
    "休假申請表格請交人事部",
    "今天天氣很好適合外出"
]

vectors = model.encode(sentences, normalize_embeddings=True)
print(vectors.shape)  # 每段文字一個向量

注意 normalize_embeddings=True 這個參數,它會把向量長度統一,之後算相似度時直接做內積就行,省掉一步正規化。第一次執行會自動從 HuggingFace 下載模型權重,大約幾 GB,之後就會存在本機快取,不會再下載。

第二步:中文文件的 Chunk 切分,這步最影響準確度

很多人做完上面那步就興沖沖把整份 PDF 丟進去,結果搜尋品質很差,問題通常不在模型,而在切分方式。一份三十頁的規章轉成一個向量,等於把整本書濃縮成一個點,什麼細節都不見了。

實務上的做法是「重疊式切分」:每段控制在 200 到 400 字之間,段落之間保留 50 字左右的重疊,避免一句話被硬生生切成兩半。以下是一個可以直接用的函數:

def chunk_text(text, size=300, overlap=50):
    chunks = []
    start = 0
    while start < len(text):
        end = start + size
        chunks.append(text[start:end])
        start += size - overlap
    return chunks

如果你處理的是公司內部文件,建議在每個 chunk 前面加上「文件名稱+章節標題」的前綴,例如「《員工手冊》第三章 請假規定:……」,這樣模型在編碼時就能保留上下文,實測準確度會明顯提升。

第三步:算出相似度,做出你的第一個語意搜尋

有了向量之後,搜尋就變成一道簡單的數學題:把使用者的問題也轉成向量,然後找出距離最近的幾個 chunk。

import numpy as np

query = "我想放假要怎麼申請?"
q_vec = model.encode(query, normalize_embeddings=True)

scores = vectors @ q_vec          # 內積即餘弦相似度
top_idx = np.argsort(scores)[::-1][:3]

for i in top_idx:
    print(round(float(scores[i]), 3), sentences[i])

跑出來的結果會讓你很有感:即使問題裡完全沒有出現「請假」兩個字,系統還是會把「休假申請」那段排在前面。這就是語意搜尋和關鍵字搜尋最根本的差別。

第四步:規模變大怎麼辦?接上向量資料庫

文件只有幾十段時,上面的寫法完全夠用。但當你的知識庫有幾萬份文件、幾十萬個 chunk 時,每次都把所有向量載入記憶體再比對,速度會開始拖。這時候就需要向量資料庫,香港與台灣團隊最常用的是 ChromaDB(輕量、本機可跑)和 Qdrant(可自架、支援過濾條件)。

以 ChromaDB 為例,加入只需要幾行:

import chromadb

client = chromadb.PersistentClient(path="./my_db")
col = client.get_or_create_collection("docs")

col.add(
    ids=[f"id{i}" for i in range(len(sentences))],
    embeddings=vectors.tolist(),
    documents=sentences
)

result = col.query(query_embeddings=[q_vec.tolist()], n_results=3)
print(result["documents"])

這樣一來,之後新增文件只要 col.add,搜尋時資料庫會自動幫你做近似最近鄰檢索,就算資料量成長到十萬筆,回應時間仍能維持在毫秒級。

實戰小技巧:讓準確度再往上跳一級

第一個技巧是「查詢改寫」。使用者打的問題往往很短,你可以先用一個小語言模型把問題擴寫成完整句子再編碼,例如把「報銷」擴寫成「員工出差費用報銷的流程與所需單據」,檢索命中率通常會提升一至兩成。

第二個技巧是「混合檢索」。語意搜尋很強,但遇到專有名詞、產品型號、人名時反而容易失手,因為它看的是意思而不是字面。實務上最穩的做法是把語意分數和關鍵字分數(例如 BM25)加權合併,業界常見的比例是七比三,你可以依自己的資料特性微調。

第三個技巧是「建立評估集」。隨手寫二十條「問題+正確答案文件」的配對,每次調整切分參數或模型版本時都跑一次,看前三名命中率有沒有進步。沒有評估集,你永遠只能憑感覺說「好像變準了」。

延伸閱讀

常見問題

Q: EmbeddingGemma-2 一定要有 GPU 才能跑嗎?

A: 不需要。這個模型在一般筆電的 CPU 上就能執行,編碼幾百段中文文件通常只需幾秒到十幾秒。若你要一次處理數十萬份文件,才建議考慮 GPU 或分批處理。

Q: 它支援繁體中文嗎?會不會只對簡體中文有效?

A: 支援。EmbeddingGemma-2 屬於多語言模型,繁體中文的檢索效果良好。實務上建議文件與查詢使用同一種語言與字形,避免簡繁混用造成向量偏移。

Q: 免費模型跟 OpenAI 的付費嵌入 API 比,差很多嗎?

A: 在中文語意檢索這種常見任務上,差距沒有想像中大,對大多數內部知識庫、客服 FAQ 已經足夠。付費 API 的優勢主要在極大規模穩定性與維運省心,但如果你在意資料不外流與長期成本,自架開源模型是很好的選擇。

Q: 一份文件應該切成多大?

A: 建議每段 200 到 400 字,重疊 50 字左右。太短會失去上下文,太長則會稀釋重點。切完後記得在前綴補上文件名稱與章節標題,效果會更好。

Q: 我可以直接拿它來做 RAG 聊天機器人嗎?

A: 可以,而且這正是它最常見的用途。把檢索到的前幾段 chunk 連同使用者問題一起塞進語言模型的提示詞裡,就能做出「根據你公司文件回答」的聊天機器人。

總結一下:EmbeddingGemma-2 把過去要付費、要調 API 的語意搜尋能力,變成一個在自己電腦上就能跑的免費工具。你只需要記住四個步驟——載入模型、好好切分文件、算相似度、必要時接上向量資料庫——就能做出一個真正「聽懂人話」的搜尋系統。別急著一次做到完美,先拿你手上最常搜不到東西的那個資料夾開刀,跑通一次流程,你會發現這件事比想像中簡單得多。今天下班前,就試試看吧。